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
This commit is contained in:
Qyriad
2026-01-06 16:35:49 +01:00
parent 03780ad850
commit ed16987d47
5 changed files with 226 additions and 166 deletions
+133
View File
@@ -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<std::pair<std::string, std::string>> & 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<std::vector<std::string>>(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<std::pair<std::string, std::string>> 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);
}
}
}
+90
View File
@@ -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 <string>
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<std::string, SettingInfo> & 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();
};
}
-102
View File
@@ -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<std::string, SettingInfo> & res, bool overriddenOnly)
{
for (const auto & opt : _settings)
@@ -91,90 +73,6 @@ void Config::getSettings(std::map<std::string, SettingInfo> & 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<std::pair<std::string, std::string>> & 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<std::vector<std::string>>(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<std::pair<std::string, std::string>> 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)
+1 -64
View File
@@ -5,6 +5,7 @@
#include <map>
#include <set>
#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<std::string, SettingInfo> & 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<T> members:
+2
View File
@@ -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',