[v17,01/12] config: Introduce global runtime configuration
diff mbox series

Message ID 20250911092945.16517-2-mzamazal@redhat.com
State New
Headers show
Series
  • Add global configuration file
Related show

Commit Message

Milan Zamazal Sept. 11, 2025, 9:29 a.m. UTC
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 was a request to define 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.  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 and stores the configuration.  Its
instance can be used by other libcamera objects to access the
configuration.  A GlobalConfiguration instance is supposed to be stored
in a well-defined place, e.g. a CameraManager instance.  It is possible
to have multiple GlobalConfiguration instances, which may or may not
make sense.

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

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@redhat.com>
---
 .../libcamera/internal/global_configuration.h |  34 ++++
 include/libcamera/internal/meson.build        |   1 +
 src/libcamera/global_configuration.cpp        | 148 ++++++++++++++++++
 src/libcamera/meson.build                     |   1 +
 4 files changed, 184 insertions(+)
 create mode 100644 include/libcamera/internal/global_configuration.h
 create mode 100644 src/libcamera/global_configuration.cpp

Comments

Paul Elder Sept. 11, 2025, 10:51 a.m. UTC | #1
Hi Milan,

Thanks for the patch.

Quoting Milan Zamazal (2025-09-11 18:29:31)
> 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 was a request to define 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.  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 and stores the configuration.  Its
> instance can be used by other libcamera objects to access the
> configuration.  A GlobalConfiguration instance is supposed to be stored
> in a well-defined place, e.g. a CameraManager instance.  It is possible
> to have multiple GlobalConfiguration instances, which may or may not
> make sense.
> 
> 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
> 
> 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@redhat.com>

Looks good to me.

Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>

> ---
>  .../libcamera/internal/global_configuration.h |  34 ++++
>  include/libcamera/internal/meson.build        |   1 +
>  src/libcamera/global_configuration.cpp        | 148 ++++++++++++++++++
>  src/libcamera/meson.build                     |   1 +
>  4 files changed, 184 insertions(+)
>  create mode 100644 include/libcamera/internal/global_configuration.h
>  create mode 100644 src/libcamera/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..f695498c4
> --- /dev/null
> +++ b/include/libcamera/internal/global_configuration.h
> @@ -0,0 +1,34 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2024-2025 Red Hat, inc.
> + *
> + * Global configuration handling
> + */
> +
> +#pragma once
> +
> +#include <filesystem>
> +
> +#include "libcamera/internal/yaml_parser.h"
> +
> +namespace libcamera {
> +
> +class GlobalConfiguration
> +{
> +public:
> +       using Configuration = const YamlObject &;
> +
> +       GlobalConfiguration();
> +
> +       unsigned int version() const;
> +       Configuration configuration() const;
> +
> +private:
> +       bool loadFile(const std::filesystem::path &fileName);
> +       void load();
> +
> +       std::unique_ptr<YamlObject> yamlConfiguration_ =
> +               std::make_unique<YamlObject>();
> +};
> +
> +} /* namespace libcamera */
> diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
> index 5c80a28c4..45c299f6a 100644
> --- a/include/libcamera/internal/meson.build
> +++ b/include/libcamera/internal/meson.build
> @@ -23,6 +23,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/global_configuration.cpp b/src/libcamera/global_configuration.cpp
> new file mode 100644
> index 000000000..d02668111
> --- /dev/null
> +++ b/src/libcamera/global_configuration.cpp
> @@ -0,0 +1,148 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2024-2025 Red Hat, inc.
> + *
> + * Global configuration handling
> + */
> +
> +#include "libcamera/internal/global_configuration.h"
> +
> +#include <filesystem>
> +#include <string_view>
> +#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 {
> +
> +namespace {
> +const std::vector<std::filesystem::path> globalConfigurationFiles = {
> +       std::filesystem::path(LIBCAMERA_SYSCONF_DIR) / "configuration.yaml",
> +       std::filesystem::path(LIBCAMERA_DATA_DIR) / "configuration.yaml",
> +};
> +}
> +
> +LOG_DEFINE_CATEGORY(Configuration)
> +
> +/**
> + * \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.
> + *
> + * If the first found configuration file cannot be opened or parsed, an error is
> + * reported and no configuration file is used. This is to prevent libcamera from
> + * using an unintended configuration file.
> + */
> +
> +bool GlobalConfiguration::loadFile(const std::filesystem::path &fileName)
> +{
> +       File file(fileName);
> +       if (!file.open(File::OpenModeFlag::ReadOnly)) {
> +               if (file.error() == -ENOENT)
> +                       return false;
> +
> +               LOG(Configuration, Error)
> +                       << "Failed to open configuration file " << fileName;
> +               return true;
> +       }
> +
> +       std::unique_ptr<YamlObject> configuration = YamlParser::parse(file);
> +       if (!configuration) {
> +               LOG(Configuration, Error)
> +                       << "Failed to parse configuration file " << fileName;
> +               return true;
> +       }
> +
> +       yamlConfiguration_ = std::move(configuration);
> +       return true;
> +}
> +
> +void GlobalConfiguration::load()
> +{
> +       std::filesystem::path userConfigurationDirectory;
> +       const 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 (const auto &path : globalConfigurationFiles) {
> +               if (loadFile(path))
> +                       return;
> +       }
> +}
> +
> +/**
> + * \brief Initialize the global configuration
> + */
> +GlobalConfiguration::GlobalConfiguration()
> +{
> +       load();
> +}
> +
> +/**
> + * \typedef GlobalConfiguration::Configuration
> + * \brief Type representing global libcamera configuration
> + *
> + * All code outside GlobalConfiguration must use this type declaration and not
> + * the underlying type.
> + */
> +
> +/**
> + * \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() const
> +{
> +       return (*yamlConfiguration_)["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() const
> +{
> +       return (*yamlConfiguration_)["configuration"];
> +}
> +
> +} /* namespace libcamera */
> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> index b3ca27f21..5b9b86f21 100644
> --- a/src/libcamera/meson.build
> +++ b/src/libcamera/meson.build
> @@ -31,6 +31,7 @@ libcamera_internal_sources = files([
>      'device_enumerator_sysfs.cpp',
>      'dma_buf_allocator.cpp',
>      'formats.cpp',
> +    'global_configuration.cpp',
>      'ipa_controls.cpp',
>      'ipa_data_serializer.cpp',
>      'ipa_interface.cpp',
> -- 
> 2.51.0
>

Patch
diff mbox series

diff --git a/include/libcamera/internal/global_configuration.h b/include/libcamera/internal/global_configuration.h
new file mode 100644
index 000000000..f695498c4
--- /dev/null
+++ b/include/libcamera/internal/global_configuration.h
@@ -0,0 +1,34 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024-2025 Red Hat, inc.
+ *
+ * Global configuration handling
+ */
+
+#pragma once
+
+#include <filesystem>
+
+#include "libcamera/internal/yaml_parser.h"
+
+namespace libcamera {
+
+class GlobalConfiguration
+{
+public:
+	using Configuration = const YamlObject &;
+
+	GlobalConfiguration();
+
+	unsigned int version() const;
+	Configuration configuration() const;
+
+private:
+	bool loadFile(const std::filesystem::path &fileName);
+	void load();
+
+	std::unique_ptr<YamlObject> yamlConfiguration_ =
+		std::make_unique<YamlObject>();
+};
+
+} /* namespace libcamera */
diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
index 5c80a28c4..45c299f6a 100644
--- a/include/libcamera/internal/meson.build
+++ b/include/libcamera/internal/meson.build
@@ -23,6 +23,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/global_configuration.cpp b/src/libcamera/global_configuration.cpp
new file mode 100644
index 000000000..d02668111
--- /dev/null
+++ b/src/libcamera/global_configuration.cpp
@@ -0,0 +1,148 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024-2025 Red Hat, inc.
+ *
+ * Global configuration handling
+ */
+
+#include "libcamera/internal/global_configuration.h"
+
+#include <filesystem>
+#include <string_view>
+#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 {
+
+namespace {
+const std::vector<std::filesystem::path> globalConfigurationFiles = {
+	std::filesystem::path(LIBCAMERA_SYSCONF_DIR) / "configuration.yaml",
+	std::filesystem::path(LIBCAMERA_DATA_DIR) / "configuration.yaml",
+};
+}
+
+LOG_DEFINE_CATEGORY(Configuration)
+
+/**
+ * \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.
+ *
+ * If the first found configuration file cannot be opened or parsed, an error is
+ * reported and no configuration file is used. This is to prevent libcamera from
+ * using an unintended configuration file.
+ */
+
+bool GlobalConfiguration::loadFile(const std::filesystem::path &fileName)
+{
+	File file(fileName);
+	if (!file.open(File::OpenModeFlag::ReadOnly)) {
+		if (file.error() == -ENOENT)
+			return false;
+
+		LOG(Configuration, Error)
+			<< "Failed to open configuration file " << fileName;
+		return true;
+	}
+
+	std::unique_ptr<YamlObject> configuration = YamlParser::parse(file);
+	if (!configuration) {
+		LOG(Configuration, Error)
+			<< "Failed to parse configuration file " << fileName;
+		return true;
+	}
+
+	yamlConfiguration_ = std::move(configuration);
+	return true;
+}
+
+void GlobalConfiguration::load()
+{
+	std::filesystem::path userConfigurationDirectory;
+	const 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 (const auto &path : globalConfigurationFiles) {
+		if (loadFile(path))
+			return;
+	}
+}
+
+/**
+ * \brief Initialize the global configuration
+ */
+GlobalConfiguration::GlobalConfiguration()
+{
+	load();
+}
+
+/**
+ * \typedef GlobalConfiguration::Configuration
+ * \brief Type representing global libcamera configuration
+ *
+ * All code outside GlobalConfiguration must use this type declaration and not
+ * the underlying type.
+ */
+
+/**
+ * \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() const
+{
+	return (*yamlConfiguration_)["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() const
+{
+	return (*yamlConfiguration_)["configuration"];
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index b3ca27f21..5b9b86f21 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -31,6 +31,7 @@  libcamera_internal_sources = files([
     'device_enumerator_sysfs.cpp',
     'dma_buf_allocator.cpp',
     'formats.cpp',
+    'global_configuration.cpp',
     'ipa_controls.cpp',
     'ipa_data_serializer.cpp',
     'ipa_interface.cpp',