From ed16987d476e171b41dfcfa2e9fd77759c716eb1 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Sun, 7 Dec 2025 18:57:08 +0100 Subject: [PATCH] libutil: extract out AbstractConfig libutil config.{hh,cc} and libstore/globals.{hh,cc} contain so many interrelated things. Hopefully we can separate them out a little bit for clarity. Change-Id: Ib78fc46fe305d13aada8886e2e168d446a6a6964 --- lix/libutil/abstract-config.cc | 133 +++++++++++++++++++++++++++++++++ lix/libutil/abstract-config.hh | 90 ++++++++++++++++++++++ lix/libutil/config.cc | 102 ------------------------- lix/libutil/config.hh | 65 +--------------- lix/libutil/meson.build | 2 + 5 files changed, 226 insertions(+), 166 deletions(-) create mode 100644 lix/libutil/abstract-config.cc create mode 100644 lix/libutil/abstract-config.hh diff --git a/lix/libutil/abstract-config.cc b/lix/libutil/abstract-config.cc new file mode 100644 index 000000000..17ea5cd6d --- /dev/null +++ b/lix/libutil/abstract-config.cc @@ -0,0 +1,133 @@ +#include "lix/libutil/abstract-config.hh" +#include "lix/libutil/file-system.hh" +#include "lix/libutil/logging.hh" +#include "lix/libutil/strings.hh" + +namespace nix { + +static void applyConfigInner( + const std::string & contents, + const ApplyConfigOptions & options, + std::vector> & parsedContents +) +{ + unsigned int pos = 0; + + while (pos < contents.size()) { + std::string line; + while (pos < contents.size() && contents[pos] != '\n') { + line += contents[pos++]; + } + pos++; + + if (auto hash = line.find('#'); hash != line.npos) { + line = std::string(line, 0, hash); + } + + auto tokens = tokenizeString>(line); + if (tokens.empty()) { + continue; + } + + if (tokens.size() < 2) { + throw UsageError( + "illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay() + ); + } + + auto include = false; + auto ignoreMissing = false; + if (tokens[0] == "include") { + include = true; + } else if (tokens[0] == "!include") { + include = true; + ignoreMissing = true; + } + + if (include) { + if (tokens.size() != 2) { + throw UsageError( + "illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay() + ); + } + if (!options.path) { + throw UsageError("can only include configuration '%1%' from files", tokens[1]); + } + auto pathToInclude = absPath(tildePath(tokens[1], options.home), dirOf(*options.path)); + if (pathExists(pathToInclude)) { + auto includeOptions = ApplyConfigOptions{ + .path = pathToInclude, + .home = options.home, + }; + try { + std::string includedContents = readFile(pathToInclude); + applyConfigInner(includedContents, includeOptions, parsedContents); + } catch (SysError &) { + // TODO: Do we actually want to ignore this? Or is it better to fail? + } + } else if (!ignoreMissing) { + throw Error( + "file '%1%' included from '%2%' not found", pathToInclude, *options.path + ); + } + continue; + } + + if (tokens[1] != "=") { + throw UsageError( + "illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay() + ); + } + + std::string name = std::move(tokens[0]); + + auto i = tokens.begin(); + advance(i, 2); + + parsedContents.push_back({ + std::move(name), + concatStringsSep(" ", Strings(i, tokens.end())), + }); + }; +} + +AbstractConfig::AbstractConfig(StringMap initials) : unknownSettings(std::move(initials)) {} + +void AbstractConfig::applyConfig(const std::string & contents, const ApplyConfigOptions & options) +{ + std::vector> parsedContents; + + applyConfigInner(contents, options, parsedContents); + + // First apply experimental-feature related settings + for (const auto & [name, value] : parsedContents) { + if (name == "experimental-features" || name == "extra-experimental-features") { + set(name, value, options); + } + } + + // Then apply other settings + for (const auto & [name, value] : parsedContents) { + if (name != "experimental-features" && name != "extra-experimental-features") { + set(name, value, options); + } + } +} + +void AbstractConfig::warnUnknownSettings() +{ + for (const auto & s : unknownSettings) { + printTaggedWarning("unknown setting '%s'", s.first); + } +} + +void AbstractConfig::reapplyUnknownSettings() +{ + auto unknownSettings2 = std::move(unknownSettings); + unknownSettings = {}; + for (auto const & [name, value] : unknownSettings2) { + set(name, value); + } +} + +} diff --git a/lix/libutil/abstract-config.hh b/lix/libutil/abstract-config.hh new file mode 100644 index 000000000..28febc4b2 --- /dev/null +++ b/lix/libutil/abstract-config.hh @@ -0,0 +1,90 @@ +#pragma once +///@file + +#include "lix/libutil/apply-config-options.hh" +#include "lix/libutil/json-fwd.hh" +#include "lix/libutil/types.hh" + +#include + +namespace nix { + +class Args; +class AbstractSetting; + +class AbstractConfig +{ + // Types. +public: + struct SettingInfo + { + std::string value; + std::string description; + }; + + // Fields. +protected: + StringMap unknownSettings; + + // Specials. +protected: + AbstractConfig(StringMap initials = {}); + + // Abstract methods. +public: + /** + * Sets the value referenced by `name` to `value`. Returns true if the + * setting is known, false otherwise. + */ + virtual bool set( + const std::string & name, + const std::string & value, + const ApplyConfigOptions & options = {} + ) = 0; + + /** + * Adds the currently known settings to the given result map `res`. + * - res: map to store settings in + * - overriddenOnly: when set to true only overridden settings will be added to `res` + */ + virtual void getSettings(std::map & res, bool overriddenOnly = false) = 0; + + /** + * Resets the `overridden` flag of all Settings + */ + virtual void resetOverridden() = 0; + + /** + * Outputs all settings to JSON + * - out: JSONObject to write the configuration to + */ + virtual JSON toJSON() = 0; + + /** + * Converts settings to `Args` to be used on the command line interface + * - args: args to write to + * - category: category of the settings + */ + virtual void convertToArgs(Args & args, const std::string & category) = 0; + + // Provided methods. +public: + /** + * Parses the configuration in `contents` and applies it + * - contents: configuration contents to be parsed and applied + * - path: location of the configuration file + */ + void applyConfig(const std::string & contents, const ApplyConfigOptions & options = {}); + + /** + * Logs a warning for each unregistered setting + */ + void warnUnknownSettings(); + + /** + * Re-applies all previously attempted changes to unknown settings + */ + void reapplyUnknownSettings(); +}; + +} diff --git a/lix/libutil/config.cc b/lix/libutil/config.cc index 2489d5573..9baf6a978 100644 --- a/lix/libutil/config.cc +++ b/lix/libutil/config.cc @@ -66,24 +66,6 @@ void Config::addSetting(AbstractSetting * setting) } } -AbstractConfig::AbstractConfig(StringMap initials) - : unknownSettings(std::move(initials)) -{ } - -void AbstractConfig::warnUnknownSettings() -{ - for (const auto & s : unknownSettings) - printTaggedWarning("unknown setting '%s'", s.first); -} - -void AbstractConfig::reapplyUnknownSettings() -{ - auto unknownSettings2 = std::move(unknownSettings); - unknownSettings = {}; - for (auto & s : unknownSettings2) - set(s.first, s.second); -} - void Config::getSettings(std::map & res, bool overriddenOnly) { for (const auto & opt : _settings) @@ -91,90 +73,6 @@ void Config::getSettings(std::map & res, bool overridd res.emplace(opt.first, SettingInfo{opt.second.setting->to_string(), opt.second.setting->description}); } - -static void applyConfigInner(const std::string & contents, const ApplyConfigOptions & options, std::vector> & parsedContents) { - unsigned int pos = 0; - - while (pos < contents.size()) { - std::string line; - while (pos < contents.size() && contents[pos] != '\n') - line += contents[pos++]; - pos++; - - if (auto hash = line.find('#'); hash != line.npos) - line = std::string(line, 0, hash); - - auto tokens = tokenizeString>(line); - if (tokens.empty()) continue; - - if (tokens.size() < 2) - throw UsageError("illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay()); - - auto include = false; - auto ignoreMissing = false; - if (tokens[0] == "include") - include = true; - else if (tokens[0] == "!include") { - include = true; - ignoreMissing = true; - } - - if (include) { - if (tokens.size() != 2) { - throw UsageError("illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay()); - } - if (!options.path) { - throw UsageError("can only include configuration '%1%' from files", tokens[1]); - } - auto pathToInclude = absPath(tildePath(tokens[1], options.home), dirOf(*options.path)); - if (pathExists(pathToInclude)) { - auto includeOptions = ApplyConfigOptions { - .path = pathToInclude, - .home = options.home, - }; - try { - std::string includedContents = readFile(pathToInclude); - applyConfigInner(includedContents, includeOptions, parsedContents); - } catch (SysError &) { - // TODO: Do we actually want to ignore this? Or is it better to fail? - } - } else if (!ignoreMissing) { - throw Error("file '%1%' included from '%2%' not found", pathToInclude, *options.path); - } - continue; - } - - if (tokens[1] != "=") - throw UsageError("illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay()); - - std::string name = std::move(tokens[0]); - - auto i = tokens.begin(); - advance(i, 2); - - parsedContents.push_back({ - std::move(name), - concatStringsSep(" ", Strings(i, tokens.end())), - }); - }; -} - -void AbstractConfig::applyConfig(const std::string & contents, const ApplyConfigOptions & options) { - std::vector> parsedContents; - - applyConfigInner(contents, options, parsedContents); - - // First apply experimental-feature related settings - for (const auto & [name, value] : parsedContents) - if (name == "experimental-features" || name == "extra-experimental-features") - set(name, value, options); - - // Then apply other settings - for (const auto & [name, value] : parsedContents) - if (name != "experimental-features" && name != "extra-experimental-features") - set(name, value, options); -} - void Config::resetOverridden() { for (auto & s : _settings) diff --git a/lix/libutil/config.hh b/lix/libutil/config.hh index 598ecdb51..a5a223053 100644 --- a/lix/libutil/config.hh +++ b/lix/libutil/config.hh @@ -5,6 +5,7 @@ #include #include +#include "lix/libutil/abstract-config.hh" #include "lix/libutil/json-fwd.hh" #include "lix/libutil/types.hh" #include "lix/libutil/experimental-features.hh" @@ -48,70 +49,6 @@ namespace nix { class Args; class AbstractSetting; -class AbstractConfig -{ -protected: - StringMap unknownSettings; - - AbstractConfig(StringMap initials = {}); - -public: - - /** - * Sets the value referenced by `name` to `value`. Returns true if the - * setting is known, false otherwise. - */ - virtual bool set(const std::string & name, const std::string & value, const ApplyConfigOptions & options = {}) = 0; - - struct SettingInfo - { - std::string value; - std::string description; - }; - - /** - * Adds the currently known settings to the given result map `res`. - * - res: map to store settings in - * - overriddenOnly: when set to true only overridden settings will be added to `res` - */ - virtual void getSettings(std::map & res, bool overriddenOnly = false) = 0; - - /** - * Parses the configuration in `contents` and applies it - * - contents: configuration contents to be parsed and applied - * - path: location of the configuration file - */ - void applyConfig(const std::string & contents, const ApplyConfigOptions & options = {}); - - /** - * Resets the `overridden` flag of all Settings - */ - virtual void resetOverridden() = 0; - - /** - * Outputs all settings to JSON - * - out: JSONObject to write the configuration to - */ - virtual JSON toJSON() = 0; - - /** - * Converts settings to `Args` to be used on the command line interface - * - args: args to write to - * - category: category of the settings - */ - virtual void convertToArgs(Args & args, const std::string & category) = 0; - - /** - * Logs a warning for each unregistered setting - */ - void warnUnknownSettings(); - - /** - * Re-applies all previously attempted changes to unknown settings - */ - void reapplyUnknownSettings(); -}; - /** * A class to simplify providing configuration settings. The typical * use is to inherit Config and add Setting members: diff --git a/lix/libutil/meson.build b/lix/libutil/meson.build index 4b8334c73..ee2b6991a 100644 --- a/lix/libutil/meson.build +++ b/lix/libutil/meson.build @@ -1,5 +1,6 @@ libutil_sources = files( # keep-sorted start + 'abstract-config.cc', 'archive.cc', 'args.cc', 'async-io.cc', @@ -58,6 +59,7 @@ libutil_sources = files( libutil_headers = files( # keep-sorted start + 'abstract-config.hh', 'abstract-setting-to-json.hh', 'ansicolor.hh', 'apply-config-options.hh',