[PATCH v5 03/15] config: Introduce global runtime configuration

Barnabás Pőcze pobrn at protonmail.com
Wed Dec 4 18:45:02 CET 2024


Hi


2024. október 1., kedd 12:27 keltezéssel, Milan Zamazal <mzamazal at redhat.com> írta:

> Currently, libcamera can be configured in runtime using several
> environment variables.  With introducing more and more variables, this
> mechanism reaches its limits.  It would be simpler and more flexible if
> it was possible to configure libcamera in a single file.
> 
> For example, there have been a request for defining pipeline precedence
> in runtime.  We want to compile in multiple pipelines, in order to have
> them accessible within single packages in distributions.  And then being
> able to select among the pipelines manually as needed based on the
> particular hardware or operating system environment.  Having the
> configuration file then allows easy switching between hardware, GPU or
> CPU IPAs.  Another possible use case is tuning image output, especially
> with software ISP, to user liking.  For example, some users may prefer
> higher contrast without the need to use the corresponding knobs, if
> present at all, in every application.  The configuration file can also
> be used to enable or disable experimental features and avoid the need to
> track local patches changing configuration options hard-wired in the
> code when working on new features.
> 
> This patch introduces basic support for configuration files.
> GlobalConfiguration class reads, stores and accesses the configuration.
> Its instance is stored as a private singleton accessed using a static
> method of the class.  There is currently no reason to have more than one
> instance.
> 
> libcamera configuration can be specified using a system-wide
> configuration file or a user configuration file.  The user configuration
> file takes precedence if present.  There is currently no way to merge
> multiple configuration files, the one found is used as the only
> configuration file.  If no configuration file is present, nothing
> changes to the current libcamera behavior (except for some log
> messages related to configuration file lookup).
> 
> The configuration file is a YAML file.  We already have a mechanism for
> handling YAML configuration files in libcamera and the given
> infrastructure can be reused for the purpose.  However, the
> configuration type is abstracted to make contingent future change of the
> underlying class easier while retaining (most of) the original API.
> 
> The configuration is versioned.  This has currently no particular
> meaning but is likely to have its purpose in future, especially once
> configuration validation is introduced.
> 
> The configuration YAML file looks as follows:
> 
>   ---
>   version: 1
>   configuration:
>     WHATEVER CONFIGURATION NEEDED
> 
> There is no logging about reading the configuration file and contingent
> errors.  This is on purpose because logging will query configuration,
> which can lead to various problems when done during configuration
> initialization.  Reporting the errors will be added later.
> 
> The global configuration is intended to be used as a hidden singleton,
> with static class methods around it providing the configuration
> information.  The constructor must be still public so that unique_ptr
> can be used.
> 
> A complication arises from the fact that when the configuration is
> loaded, logging may be called and logging will ask for the
> configuration.  This is error-prone and may lead to subtle problems.
> For this reason, the global configuration is instantiated to a pointer,
> with an empty configuration initially.  The real configuration will be
> created through initialize() method.  It will be clearer how it helps in
> the followup patch introducing logging configuration.
> 
> Logging is also the most notable component from base that uses global
> configuration.  In order to be able to do it, the global configuration
> must be put to base.
> 
> This patch introduces just the basic idea.  Actually using the
> configuration in the corresponding places (everything what is currently
> configurable via environment variables should be configurable in the
> file configuration) and other enhancements are implemented in the
> followup patches.
> 
> Signed-off-by: Milan Zamazal <mzamazal at redhat.com>
> ---
>  .../libcamera/internal/global_configuration.h |  41 +++++
>  include/libcamera/internal/meson.build        |   1 +
>  src/libcamera/base/global_configuration.cpp   | 154 ++++++++++++++++++
>  src/libcamera/base/meson.build                |   1 +
>  4 files changed, 197 insertions(+)
>  create mode 100644 include/libcamera/internal/global_configuration.h
>  create mode 100644 src/libcamera/base/global_configuration.cpp
> 
> diff --git a/include/libcamera/internal/global_configuration.h b/include/libcamera/internal/global_configuration.h
> new file mode 100644
> index 000000000..628d85cb8
> --- /dev/null
> +++ b/include/libcamera/internal/global_configuration.h
> @@ -0,0 +1,41 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2024 Red Hat, inc.
> + *
> + * global_configuration.h - Global configuration handling
> + */
> +
> +#pragma once
> +
> +#include <filesystem>
> +#include <vector>
> +
> +#include "libcamera/internal/yaml_parser.h"
> +
> +namespace libcamera {
> +
> +class GlobalConfiguration
> +{
> +public:
> +	using Configuration = const YamlObject &;
> +
> +	/* The constructor must be public to be able to use unique_ptr. */

If one does

  std::unique_ptr<T>(new T)

then the constructor does not need to be public.


> +	GlobalConfiguration();
> +	static void initialize();
> +
> +	static unsigned int version();
> +	static Configuration configuration();
> +
> +private:
> +	static const std::vector<std::filesystem::path> globalConfigurationFiles;
> +
> +	static std::unique_ptr<GlobalConfiguration> instance_;
> +
> +	std::unique_ptr<YamlObject> configuration_;
> +
> +	bool loadFile(const std::filesystem::path &fileName);
> +	void load();
> +	static Configuration get();
> +};
> +
> +} /* namespace libcamera */
> diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
> index 1c5eef9ca..6fc6406d7 100644
> --- a/include/libcamera/internal/meson.build
> +++ b/include/libcamera/internal/meson.build
> @@ -21,6 +21,7 @@ libcamera_internal_headers = files([
>      'dma_buf_allocator.h',
>      'formats.h',
>      'framebuffer.h',
> +    'global_configuration.h',
>      'ipa_data_serializer.h',
>      'ipa_manager.h',
>      'ipa_module.h',
> diff --git a/src/libcamera/base/global_configuration.cpp b/src/libcamera/base/global_configuration.cpp
> new file mode 100644
> index 000000000..7dcf97265
> --- /dev/null
> +++ b/src/libcamera/base/global_configuration.cpp
> @@ -0,0 +1,154 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2024 Red Hat, inc.
> + *
> + * global_configuration.cpp - Global configuration handling
> + */
> +
> +#include "libcamera/internal/global_configuration.h"
> +
> +#include <filesystem>
> +#include <memory>
> +#include <sys/types.h>
> +
> +#include <libcamera/base/file.h>
> +#include <libcamera/base/log.h>
> +#include <libcamera/base/utils.h>
> +
> +#include "libcamera/internal/yaml_parser.h"
> +
> +namespace libcamera {
> +
> +std::unique_ptr<GlobalConfiguration> GlobalConfiguration::instance_ =
> +	std::make_unique<GlobalConfiguration>();

Why is a unique_ptr needed here?


> +
> +/**
> + * \brief Do not create GlobalConfiguration instance directly, use initialize()
> + */
> +void GlobalConfiguration::initialize()
> +{
> +	std::unique_ptr<GlobalConfiguration> instance =
> +		std::make_unique<GlobalConfiguration>();
> +	instance->load();
> +	instance_ = std::move(instance);
> +}
> +
> +/**
> + * \class GlobalConfiguration
> + * \brief Support for global libcamera configuration
> + *
> + * The configuration file is a YAML file and the configuration itself is stored
> + * under `configuration' top-level item.
> + *
> + * The configuration file is looked up in user's home directory first and if it
> + * is not found then in system-wide configuration directories. If multiple
> + * configuration files exist then only the first one found is used and no
> + * configuration merging is performed.
> + *
> + * The class is used as a private singleton and the configuration can be
> + * accessed using GlobalConfiguration::configuration().
> + */
> +
> +/**
> + * \typedef GlobalConfiguration::Configuration
> + * \brief Type representing global libcamera configuration
> + *
> + * All code outside GlobalConfiguration must use this type declaration and not
> + * the underlying type.
> + */
> +
> +GlobalConfiguration::GlobalConfiguration()
> +	: configuration_(std::make_unique<YamlObject>())
> +{
> +}
> +
> +const std::vector<std::filesystem::path>
> +	GlobalConfiguration::globalConfigurationFiles = {
> +		std::filesystem::path(LIBCAMERA_SYSCONF_DIR) / "configuration.yaml",
> +		std::filesystem::path("/etc/libcamera/configuration.yaml"),
> +	};

I think the above array can go into an anonymous namespace as there does not
appear to be used anywhere else.


> +
> +void GlobalConfiguration::load()
> +{
> +	std::filesystem::path userConfigurationDirectory;
> +	char *xdgConfigHome = utils::secure_getenv("XDG_CONFIG_HOME");
> +	if (xdgConfigHome) {
> +		userConfigurationDirectory = xdgConfigHome;
> +	} else {
> +		const char *home = utils::secure_getenv("HOME");
> +		if (home)
> +			userConfigurationDirectory =
> +				std::filesystem::path(home) / ".config";
> +	}
> +
> +	if (!userConfigurationDirectory.empty()) {
> +		std::filesystem::path user_configuration_file =
> +			userConfigurationDirectory / "libcamera" / "configuration.yaml";
> +		if (loadFile(user_configuration_file))
> +			return;
> +	}
> +
> +	for (auto path : globalConfigurationFiles)

const auto &path


> +		if (loadFile(path))
> +			return;
> +}
> +
> +bool GlobalConfiguration::loadFile(const std::filesystem::path &fileName)
> +{
> +	File file(fileName);
> +	if (!file.exists()) {
> +		return false;
> +	}
> +
> +	if (!file.open(File::OpenModeFlag::ReadOnly))
> +		return true;
> +
> +	auto root = YamlParser::parse(file);
> +	if (!root)
> +		return true;
> +	configuration_ = std::move(root);
> +
> +	return true;
> +}
> +
> +GlobalConfiguration::Configuration GlobalConfiguration::get()
> +{
> +	return *instance_->configuration_;
> +}
> +
> +/**
> + * \brief Return configuration version
> + *
> + * The version is (optionally) declared in the configuration file in the
> + * top-level section `version', alongside `configuration'. This has currently no
> + * real use but may be needed in future if configuration incompatibilities
> + * occur.
> + *
> + * \return Configuration version as declared in the configuration file or 0 if
> + * no version is declared there
> + */
> +unsigned int GlobalConfiguration::version()
> +{
> +	return get()["version"].get<unsigned int>().value_or(0);
> +}
> +
> +/**
> + * \brief Return libcamera global configuration
> + *
> + * This returns the whole configuration stored in the top-level section
> + * `configuration' of the YAML configuration file.
> + *
> + * The requested part of the configuration can be accessed using \a YamlObject
> + * methods.
> + *
> + * \note \a YamlObject type itself shouldn't be used in type declarations to
> + * avoid trouble if we decide to change the underlying data objects in future.
> + *
> + * \return The whole configuration section
> + */
> +GlobalConfiguration::Configuration GlobalConfiguration::configuration()
> +{
> +	return get()["configuration"];
> +}
> +
> +} /* namespace libcamera */
> diff --git a/src/libcamera/base/meson.build b/src/libcamera/base/meson.build
> index 94843eb95..4c0032845 100644
> --- a/src/libcamera/base/meson.build
> +++ b/src/libcamera/base/meson.build
> @@ -16,6 +16,7 @@ libcamera_base_internal_sources = files([
>      'event_dispatcher_poll.cpp',
>      'event_notifier.cpp',
>      'file.cpp',
> +    'global_configuration.cpp',
>      'log.cpp',
>      'memfd.cpp',
>      'message.cpp',
> --
> 2.44.1
> 
> 

Inspired by pipewire, I think the following two would be quite useful:
  1. `LIBCAMERA_CONFIG_DIR` environmental variable, which denotes a (list of) directory(s)
     that is searched first.
  2. `LIBCAMERA_CONFIG_NAME` environmental variable, which denotes
      a) the path of the configuration file to load if it is an absolute path; or
      b) the path of the configuration file to load, relative to the configuration
         directories

  (1) would make it easy to integrate with `meson devenv`
  (2) would make it easy to switch between configurations, which is not too easy
      to do if the filename is hard-coded


Regards,
Barnabás Pőcze


More information about the libcamera-devel mailing list