From ba9ad29fdbfda3836bb06b35817f08fd10beaa22 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 13 Apr 2017 20:53:23 +0200 Subject: [PATCH] Convert Settings to the new config system This makes all config options self-documenting. Unknown or unparseable config settings and --option flags now cause a warning. --- doc/manual/command-ref/conf-file.xml | 10 - perl/lib/Nix/Config.pm.in | 21 +- src/libmain/common-args.cc | 6 +- src/libmain/shared.cc | 10 +- src/libstore/build.cc | 11 +- src/libstore/crypto.cc | 4 +- src/libstore/download.cc | 2 +- src/libstore/globals.cc | 351 +++++---------------------- src/libstore/globals.hh | 312 ++++++++++++------------ src/libstore/local-store.cc | 4 +- src/libstore/remote-store.cc | 4 +- src/libstore/store-api.cc | 9 +- src/libutil/config.cc | 104 ++++++-- src/libutil/config.hh | 22 +- src/nix-daemon/nix-daemon.cc | 13 +- src/nix-store/nix-store.cc | 8 +- src/nix/main.cc | 1 - tests/shell.shebang.sh | 2 +- 18 files changed, 348 insertions(+), 546 deletions(-) diff --git a/doc/manual/command-ref/conf-file.xml b/doc/manual/command-ref/conf-file.xml index 6952829e8..3de9647aa 100644 --- a/doc/manual/command-ref/conf-file.xml +++ b/doc/manual/command-ref/conf-file.xml @@ -334,16 +334,6 @@ flag, e.g. --option gc-keep-outputs false. - use-binary-caches - - If set to true (the default), - Nix will check the binary caches specified by - and related options to obtain - binary substitutes. - - - - binary-caches A list of URLs of binary caches, separated by diff --git a/perl/lib/Nix/Config.pm.in b/perl/lib/Nix/Config.pm.in index f494e34a5..4bdee7fd8 100644 --- a/perl/lib/Nix/Config.pm.in +++ b/perl/lib/Nix/Config.pm.in @@ -20,22 +20,15 @@ $useBindings = 1; %config = (); sub readConfig { - if (defined $ENV{'_NIX_OPTIONS'}) { - foreach my $s (split '\n', $ENV{'_NIX_OPTIONS'}) { - my ($n, $v) = split '=', $s, 2; - $config{$n} = $v; - } - } else { - my $config = "$confDir/nix.conf"; - return unless -f $config; + my $config = "$confDir/nix.conf"; + return unless -f $config; - open CONFIG, "<$config" or die "cannot open ‘$config’"; - while () { - /^\s*([\w\-\.]+)\s*=\s*(.*)$/ or next; - $config{$1} = $2; - } - close CONFIG; + open CONFIG, "<$config" or die "cannot open ‘$config’"; + while () { + /^\s*([\w\-\.]+)\s*=\s*(.*)$/ or next; + $config{$1} = $2; } + close CONFIG; } return 1; diff --git a/src/libmain/common-args.cc b/src/libmain/common-args.cc index 98693d78a..9a7a89313 100644 --- a/src/libmain/common-args.cc +++ b/src/libmain/common-args.cc @@ -22,7 +22,11 @@ MixCommonArgs::MixCommonArgs(const string & programName) [](Strings ss) { auto name = ss.front(); ss.pop_front(); auto value = ss.front(); - settings.set(name, value); + try { + settings.set(name, value); + } catch (UsageError & e) { + warn(e.what()); + } }); } diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index c1828aa7d..9d506d019 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -138,9 +138,6 @@ void initNix() struct timeval tv; gettimeofday(&tv, 0); srandom(tv.tv_usec); - - if (char *pack = getenv("_NIX_OPTIONS")) - settings.unpack(pack); } @@ -156,10 +153,10 @@ struct LegacyArgs : public MixCommonArgs &settings.verboseBuild, false); mkFlag('K', "keep-failed", "keep temporary directories of failed builds", - &settings.keepFailed); + &(bool&) settings.keepFailed); mkFlag('k', "keep-going", "keep going after a build fails", - &settings.keepGoing); + &(bool&) settings.keepGoing); mkFlag(0, "fallback", "build from source if substitution fails", []() { settings.set("build-fallback", "true"); @@ -184,7 +181,7 @@ struct LegacyArgs : public MixCommonArgs &settings.readOnlyMode); mkFlag(0, "no-build-hook", "disable use of the build hook mechanism", - &settings.useBuildHook, false); + &(bool&) settings.useBuildHook, false); mkFlag(0, "show-trace", "show Nix expression stack trace in evaluation errors", &settings.showTrace); @@ -218,7 +215,6 @@ void parseCmdLine(int argc, char * * argv, std::function parseArg) { LegacyArgs(baseNameOf(argv[0]), parseArg).parseCmdline(argvToStrings(argc, argv)); - settings.update(); } diff --git a/src/libstore/build.cc b/src/libstore/build.cc index b23447fa0..33c9e3704 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -462,7 +462,7 @@ UserLock::UserLock() assert(settings.buildUsersGroup != ""); /* Get the members of the build-users-group. */ - struct group * gr = getgrnam(settings.buildUsersGroup.c_str()); + struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str()); if (!gr) throw Error(format("the group ‘%1%’ specified in ‘build-users-group’ does not exist") % settings.buildUsersGroup); @@ -1690,10 +1690,7 @@ void DerivationGoal::startBuilder() /* Are we doing a chroot build? */ { - string x = settings.useSandbox; - if (x != "true" && x != "false" && x != "relaxed") - throw Error("option ‘build-use-sandbox’ must be set to one of ‘true’, ‘false’ or ‘relaxed’"); - if (x == "true") { + if (settings.sandboxMode == smEnabled) { if (get(drv->env, "__noChroot") == "1") throw Error(format("derivation ‘%1%’ has ‘__noChroot’ set, " "but that's not allowed when ‘build-use-sandbox’ is ‘true’") % drvPath); @@ -1704,9 +1701,9 @@ void DerivationGoal::startBuilder() #endif useChroot = true; } - else if (x == "false") + else if (settings.sandboxMode == smDisabled) useChroot = false; - else if (x == "relaxed") + else if (settings.sandboxMode == smRelaxed) useChroot = !fixedOutput && get(drv->env, "__noChroot") != "1"; } diff --git a/src/libstore/crypto.cc b/src/libstore/crypto.cc index 9692dd83b..f56a6adab 100644 --- a/src/libstore/crypto.cc +++ b/src/libstore/crypto.cc @@ -105,12 +105,12 @@ PublicKeys getDefaultPublicKeys() // FIXME: filter duplicates - for (auto s : settings.binaryCachePublicKeys) { + for (auto s : settings.binaryCachePublicKeys.get()) { PublicKey key(s); publicKeys.emplace(key.name, key); } - for (auto secretKeyFile : settings.secretKeyFiles) { + for (auto secretKeyFile : settings.secretKeyFiles.get()) { try { SecretKey secretKey(readFile(secretKeyFile)); publicKeys.emplace(secretKey.name, secretKey.toPublicKey()); diff --git a/src/libstore/download.cc b/src/libstore/download.cc index 95e6f7bac..d073e870b 100644 --- a/src/libstore/download.cc +++ b/src/libstore/download.cc @@ -251,7 +251,7 @@ struct CurlDownloader : public Downloader /* If no file exist in the specified path, curl continues to work anyway as if netrc support was disabled. */ - curl_easy_setopt(req, CURLOPT_NETRC_FILE, settings.netrcFile.c_str()); + curl_easy_setopt(req, CURLOPT_NETRC_FILE, settings.netrcFile.get().c_str()); curl_easy_setopt(req, CURLOPT_NETRC, CURL_NETRC_OPTIONAL); result.data = std::make_shared(); diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index b9f4fada5..bb61daa51 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -1,6 +1,7 @@ #include "globals.hh" #include "util.hh" #include "archive.hh" +#include "args.hh" #include #include @@ -26,329 +27,89 @@ namespace nix { Settings settings; - Settings::Settings() + : Config({}) + , nixPrefix(NIX_PREFIX) + , nixStore(canonPath(getEnv("NIX_STORE_DIR", getEnv("NIX_STORE", NIX_STORE_DIR)))) + , nixDataDir(canonPath(getEnv("NIX_DATA_DIR", NIX_DATA_DIR))) + , nixLogDir(canonPath(getEnv("NIX_LOG_DIR", NIX_LOG_DIR))) + , nixStateDir(canonPath(getEnv("NIX_STATE_DIR", NIX_STATE_DIR))) + , nixConfDir(canonPath(getEnv("NIX_CONF_DIR", NIX_CONF_DIR))) + , nixLibexecDir(canonPath(getEnv("NIX_LIBEXEC_DIR", NIX_LIBEXEC_DIR))) + , nixBinDir(canonPath(getEnv("NIX_BIN_DIR", NIX_BIN_DIR))) + , nixDaemonSocketFile(canonPath(nixStateDir + DEFAULT_SOCKET_PATH)) { - deprecatedOptions = StringSet({ - "build-use-chroot", "build-chroot-dirs", "build-extra-chroot-dirs", - "this-option-never-existed-but-who-will-know" - }); - - nixPrefix = NIX_PREFIX; - nixStore = canonPath(getEnv("NIX_STORE_DIR", getEnv("NIX_STORE", NIX_STORE_DIR))); - nixDataDir = canonPath(getEnv("NIX_DATA_DIR", NIX_DATA_DIR)); - nixLogDir = canonPath(getEnv("NIX_LOG_DIR", NIX_LOG_DIR)); - nixStateDir = canonPath(getEnv("NIX_STATE_DIR", NIX_STATE_DIR)); - nixConfDir = canonPath(getEnv("NIX_CONF_DIR", NIX_CONF_DIR)); - nixLibexecDir = canonPath(getEnv("NIX_LIBEXEC_DIR", NIX_LIBEXEC_DIR)); - nixBinDir = canonPath(getEnv("NIX_BIN_DIR", NIX_BIN_DIR)); - nixDaemonSocketFile = canonPath(nixStateDir + DEFAULT_SOCKET_PATH); - - // should be set with the other config options, but depends on nixLibexecDir -#ifdef __APPLE__ - preBuildHook = nixLibexecDir + "/nix/resolve-system-dependencies"; -#endif - - keepFailed = false; - keepGoing = false; - tryFallback = false; - maxBuildJobs = 1; - buildCores = std::max(1U, std::thread::hardware_concurrency()); - readOnlyMode = false; - thisSystem = SYSTEM; - maxSilentTime = 0; - buildTimeout = 0; - useBuildHook = true; - reservedSize = 8 * 1024 * 1024; - fsyncMetadata = true; - useSQLiteWAL = true; - syncBeforeRegistering = false; - useSubstitutes = true; buildUsersGroup = getuid() == 0 ? "nixbld" : ""; - useSshSubstituter = true; - impersonateLinux26 = false; - keepLog = true; - compressLog = true; - maxLogSize = 0; - pollInterval = 5; - checkRootReachability = false; - gcKeepOutputs = false; - gcKeepDerivations = true; - autoOptimiseStore = false; - envKeepDerivations = false; lockCPU = getEnv("NIX_AFFINITY_HACK", "1") == "1"; - showTrace = false; - enableNativeCode = false; - netrcFile = fmt("%s/%s", nixConfDir, "netrc"); caFile = getEnv("NIX_SSL_CERT_FILE", getEnv("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt")); - enableImportFromDerivation = true; - useSandbox = "false"; // TODO: make into an enum #if __linux__ sandboxPaths = tokenizeString("/bin/sh=" BASH_PATH); #endif - restrictEval = false; - buildRepeat = 0; allowedImpureHostPrefixes = tokenizeString(DEFAULT_ALLOWED_IMPURE_PREFIXES); - sandboxShmSize = "50%"; - darwinLogSandboxViolations = false; - runDiffHook = false; - diffHook = ""; - enforceDeterminism = true; - binaryCachePublicKeys = Strings{"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="}; - secretKeyFiles = Strings(); - binaryCachesParallelConnections = 25; - enableHttp2 = true; - tarballTtl = 60 * 60; - signedBinaryCaches = ""; - substituters = Strings(); - binaryCaches = nixStore == "/nix/store" ? Strings{"https://cache.nixos.org/"} : Strings(); - extraBinaryCaches = Strings(); - trustedUsers = Strings({"root"}); - allowedUsers = Strings({"*"}); - printMissing = true; } - void Settings::loadConfFile() { - Path settingsFile = (format("%1%/%2%") % nixConfDir % "nix.conf").str(); - if (!pathExists(settingsFile)) return; - string contents = readFile(settingsFile); - - unsigned int pos = 0; - - while (pos < contents.size()) { - string line; - while (pos < contents.size() && contents[pos] != '\n') - line += contents[pos++]; - pos++; - - string::size_type hash = line.find('#'); - if (hash != string::npos) - line = string(line, 0, hash); - - vector tokens = tokenizeString >(line); - if (tokens.empty()) continue; - - if (tokens.size() < 2 || tokens[1] != "=") - throw Error(format("illegal configuration line ‘%1%’ in ‘%2%’") % line % settingsFile); - - string name = tokens[0]; - - vector::iterator i = tokens.begin(); - advance(i, 2); - settings[name] = concatStringsSep(" ", Strings(i, tokens.end())); // FIXME: slow - }; + applyConfigFile(nixConfDir + "/nix.conf"); } - void Settings::set(const string & name, const string & value) { - settings[name] = value; overrides[name] = value; + Config::set(name, value); } -void Settings::update() -{ - _get(tryFallback, "build-fallback"); - - std::string s = "1"; - _get(s, "build-max-jobs"); - if (s == "auto") - maxBuildJobs = std::max(1U, std::thread::hardware_concurrency()); - else - if (!string2Int(s, maxBuildJobs)) - throw Error("configuration setting ‘build-max-jobs’ should be ‘auto’ or an integer"); - - _get(buildCores, "build-cores"); - _get(thisSystem, "system"); - _get(maxSilentTime, "build-max-silent-time"); - _get(buildTimeout, "build-timeout"); - _get(reservedSize, "gc-reserved-space"); - _get(fsyncMetadata, "fsync-metadata"); - _get(useSQLiteWAL, "use-sqlite-wal"); - _get(syncBeforeRegistering, "sync-before-registering"); - _get(useSubstitutes, "build-use-substitutes"); - _get(buildUsersGroup, "build-users-group"); - _get(impersonateLinux26, "build-impersonate-linux-26"); - _get(keepLog, "build-keep-log"); - _get(compressLog, "build-compress-log"); - _get(maxLogSize, "build-max-log-size"); - _get(pollInterval, "build-poll-interval"); - _get(checkRootReachability, "gc-check-reachability"); - _get(gcKeepOutputs, "gc-keep-outputs"); - _get(gcKeepDerivations, "gc-keep-derivations"); - _get(autoOptimiseStore, "auto-optimise-store"); - _get(envKeepDerivations, "env-keep-derivations"); - _get(sshSubstituterHosts, "ssh-substituter-hosts"); - _get(useSshSubstituter, "use-ssh-substituter"); - _get(enableNativeCode, "allow-unsafe-native-code-during-evaluation"); - _get(useCaseHack, "use-case-hack"); - _get(preBuildHook, "pre-build-hook"); - _get(keepGoing, "keep-going"); - _get(keepFailed, "keep-failed"); - _get(netrcFile, "netrc-file"); - _get(enableImportFromDerivation, "allow-import-from-derivation"); - _get(useSandbox, "build-use-sandbox", "build-use-chroot"); - _get(sandboxPaths, "build-sandbox-paths", "build-chroot-dirs"); - _get(extraSandboxPaths, "build-extra-sandbox-paths", "build-extra-chroot-dirs"); - _get(restrictEval, "restrict-eval"); - _get(buildRepeat, "build-repeat"); - _get(allowedImpureHostPrefixes, "allowed-impure-host-deps"); - _get(sandboxShmSize, "sandbox-dev-shm-size"); - _get(darwinLogSandboxViolations, "darwin-log-sandbox-violations"); - _get(runDiffHook, "run-diff-hook"); - _get(diffHook, "diff-hook"); - _get(enforceDeterminism, "enforce-determinism"); - _get(binaryCachePublicKeys, "binary-cache-public-keys"); - _get(secretKeyFiles, "secret-key-files"); - _get(binaryCachesParallelConnections, "binary-caches-parallel-connections"); - _get(enableHttp2, "enable-http2"); - _get(tarballTtl, "tarball-ttl"); - _get(signedBinaryCaches, "signed-binary-caches"); - _get(substituters, "substituters"); - _get(binaryCaches, "binary-caches"); - _get(extraBinaryCaches, "extra-binary-caches"); - _get(trustedUsers, "trusted-users"); - _get(allowedUsers, "allowed-users"); - _get(printMissing, "print-missing"); - - /* Clear out any deprecated options that might be left, so users know we recognize the option - but aren't processing it anymore */ - for (auto &i : deprecatedOptions) { - if (settings.find(i) != settings.end()) { - printError(format("warning: deprecated option '%1%' is no longer supported and will be ignored") % i); - settings.erase(i); - } - } - - if (settings.size() != 0) { - string bad; - for (auto &i : settings) - bad += "'" + i.first + "', "; - bad.pop_back(); - bad.pop_back(); - throw Error(format("unrecognized options: %s") % bad); - } -} - -void Settings::checkDeprecated(const string & name) -{ - if (deprecatedOptions.find(name) != deprecatedOptions.end()) - printError(format("warning: deprecated option '%1%' will soon be unsupported") % name); -} - -void Settings::_get(string & res, const string & name) -{ - SettingsMap::iterator i = settings.find(name); - if (i == settings.end()) return; - checkDeprecated(i->first); - settings.erase(i); - res = i->second; -} - -void Settings::_get(string & res, const string & name1, const string & name2) -{ - SettingsMap::iterator i = settings.find(name1); - if (i == settings.end()) i = settings.find(name2); - if (i == settings.end()) return; - checkDeprecated(i->first); - settings.erase(i); - res = i->second; -} - - -void Settings::_get(bool & res, const string & name) -{ - SettingsMap::iterator i = settings.find(name); - if (i == settings.end()) return; - checkDeprecated(i->first); - settings.erase(i); - if (i->second == "true") res = true; - else if (i->second == "false") res = false; - else throw Error(format("configuration option ‘%1%’ should be either ‘true’ or ‘false’, not ‘%2%’") - % name % i->second); -} - - -void Settings::_get(StringSet & res, const string & name) -{ - SettingsMap::iterator i = settings.find(name); - if (i == settings.end()) return; - checkDeprecated(i->first); - settings.erase(i); - res.clear(); - Strings ss = tokenizeString(i->second); - res.insert(ss.begin(), ss.end()); -} - -void Settings::_get(StringSet & res, const string & name1, const string & name2) -{ - SettingsMap::iterator i = settings.find(name1); - if (i == settings.end()) i = settings.find(name2); - if (i == settings.end()) return; - checkDeprecated(i->first); - settings.erase(i); - res.clear(); - Strings ss = tokenizeString(i->second); - res.insert(ss.begin(), ss.end()); -} - -void Settings::_get(Strings & res, const string & name) -{ - SettingsMap::iterator i = settings.find(name); - if (i == settings.end()) return; - checkDeprecated(i->first); - settings.erase(i); - res = tokenizeString(i->second); -} - - -template void Settings::_get(N & res, const string & name) -{ - SettingsMap::iterator i = settings.find(name); - if (i == settings.end()) return; - checkDeprecated(i->first); - settings.erase(i); - if (!string2Int(i->second, res)) - throw Error(format("configuration setting ‘%1%’ should have an integer value") % name); -} - - -string Settings::pack() -{ - string s; - for (auto & i : settings) { - if (i.first.find('\n') != string::npos || - i.first.find('=') != string::npos || - i.second.find('\n') != string::npos) - throw Error("illegal option name/value"); - s += i.first; s += '='; s += i.second; s += '\n'; - } - return s; -} - - -void Settings::unpack(const string & pack) { - Strings lines = tokenizeString(pack, "\n"); - for (auto & i : lines) { - string::size_type eq = i.find('='); - if (eq == string::npos) - throw Error("illegal option name/value"); - set(i.substr(0, eq), i.substr(eq + 1)); - } -} - - -Settings::SettingsMap Settings::getOverrides() +StringMap Settings::getOverrides() { return overrides; } +unsigned int Settings::getDefaultCores() +{ + return std::max(1U, std::thread::hardware_concurrency()); +} const string nixVersion = PACKAGE_VERSION; +template<> void Setting::set(const std::string & str) +{ + if (str == "true") value = smEnabled; + else if (str == "relaxed") value = smRelaxed; + else if (str == "false") value = smDisabled; + else throw UsageError("option '%s' has invalid value '%s'", name, str); +} + +template<> std::string Setting::to_string() +{ + if (value == smEnabled) return "true"; + else if (value == smRelaxed) return "relaxed"; + else if (value == smDisabled) return "false"; + else abort(); +} + +template<> void Setting::set(const std::string & str) +{ + if (str == "auto") value = std::max(1U, std::thread::hardware_concurrency()); + else if (!string2Int(str, value)) + throw UsageError("configuration setting ‘%s’ should be ‘auto’ or an integer", name); +} + +template<> std::string Setting::to_string() +{ + return std::to_string(value); +} + +template<> void Setting::set(const std::string & str) +{ + value = parseBool(str); + nix::useCaseHack = true; +} + +template<> std::string Setting::to_string() +{ + return printBool(value); +} } diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index d47fdb7c9..95c8859cf 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -1,7 +1,7 @@ #pragma once #include "types.hh" -#include "logging.hh" +#include "config.hh" #include #include @@ -9,10 +9,17 @@ namespace nix { +typedef enum { smEnabled, smRelaxed, smDisabled } SandboxMode; -struct Settings { +extern bool useCaseHack; // FIXME - typedef std::map SettingsMap; +class Settings : public Config { + + StringMap overrides; + + unsigned int getDefaultCores(); + +public: Settings(); @@ -20,25 +27,15 @@ struct Settings { void set(const string & name, const string & value); - void update(); + StringMap getOverrides(); - string pack(); - - void unpack(const string & pack); - - SettingsMap getOverrides(); - - /* TODO: the comments below should be strings and exposed via a nice command-line UI or similar. - We should probably replace it with some sort of magic template or macro to minimize the amount - of duplication and pain here. */ + Path nixPrefix; /* The directory where we store sources and derived files. */ Path nixStore; Path nixDataDir; /* !!! fix */ - Path nixPrefix; - /* The directory where we log various operations. */ Path nixLogDir; @@ -57,17 +54,14 @@ struct Settings { /* File name of the socket the daemon listens to. */ Path nixDaemonSocketFile; - /* Whether to keep temporary directories of failed builds. */ - bool keepFailed; + Setting keepFailed{this, false, "keep-failed", + "Whether to keep temporary directories of failed builds."}; - /* Whether to keep building subgoals when a sibling (another - subgoal of the same goal) fails. */ - bool keepGoing; + Setting keepGoing{this, false, "keep-going", + "Whether to keep building derivations when another build fails."}; - /* Whether, if we cannot realise the known closure corresponding - to a derivation, we should try to normalise the derivation - instead. */ - bool tryFallback; + Setting tryFallback{this, tryFallback, "build-fallback", + "Whether to fall back to building when substitution fails."}; /* Whether to show build log output in real time. */ bool verboseBuild = true; @@ -76,206 +70,206 @@ struct Settings { the log to show if a build fails. */ size_t logLines = 10; - /* Maximum number of parallel build jobs. 0 means unlimited. */ - unsigned int maxBuildJobs; + struct MaxBuildJobsTag { }; + Setting maxBuildJobs{this, 1, "build-max-jobs", + "Maximum number of parallel build jobs. \"auto\" means use number of cores."}; - /* Number of CPU cores to utilize in parallel within a build, - i.e. by passing this number to Make via '-j'. 0 means that the - number of actual CPU cores on the local host ought to be - auto-detected. */ - unsigned int buildCores; + Setting buildCores{this, getDefaultCores(), "build-cores", + "Number of CPU cores to utilize in parallel within a build, " + "i.e. by passing this number to Make via '-j'. 0 means that the " + "number of actual CPU cores on the local host ought to be " + "auto-detected."}; /* Read-only mode. Don't copy stuff to the store, don't change the database. */ - bool readOnlyMode; + bool readOnlyMode = false; - /* The canonical system name, as returned by config.guess. */ - string thisSystem; + Setting thisSystem{this, SYSTEM, "system", + "The canonical Nix system name."}; - /* The maximum time in seconds that a builer can go without - producing any output on stdout/stderr before it is killed. 0 - means infinity. */ - time_t maxSilentTime; + Setting maxSilentTime{this, 0, "build-max-silent-time", + "The maximum time in seconds that a builer can go without " + "producing any output on stdout/stderr before it is killed. " + "0 means infinity."}; - /* The maximum duration in seconds that a builder can run. 0 - means infinity. */ - time_t buildTimeout; + Setting buildTimeout{this, 0, "build-timeout", + "The maximum duration in seconds that a builder can run. " + "0 means infinity."}; - /* Whether to use build hooks (for distributed builds). Sometimes - users want to disable this from the command-line. */ - bool useBuildHook; + Setting useBuildHook{this, true, "remote-builds", + "Whether to use build hooks (for distributed builds)."}; - /* Amount of reserved space for the garbage collector - (/nix/var/nix/db/reserved). */ - off_t reservedSize; + Setting reservedSize{this, 8 * 1024 * 1024, "gc-reserved-space", + "Amount of reserved disk space for the garbage collector."}; - /* Whether SQLite should use fsync. */ - bool fsyncMetadata; + Setting fsyncMetadata{this, true, "fsync-metadata", + "Whether SQLite should use fsync()."}; - /* Whether SQLite should use WAL mode. */ - bool useSQLiteWAL; + Setting useSQLiteWAL{this, true, "use-sqlite-wal", + "Whether SQLite should use WAL mode."}; - /* Whether to call sync() before registering a path as valid. */ - bool syncBeforeRegistering; + Setting syncBeforeRegistering{this, false, "sync-before-registering", + "Whether to call sync() before registering a path as valid."}; - /* Whether to use substitutes. */ - bool useSubstitutes; + Setting useSubstitutes{this, true, "build-use-substitutes", + "Whether to use substitutes."}; - /* The Unix group that contains the build users. */ - string buildUsersGroup; + Setting buildUsersGroup{this, "", "build-users-group", + "The Unix group that contains the build users."}; - /* Set of ssh connection strings for the ssh substituter */ - Strings sshSubstituterHosts; + Setting impersonateLinux26{this, false, "build-impersonate-linux-26", + "Whether to impersonate a Linux 2.6 machine on newer kernels."}; - /* Whether to use the ssh substituter at all */ - bool useSshSubstituter; + Setting keepLog{this, true, "build-keep-log", + "Whether to store build logs."}; - /* Whether to impersonate a Linux 2.6 machine on newer kernels. */ - bool impersonateLinux26; + Setting compressLog{this, true, "build-compress-log", + "Whether to compress logs."}; - /* Whether to store build logs. */ - bool keepLog; - - /* Whether to compress logs. */ - bool compressLog; - - /* Maximum number of bytes a builder can write to stdout/stderr - before being killed (0 means no limit). */ - unsigned long maxLogSize; + Setting maxLogSize{this, 0, "build-max-log-size", + "Maximum number of bytes a builder can write to stdout/stderr " + "before being killed (0 means no limit)."}; /* When build-repeat > 0 and verboseBuild == true, whether to print repeated builds (i.e. builds other than the first one) to stderr. Hack to prevent Hydra logs from being polluted. */ bool printRepeatedBuilds = true; - /* How often (in seconds) to poll for locks. */ - unsigned int pollInterval; + Setting pollInterval{this, 5, "build-poll-interval", + "How often (in seconds) to poll for locks."}; - /* Whether to check if new GC roots can in fact be found by the - garbage collector. */ - bool checkRootReachability; + Setting checkRootReachability{this, false, "gc-check-reachability", + "Whether to check if new GC roots can in fact be found by the " + "garbage collector."}; - /* Whether the garbage collector should keep outputs of live - derivations. */ - bool gcKeepOutputs; + Setting gcKeepOutputs{this, false, "gc-keep-outputs", + "Whether the garbage collector should keep outputs of live derivations."}; - /* Whether the garbage collector should keep derivers of live - paths. */ - bool gcKeepDerivations; + Setting gcKeepDerivations{this, true, "gc-keep-derivations", + "Whether the garbage collector should keep derivers of live paths."}; - /* Whether to automatically replace files with identical contents - with hard links. */ - bool autoOptimiseStore; + Setting autoOptimiseStore{this, false, "auto-optimise-store", + "Whether to automatically replace files with identical contents with hard links."}; - /* Whether to add derivations as a dependency of user environments - (to prevent them from being GCed). */ - bool envKeepDerivations; + Setting envKeepDerivations{this, false, "env-keep-derivations", + "Whether to add derivations as a dependency of user environments " + "(to prevent them from being GCed)."}; /* Whether to lock the Nix client and worker to the same CPU. */ bool lockCPU; /* Whether to show a stack trace if Nix evaluation fails. */ - bool showTrace; + bool showTrace = false; - /* Whether native-code enabling primops should be enabled */ - bool enableNativeCode; + Setting enableNativeCode{this, false, "allow-unsafe-native-code-during-evaluation", + "Whether builtin functions that allow executing native code should be enabled."}; - /* Whether to enable sandboxed builds (string until we get an enum for true/false/relaxed) */ - string useSandbox; + Setting sandboxMode{this, smDisabled, "build-use-sandbox", + "Whether to enable sandboxed builds. Can be \"true\", \"false\" or \"relaxed\".", + {"build-use-chroot"}}; - /* The basic set of paths to expose in a sandbox */ - PathSet sandboxPaths; + Setting sandboxPaths{this, {}, "build-sandbox-paths", + "The paths to make available inside the build sandbox.", + {"build-chroot-dirs"}}; - /* Any extra sandbox paths to expose */ - PathSet extraSandboxPaths; + Setting extraSandboxPaths{this, {}, "build-extra-sandbox-paths", + "Additional paths to make available inside the build sandbox.", + {"build-extra-chroot-dirs"}}; - /* Whether to allow certain questionable operations (like fetching) during evaluation */ - bool restrictEval; + Setting restrictEval{this, false, "restrict-eval", + "Whether to restrict file system access to paths in $NIX_PATH, " + "and to disallow fetching files from the network."}; - /* The number of times to repeat a build to check for determinism */ - int buildRepeat; + Setting buildRepeat{this, 0, "build-repeat", + "The number of times to repeat a build in order to verify determinism."}; - /* Which prefixes to allow derivations to ask for access to (primarily for Darwin) */ - PathSet allowedImpureHostPrefixes; +#if __linux__ + Setting sandboxShmSize{this, "50%", "sandbox-dev-shm-size", + "The size of /dev/shm in the build sandbox."}; +#endif - /* The size of /dev/shm in the build sandbox (for Linux) */ - string sandboxShmSize; + Setting allowedImpureHostPrefixes{this, {}, "allowed-impure-host-deps", + "Which prefixes to allow derivations to ask for access to (primarily for Darwin)."}; - /* Whether to log Darwin sandbox access violations to the system log */ - bool darwinLogSandboxViolations; +#if __APPLE__ + Setting darwinLogSandboxViolations{this, false, "darwin-log-sandbox-violations", + "Whether to log Darwin sandbox access violations to the system log."}; +#endif - /* ??? */ - bool runDiffHook; + Setting runDiffHook{this, false, "run-diff-hook", + "Whether to run the program specified by the diff-hook setting " + "repeated builds produce a different result. Typically used to " + "plug in diffoscope."}; - /* ??? */ - string diffHook; + PathSetting diffHook{this, true, "", "diff-hook", + "A program that prints out the differences between the two paths " + "specified on its command line."}; - /* Whether to fail if repeated builds produce different output */ - bool enforceDeterminism; + Setting enforceDeterminism{this, true, "enforce-determinism", + "Whether to fail if repeated builds produce different output."}; - /* The known public keys for a binary cache */ - Strings binaryCachePublicKeys; + Setting binaryCachePublicKeys{this, + {"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="}, + "binary-cache-public-keys", + "Trusted public keys for secure substitution."}; - /* Secret keys to use for build output signing */ - Strings secretKeyFiles; + Setting secretKeyFiles{this, {}, "secret-key-files", + "Secret keys with which to sign local builds."}; - /* Number of parallel connections to hit a binary cache with when finding out if it contains hashes */ - int binaryCachesParallelConnections; + Setting binaryCachesParallelConnections{this, 25, "binary-caches-parallel-connections", + "Number of parallel connections to binary caches."}; - /* Whether to enable HTTP2 */ - bool enableHttp2; + Setting enableHttp2{this, true, "enable-http2", + "Whether to enable HTTP/2 support."}; - /* How soon to expire tarballs like builtins.fetchTarball and (ugh, bad name) builtins.fetchurl */ - int tarballTtl; + Setting tarballTtl{this, 60 * 60, "tarball-ttl", + "How soon to expire files fetched by builtins.fetchTarball and builtins.fetchurl."}; - /* ??? */ - string signedBinaryCaches; + Setting signedBinaryCaches{this, "*", "signed-binary-caches", + "Obsolete."}; - /* ??? */ - Strings substituters; + Setting substituters{this, + nixStore == "/nix/store" ? Strings{"https://cache.nixos.org/"} : Strings(), + "substituters", + "The URIs of substituters (such as https://cache.nixos.org/).", + {"binary-caches"}}; - /* ??? */ - Strings binaryCaches; + // FIXME: provide a way to add to option values. + Setting extraSubstituters{this, {}, "extra-substituters", + "Additional URIs of substituters.", + {"extra-binary-caches"}}; - /* ??? */ - Strings extraBinaryCaches; - - /* Who we trust to ask the daemon to do unsafe things */ - Strings trustedUsers; + Setting trustedUsers{this, {"root"}, "trusted-users", + "Which users or groups are trusted to ask the daemon to do unsafe things."}; /* ?Who we trust to use the daemon in safe ways */ - Strings allowedUsers; + Setting allowedUsers{this, {"*"}, "allowed-users", + "Which users or groups are allowed to connect to the daemon."}; - /* ??? */ - bool printMissing; + Setting printMissing{this, true, "print-missing", + "Whether to print what paths need to be built or downloaded."}; - /* The hook to run just before a build to set derivation-specific - build settings */ - Path preBuildHook; + Setting preBuildHook{this, +#if __APPLE__ + nixLibexecDir + "/nix/resolve-system-dependencies", +#else + "", +#endif + "pre-build-hook", + "A program to run just before a build to set derivation-specific build settings."}; - /* Path to the netrc file used to obtain usernames/passwords for - downloads. */ - Path netrcFile; + Setting netrcFile{this, fmt("%s/%s", nixConfDir, "netrc"), "netrc-file", + "Path to the netrc file used to obtain usernames/passwords for downloads."}; /* Path to the SSL CA file used */ Path caFile; - /* Whether we allow import-from-derivation */ - bool enableImportFromDerivation; + Setting enableImportFromDerivation{this, true, "allow-import-from-derivation", + "Whether the evaluator allows importing the result of a derivation."}; -private: - StringSet deprecatedOptions; - SettingsMap settings, overrides; - - void checkDeprecated(const string & name); - - void _get(string & res, const string & name); - void _get(string & res, const string & name1, const string & name2); - void _get(bool & res, const string & name); - void _get(StringSet & res, const string & name); - void _get(StringSet & res, const string & name1, const string & name2); - void _get(Strings & res, const string & name); - template void _get(N & res, const string & name); + struct CaseHackTag { }; + Setting useCaseHack{this, nix::useCaseHack, "use-case-hack", + "Whether to enable a Darwin-specific hack for dealing with file name collisions."}; }; diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index 9111a45f8..5a98454ab 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -75,7 +75,7 @@ LocalStore::LocalStore(const Params & params) mode_t perm = 01775; - struct group * gr = getgrnam(settings.buildUsersGroup.c_str()); + struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str()); if (!gr) printError(format("warning: the group ‘%1%’ specified in ‘build-users-group’ does not exist") % settings.buildUsersGroup); @@ -1335,7 +1335,7 @@ void LocalStore::signPathInfo(ValidPathInfo & info) auto secretKeyFiles = settings.secretKeyFiles; - for (auto & secretKeyFile : secretKeyFiles) { + for (auto & secretKeyFile : secretKeyFiles.get()) { SecretKey secretKey(readFile(secretKeyFile)); info.sign(secretKey); } diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc index e1df137e4..da3c8eb8d 100644 --- a/src/libstore/remote-store.cc +++ b/src/libstore/remote-store.cc @@ -166,9 +166,7 @@ void RemoteStore::setOptions(Connection & conn) << settings.useSubstitutes; if (GET_PROTOCOL_MINOR(conn.daemonVersion) >= 12) { - Settings::SettingsMap overrides = settings.getOverrides(); - if (overrides["ssh-auth-sock"] == "") - overrides["ssh-auth-sock"] = getEnv("SSH_AUTH_SOCK"); + StringMap overrides = settings.getOverrides(); conn.to << overrides.size(); for (auto & i : overrides) conn.to << i.first << i.second; diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index 514d1c2ff..835bbb90e 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -719,7 +719,7 @@ ref openStore(const std::string & uri, const Store::Params & params) for (auto fun : *RegisterStoreImplementation::implementations) { auto store = fun(uri, params); if (store) { - store->warnUnused(); + store->warnUnknownSettings(); return ref(store); } } @@ -782,13 +782,10 @@ std::list> getDefaultSubstituters() state->stores.push_back(openStore(uri)); }; - for (auto uri : settings.substituters) + for (auto uri : settings.substituters.get()) addStore(uri); - for (auto uri : settings.binaryCaches) - addStore(uri); - - for (auto uri : settings.extraBinaryCaches) + for (auto uri : settings.extraSubstituters.get()) addStore(uri); state->done = true; diff --git a/src/libutil/config.cc b/src/libutil/config.cc index c05a3253b..85e5ce330 100644 --- a/src/libutil/config.cc +++ b/src/libutil/config.cc @@ -11,7 +11,7 @@ void Config::set(const std::string & name, const std::string & value) i->second.setting->set(value); } -void Config::add(AbstractSetting * setting) +void Config::addSetting(AbstractSetting * setting) { _settings.emplace(setting->name, Config::SettingData{false, setting}); for (auto & alias : setting->aliases) @@ -41,21 +41,59 @@ void Config::add(AbstractSetting * setting) } } -void Config::warnUnused() +void Config::warnUnknownSettings() { for (auto & i : initials) warn("unknown setting '%s'", i.first); } -std::string Config::dump() +StringMap Config::getSettings() { - std::string res; + StringMap res; for (auto & opt : _settings) if (!opt.second.isAlias) - res += opt.first + " = " + opt.second.setting->to_string() + "\n"; + res.emplace(opt.first, opt.second.setting->to_string()); return res; } +void Config::applyConfigFile(const Path & path, bool fatal) +{ + try { + string contents = readFile(path); + + unsigned int pos = 0; + + while (pos < contents.size()) { + string line; + while (pos < contents.size() && contents[pos] != '\n') + line += contents[pos++]; + pos++; + + string::size_type hash = line.find('#'); + if (hash != string::npos) + line = string(line, 0, hash); + + vector tokens = tokenizeString >(line); + if (tokens.empty()) continue; + + if (tokens.size() < 2 || tokens[1] != "=") + throw UsageError("illegal configuration line ‘%1%’ in ‘%2%’", line, path); + + string name = tokens[0]; + + vector::iterator i = tokens.begin(); + advance(i, 2); + + try { + set(name, concatStringsSep(" ", Strings(i, tokens.end()))); // FIXME: slow + } catch (UsageError & e) { + if (fatal) throw; + warn("in configuration file '%s': %s", path, e.what()); + } + }; + } catch (SysError &) { } +} + AbstractSetting::AbstractSetting( const std::string & name, const std::string & description, @@ -74,41 +112,65 @@ template<> std::string Setting::to_string() return value; } -template -void Setting::set(const std::string & str) +template +void Setting::set(const std::string & str) { static_assert(std::is_integral::value, "Integer required."); - try { - auto i = std::stoll(str); - if (i < std::numeric_limits::min() || - i > std::numeric_limits::max()) - throw UsageError("setting '%s' has out-of-range value %d", name, i); - value = i; - } catch (std::logic_error&) { + if (!string2Int(str, value)) throw UsageError("setting '%s' has invalid value '%s'", name, str); - } } -template -std::string Setting::to_string() +template +std::string Setting::to_string() { static_assert(std::is_integral::value, "Integer required."); return std::to_string(value); } -template<> void Setting::set(const std::string & str) +bool AbstractSetting::parseBool(const std::string & str) { if (str == "true" || str == "yes" || str == "1") - value = true; + return true; else if (str == "false" || str == "no" || str == "0") - value = false; + return false; else throw UsageError("Boolean setting '%s' has invalid value '%s'", name, str); } +template<> void Setting::set(const std::string & str) +{ + value = parseBool(str); +} + +std::string AbstractSetting::printBool(bool b) +{ + return b ? "true" : "false"; +} + + template<> std::string Setting::to_string() { - return value ? "true" : "false"; + return printBool(value); +} + +template<> void Setting::set(const std::string & str) +{ + value = tokenizeString(str); +} + +template<> std::string Setting::to_string() +{ + return concatStringsSep(" ", value); +} + +template<> void Setting::set(const std::string & str) +{ + value = tokenizeString(str); +} + +template<> std::string Setting::to_string() +{ + return concatStringsSep(" ", value); } template class Setting; diff --git a/src/libutil/config.hh b/src/libutil/config.hh index fb2d48e9c..6c8612f67 100644 --- a/src/libutil/config.hh +++ b/src/libutil/config.hh @@ -47,11 +47,13 @@ public: void set(const std::string & name, const std::string & value); - void add(AbstractSetting * setting); + void addSetting(AbstractSetting * setting); - void warnUnused(); + void warnUnknownSettings(); - std::string dump(); + StringMap getSettings(); + + void applyConfigFile(const Path & path, bool fatal = false); }; class AbstractSetting @@ -83,10 +85,15 @@ protected: virtual void set(const std::string & value) = 0; virtual std::string to_string() = 0; + + bool parseBool(const std::string & str); + std::string printBool(bool b); }; +struct DefaultSettingTag { }; + /* A setting of type T. */ -template +template class Setting : public AbstractSetting { protected: @@ -103,10 +110,12 @@ public: : AbstractSetting(name, description, aliases) , value(def) { - options->add(this); + options->addSetting(this); } operator const T &() const { return value; } + operator T &() { return value; } + const T & get() const { return value; } bool operator ==(const T & v2) const { return value == v2; } bool operator !=(const T & v2) const { return value != v2; } void operator =(const T & v) { value = v; } @@ -123,6 +132,9 @@ std::ostream & operator <<(std::ostream & str, const Setting & opt) return str; } +template +bool operator ==(const T & v1, const Setting & v2) { return v1 == (const T &) v2; } + /* A special setting for Paths. These are automatically canonicalised (e.g. "/foo//bar/" becomes "/foo/bar"). */ class PathSetting : public Setting diff --git a/src/nix-daemon/nix-daemon.cc b/src/nix-daemon/nix-daemon.cc index f4285693f..9fcb81dd5 100644 --- a/src/nix-daemon/nix-daemon.cc +++ b/src/nix-daemon/nix-daemon.cc @@ -436,30 +436,29 @@ static void performOp(ref store, bool trusted, unsigned int clientVe } case wopSetOptions: { - from >> settings.keepFailed; - from >> settings.keepGoing; - settings.set("build-fallback", readInt(from) ? "true" : "false"); + settings.keepFailed = readInt(from); + settings.keepGoing = readInt(from); + settings.tryFallback = readInt(from); verbosity = (Verbosity) readInt(from); settings.set("build-max-jobs", std::to_string(readInt(from))); - settings.set("build-max-silent-time", std::to_string(readInt(from))); + settings.maxSilentTime = readInt(from); settings.useBuildHook = readInt(from) != 0; settings.verboseBuild = lvlError == (Verbosity) readInt(from); readInt(from); // obsolete logType readInt(from); // obsolete printBuildTrace settings.set("build-cores", std::to_string(readInt(from))); - settings.set("build-use-substitutes", readInt(from) ? "true" : "false"); + settings.useSubstitutes = readInt(from); if (GET_PROTOCOL_MINOR(clientVersion) >= 12) { unsigned int n = readInt(from); for (unsigned int i = 0; i < n; i++) { string name = readString(from); string value = readString(from); - if (name == "build-timeout" || name == "use-ssh-substituter") + if (name == "build-timeout") settings.set(name, value); else settings.set(trusted ? name : "untrusted-" + name, value); } } - settings.update(); startWork(); stopWork(); break; diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index a40cca982..9131b74df 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -795,11 +795,11 @@ static void opServe(Strings opFlags, Strings opArgs) settings.maxSilentTime = readInt(in); settings.buildTimeout = readInt(in); if (GET_PROTOCOL_MINOR(clientVersion) >= 2) - in >> settings.maxLogSize; + settings.maxLogSize = readNum(in); if (GET_PROTOCOL_MINOR(clientVersion) >= 3) { - settings.set("build-repeat", std::to_string(readInt(in))); - settings.set("enforce-determinism", readInt(in) != 0 ? "true" : "false"); - settings.set("run-diff-hook", "true"); + settings.buildRepeat = readInt(in); + settings.enforceDeterminism = readInt(in); + settings.runDiffHook = readInt(in); } settings.printRepeatedBuilds = false; }; diff --git a/src/nix/main.cc b/src/nix/main.cc index fdb8f6e3a..440ced97d 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -42,7 +42,6 @@ void mainWrapped(int argc, char * * argv) NixArgs args; args.parseCmdline(argvToStrings(argc, argv)); - settings.update(); assert(args.command); diff --git a/tests/shell.shebang.sh b/tests/shell.shebang.sh index 3dadd5915..c9a83aaf8 100755 --- a/tests/shell.shebang.sh +++ b/tests/shell.shebang.sh @@ -1,4 +1,4 @@ #! @ENV_PROG@ nix-shell -#! nix-shell -I nixpkgs=shell.nix --option use-binary-caches false +#! nix-shell -I nixpkgs=shell.nix --option build-use-substitutes false #! nix-shell --pure -i bash -p foo bar echo "$(foo) $(bar) $@"