forked from lix-project/lix
Merge changes I5566a985,I88cf53d3 into main
* changes: Support relative and `~/` paths in config settings Thread `ApplyConfigOptions` through config parsing
This commit is contained in:
commit
02eb07cfd5
30
doc/manual/rl-next/relative-and-tilde-paths-in-config.md
Normal file
30
doc/manual/rl-next/relative-and-tilde-paths-in-config.md
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
---
|
||||||
|
synopsis: Relative and tilde paths in configuration
|
||||||
|
issues: [fj#482]
|
||||||
|
cls: [1851, 1863, 1864]
|
||||||
|
category: Features
|
||||||
|
credits: [9999years]
|
||||||
|
---
|
||||||
|
|
||||||
|
[Configuration settings](@docroot@/command-ref/conf-file.md) can now refer to
|
||||||
|
files with paths relative to the file they're written in or relative to your
|
||||||
|
home directory (with `~/`).
|
||||||
|
|
||||||
|
This makes settings like
|
||||||
|
[`repl-overlays`](@docroot@/command-ref/conf-file.md#conf-repl-overlays) and
|
||||||
|
[`secret-key-files`](@docroot@/command-ref/conf-file.md#conf-repl-overlays)
|
||||||
|
much easier to set, especially if you'd like to refer to files in an existing
|
||||||
|
dotfiles repo cloned into your home directory.
|
||||||
|
|
||||||
|
If you put `repl-overlays = repl.nix` in your `~/.config/nix/nix.conf`, it'll
|
||||||
|
load `~/.config/nix/repl.nix`. Similarly, you can set `repl-overlays =
|
||||||
|
~/.dotfiles/repl.nix` to load a file relative to your home directory.
|
||||||
|
|
||||||
|
Configuration files can also
|
||||||
|
[`include`](@docroot@/command-ref/conf-file.md#file-format) paths relative to
|
||||||
|
your home directory.
|
||||||
|
|
||||||
|
Only user configuration files (like `$XDG_CONFIG_HOME/nix/nix.conf` or the
|
||||||
|
files listed in `$NIX_USER_CONF_FILES`) can use tilde paths relative to your
|
||||||
|
home directory. Configuration listed in the `$NIX_CONFIG` environment variable
|
||||||
|
may not use relative paths.
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
template<> AcceptFlakeConfig BaseSetting<AcceptFlakeConfig>::parse(const std::string & str) const
|
template<> AcceptFlakeConfig BaseSetting<AcceptFlakeConfig>::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
if (str == "true") return AcceptFlakeConfig::True;
|
if (str == "true") return AcceptFlakeConfig::True;
|
||||||
else if (str == "ask") return AcceptFlakeConfig::Ask;
|
else if (str == "ask") return AcceptFlakeConfig::Ask;
|
||||||
|
|
|
@ -126,29 +126,30 @@ Settings::Settings()
|
||||||
|
|
||||||
void loadConfFile()
|
void loadConfFile()
|
||||||
{
|
{
|
||||||
auto applyConfigFile = [&](const Path & path) {
|
auto applyConfigFile = [&](const ApplyConfigOptions & options) {
|
||||||
try {
|
try {
|
||||||
std::string contents = readFile(path);
|
std::string contents = readFile(*options.path);
|
||||||
globalConfig.applyConfig(contents, path);
|
globalConfig.applyConfig(contents, options);
|
||||||
} catch (SysError &) { }
|
} catch (SysError &) {
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
applyConfigFile(settings.nixConfDir + "/nix.conf");
|
applyConfigFile(ApplyConfigOptions{.path = settings.nixConfDir + "/nix.conf"});
|
||||||
|
|
||||||
/* We only want to send overrides to the daemon, i.e. stuff from
|
/* We only want to send overrides to the daemon, i.e. stuff from
|
||||||
~/.nix/nix.conf or the command line. */
|
~/.nix/nix.conf or the command line. */
|
||||||
globalConfig.resetOverridden();
|
globalConfig.resetOverridden();
|
||||||
|
|
||||||
auto files = settings.nixUserConfFiles;
|
auto files = settings.nixUserConfFiles;
|
||||||
|
auto home = getHome();
|
||||||
for (auto file = files.rbegin(); file != files.rend(); file++) {
|
for (auto file = files.rbegin(); file != files.rend(); file++) {
|
||||||
applyConfigFile(*file);
|
applyConfigFile(ApplyConfigOptions{.path = *file, .home = home});
|
||||||
}
|
}
|
||||||
|
|
||||||
auto nixConfEnv = getEnv("NIX_CONFIG");
|
auto nixConfEnv = getEnv("NIX_CONFIG");
|
||||||
if (nixConfEnv.has_value()) {
|
if (nixConfEnv.has_value()) {
|
||||||
globalConfig.applyConfig(nixConfEnv.value(), "NIX_CONFIG");
|
globalConfig.applyConfig(nixConfEnv.value(), ApplyConfigOptions{.fromEnvVar = true});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Path> getUserConfigFiles()
|
std::vector<Path> getUserConfigFiles()
|
||||||
|
@ -274,7 +275,7 @@ NLOHMANN_JSON_SERIALIZE_ENUM(SandboxMode, {
|
||||||
{SandboxMode::smDisabled, false},
|
{SandboxMode::smDisabled, false},
|
||||||
});
|
});
|
||||||
|
|
||||||
template<> SandboxMode BaseSetting<SandboxMode>::parse(const std::string & str) const
|
template<> SandboxMode BaseSetting<SandboxMode>::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
if (str == "true") return smEnabled;
|
if (str == "true") return smEnabled;
|
||||||
else if (str == "relaxed") return smRelaxed;
|
else if (str == "relaxed") return smRelaxed;
|
||||||
|
@ -317,7 +318,7 @@ template<> void BaseSetting<SandboxMode>::convertToArg(Args & args, const std::s
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int MaxBuildJobsSetting::parse(const std::string & str) const
|
unsigned int MaxBuildJobsSetting::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
if (str == "auto") return std::max(1U, std::thread::hardware_concurrency());
|
if (str == "auto") return std::max(1U, std::thread::hardware_concurrency());
|
||||||
else {
|
else {
|
||||||
|
@ -326,14 +327,14 @@ unsigned int MaxBuildJobsSetting::parse(const std::string & str) const
|
||||||
else
|
else
|
||||||
throw UsageError("configuration setting '%s' should be 'auto' or an integer", name);
|
throw UsageError("configuration setting '%s' should be 'auto' or an integer", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Paths PluginFilesSetting::parse(const std::string & str) const
|
Paths PluginFilesSetting::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
if (pluginsLoaded)
|
if (pluginsLoaded)
|
||||||
throw UsageError("plugin-files set after plugins were loaded, you may need to move the flag before the subcommand");
|
throw UsageError("plugin-files set after plugins were loaded, you may need to move the flag before the subcommand");
|
||||||
return BaseSetting<Paths>::parse(str);
|
return BaseSetting<Paths>::parse(str, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ struct MaxBuildJobsSetting : public BaseSetting<unsigned int>
|
||||||
options->addSetting(this);
|
options->addSetting(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int parse(const std::string & str) const override;
|
unsigned int parse(const std::string & str, const ApplyConfigOptions & options) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PluginFilesSetting : public BaseSetting<Paths>
|
struct PluginFilesSetting : public BaseSetting<Paths>
|
||||||
|
@ -43,7 +43,7 @@ struct PluginFilesSetting : public BaseSetting<Paths>
|
||||||
options->addSetting(this);
|
options->addSetting(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
Paths parse(const std::string & str) const override;
|
Paths parse(const std::string & str, const ApplyConfigOptions & options) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
const uint32_t maxIdsPerBuild =
|
const uint32_t maxIdsPerBuild =
|
||||||
|
@ -1088,6 +1088,7 @@ void loadConfFile();
|
||||||
|
|
||||||
// Used by the Settings constructor
|
// Used by the Settings constructor
|
||||||
std::vector<Path> getUserConfigFiles();
|
std::vector<Path> getUserConfigFiles();
|
||||||
|
std::vector<Path> getHomeConfigFile();
|
||||||
|
|
||||||
extern const std::string nixVersion;
|
extern const std::string nixVersion;
|
||||||
|
|
||||||
|
|
|
@ -51,14 +51,14 @@ bool BaseSetting<T>::isAppendable()
|
||||||
return trait::appendable;
|
return trait::appendable;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> void BaseSetting<Strings>::appendOrSet(Strings newValue, bool append);
|
template<> void BaseSetting<Strings>::appendOrSet(Strings newValue, bool append, const ApplyConfigOptions & options);
|
||||||
template<> void BaseSetting<StringSet>::appendOrSet(StringSet newValue, bool append);
|
template<> void BaseSetting<StringSet>::appendOrSet(StringSet newValue, bool append, const ApplyConfigOptions & options);
|
||||||
template<> void BaseSetting<StringMap>::appendOrSet(StringMap newValue, bool append);
|
template<> void BaseSetting<StringMap>::appendOrSet(StringMap newValue, bool append, const ApplyConfigOptions & options);
|
||||||
template<> void BaseSetting<ExperimentalFeatures>::appendOrSet(ExperimentalFeatures newValue, bool append);
|
template<> void BaseSetting<ExperimentalFeatures>::appendOrSet(ExperimentalFeatures newValue, bool append, const ApplyConfigOptions & options);
|
||||||
template<> void BaseSetting<DeprecatedFeatures>::appendOrSet(DeprecatedFeatures newValue, bool append);
|
template<> void BaseSetting<DeprecatedFeatures>::appendOrSet(DeprecatedFeatures newValue, bool append, const ApplyConfigOptions & options);
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void BaseSetting<T>::appendOrSet(T newValue, bool append)
|
void BaseSetting<T>::appendOrSet(T newValue, bool append, const ApplyConfigOptions & options)
|
||||||
{
|
{
|
||||||
static_assert(
|
static_assert(
|
||||||
!trait::appendable,
|
!trait::appendable,
|
||||||
|
@ -69,14 +69,14 @@ void BaseSetting<T>::appendOrSet(T newValue, bool append)
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void BaseSetting<T>::set(const std::string & str, bool append)
|
void BaseSetting<T>::set(const std::string & str, bool append, const ApplyConfigOptions & options)
|
||||||
{
|
{
|
||||||
if (experimentalFeatureSettings.isEnabled(experimentalFeature)) {
|
if (experimentalFeatureSettings.isEnabled(experimentalFeature)) {
|
||||||
auto parsed = parse(str);
|
auto parsed = parse(str, options);
|
||||||
if (deprecated && (append || parsed != value)) {
|
if (deprecated && (append || parsed != value)) {
|
||||||
warn("deprecated setting '%s' found (set to '%s')", name, str);
|
warn("deprecated setting '%s' found (set to '%s')", name, str);
|
||||||
}
|
}
|
||||||
appendOrSet(std::move(parsed), append);
|
appendOrSet(std::move(parsed), append, options);
|
||||||
} else {
|
} else {
|
||||||
assert(experimentalFeature);
|
assert(experimentalFeature);
|
||||||
warn("Ignoring setting '%s' because experimental feature '%s' is not enabled",
|
warn("Ignoring setting '%s' because experimental feature '%s' is not enabled",
|
||||||
|
@ -111,7 +111,7 @@ void BaseSetting<T>::convertToArg(Args & args, const std::string & category)
|
||||||
}
|
}
|
||||||
|
|
||||||
#define DECLARE_CONFIG_SERIALISER(TY) \
|
#define DECLARE_CONFIG_SERIALISER(TY) \
|
||||||
template<> TY BaseSetting< TY >::parse(const std::string & str) const; \
|
template<> TY BaseSetting< TY >::parse(const std::string & str, const ApplyConfigOptions & options) const; \
|
||||||
template<> std::string BaseSetting< TY >::to_string() const;
|
template<> std::string BaseSetting< TY >::to_string() const;
|
||||||
|
|
||||||
DECLARE_CONFIG_SERIALISER(std::string)
|
DECLARE_CONFIG_SERIALISER(std::string)
|
||||||
|
@ -124,7 +124,7 @@ DECLARE_CONFIG_SERIALISER(ExperimentalFeatures)
|
||||||
DECLARE_CONFIG_SERIALISER(DeprecatedFeatures)
|
DECLARE_CONFIG_SERIALISER(DeprecatedFeatures)
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
T BaseSetting<T>::parse(const std::string & str) const
|
T BaseSetting<T>::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
static_assert(std::is_integral<T>::value, "Integer required.");
|
static_assert(std::is_integral<T>::value, "Integer required.");
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "config.hh"
|
#include "config.hh"
|
||||||
|
#include "apply-config-options.hh"
|
||||||
#include "args.hh"
|
#include "args.hh"
|
||||||
#include "abstract-setting-to-json.hh"
|
#include "abstract-setting-to-json.hh"
|
||||||
#include "experimental-features.hh"
|
#include "experimental-features.hh"
|
||||||
|
@ -17,7 +18,7 @@ Config::Config(StringMap initials)
|
||||||
: AbstractConfig(std::move(initials))
|
: AbstractConfig(std::move(initials))
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
bool Config::set(const std::string & name, const std::string & value)
|
bool Config::set(const std::string & name, const std::string & value, const ApplyConfigOptions & options)
|
||||||
{
|
{
|
||||||
bool append = false;
|
bool append = false;
|
||||||
auto i = _settings.find(name);
|
auto i = _settings.find(name);
|
||||||
|
@ -30,7 +31,7 @@ bool Config::set(const std::string & name, const std::string & value)
|
||||||
} else
|
} else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
i->second.setting->set(value, append);
|
i->second.setting->set(value, append, options);
|
||||||
i->second.setting->overridden = true;
|
i->second.setting->overridden = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -91,7 +92,7 @@ void Config::getSettings(std::map<std::string, SettingInfo> & res, bool overridd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void applyConfigInner(const std::string & contents, const std::string & path, std::vector<std::pair<std::string, std::string>> & parsedContents) {
|
static void applyConfigInner(const std::string & contents, const ApplyConfigOptions & options, std::vector<std::pair<std::string, std::string>> & parsedContents) {
|
||||||
unsigned int pos = 0;
|
unsigned int pos = 0;
|
||||||
|
|
||||||
while (pos < contents.size()) {
|
while (pos < contents.size()) {
|
||||||
|
@ -107,7 +108,7 @@ static void applyConfigInner(const std::string & contents, const std::string & p
|
||||||
if (tokens.empty()) continue;
|
if (tokens.empty()) continue;
|
||||||
|
|
||||||
if (tokens.size() < 2)
|
if (tokens.size() < 2)
|
||||||
throw UsageError("illegal configuration line '%1%' in '%2%'", line, path);
|
throw UsageError("illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay());
|
||||||
|
|
||||||
auto include = false;
|
auto include = false;
|
||||||
auto ignoreMissing = false;
|
auto ignoreMissing = false;
|
||||||
|
@ -119,24 +120,32 @@ static void applyConfigInner(const std::string & contents, const std::string & p
|
||||||
}
|
}
|
||||||
|
|
||||||
if (include) {
|
if (include) {
|
||||||
if (tokens.size() != 2)
|
if (tokens.size() != 2) {
|
||||||
throw UsageError("illegal configuration line '%1%' in '%2%'", line, path);
|
throw UsageError("illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay());
|
||||||
auto p = absPath(tokens[1], dirOf(path));
|
}
|
||||||
if (pathExists(p)) {
|
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 {
|
try {
|
||||||
std::string includedContents = readFile(path);
|
std::string includedContents = readFile(pathToInclude);
|
||||||
applyConfigInner(includedContents, p, parsedContents);
|
applyConfigInner(includedContents, includeOptions, parsedContents);
|
||||||
} catch (SysError &) {
|
} catch (SysError &) {
|
||||||
// TODO: Do we actually want to ignore this? Or is it better to fail?
|
// TODO: Do we actually want to ignore this? Or is it better to fail?
|
||||||
}
|
}
|
||||||
} else if (!ignoreMissing) {
|
} else if (!ignoreMissing) {
|
||||||
throw Error("file '%1%' included from '%2%' not found", p, path);
|
throw Error("file '%1%' included from '%2%' not found", pathToInclude, *options.path);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokens[1] != "=")
|
if (tokens[1] != "=")
|
||||||
throw UsageError("illegal configuration line '%1%' in '%2%'", line, path);
|
throw UsageError("illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay());
|
||||||
|
|
||||||
std::string name = std::move(tokens[0]);
|
std::string name = std::move(tokens[0]);
|
||||||
|
|
||||||
|
@ -150,20 +159,20 @@ static void applyConfigInner(const std::string & contents, const std::string & p
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractConfig::applyConfig(const std::string & contents, const std::string & path) {
|
void AbstractConfig::applyConfig(const std::string & contents, const ApplyConfigOptions & options) {
|
||||||
std::vector<std::pair<std::string, std::string>> parsedContents;
|
std::vector<std::pair<std::string, std::string>> parsedContents;
|
||||||
|
|
||||||
applyConfigInner(contents, path, parsedContents);
|
applyConfigInner(contents, options, parsedContents);
|
||||||
|
|
||||||
// First apply experimental-feature related settings
|
// First apply experimental-feature related settings
|
||||||
for (const auto & [name, value] : parsedContents)
|
for (const auto & [name, value] : parsedContents)
|
||||||
if (name == "experimental-features" || name == "extra-experimental-features")
|
if (name == "experimental-features" || name == "extra-experimental-features")
|
||||||
set(name, value);
|
set(name, value, options);
|
||||||
|
|
||||||
// Then apply other settings
|
// Then apply other settings
|
||||||
for (const auto & [name, value] : parsedContents)
|
for (const auto & [name, value] : parsedContents)
|
||||||
if (name != "experimental-features" && name != "extra-experimental-features")
|
if (name != "experimental-features" && name != "extra-experimental-features")
|
||||||
set(name, value);
|
set(name, value, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::resetOverridden()
|
void Config::resetOverridden()
|
||||||
|
@ -241,7 +250,7 @@ void AbstractSetting::convertToArg(Args & args, const std::string & category)
|
||||||
|
|
||||||
bool AbstractSetting::isOverridden() const { return overridden; }
|
bool AbstractSetting::isOverridden() const { return overridden; }
|
||||||
|
|
||||||
template<> std::string BaseSetting<std::string>::parse(const std::string & str) const
|
template<> std::string BaseSetting<std::string>::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
@ -251,7 +260,7 @@ template<> std::string BaseSetting<std::string>::to_string() const
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> std::optional<std::string> BaseSetting<std::optional<std::string>>::parse(const std::string & str) const
|
template<> std::optional<std::string> BaseSetting<std::optional<std::string>>::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
if (str == "")
|
if (str == "")
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
@ -264,7 +273,7 @@ template<> std::string BaseSetting<std::optional<std::string>>::to_string() cons
|
||||||
return value ? *value : "";
|
return value ? *value : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> bool BaseSetting<bool>::parse(const std::string & str) const
|
template<> bool BaseSetting<bool>::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
if (str == "true" || str == "yes" || str == "1")
|
if (str == "true" || str == "yes" || str == "1")
|
||||||
return true;
|
return true;
|
||||||
|
@ -297,12 +306,12 @@ template<> void BaseSetting<bool>::convertToArg(Args & args, const std::string &
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> Strings BaseSetting<Strings>::parse(const std::string & str) const
|
template<> Strings BaseSetting<Strings>::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
return tokenizeString<Strings>(str);
|
return tokenizeString<Strings>(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> void BaseSetting<Strings>::appendOrSet(Strings newValue, bool append)
|
template<> void BaseSetting<Strings>::appendOrSet(Strings newValue, bool append, const ApplyConfigOptions & options)
|
||||||
{
|
{
|
||||||
if (!append) value.clear();
|
if (!append) value.clear();
|
||||||
value.insert(value.end(), std::make_move_iterator(newValue.begin()),
|
value.insert(value.end(), std::make_move_iterator(newValue.begin()),
|
||||||
|
@ -314,12 +323,12 @@ template<> std::string BaseSetting<Strings>::to_string() const
|
||||||
return concatStringsSep(" ", value);
|
return concatStringsSep(" ", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> StringSet BaseSetting<StringSet>::parse(const std::string & str) const
|
template<> StringSet BaseSetting<StringSet>::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
return tokenizeString<StringSet>(str);
|
return tokenizeString<StringSet>(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> void BaseSetting<StringSet>::appendOrSet(StringSet newValue, bool append)
|
template<> void BaseSetting<StringSet>::appendOrSet(StringSet newValue, bool append, const ApplyConfigOptions & options)
|
||||||
{
|
{
|
||||||
if (!append) value.clear();
|
if (!append) value.clear();
|
||||||
value.insert(std::make_move_iterator(newValue.begin()), std::make_move_iterator(newValue.end()));
|
value.insert(std::make_move_iterator(newValue.begin()), std::make_move_iterator(newValue.end()));
|
||||||
|
@ -330,7 +339,7 @@ template<> std::string BaseSetting<StringSet>::to_string() const
|
||||||
return concatStringsSep(" ", value);
|
return concatStringsSep(" ", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> ExperimentalFeatures BaseSetting<ExperimentalFeatures>::parse(const std::string & str) const
|
template<> ExperimentalFeatures BaseSetting<ExperimentalFeatures>::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
ExperimentalFeatures res{};
|
ExperimentalFeatures res{};
|
||||||
for (auto & s : tokenizeString<StringSet>(str)) {
|
for (auto & s : tokenizeString<StringSet>(str)) {
|
||||||
|
@ -342,7 +351,7 @@ template<> ExperimentalFeatures BaseSetting<ExperimentalFeatures>::parse(const s
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> void BaseSetting<ExperimentalFeatures>::appendOrSet(ExperimentalFeatures newValue, bool append)
|
template<> void BaseSetting<ExperimentalFeatures>::appendOrSet(ExperimentalFeatures newValue, bool append, const ApplyConfigOptions & options)
|
||||||
{
|
{
|
||||||
if (append)
|
if (append)
|
||||||
value = value | newValue;
|
value = value | newValue;
|
||||||
|
@ -359,7 +368,7 @@ template<> std::string BaseSetting<ExperimentalFeatures>::to_string() const
|
||||||
return concatStringsSep(" ", stringifiedXpFeatures);
|
return concatStringsSep(" ", stringifiedXpFeatures);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> DeprecatedFeatures BaseSetting<DeprecatedFeatures>::parse(const std::string & str) const
|
template<> DeprecatedFeatures BaseSetting<DeprecatedFeatures>::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
DeprecatedFeatures res{};
|
DeprecatedFeatures res{};
|
||||||
for (auto & s : tokenizeString<StringSet>(str)) {
|
for (auto & s : tokenizeString<StringSet>(str)) {
|
||||||
|
@ -371,7 +380,7 @@ template<> DeprecatedFeatures BaseSetting<DeprecatedFeatures>::parse(const std::
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> void BaseSetting<DeprecatedFeatures>::appendOrSet(DeprecatedFeatures newValue, bool append)
|
template<> void BaseSetting<DeprecatedFeatures>::appendOrSet(DeprecatedFeatures newValue, bool append, const ApplyConfigOptions & options)
|
||||||
{
|
{
|
||||||
if (append)
|
if (append)
|
||||||
value = value | newValue;
|
value = value | newValue;
|
||||||
|
@ -388,7 +397,7 @@ template<> std::string BaseSetting<DeprecatedFeatures>::to_string() const
|
||||||
return concatStringsSep(" ", stringifiedDpFeatures);
|
return concatStringsSep(" ", stringifiedDpFeatures);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> StringMap BaseSetting<StringMap>::parse(const std::string & str) const
|
template<> StringMap BaseSetting<StringMap>::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
StringMap res;
|
StringMap res;
|
||||||
for (const auto & s : tokenizeString<Strings>(str)) {
|
for (const auto & s : tokenizeString<Strings>(str)) {
|
||||||
|
@ -399,7 +408,7 @@ template<> StringMap BaseSetting<StringMap>::parse(const std::string & str) cons
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> void BaseSetting<StringMap>::appendOrSet(StringMap newValue, bool append)
|
template<> void BaseSetting<StringMap>::appendOrSet(StringMap newValue, bool append, const ApplyConfigOptions & options)
|
||||||
{
|
{
|
||||||
if (!append) value.clear();
|
if (!append) value.clear();
|
||||||
value.insert(std::make_move_iterator(newValue.begin()), std::make_move_iterator(newValue.end()));
|
value.insert(std::make_move_iterator(newValue.begin()), std::make_move_iterator(newValue.end()));
|
||||||
|
@ -426,34 +435,40 @@ template class BaseSetting<StringMap>;
|
||||||
template class BaseSetting<ExperimentalFeatures>;
|
template class BaseSetting<ExperimentalFeatures>;
|
||||||
template class BaseSetting<DeprecatedFeatures>;
|
template class BaseSetting<DeprecatedFeatures>;
|
||||||
|
|
||||||
static Path parsePath(const AbstractSetting & s, const std::string & str)
|
static Path parsePath(const AbstractSetting & s, const std::string & str, const ApplyConfigOptions & options)
|
||||||
{
|
{
|
||||||
if (str == "")
|
if (str == "") {
|
||||||
throw UsageError("setting '%s' is a path and paths cannot be empty", s.name);
|
throw UsageError("setting '%s' is a path and paths cannot be empty", s.name);
|
||||||
else
|
} else {
|
||||||
return canonPath(str);
|
auto tildeResolvedPath = tildePath(str, options.home);
|
||||||
|
if (options.path) {
|
||||||
|
return absPath(tildeResolvedPath, dirOf(*options.path));
|
||||||
|
} else {
|
||||||
|
return canonPath(tildeResolvedPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> Path PathsSetting<Path>::parse(const std::string & str) const
|
template<> Path PathsSetting<Path>::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
return parsePath(*this, str);
|
return parsePath(*this, str, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> std::optional<Path> PathsSetting<std::optional<Path>>::parse(const std::string & str) const
|
template<> std::optional<Path> PathsSetting<std::optional<Path>>::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
if (str == "")
|
if (str == "")
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
else
|
else
|
||||||
return parsePath(*this, str);
|
return parsePath(*this, str, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> Paths PathsSetting<Paths>::parse(const std::string & str) const
|
template<> Paths PathsSetting<Paths>::parse(const std::string & str, const ApplyConfigOptions & options) const
|
||||||
{
|
{
|
||||||
auto strings = tokenizeString<Strings>(str);
|
auto strings = tokenizeString<Strings>(str);
|
||||||
Paths parsed;
|
Paths parsed;
|
||||||
|
|
||||||
for (auto str : strings) {
|
for (auto str : strings) {
|
||||||
parsed.push_back(canonPath(str));
|
parsed.push_back(parsePath(*this, str, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsed;
|
return parsed;
|
||||||
|
@ -464,10 +479,10 @@ template class PathsSetting<std::optional<Path>>;
|
||||||
template class PathsSetting<Paths>;
|
template class PathsSetting<Paths>;
|
||||||
|
|
||||||
|
|
||||||
bool GlobalConfig::set(const std::string & name, const std::string & value)
|
bool GlobalConfig::set(const std::string & name, const std::string & value, const ApplyConfigOptions & options)
|
||||||
{
|
{
|
||||||
for (auto & config : *configRegistrations)
|
for (auto & config : *configRegistrations)
|
||||||
if (config->set(name, value)) return true;
|
if (config->set(name, value, options)) return true;
|
||||||
|
|
||||||
unknownSettings.emplace(name, value);
|
unknownSettings.emplace(name, value);
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
#include "experimental-features.hh"
|
#include "experimental-features.hh"
|
||||||
#include "deprecated-features.hh"
|
#include "deprecated-features.hh"
|
||||||
|
#include "apply-config-options.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -61,7 +62,7 @@ public:
|
||||||
* Sets the value referenced by `name` to `value`. Returns true if the
|
* Sets the value referenced by `name` to `value`. Returns true if the
|
||||||
* setting is known, false otherwise.
|
* setting is known, false otherwise.
|
||||||
*/
|
*/
|
||||||
virtual bool set(const std::string & name, const std::string & value) = 0;
|
virtual bool set(const std::string & name, const std::string & value, const ApplyConfigOptions & options = {}) = 0;
|
||||||
|
|
||||||
struct SettingInfo
|
struct SettingInfo
|
||||||
{
|
{
|
||||||
|
@ -81,7 +82,7 @@ public:
|
||||||
* - contents: configuration contents to be parsed and applied
|
* - contents: configuration contents to be parsed and applied
|
||||||
* - path: location of the configuration file
|
* - path: location of the configuration file
|
||||||
*/
|
*/
|
||||||
void applyConfig(const std::string & contents, const std::string & path = "<unknown>");
|
void applyConfig(const std::string & contents, const ApplyConfigOptions & options = {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the `overridden` flag of all Settings
|
* Resets the `overridden` flag of all Settings
|
||||||
|
@ -155,7 +156,7 @@ public:
|
||||||
|
|
||||||
Config(StringMap initials = {});
|
Config(StringMap initials = {});
|
||||||
|
|
||||||
bool set(const std::string & name, const std::string & value) override;
|
bool set(const std::string & name, const std::string & value, const ApplyConfigOptions & options = {}) override;
|
||||||
|
|
||||||
void addSetting(AbstractSetting * setting);
|
void addSetting(AbstractSetting * setting);
|
||||||
|
|
||||||
|
@ -200,7 +201,7 @@ protected:
|
||||||
|
|
||||||
virtual ~AbstractSetting();
|
virtual ~AbstractSetting();
|
||||||
|
|
||||||
virtual void set(const std::string & value, bool append = false) = 0;
|
virtual void set(const std::string & value, bool append = false, const ApplyConfigOptions & options = {}) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the type is appendable; i.e. whether the `append`
|
* Whether the type is appendable; i.e. whether the `append`
|
||||||
|
@ -237,7 +238,7 @@ protected:
|
||||||
*
|
*
|
||||||
* Used by `set()`.
|
* Used by `set()`.
|
||||||
*/
|
*/
|
||||||
virtual T parse(const std::string & str) const;
|
virtual T parse(const std::string & str, const ApplyConfigOptions & options) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Append or overwrite `value` with `newValue`.
|
* Append or overwrite `value` with `newValue`.
|
||||||
|
@ -247,7 +248,7 @@ protected:
|
||||||
*
|
*
|
||||||
* @param append Whether to append or overwrite.
|
* @param append Whether to append or overwrite.
|
||||||
*/
|
*/
|
||||||
virtual void appendOrSet(T newValue, bool append);
|
virtual void appendOrSet(T newValue, bool append, const ApplyConfigOptions & options);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -284,7 +285,7 @@ public:
|
||||||
* Uses `parse()` to get the value from `str`, and `appendOrSet()`
|
* Uses `parse()` to get the value from `str`, and `appendOrSet()`
|
||||||
* to set it.
|
* to set it.
|
||||||
*/
|
*/
|
||||||
void set(const std::string & str, bool append = false) override final;
|
void set(const std::string & str, bool append = false, const ApplyConfigOptions & options = {}) override final;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* C++ trick; This is template-specialized to compile-time indicate whether
|
* C++ trick; This is template-specialized to compile-time indicate whether
|
||||||
|
@ -373,7 +374,7 @@ public:
|
||||||
options->addSetting(this);
|
options->addSetting(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
T parse(const std::string & str) const override;
|
T parse(const std::string & str, const ApplyConfigOptions & options) const override;
|
||||||
|
|
||||||
void operator =(const T & v) { this->assign(v); }
|
void operator =(const T & v) { this->assign(v); }
|
||||||
};
|
};
|
||||||
|
@ -384,7 +385,7 @@ struct GlobalConfig : public AbstractConfig
|
||||||
typedef std::vector<Config*> ConfigRegistrations;
|
typedef std::vector<Config*> ConfigRegistrations;
|
||||||
static ConfigRegistrations * configRegistrations;
|
static ConfigRegistrations * configRegistrations;
|
||||||
|
|
||||||
bool set(const std::string & name, const std::string & value) override;
|
bool set(const std::string & name, const std::string & value, const ApplyConfigOptions & options = {}) override;
|
||||||
|
|
||||||
void getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly = false) override;
|
void getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly = false) override;
|
||||||
|
|
||||||
|
|
|
@ -118,6 +118,21 @@ Path realPath(Path const & path)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Path tildePath(Path const & path, const std::optional<Path> & home)
|
||||||
|
{
|
||||||
|
if (path.starts_with("~/")) {
|
||||||
|
if (home) {
|
||||||
|
return *home + "/" + path.substr(2);
|
||||||
|
} else {
|
||||||
|
throw UsageError("`~` path not allowed: %1%", path);
|
||||||
|
}
|
||||||
|
} else if (path.starts_with('~')) {
|
||||||
|
throw UsageError("`~` paths must start with `~/`: %1%", path);
|
||||||
|
} else {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void chmodPath(const Path & path, mode_t mode)
|
void chmodPath(const Path & path, mode_t mode)
|
||||||
{
|
{
|
||||||
if (chmod(path.c_str(), mode) == -1)
|
if (chmod(path.c_str(), mode) == -1)
|
||||||
|
|
|
@ -62,6 +62,16 @@ Path canonPath(PathView path, bool resolveSymlinks = false);
|
||||||
*/
|
*/
|
||||||
Path realPath(Path const & path);
|
Path realPath(Path const & path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a tilde path like `~/puppy.nix` into an absolute path.
|
||||||
|
*
|
||||||
|
* If `home` is given, it's substituted for `~/` at the start of the input
|
||||||
|
* `path`. Otherwise, an error is thrown.
|
||||||
|
*
|
||||||
|
* If the path starts with `~` but not `~/`, an error is thrown.
|
||||||
|
*/
|
||||||
|
Path tildePath(Path const & path, const std::optional<Path> & home = std::nullopt);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the permissions of a path
|
* Change the permissions of a path
|
||||||
* Not called `chmod` as it shadows and could be confused with
|
* Not called `chmod` as it shadows and could be confused with
|
||||||
|
|
|
@ -80,7 +80,7 @@ namespace nix {
|
||||||
class TestSetting : public AbstractSetting {
|
class TestSetting : public AbstractSetting {
|
||||||
public:
|
public:
|
||||||
TestSetting() : AbstractSetting("test", "test", {}) {}
|
TestSetting() : AbstractSetting("test", "test", {}) {}
|
||||||
void set(const std::string & value, bool append) override {}
|
void set(const std::string & value, bool append, const ApplyConfigOptions & options) override {}
|
||||||
std::string to_string() const override { return {}; }
|
std::string to_string() const override { return {}; }
|
||||||
bool isAppendable() override { return false; }
|
bool isAppendable() override { return false; }
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,14 +11,13 @@ namespace nix {
|
||||||
class PathsSettingTestConfig : public Config
|
class PathsSettingTestConfig : public Config
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PathsSettingTestConfig()
|
PathsSettingTestConfig() : Config() {}
|
||||||
: Config()
|
|
||||||
{ }
|
|
||||||
|
|
||||||
PathsSetting<Paths> paths{this, Paths(), "paths", "documentation"};
|
PathsSetting<Paths> paths{this, Paths(), "paths", "documentation"};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PathsSettingTest : public ::testing::Test {
|
struct PathsSettingTest : public ::testing::Test
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
PathsSettingTestConfig mkConfig()
|
PathsSettingTestConfig mkConfig()
|
||||||
{
|
{
|
||||||
|
@ -26,61 +25,99 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(PathsSettingTest, parse) {
|
TEST_F(PathsSettingTest, parse)
|
||||||
|
{
|
||||||
auto config = mkConfig();
|
auto config = mkConfig();
|
||||||
// Not an absolute path:
|
// Not an absolute path:
|
||||||
ASSERT_THROW(config.paths.parse("puppy.nix"), Error);
|
ASSERT_THROW(config.paths.parse("puppy.nix", {}), Error);
|
||||||
|
|
||||||
ASSERT_THAT(
|
ASSERT_THAT(config.paths.parse("/puppy.nix", {}), Eq<Paths>({"/puppy.nix"}));
|
||||||
config.paths.parse("/puppy.nix"),
|
|
||||||
Eq<Paths>({"/puppy.nix"})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Splits on whitespace:
|
// Splits on whitespace:
|
||||||
ASSERT_THAT(
|
ASSERT_THAT(
|
||||||
config.paths.parse("/puppy.nix /doggy.nix"),
|
config.paths.parse("/puppy.nix /doggy.nix", {}), Eq<Paths>({"/puppy.nix", "/doggy.nix"})
|
||||||
Eq<Paths>({"/puppy.nix", "/doggy.nix"})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Splits on _any_ whitespace:
|
// Splits on _any_ whitespace:
|
||||||
ASSERT_THAT(
|
ASSERT_THAT(
|
||||||
config.paths.parse("/puppy.nix \t /doggy.nix\n\n\n/borzoi.nix\r/goldie.nix"),
|
config.paths.parse("/puppy.nix \t /doggy.nix\n\n\n/borzoi.nix\r/goldie.nix", {}),
|
||||||
Eq<Paths>({"/puppy.nix", "/doggy.nix", "/borzoi.nix", "/goldie.nix"})
|
Eq<Paths>({"/puppy.nix", "/doggy.nix", "/borzoi.nix", "/goldie.nix"})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Canonicizes paths:
|
// Canonicizes paths:
|
||||||
ASSERT_THAT(
|
ASSERT_THAT(config.paths.parse("/puppy/../doggy.nix", {}), Eq<Paths>({"/doggy.nix"}));
|
||||||
config.paths.parse("/puppy/../doggy.nix"),
|
|
||||||
Eq<Paths>({"/doggy.nix"})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(PathsSettingTest, append) {
|
TEST_F(PathsSettingTest, parseRelative)
|
||||||
|
{
|
||||||
|
auto options = ApplyConfigOptions{.path = "/doggy/kinds/config.nix"};
|
||||||
|
auto config = mkConfig();
|
||||||
|
ASSERT_THAT(
|
||||||
|
config.paths.parse("puppy.nix", options),
|
||||||
|
Eq<Paths>({"/doggy/kinds/puppy.nix"})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Splits on whitespace:
|
||||||
|
ASSERT_THAT(
|
||||||
|
config.paths.parse("puppy.nix /doggy.nix", options), Eq<Paths>({"/doggy/kinds/puppy.nix", "/doggy.nix"})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Canonicizes paths:
|
||||||
|
ASSERT_THAT(config.paths.parse("../soft.nix", options), Eq<Paths>({"/doggy/soft.nix"}));
|
||||||
|
|
||||||
|
// Canonicizes paths:
|
||||||
|
ASSERT_THAT(config.paths.parse("./soft.nix", options), Eq<Paths>({"/doggy/kinds/soft.nix"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PathsSettingTest, parseHome)
|
||||||
|
{
|
||||||
|
auto options = ApplyConfigOptions{
|
||||||
|
.path = "/doggy/kinds/config.nix",
|
||||||
|
.home = "/home/puppy"
|
||||||
|
};
|
||||||
|
auto config = mkConfig();
|
||||||
|
|
||||||
|
ASSERT_THAT(
|
||||||
|
config.paths.parse("puppy.nix", options),
|
||||||
|
Eq<Paths>({"/doggy/kinds/puppy.nix"})
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_THAT(
|
||||||
|
config.paths.parse("~/.config/nix/puppy.nix", options),
|
||||||
|
Eq<Paths>({"/home/puppy/.config/nix/puppy.nix"})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Splits on whitespace:
|
||||||
|
ASSERT_THAT(
|
||||||
|
config.paths.parse("~/puppy.nix ~/doggy.nix", options),
|
||||||
|
Eq<Paths>({"/home/puppy/puppy.nix", "/home/puppy/doggy.nix"})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Canonicizes paths:
|
||||||
|
ASSERT_THAT(config.paths.parse("~/../why.nix", options), Eq<Paths>({"/home/why.nix"}));
|
||||||
|
|
||||||
|
// Home paths for other users not allowed. Needs to start with `~/`.
|
||||||
|
ASSERT_THROW(config.paths.parse("~root/config.nix", options), Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PathsSettingTest, append)
|
||||||
|
{
|
||||||
auto config = mkConfig();
|
auto config = mkConfig();
|
||||||
|
|
||||||
ASSERT_TRUE(config.paths.isAppendable());
|
ASSERT_TRUE(config.paths.isAppendable());
|
||||||
|
|
||||||
// Starts with no paths:
|
// Starts with no paths:
|
||||||
ASSERT_THAT(
|
ASSERT_THAT(config.paths.get(), Eq<Paths>({}));
|
||||||
config.paths.get(),
|
|
||||||
Eq<Paths>({})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Can append a path:
|
// Can append a path:
|
||||||
config.paths.set("/puppy.nix", true);
|
config.paths.set("/puppy.nix", true);
|
||||||
|
|
||||||
ASSERT_THAT(
|
ASSERT_THAT(config.paths.get(), Eq<Paths>({"/puppy.nix"}));
|
||||||
config.paths.get(),
|
|
||||||
Eq<Paths>({"/puppy.nix"})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Can append multiple paths:
|
// Can append multiple paths:
|
||||||
config.paths.set("/silly.nix /doggy.nix", true);
|
config.paths.set("/silly.nix /doggy.nix", true);
|
||||||
|
|
||||||
ASSERT_THAT(
|
ASSERT_THAT(config.paths.get(), Eq<Paths>({"/puppy.nix", "/silly.nix", "/doggy.nix"}));
|
||||||
config.paths.get(),
|
|
||||||
Eq<Paths>({"/puppy.nix", "/silly.nix", "/doggy.nix"})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace nix
|
} // namespace nix
|
||||||
|
|
Loading…
Reference in a new issue