forked from lix-project/lix
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.
This commit is contained in:
parent
6bd9576aeb
commit
ba9ad29fdb
|
@ -334,16 +334,6 @@ flag, e.g. <literal>--option gc-keep-outputs false</literal>.</para>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
|
||||||
<varlistentry><term><literal>use-binary-caches</literal></term>
|
|
||||||
|
|
||||||
<listitem><para>If set to <literal>true</literal> (the default),
|
|
||||||
Nix will check the binary caches specified by
|
|
||||||
<option>binary-caches</option> and related options to obtain
|
|
||||||
binary substitutes.</para></listitem>
|
|
||||||
|
|
||||||
</varlistentry>
|
|
||||||
|
|
||||||
|
|
||||||
<varlistentry><term><literal>binary-caches</literal></term>
|
<varlistentry><term><literal>binary-caches</literal></term>
|
||||||
|
|
||||||
<listitem><para>A list of URLs of binary caches, separated by
|
<listitem><para>A list of URLs of binary caches, separated by
|
||||||
|
|
|
@ -20,22 +20,15 @@ $useBindings = 1;
|
||||||
%config = ();
|
%config = ();
|
||||||
|
|
||||||
sub readConfig {
|
sub readConfig {
|
||||||
if (defined $ENV{'_NIX_OPTIONS'}) {
|
my $config = "$confDir/nix.conf";
|
||||||
foreach my $s (split '\n', $ENV{'_NIX_OPTIONS'}) {
|
return unless -f $config;
|
||||||
my ($n, $v) = split '=', $s, 2;
|
|
||||||
$config{$n} = $v;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
my $config = "$confDir/nix.conf";
|
|
||||||
return unless -f $config;
|
|
||||||
|
|
||||||
open CONFIG, "<$config" or die "cannot open ‘$config’";
|
open CONFIG, "<$config" or die "cannot open ‘$config’";
|
||||||
while (<CONFIG>) {
|
while (<CONFIG>) {
|
||||||
/^\s*([\w\-\.]+)\s*=\s*(.*)$/ or next;
|
/^\s*([\w\-\.]+)\s*=\s*(.*)$/ or next;
|
||||||
$config{$1} = $2;
|
$config{$1} = $2;
|
||||||
}
|
|
||||||
close CONFIG;
|
|
||||||
}
|
}
|
||||||
|
close CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
@ -22,7 +22,11 @@ MixCommonArgs::MixCommonArgs(const string & programName)
|
||||||
[](Strings ss) {
|
[](Strings ss) {
|
||||||
auto name = ss.front(); ss.pop_front();
|
auto name = ss.front(); ss.pop_front();
|
||||||
auto value = ss.front();
|
auto value = ss.front();
|
||||||
settings.set(name, value);
|
try {
|
||||||
|
settings.set(name, value);
|
||||||
|
} catch (UsageError & e) {
|
||||||
|
warn(e.what());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -138,9 +138,6 @@ void initNix()
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
gettimeofday(&tv, 0);
|
gettimeofday(&tv, 0);
|
||||||
srandom(tv.tv_usec);
|
srandom(tv.tv_usec);
|
||||||
|
|
||||||
if (char *pack = getenv("_NIX_OPTIONS"))
|
|
||||||
settings.unpack(pack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -156,10 +153,10 @@ struct LegacyArgs : public MixCommonArgs
|
||||||
&settings.verboseBuild, false);
|
&settings.verboseBuild, false);
|
||||||
|
|
||||||
mkFlag('K', "keep-failed", "keep temporary directories of failed builds",
|
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",
|
mkFlag('k', "keep-going", "keep going after a build fails",
|
||||||
&settings.keepGoing);
|
&(bool&) settings.keepGoing);
|
||||||
|
|
||||||
mkFlag(0, "fallback", "build from source if substitution fails", []() {
|
mkFlag(0, "fallback", "build from source if substitution fails", []() {
|
||||||
settings.set("build-fallback", "true");
|
settings.set("build-fallback", "true");
|
||||||
|
@ -184,7 +181,7 @@ struct LegacyArgs : public MixCommonArgs
|
||||||
&settings.readOnlyMode);
|
&settings.readOnlyMode);
|
||||||
|
|
||||||
mkFlag(0, "no-build-hook", "disable use of the build hook mechanism",
|
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",
|
mkFlag(0, "show-trace", "show Nix expression stack trace in evaluation errors",
|
||||||
&settings.showTrace);
|
&settings.showTrace);
|
||||||
|
@ -218,7 +215,6 @@ void parseCmdLine(int argc, char * * argv,
|
||||||
std::function<bool(Strings::iterator & arg, const Strings::iterator & end)> parseArg)
|
std::function<bool(Strings::iterator & arg, const Strings::iterator & end)> parseArg)
|
||||||
{
|
{
|
||||||
LegacyArgs(baseNameOf(argv[0]), parseArg).parseCmdline(argvToStrings(argc, argv));
|
LegacyArgs(baseNameOf(argv[0]), parseArg).parseCmdline(argvToStrings(argc, argv));
|
||||||
settings.update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -462,7 +462,7 @@ UserLock::UserLock()
|
||||||
assert(settings.buildUsersGroup != "");
|
assert(settings.buildUsersGroup != "");
|
||||||
|
|
||||||
/* Get the members of the build-users-group. */
|
/* 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)
|
if (!gr)
|
||||||
throw Error(format("the group ‘%1%’ specified in ‘build-users-group’ does not exist")
|
throw Error(format("the group ‘%1%’ specified in ‘build-users-group’ does not exist")
|
||||||
% settings.buildUsersGroup);
|
% settings.buildUsersGroup);
|
||||||
|
@ -1690,10 +1690,7 @@ void DerivationGoal::startBuilder()
|
||||||
|
|
||||||
/* Are we doing a chroot build? */
|
/* Are we doing a chroot build? */
|
||||||
{
|
{
|
||||||
string x = settings.useSandbox;
|
if (settings.sandboxMode == smEnabled) {
|
||||||
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 (get(drv->env, "__noChroot") == "1")
|
if (get(drv->env, "__noChroot") == "1")
|
||||||
throw Error(format("derivation ‘%1%’ has ‘__noChroot’ set, "
|
throw Error(format("derivation ‘%1%’ has ‘__noChroot’ set, "
|
||||||
"but that's not allowed when ‘build-use-sandbox’ is ‘true’") % drvPath);
|
"but that's not allowed when ‘build-use-sandbox’ is ‘true’") % drvPath);
|
||||||
|
@ -1704,9 +1701,9 @@ void DerivationGoal::startBuilder()
|
||||||
#endif
|
#endif
|
||||||
useChroot = true;
|
useChroot = true;
|
||||||
}
|
}
|
||||||
else if (x == "false")
|
else if (settings.sandboxMode == smDisabled)
|
||||||
useChroot = false;
|
useChroot = false;
|
||||||
else if (x == "relaxed")
|
else if (settings.sandboxMode == smRelaxed)
|
||||||
useChroot = !fixedOutput && get(drv->env, "__noChroot") != "1";
|
useChroot = !fixedOutput && get(drv->env, "__noChroot") != "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -105,12 +105,12 @@ PublicKeys getDefaultPublicKeys()
|
||||||
|
|
||||||
// FIXME: filter duplicates
|
// FIXME: filter duplicates
|
||||||
|
|
||||||
for (auto s : settings.binaryCachePublicKeys) {
|
for (auto s : settings.binaryCachePublicKeys.get()) {
|
||||||
PublicKey key(s);
|
PublicKey key(s);
|
||||||
publicKeys.emplace(key.name, key);
|
publicKeys.emplace(key.name, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto secretKeyFile : settings.secretKeyFiles) {
|
for (auto secretKeyFile : settings.secretKeyFiles.get()) {
|
||||||
try {
|
try {
|
||||||
SecretKey secretKey(readFile(secretKeyFile));
|
SecretKey secretKey(readFile(secretKeyFile));
|
||||||
publicKeys.emplace(secretKey.name, secretKey.toPublicKey());
|
publicKeys.emplace(secretKey.name, secretKey.toPublicKey());
|
||||||
|
|
|
@ -251,7 +251,7 @@ struct CurlDownloader : public Downloader
|
||||||
|
|
||||||
/* If no file exist in the specified path, curl continues to work
|
/* If no file exist in the specified path, curl continues to work
|
||||||
anyway as if netrc support was disabled. */
|
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);
|
curl_easy_setopt(req, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
|
||||||
|
|
||||||
result.data = std::make_shared<std::string>();
|
result.data = std::make_shared<std::string>();
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
#include "util.hh"
|
#include "util.hh"
|
||||||
#include "archive.hh"
|
#include "archive.hh"
|
||||||
|
#include "args.hh"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
@ -26,329 +27,89 @@ namespace nix {
|
||||||
|
|
||||||
Settings settings;
|
Settings settings;
|
||||||
|
|
||||||
|
|
||||||
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" : "";
|
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";
|
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"));
|
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__
|
#if __linux__
|
||||||
sandboxPaths = tokenizeString<StringSet>("/bin/sh=" BASH_PATH);
|
sandboxPaths = tokenizeString<StringSet>("/bin/sh=" BASH_PATH);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
restrictEval = false;
|
|
||||||
buildRepeat = 0;
|
|
||||||
allowedImpureHostPrefixes = tokenizeString<StringSet>(DEFAULT_ALLOWED_IMPURE_PREFIXES);
|
allowedImpureHostPrefixes = tokenizeString<StringSet>(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()
|
void Settings::loadConfFile()
|
||||||
{
|
{
|
||||||
Path settingsFile = (format("%1%/%2%") % nixConfDir % "nix.conf").str();
|
applyConfigFile(nixConfDir + "/nix.conf");
|
||||||
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<string> tokens = tokenizeString<vector<string> >(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<string>::iterator i = tokens.begin();
|
|
||||||
advance(i, 2);
|
|
||||||
settings[name] = concatStringsSep(" ", Strings(i, tokens.end())); // FIXME: slow
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Settings::set(const string & name, const string & value)
|
void Settings::set(const string & name, const string & value)
|
||||||
{
|
{
|
||||||
settings[name] = value;
|
|
||||||
overrides[name] = value;
|
overrides[name] = value;
|
||||||
|
Config::set(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::update()
|
StringMap Settings::getOverrides()
|
||||||
{
|
|
||||||
_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<Strings>(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<Strings>(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<Strings>(i->second);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
template<class N> 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<Strings>(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()
|
|
||||||
{
|
{
|
||||||
return overrides;
|
return overrides;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned int Settings::getDefaultCores()
|
||||||
|
{
|
||||||
|
return std::max(1U, std::thread::hardware_concurrency());
|
||||||
|
}
|
||||||
|
|
||||||
const string nixVersion = PACKAGE_VERSION;
|
const string nixVersion = PACKAGE_VERSION;
|
||||||
|
|
||||||
|
template<> void Setting<SandboxMode>::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<SandboxMode>::to_string()
|
||||||
|
{
|
||||||
|
if (value == smEnabled) return "true";
|
||||||
|
else if (value == smRelaxed) return "relaxed";
|
||||||
|
else if (value == smDisabled) return "false";
|
||||||
|
else abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> void Setting<unsigned int, Settings::MaxBuildJobsTag>::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<unsigned int, Settings::MaxBuildJobsTag>::to_string()
|
||||||
|
{
|
||||||
|
return std::to_string(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> void Setting<bool, Settings::CaseHackTag>::set(const std::string & str)
|
||||||
|
{
|
||||||
|
value = parseBool(str);
|
||||||
|
nix::useCaseHack = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> std::string Setting<bool, Settings::CaseHackTag>::to_string()
|
||||||
|
{
|
||||||
|
return printBool(value);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
#include "logging.hh"
|
#include "config.hh"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
@ -9,10 +9,17 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
typedef enum { smEnabled, smRelaxed, smDisabled } SandboxMode;
|
||||||
|
|
||||||
struct Settings {
|
extern bool useCaseHack; // FIXME
|
||||||
|
|
||||||
typedef std::map<string, string> SettingsMap;
|
class Settings : public Config {
|
||||||
|
|
||||||
|
StringMap overrides;
|
||||||
|
|
||||||
|
unsigned int getDefaultCores();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
Settings();
|
Settings();
|
||||||
|
|
||||||
|
@ -20,25 +27,15 @@ struct Settings {
|
||||||
|
|
||||||
void set(const string & name, const string & value);
|
void set(const string & name, const string & value);
|
||||||
|
|
||||||
void update();
|
StringMap getOverrides();
|
||||||
|
|
||||||
string pack();
|
Path nixPrefix;
|
||||||
|
|
||||||
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. */
|
|
||||||
|
|
||||||
/* The directory where we store sources and derived files. */
|
/* The directory where we store sources and derived files. */
|
||||||
Path nixStore;
|
Path nixStore;
|
||||||
|
|
||||||
Path nixDataDir; /* !!! fix */
|
Path nixDataDir; /* !!! fix */
|
||||||
|
|
||||||
Path nixPrefix;
|
|
||||||
|
|
||||||
/* The directory where we log various operations. */
|
/* The directory where we log various operations. */
|
||||||
Path nixLogDir;
|
Path nixLogDir;
|
||||||
|
|
||||||
|
@ -57,17 +54,14 @@ struct Settings {
|
||||||
/* File name of the socket the daemon listens to. */
|
/* File name of the socket the daemon listens to. */
|
||||||
Path nixDaemonSocketFile;
|
Path nixDaemonSocketFile;
|
||||||
|
|
||||||
/* Whether to keep temporary directories of failed builds. */
|
Setting<bool> keepFailed{this, false, "keep-failed",
|
||||||
bool keepFailed;
|
"Whether to keep temporary directories of failed builds."};
|
||||||
|
|
||||||
/* Whether to keep building subgoals when a sibling (another
|
Setting<bool> keepGoing{this, false, "keep-going",
|
||||||
subgoal of the same goal) fails. */
|
"Whether to keep building derivations when another build fails."};
|
||||||
bool keepGoing;
|
|
||||||
|
|
||||||
/* Whether, if we cannot realise the known closure corresponding
|
Setting<bool> tryFallback{this, tryFallback, "build-fallback",
|
||||||
to a derivation, we should try to normalise the derivation
|
"Whether to fall back to building when substitution fails."};
|
||||||
instead. */
|
|
||||||
bool tryFallback;
|
|
||||||
|
|
||||||
/* Whether to show build log output in real time. */
|
/* Whether to show build log output in real time. */
|
||||||
bool verboseBuild = true;
|
bool verboseBuild = true;
|
||||||
|
@ -76,206 +70,206 @@ struct Settings {
|
||||||
the log to show if a build fails. */
|
the log to show if a build fails. */
|
||||||
size_t logLines = 10;
|
size_t logLines = 10;
|
||||||
|
|
||||||
/* Maximum number of parallel build jobs. 0 means unlimited. */
|
struct MaxBuildJobsTag { };
|
||||||
unsigned int maxBuildJobs;
|
Setting<unsigned int, MaxBuildJobsTag> 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,
|
Setting<unsigned int> buildCores{this, getDefaultCores(), "build-cores",
|
||||||
i.e. by passing this number to Make via '-j'. 0 means that the
|
"Number of CPU cores to utilize in parallel within a build, "
|
||||||
number of actual CPU cores on the local host ought to be
|
"i.e. by passing this number to Make via '-j'. 0 means that the "
|
||||||
auto-detected. */
|
"number of actual CPU cores on the local host ought to be "
|
||||||
unsigned int buildCores;
|
"auto-detected."};
|
||||||
|
|
||||||
/* Read-only mode. Don't copy stuff to the store, don't change
|
/* Read-only mode. Don't copy stuff to the store, don't change
|
||||||
the database. */
|
the database. */
|
||||||
bool readOnlyMode;
|
bool readOnlyMode = false;
|
||||||
|
|
||||||
/* The canonical system name, as returned by config.guess. */
|
Setting<std::string> thisSystem{this, SYSTEM, "system",
|
||||||
string thisSystem;
|
"The canonical Nix system name."};
|
||||||
|
|
||||||
/* The maximum time in seconds that a builer can go without
|
Setting<time_t> maxSilentTime{this, 0, "build-max-silent-time",
|
||||||
producing any output on stdout/stderr before it is killed. 0
|
"The maximum time in seconds that a builer can go without "
|
||||||
means infinity. */
|
"producing any output on stdout/stderr before it is killed. "
|
||||||
time_t maxSilentTime;
|
"0 means infinity."};
|
||||||
|
|
||||||
/* The maximum duration in seconds that a builder can run. 0
|
Setting<time_t> buildTimeout{this, 0, "build-timeout",
|
||||||
means infinity. */
|
"The maximum duration in seconds that a builder can run. "
|
||||||
time_t buildTimeout;
|
"0 means infinity."};
|
||||||
|
|
||||||
/* Whether to use build hooks (for distributed builds). Sometimes
|
Setting<bool> useBuildHook{this, true, "remote-builds",
|
||||||
users want to disable this from the command-line. */
|
"Whether to use build hooks (for distributed builds)."};
|
||||||
bool useBuildHook;
|
|
||||||
|
|
||||||
/* Amount of reserved space for the garbage collector
|
Setting<off_t> reservedSize{this, 8 * 1024 * 1024, "gc-reserved-space",
|
||||||
(/nix/var/nix/db/reserved). */
|
"Amount of reserved disk space for the garbage collector."};
|
||||||
off_t reservedSize;
|
|
||||||
|
|
||||||
/* Whether SQLite should use fsync. */
|
Setting<bool> fsyncMetadata{this, true, "fsync-metadata",
|
||||||
bool fsyncMetadata;
|
"Whether SQLite should use fsync()."};
|
||||||
|
|
||||||
/* Whether SQLite should use WAL mode. */
|
Setting<bool> useSQLiteWAL{this, true, "use-sqlite-wal",
|
||||||
bool useSQLiteWAL;
|
"Whether SQLite should use WAL mode."};
|
||||||
|
|
||||||
/* Whether to call sync() before registering a path as valid. */
|
Setting<bool> syncBeforeRegistering{this, false, "sync-before-registering",
|
||||||
bool syncBeforeRegistering;
|
"Whether to call sync() before registering a path as valid."};
|
||||||
|
|
||||||
/* Whether to use substitutes. */
|
Setting<bool> useSubstitutes{this, true, "build-use-substitutes",
|
||||||
bool useSubstitutes;
|
"Whether to use substitutes."};
|
||||||
|
|
||||||
/* The Unix group that contains the build users. */
|
Setting<std::string> buildUsersGroup{this, "", "build-users-group",
|
||||||
string buildUsersGroup;
|
"The Unix group that contains the build users."};
|
||||||
|
|
||||||
/* Set of ssh connection strings for the ssh substituter */
|
Setting<bool> impersonateLinux26{this, false, "build-impersonate-linux-26",
|
||||||
Strings sshSubstituterHosts;
|
"Whether to impersonate a Linux 2.6 machine on newer kernels."};
|
||||||
|
|
||||||
/* Whether to use the ssh substituter at all */
|
Setting<bool> keepLog{this, true, "build-keep-log",
|
||||||
bool useSshSubstituter;
|
"Whether to store build logs."};
|
||||||
|
|
||||||
/* Whether to impersonate a Linux 2.6 machine on newer kernels. */
|
Setting<bool> compressLog{this, true, "build-compress-log",
|
||||||
bool impersonateLinux26;
|
"Whether to compress logs."};
|
||||||
|
|
||||||
/* Whether to store build logs. */
|
Setting<unsigned long> maxLogSize{this, 0, "build-max-log-size",
|
||||||
bool keepLog;
|
"Maximum number of bytes a builder can write to stdout/stderr "
|
||||||
|
"before being killed (0 means no limit)."};
|
||||||
/* 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;
|
|
||||||
|
|
||||||
/* When build-repeat > 0 and verboseBuild == true, whether to
|
/* When build-repeat > 0 and verboseBuild == true, whether to
|
||||||
print repeated builds (i.e. builds other than the first one) to
|
print repeated builds (i.e. builds other than the first one) to
|
||||||
stderr. Hack to prevent Hydra logs from being polluted. */
|
stderr. Hack to prevent Hydra logs from being polluted. */
|
||||||
bool printRepeatedBuilds = true;
|
bool printRepeatedBuilds = true;
|
||||||
|
|
||||||
/* How often (in seconds) to poll for locks. */
|
Setting<unsigned int> pollInterval{this, 5, "build-poll-interval",
|
||||||
unsigned int pollInterval;
|
"How often (in seconds) to poll for locks."};
|
||||||
|
|
||||||
/* Whether to check if new GC roots can in fact be found by the
|
Setting<bool> checkRootReachability{this, false, "gc-check-reachability",
|
||||||
garbage collector. */
|
"Whether to check if new GC roots can in fact be found by the "
|
||||||
bool checkRootReachability;
|
"garbage collector."};
|
||||||
|
|
||||||
/* Whether the garbage collector should keep outputs of live
|
Setting<bool> gcKeepOutputs{this, false, "gc-keep-outputs",
|
||||||
derivations. */
|
"Whether the garbage collector should keep outputs of live derivations."};
|
||||||
bool gcKeepOutputs;
|
|
||||||
|
|
||||||
/* Whether the garbage collector should keep derivers of live
|
Setting<bool> gcKeepDerivations{this, true, "gc-keep-derivations",
|
||||||
paths. */
|
"Whether the garbage collector should keep derivers of live paths."};
|
||||||
bool gcKeepDerivations;
|
|
||||||
|
|
||||||
/* Whether to automatically replace files with identical contents
|
Setting<bool> autoOptimiseStore{this, false, "auto-optimise-store",
|
||||||
with hard links. */
|
"Whether to automatically replace files with identical contents with hard links."};
|
||||||
bool autoOptimiseStore;
|
|
||||||
|
|
||||||
/* Whether to add derivations as a dependency of user environments
|
Setting<bool> envKeepDerivations{this, false, "env-keep-derivations",
|
||||||
(to prevent them from being GCed). */
|
"Whether to add derivations as a dependency of user environments "
|
||||||
bool envKeepDerivations;
|
"(to prevent them from being GCed)."};
|
||||||
|
|
||||||
/* Whether to lock the Nix client and worker to the same CPU. */
|
/* Whether to lock the Nix client and worker to the same CPU. */
|
||||||
bool lockCPU;
|
bool lockCPU;
|
||||||
|
|
||||||
/* Whether to show a stack trace if Nix evaluation fails. */
|
/* Whether to show a stack trace if Nix evaluation fails. */
|
||||||
bool showTrace;
|
bool showTrace = false;
|
||||||
|
|
||||||
/* Whether native-code enabling primops should be enabled */
|
Setting<bool> enableNativeCode{this, false, "allow-unsafe-native-code-during-evaluation",
|
||||||
bool enableNativeCode;
|
"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) */
|
Setting<SandboxMode> sandboxMode{this, smDisabled, "build-use-sandbox",
|
||||||
string useSandbox;
|
"Whether to enable sandboxed builds. Can be \"true\", \"false\" or \"relaxed\".",
|
||||||
|
{"build-use-chroot"}};
|
||||||
|
|
||||||
/* The basic set of paths to expose in a sandbox */
|
Setting<PathSet> sandboxPaths{this, {}, "build-sandbox-paths",
|
||||||
PathSet sandboxPaths;
|
"The paths to make available inside the build sandbox.",
|
||||||
|
{"build-chroot-dirs"}};
|
||||||
|
|
||||||
/* Any extra sandbox paths to expose */
|
Setting<PathSet> extraSandboxPaths{this, {}, "build-extra-sandbox-paths",
|
||||||
PathSet extraSandboxPaths;
|
"Additional paths to make available inside the build sandbox.",
|
||||||
|
{"build-extra-chroot-dirs"}};
|
||||||
|
|
||||||
/* Whether to allow certain questionable operations (like fetching) during evaluation */
|
Setting<bool> restrictEval{this, false, "restrict-eval",
|
||||||
bool restrictEval;
|
"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 */
|
Setting<size_t> buildRepeat{this, 0, "build-repeat",
|
||||||
int buildRepeat;
|
"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) */
|
#if __linux__
|
||||||
PathSet allowedImpureHostPrefixes;
|
Setting<std::string> 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) */
|
Setting<PathSet> allowedImpureHostPrefixes{this, {}, "allowed-impure-host-deps",
|
||||||
string sandboxShmSize;
|
"Which prefixes to allow derivations to ask for access to (primarily for Darwin)."};
|
||||||
|
|
||||||
/* Whether to log Darwin sandbox access violations to the system log */
|
#if __APPLE__
|
||||||
bool darwinLogSandboxViolations;
|
Setting<bool> darwinLogSandboxViolations{this, false, "darwin-log-sandbox-violations",
|
||||||
|
"Whether to log Darwin sandbox access violations to the system log."};
|
||||||
|
#endif
|
||||||
|
|
||||||
/* ??? */
|
Setting<bool> runDiffHook{this, false, "run-diff-hook",
|
||||||
bool runDiffHook;
|
"Whether to run the program specified by the diff-hook setting "
|
||||||
|
"repeated builds produce a different result. Typically used to "
|
||||||
|
"plug in diffoscope."};
|
||||||
|
|
||||||
/* ??? */
|
PathSetting diffHook{this, true, "", "diff-hook",
|
||||||
string diffHook;
|
"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 */
|
Setting<bool> enforceDeterminism{this, true, "enforce-determinism",
|
||||||
bool enforceDeterminism;
|
"Whether to fail if repeated builds produce different output."};
|
||||||
|
|
||||||
/* The known public keys for a binary cache */
|
Setting<Strings> binaryCachePublicKeys{this,
|
||||||
Strings binaryCachePublicKeys;
|
{"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="},
|
||||||
|
"binary-cache-public-keys",
|
||||||
|
"Trusted public keys for secure substitution."};
|
||||||
|
|
||||||
/* Secret keys to use for build output signing */
|
Setting<Strings> secretKeyFiles{this, {}, "secret-key-files",
|
||||||
Strings secretKeyFiles;
|
"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 */
|
Setting<size_t> binaryCachesParallelConnections{this, 25, "binary-caches-parallel-connections",
|
||||||
int binaryCachesParallelConnections;
|
"Number of parallel connections to binary caches."};
|
||||||
|
|
||||||
/* Whether to enable HTTP2 */
|
Setting<bool> enableHttp2{this, true, "enable-http2",
|
||||||
bool enableHttp2;
|
"Whether to enable HTTP/2 support."};
|
||||||
|
|
||||||
/* How soon to expire tarballs like builtins.fetchTarball and (ugh, bad name) builtins.fetchurl */
|
Setting<unsigned int> tarballTtl{this, 60 * 60, "tarball-ttl",
|
||||||
int tarballTtl;
|
"How soon to expire files fetched by builtins.fetchTarball and builtins.fetchurl."};
|
||||||
|
|
||||||
/* ??? */
|
Setting<std::string> signedBinaryCaches{this, "*", "signed-binary-caches",
|
||||||
string signedBinaryCaches;
|
"Obsolete."};
|
||||||
|
|
||||||
/* ??? */
|
Setting<Strings> substituters{this,
|
||||||
Strings substituters;
|
nixStore == "/nix/store" ? Strings{"https://cache.nixos.org/"} : Strings(),
|
||||||
|
"substituters",
|
||||||
|
"The URIs of substituters (such as https://cache.nixos.org/).",
|
||||||
|
{"binary-caches"}};
|
||||||
|
|
||||||
/* ??? */
|
// FIXME: provide a way to add to option values.
|
||||||
Strings binaryCaches;
|
Setting<Strings> extraSubstituters{this, {}, "extra-substituters",
|
||||||
|
"Additional URIs of substituters.",
|
||||||
|
{"extra-binary-caches"}};
|
||||||
|
|
||||||
/* ??? */
|
Setting<Strings> trustedUsers{this, {"root"}, "trusted-users",
|
||||||
Strings extraBinaryCaches;
|
"Which users or groups are trusted to ask the daemon to do unsafe things."};
|
||||||
|
|
||||||
/* Who we trust to ask the daemon to do unsafe things */
|
|
||||||
Strings trustedUsers;
|
|
||||||
|
|
||||||
/* ?Who we trust to use the daemon in safe ways */
|
/* ?Who we trust to use the daemon in safe ways */
|
||||||
Strings allowedUsers;
|
Setting<Strings> allowedUsers{this, {"*"}, "allowed-users",
|
||||||
|
"Which users or groups are allowed to connect to the daemon."};
|
||||||
|
|
||||||
/* ??? */
|
Setting<bool> printMissing{this, true, "print-missing",
|
||||||
bool printMissing;
|
"Whether to print what paths need to be built or downloaded."};
|
||||||
|
|
||||||
/* The hook to run just before a build to set derivation-specific
|
Setting<std::string> preBuildHook{this,
|
||||||
build settings */
|
#if __APPLE__
|
||||||
Path preBuildHook;
|
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
|
Setting<std::string> netrcFile{this, fmt("%s/%s", nixConfDir, "netrc"), "netrc-file",
|
||||||
downloads. */
|
"Path to the netrc file used to obtain usernames/passwords for downloads."};
|
||||||
Path netrcFile;
|
|
||||||
|
|
||||||
/* Path to the SSL CA file used */
|
/* Path to the SSL CA file used */
|
||||||
Path caFile;
|
Path caFile;
|
||||||
|
|
||||||
/* Whether we allow import-from-derivation */
|
Setting<bool> enableImportFromDerivation{this, true, "allow-import-from-derivation",
|
||||||
bool enableImportFromDerivation;
|
"Whether the evaluator allows importing the result of a derivation."};
|
||||||
|
|
||||||
private:
|
struct CaseHackTag { };
|
||||||
StringSet deprecatedOptions;
|
Setting<bool, CaseHackTag> useCaseHack{this, nix::useCaseHack, "use-case-hack",
|
||||||
SettingsMap settings, overrides;
|
"Whether to enable a Darwin-specific hack for dealing with file name collisions."};
|
||||||
|
|
||||||
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<class N> void _get(N & res, const string & name);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,7 @@ LocalStore::LocalStore(const Params & params)
|
||||||
|
|
||||||
mode_t perm = 01775;
|
mode_t perm = 01775;
|
||||||
|
|
||||||
struct group * gr = getgrnam(settings.buildUsersGroup.c_str());
|
struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str());
|
||||||
if (!gr)
|
if (!gr)
|
||||||
printError(format("warning: the group ‘%1%’ specified in ‘build-users-group’ does not exist")
|
printError(format("warning: the group ‘%1%’ specified in ‘build-users-group’ does not exist")
|
||||||
% settings.buildUsersGroup);
|
% settings.buildUsersGroup);
|
||||||
|
@ -1335,7 +1335,7 @@ void LocalStore::signPathInfo(ValidPathInfo & info)
|
||||||
|
|
||||||
auto secretKeyFiles = settings.secretKeyFiles;
|
auto secretKeyFiles = settings.secretKeyFiles;
|
||||||
|
|
||||||
for (auto & secretKeyFile : secretKeyFiles) {
|
for (auto & secretKeyFile : secretKeyFiles.get()) {
|
||||||
SecretKey secretKey(readFile(secretKeyFile));
|
SecretKey secretKey(readFile(secretKeyFile));
|
||||||
info.sign(secretKey);
|
info.sign(secretKey);
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,9 +166,7 @@ void RemoteStore::setOptions(Connection & conn)
|
||||||
<< settings.useSubstitutes;
|
<< settings.useSubstitutes;
|
||||||
|
|
||||||
if (GET_PROTOCOL_MINOR(conn.daemonVersion) >= 12) {
|
if (GET_PROTOCOL_MINOR(conn.daemonVersion) >= 12) {
|
||||||
Settings::SettingsMap overrides = settings.getOverrides();
|
StringMap overrides = settings.getOverrides();
|
||||||
if (overrides["ssh-auth-sock"] == "")
|
|
||||||
overrides["ssh-auth-sock"] = getEnv("SSH_AUTH_SOCK");
|
|
||||||
conn.to << overrides.size();
|
conn.to << overrides.size();
|
||||||
for (auto & i : overrides)
|
for (auto & i : overrides)
|
||||||
conn.to << i.first << i.second;
|
conn.to << i.first << i.second;
|
||||||
|
|
|
@ -719,7 +719,7 @@ ref<Store> openStore(const std::string & uri, const Store::Params & params)
|
||||||
for (auto fun : *RegisterStoreImplementation::implementations) {
|
for (auto fun : *RegisterStoreImplementation::implementations) {
|
||||||
auto store = fun(uri, params);
|
auto store = fun(uri, params);
|
||||||
if (store) {
|
if (store) {
|
||||||
store->warnUnused();
|
store->warnUnknownSettings();
|
||||||
return ref<Store>(store);
|
return ref<Store>(store);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -782,13 +782,10 @@ std::list<ref<Store>> getDefaultSubstituters()
|
||||||
state->stores.push_back(openStore(uri));
|
state->stores.push_back(openStore(uri));
|
||||||
};
|
};
|
||||||
|
|
||||||
for (auto uri : settings.substituters)
|
for (auto uri : settings.substituters.get())
|
||||||
addStore(uri);
|
addStore(uri);
|
||||||
|
|
||||||
for (auto uri : settings.binaryCaches)
|
for (auto uri : settings.extraSubstituters.get())
|
||||||
addStore(uri);
|
|
||||||
|
|
||||||
for (auto uri : settings.extraBinaryCaches)
|
|
||||||
addStore(uri);
|
addStore(uri);
|
||||||
|
|
||||||
state->done = true;
|
state->done = true;
|
||||||
|
|
|
@ -11,7 +11,7 @@ void Config::set(const std::string & name, const std::string & value)
|
||||||
i->second.setting->set(value);
|
i->second.setting->set(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::add(AbstractSetting * setting)
|
void Config::addSetting(AbstractSetting * setting)
|
||||||
{
|
{
|
||||||
_settings.emplace(setting->name, Config::SettingData{false, setting});
|
_settings.emplace(setting->name, Config::SettingData{false, setting});
|
||||||
for (auto & alias : setting->aliases)
|
for (auto & alias : setting->aliases)
|
||||||
|
@ -41,21 +41,59 @@ void Config::add(AbstractSetting * setting)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::warnUnused()
|
void Config::warnUnknownSettings()
|
||||||
{
|
{
|
||||||
for (auto & i : initials)
|
for (auto & i : initials)
|
||||||
warn("unknown setting '%s'", i.first);
|
warn("unknown setting '%s'", i.first);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Config::dump()
|
StringMap Config::getSettings()
|
||||||
{
|
{
|
||||||
std::string res;
|
StringMap res;
|
||||||
for (auto & opt : _settings)
|
for (auto & opt : _settings)
|
||||||
if (!opt.second.isAlias)
|
if (!opt.second.isAlias)
|
||||||
res += opt.first + " = " + opt.second.setting->to_string() + "\n";
|
res.emplace(opt.first, opt.second.setting->to_string());
|
||||||
return res;
|
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<string> tokens = tokenizeString<vector<string> >(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<string>::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(
|
AbstractSetting::AbstractSetting(
|
||||||
const std::string & name,
|
const std::string & name,
|
||||||
const std::string & description,
|
const std::string & description,
|
||||||
|
@ -74,41 +112,65 @@ template<> std::string Setting<std::string>::to_string()
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T, typename Tag>
|
||||||
void Setting<T>::set(const std::string & str)
|
void Setting<T, Tag>::set(const std::string & str)
|
||||||
{
|
{
|
||||||
static_assert(std::is_integral<T>::value, "Integer required.");
|
static_assert(std::is_integral<T>::value, "Integer required.");
|
||||||
try {
|
if (!string2Int(str, value))
|
||||||
auto i = std::stoll(str);
|
|
||||||
if (i < std::numeric_limits<T>::min() ||
|
|
||||||
i > std::numeric_limits<T>::max())
|
|
||||||
throw UsageError("setting '%s' has out-of-range value %d", name, i);
|
|
||||||
value = i;
|
|
||||||
} catch (std::logic_error&) {
|
|
||||||
throw UsageError("setting '%s' has invalid value '%s'", name, str);
|
throw UsageError("setting '%s' has invalid value '%s'", name, str);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T, typename Tag>
|
||||||
std::string Setting<T>::to_string()
|
std::string Setting<T, Tag>::to_string()
|
||||||
{
|
{
|
||||||
static_assert(std::is_integral<T>::value, "Integer required.");
|
static_assert(std::is_integral<T>::value, "Integer required.");
|
||||||
return std::to_string(value);
|
return std::to_string(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> void Setting<bool>::set(const std::string & str)
|
bool AbstractSetting::parseBool(const std::string & str)
|
||||||
{
|
{
|
||||||
if (str == "true" || str == "yes" || str == "1")
|
if (str == "true" || str == "yes" || str == "1")
|
||||||
value = true;
|
return true;
|
||||||
else if (str == "false" || str == "no" || str == "0")
|
else if (str == "false" || str == "no" || str == "0")
|
||||||
value = false;
|
return false;
|
||||||
else
|
else
|
||||||
throw UsageError("Boolean setting '%s' has invalid value '%s'", name, str);
|
throw UsageError("Boolean setting '%s' has invalid value '%s'", name, str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<> void Setting<bool>::set(const std::string & str)
|
||||||
|
{
|
||||||
|
value = parseBool(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AbstractSetting::printBool(bool b)
|
||||||
|
{
|
||||||
|
return b ? "true" : "false";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
template<> std::string Setting<bool>::to_string()
|
template<> std::string Setting<bool>::to_string()
|
||||||
{
|
{
|
||||||
return value ? "true" : "false";
|
return printBool(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> void Setting<Strings>::set(const std::string & str)
|
||||||
|
{
|
||||||
|
value = tokenizeString<Strings>(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> std::string Setting<Strings>::to_string()
|
||||||
|
{
|
||||||
|
return concatStringsSep(" ", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> void Setting<StringSet>::set(const std::string & str)
|
||||||
|
{
|
||||||
|
value = tokenizeString<StringSet>(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> std::string Setting<StringSet>::to_string()
|
||||||
|
{
|
||||||
|
return concatStringsSep(" ", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
template class Setting<int>;
|
template class Setting<int>;
|
||||||
|
|
|
@ -47,11 +47,13 @@ public:
|
||||||
|
|
||||||
void set(const std::string & name, const std::string & value);
|
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
|
class AbstractSetting
|
||||||
|
@ -83,10 +85,15 @@ protected:
|
||||||
virtual void set(const std::string & value) = 0;
|
virtual void set(const std::string & value) = 0;
|
||||||
|
|
||||||
virtual std::string to_string() = 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. */
|
/* A setting of type T. */
|
||||||
template<typename T>
|
template<typename T, typename Tag = DefaultSettingTag>
|
||||||
class Setting : public AbstractSetting
|
class Setting : public AbstractSetting
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
|
@ -103,10 +110,12 @@ public:
|
||||||
: AbstractSetting(name, description, aliases)
|
: AbstractSetting(name, description, aliases)
|
||||||
, value(def)
|
, value(def)
|
||||||
{
|
{
|
||||||
options->add(this);
|
options->addSetting(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
operator const T &() const { return value; }
|
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; }
|
||||||
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; }
|
void operator =(const T & v) { value = v; }
|
||||||
|
@ -123,6 +132,9 @@ std::ostream & operator <<(std::ostream & str, const Setting<T> & opt)
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool operator ==(const T & v1, const Setting<T> & v2) { return v1 == (const T &) v2; }
|
||||||
|
|
||||||
/* A special setting for Paths. These are automatically canonicalised
|
/* A special setting for Paths. These are automatically canonicalised
|
||||||
(e.g. "/foo//bar/" becomes "/foo/bar"). */
|
(e.g. "/foo//bar/" becomes "/foo/bar"). */
|
||||||
class PathSetting : public Setting<Path>
|
class PathSetting : public Setting<Path>
|
||||||
|
|
|
@ -436,30 +436,29 @@ static void performOp(ref<LocalStore> store, bool trusted, unsigned int clientVe
|
||||||
}
|
}
|
||||||
|
|
||||||
case wopSetOptions: {
|
case wopSetOptions: {
|
||||||
from >> settings.keepFailed;
|
settings.keepFailed = readInt(from);
|
||||||
from >> settings.keepGoing;
|
settings.keepGoing = readInt(from);
|
||||||
settings.set("build-fallback", readInt(from) ? "true" : "false");
|
settings.tryFallback = readInt(from);
|
||||||
verbosity = (Verbosity) readInt(from);
|
verbosity = (Verbosity) readInt(from);
|
||||||
settings.set("build-max-jobs", std::to_string(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.useBuildHook = readInt(from) != 0;
|
||||||
settings.verboseBuild = lvlError == (Verbosity) readInt(from);
|
settings.verboseBuild = lvlError == (Verbosity) readInt(from);
|
||||||
readInt(from); // obsolete logType
|
readInt(from); // obsolete logType
|
||||||
readInt(from); // obsolete printBuildTrace
|
readInt(from); // obsolete printBuildTrace
|
||||||
settings.set("build-cores", std::to_string(readInt(from)));
|
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) {
|
if (GET_PROTOCOL_MINOR(clientVersion) >= 12) {
|
||||||
unsigned int n = readInt(from);
|
unsigned int n = readInt(from);
|
||||||
for (unsigned int i = 0; i < n; i++) {
|
for (unsigned int i = 0; i < n; i++) {
|
||||||
string name = readString(from);
|
string name = readString(from);
|
||||||
string value = readString(from);
|
string value = readString(from);
|
||||||
if (name == "build-timeout" || name == "use-ssh-substituter")
|
if (name == "build-timeout")
|
||||||
settings.set(name, value);
|
settings.set(name, value);
|
||||||
else
|
else
|
||||||
settings.set(trusted ? name : "untrusted-" + name, value);
|
settings.set(trusted ? name : "untrusted-" + name, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
settings.update();
|
|
||||||
startWork();
|
startWork();
|
||||||
stopWork();
|
stopWork();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -795,11 +795,11 @@ static void opServe(Strings opFlags, Strings opArgs)
|
||||||
settings.maxSilentTime = readInt(in);
|
settings.maxSilentTime = readInt(in);
|
||||||
settings.buildTimeout = readInt(in);
|
settings.buildTimeout = readInt(in);
|
||||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 2)
|
if (GET_PROTOCOL_MINOR(clientVersion) >= 2)
|
||||||
in >> settings.maxLogSize;
|
settings.maxLogSize = readNum<unsigned long>(in);
|
||||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 3) {
|
if (GET_PROTOCOL_MINOR(clientVersion) >= 3) {
|
||||||
settings.set("build-repeat", std::to_string(readInt(in)));
|
settings.buildRepeat = readInt(in);
|
||||||
settings.set("enforce-determinism", readInt(in) != 0 ? "true" : "false");
|
settings.enforceDeterminism = readInt(in);
|
||||||
settings.set("run-diff-hook", "true");
|
settings.runDiffHook = readInt(in);
|
||||||
}
|
}
|
||||||
settings.printRepeatedBuilds = false;
|
settings.printRepeatedBuilds = false;
|
||||||
};
|
};
|
||||||
|
|
|
@ -42,7 +42,6 @@ void mainWrapped(int argc, char * * argv)
|
||||||
NixArgs args;
|
NixArgs args;
|
||||||
|
|
||||||
args.parseCmdline(argvToStrings(argc, argv));
|
args.parseCmdline(argvToStrings(argc, argv));
|
||||||
settings.update();
|
|
||||||
|
|
||||||
assert(args.command);
|
assert(args.command);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#! @ENV_PROG@ nix-shell
|
#! @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
|
#! nix-shell --pure -i bash -p foo bar
|
||||||
echo "$(foo) $(bar) $@"
|
echo "$(foo) $(bar) $@"
|
||||||
|
|
Loading…
Reference in a new issue