[{"id":35775,"web_url":"https://patchwork.libcamera.org/comment/35775/","msgid":"<175758790440.2127323.13182643782169415483@neptunite.rasen.tech>","date":"2025-09-11T10:51:44","subject":"Re: [PATCH v17 01/12] config: Introduce global runtime configuration","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"content":"Hi Milan,\n\nThanks for the patch.\n\nQuoting Milan Zamazal (2025-09-11 18:29:31)\n> Currently, libcamera can be configured in runtime using several\n> environment variables.  With introducing more and more variables, this\n> mechanism reaches its limits.  It would be simpler and more flexible if\n> it was possible to configure libcamera in a single file.\n> \n> For example, there was a request to define pipeline precedence in\n> runtime.  We want to compile in multiple pipelines, in order to have\n> them accessible within single packages in distributions.  And then being\n> able to select among the pipelines manually as needed based on the\n> particular hardware or operating system environment.  Having the\n> configuration file then allows easy switching between hardware, GPU or\n> CPU IPAs.  The configuration file can also be used to enable or disable\n> experimental features and avoid the need to track local patches changing\n> configuration options hard-wired in the code when working on new\n> features.\n> \n> This patch introduces basic support for configuration files.\n> GlobalConfiguration class reads and stores the configuration.  Its\n> instance can be used by other libcamera objects to access the\n> configuration.  A GlobalConfiguration instance is supposed to be stored\n> in a well-defined place, e.g. a CameraManager instance.  It is possible\n> to have multiple GlobalConfiguration instances, which may or may not\n> make sense.\n> \n> libcamera configuration can be specified using a system-wide\n> configuration file or a user configuration file.  The user configuration\n> file takes precedence if present.  There is currently no way to merge\n> multiple configuration files, the one found is used as the only\n> configuration file.  If no configuration file is present, nothing\n> changes to the current libcamera behavior (except for some log\n> messages related to configuration file lookup).\n> \n> The configuration file is a YAML file.  We already have a mechanism for\n> handling YAML configuration files in libcamera and the given\n> infrastructure can be reused for the purpose.  However, the\n> configuration type is abstracted to make contingent future change of the\n> underlying class easier while retaining (most of) the original API.\n> \n> The configuration is versioned.  This has currently no particular\n> meaning but is likely to have its purpose in future, especially once\n> configuration validation is introduced.\n> \n> The configuration YAML file looks as follows:\n> \n>   ---\n>   version: 1\n>   configuration:\n>     WHATEVER CONFIGURATION NEEDED\n> \n> This patch introduces just the basic idea.  Actually using the\n> configuration in the corresponding places (everything what is currently\n> configurable via environment variables should be configurable in the\n> file configuration) and other enhancements are implemented in the\n> followup patches.\n> \n> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>\n\nLooks good to me.\n\nReviewed-by: Paul Elder <paul.elder@ideasonboard.com>\n\n> ---\n>  .../libcamera/internal/global_configuration.h |  34 ++++\n>  include/libcamera/internal/meson.build        |   1 +\n>  src/libcamera/global_configuration.cpp        | 148 ++++++++++++++++++\n>  src/libcamera/meson.build                     |   1 +\n>  4 files changed, 184 insertions(+)\n>  create mode 100644 include/libcamera/internal/global_configuration.h\n>  create mode 100644 src/libcamera/global_configuration.cpp\n> \n> diff --git a/include/libcamera/internal/global_configuration.h b/include/libcamera/internal/global_configuration.h\n> new file mode 100644\n> index 000000000..f695498c4\n> --- /dev/null\n> +++ b/include/libcamera/internal/global_configuration.h\n> @@ -0,0 +1,34 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024-2025 Red Hat, inc.\n> + *\n> + * Global configuration handling\n> + */\n> +\n> +#pragma once\n> +\n> +#include <filesystem>\n> +\n> +#include \"libcamera/internal/yaml_parser.h\"\n> +\n> +namespace libcamera {\n> +\n> +class GlobalConfiguration\n> +{\n> +public:\n> +       using Configuration = const YamlObject &;\n> +\n> +       GlobalConfiguration();\n> +\n> +       unsigned int version() const;\n> +       Configuration configuration() const;\n> +\n> +private:\n> +       bool loadFile(const std::filesystem::path &fileName);\n> +       void load();\n> +\n> +       std::unique_ptr<YamlObject> yamlConfiguration_ =\n> +               std::make_unique<YamlObject>();\n> +};\n> +\n> +} /* namespace libcamera */\n> diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build\n> index 5c80a28c4..45c299f6a 100644\n> --- a/include/libcamera/internal/meson.build\n> +++ b/include/libcamera/internal/meson.build\n> @@ -23,6 +23,7 @@ libcamera_internal_headers = files([\n>      'dma_buf_allocator.h',\n>      'formats.h',\n>      'framebuffer.h',\n> +    'global_configuration.h',\n>      'ipa_data_serializer.h',\n>      'ipa_manager.h',\n>      'ipa_module.h',\n> diff --git a/src/libcamera/global_configuration.cpp b/src/libcamera/global_configuration.cpp\n> new file mode 100644\n> index 000000000..d02668111\n> --- /dev/null\n> +++ b/src/libcamera/global_configuration.cpp\n> @@ -0,0 +1,148 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024-2025 Red Hat, inc.\n> + *\n> + * Global configuration handling\n> + */\n> +\n> +#include \"libcamera/internal/global_configuration.h\"\n> +\n> +#include <filesystem>\n> +#include <string_view>\n> +#include <sys/types.h>\n> +\n> +#include <libcamera/base/file.h>\n> +#include <libcamera/base/log.h>\n> +#include <libcamera/base/utils.h>\n> +\n> +#include \"libcamera/internal/yaml_parser.h\"\n> +\n> +namespace libcamera {\n> +\n> +namespace {\n> +const std::vector<std::filesystem::path> globalConfigurationFiles = {\n> +       std::filesystem::path(LIBCAMERA_SYSCONF_DIR) / \"configuration.yaml\",\n> +       std::filesystem::path(LIBCAMERA_DATA_DIR) / \"configuration.yaml\",\n> +};\n> +}\n> +\n> +LOG_DEFINE_CATEGORY(Configuration)\n> +\n> +/**\n> + * \\class GlobalConfiguration\n> + * \\brief Support for global libcamera configuration\n> + *\n> + * The configuration file is a YAML file and the configuration itself is stored\n> + * under `configuration' top-level item.\n> + *\n> + * The configuration file is looked up in user's home directory first and if it\n> + * is not found then in system-wide configuration directories. If multiple\n> + * configuration files exist then only the first one found is used and no\n> + * configuration merging is performed.\n> + *\n> + * If the first found configuration file cannot be opened or parsed, an error is\n> + * reported and no configuration file is used. This is to prevent libcamera from\n> + * using an unintended configuration file.\n> + */\n> +\n> +bool GlobalConfiguration::loadFile(const std::filesystem::path &fileName)\n> +{\n> +       File file(fileName);\n> +       if (!file.open(File::OpenModeFlag::ReadOnly)) {\n> +               if (file.error() == -ENOENT)\n> +                       return false;\n> +\n> +               LOG(Configuration, Error)\n> +                       << \"Failed to open configuration file \" << fileName;\n> +               return true;\n> +       }\n> +\n> +       std::unique_ptr<YamlObject> configuration = YamlParser::parse(file);\n> +       if (!configuration) {\n> +               LOG(Configuration, Error)\n> +                       << \"Failed to parse configuration file \" << fileName;\n> +               return true;\n> +       }\n> +\n> +       yamlConfiguration_ = std::move(configuration);\n> +       return true;\n> +}\n> +\n> +void GlobalConfiguration::load()\n> +{\n> +       std::filesystem::path userConfigurationDirectory;\n> +       const char *xdgConfigHome = utils::secure_getenv(\"XDG_CONFIG_HOME\");\n> +       if (xdgConfigHome) {\n> +               userConfigurationDirectory = xdgConfigHome;\n> +       } else {\n> +               const char *home = utils::secure_getenv(\"HOME\");\n> +               if (home)\n> +                       userConfigurationDirectory =\n> +                               std::filesystem::path(home) / \".config\";\n> +       }\n> +\n> +       if (!userConfigurationDirectory.empty()) {\n> +               std::filesystem::path user_configuration_file =\n> +                       userConfigurationDirectory / \"libcamera\" / \"configuration.yaml\";\n> +               if (loadFile(user_configuration_file))\n> +                       return;\n> +       }\n> +\n> +       for (const auto &path : globalConfigurationFiles) {\n> +               if (loadFile(path))\n> +                       return;\n> +       }\n> +}\n> +\n> +/**\n> + * \\brief Initialize the global configuration\n> + */\n> +GlobalConfiguration::GlobalConfiguration()\n> +{\n> +       load();\n> +}\n> +\n> +/**\n> + * \\typedef GlobalConfiguration::Configuration\n> + * \\brief Type representing global libcamera configuration\n> + *\n> + * All code outside GlobalConfiguration must use this type declaration and not\n> + * the underlying type.\n> + */\n> +\n> +/**\n> + * \\brief Return configuration version\n> + *\n> + * The version is (optionally) declared in the configuration file in the\n> + * top-level section `version', alongside `configuration'. This has currently no\n> + * real use but may be needed in future if configuration incompatibilities\n> + * occur.\n> + *\n> + * \\return Configuration version as declared in the configuration file or 0 if\n> + * no version is declared there\n> + */\n> +unsigned int GlobalConfiguration::version() const\n> +{\n> +       return (*yamlConfiguration_)[\"version\"].get<unsigned int>().value_or(0);\n> +}\n> +\n> +/**\n> + * \\brief Return libcamera global configuration\n> + *\n> + * This returns the whole configuration stored in the top-level section\n> + * `configuration' of the YAML configuration file.\n> + *\n> + * The requested part of the configuration can be accessed using \\a YamlObject\n> + * methods.\n> + *\n> + * \\note \\a YamlObject type itself shouldn't be used in type declarations to\n> + * avoid trouble if we decide to change the underlying data objects in future.\n> + *\n> + * \\return The whole configuration section\n> + */\n> +GlobalConfiguration::Configuration GlobalConfiguration::configuration() const\n> +{\n> +       return (*yamlConfiguration_)[\"configuration\"];\n> +}\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n> index b3ca27f21..5b9b86f21 100644\n> --- a/src/libcamera/meson.build\n> +++ b/src/libcamera/meson.build\n> @@ -31,6 +31,7 @@ libcamera_internal_sources = files([\n>      'device_enumerator_sysfs.cpp',\n>      'dma_buf_allocator.cpp',\n>      'formats.cpp',\n> +    'global_configuration.cpp',\n>      'ipa_controls.cpp',\n>      'ipa_data_serializer.cpp',\n>      'ipa_interface.cpp',\n> -- \n> 2.51.0\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 27875BDB13\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 11 Sep 2025 10:51:54 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0F0726936E;\n\tThu, 11 Sep 2025 12:51:53 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 9116369339\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 11 Sep 2025 12:51:51 +0200 (CEST)","from neptunite.rasen.tech (unknown\n\t[IPv6:2404:7a81:160:2100:5e49:1615:2276:f31c])\n\tby perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id 587DE666;\n\tThu, 11 Sep 2025 12:50:36 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"f3t3F3EX\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1757587836;\n\tbh=9yfwsQS6k2/RxDrmPv+q1lDijJREfZIE3JhmIYBgCMg=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=f3t3F3EXrxPakoN0Uyh4OZl34qkeUK73fy5DNiWXsuT36hfRSjOo/HKFxo5tfnVFh\n\tJkuafipRq9hp/9Up5zh+2rf+VNd0XmJctExfBIj6JMqLoduqi0BQv1iISraAbtpmnN\n\tUPNHK55xm6KYoRdi8JtrE/XUtIURRCqqaKexsDos=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20250911092945.16517-2-mzamazal@redhat.com>","References":"<20250911092945.16517-1-mzamazal@redhat.com>\n\t<20250911092945.16517-2-mzamazal@redhat.com>","Subject":"Re: [PATCH v17 01/12] config: Introduce global runtime configuration","From":"Paul Elder <paul.elder@ideasonboard.com>","Cc":"Milan Zamazal <mzamazal@redhat.com>, Kieran Bingham\n\t<kieran.bingham@ideasonboard.com>, =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?=\n\t<barnabas.pocze@ideasonboard.com>, Laurent Pinchart\n\t<laurent.pinchart@ideasonboard.com>","To":"Milan Zamazal <mzamazal@redhat.com>, libcamera-devel@lists.libcamera.org","Date":"Thu, 11 Sep 2025 19:51:44 +0900","Message-ID":"<175758790440.2127323.13182643782169415483@neptunite.rasen.tech>","User-Agent":"alot/0.0.0","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]