From c5b8fe315162440c1d808bc0a684a412d78bfa76 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 5 Dec 2013 14:31:57 -0500 Subject: [PATCH 01/36] Print a trace message if a build fails due to the platform being unknown --- src/libstore/build.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libstore/build.cc b/src/libstore/build.cc index 63e34d2560..0a8bdbaf5e 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -1623,10 +1623,13 @@ void DerivationGoal::startBuilder() startNest(nest, lvlInfo, format(repair ? "repairing path(s) %1%" : "building path(s) %1%") % showPaths(missingPaths)); /* Right platform? */ - if (!canBuildLocally(drv.platform)) + if (!canBuildLocally(drv.platform)) { + if (settings.printBuildTrace) + printMsg(lvlError, format("@ unsupported-platform %1% %2%") % drvPath % drv.platform); throw Error( format("a `%1%' is required to build `%3%', but I am a `%2%'") % drv.platform % settings.thisSystem % drvPath); + } /* Construct the environment passed to the builder. */ From a6add93d734279db8503951ac6466c275b3c8e4e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 10 Dec 2013 13:13:59 +0100 Subject: [PATCH 02/36] Garbage collector: Release locks on temporary root files This allows processes waiting for such locks to proceed during the trash deletion phase of the garbage collector. --- src/libstore/gc.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index ce9b10f053..d212259f3b 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -734,6 +734,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) /* Allow other processes to add to the store from here on. */ fdGCLock.close(); + fds.clear(); /* Delete the trash directory. */ printMsg(lvlInfo, format("deleting `%1%'") % state.trashDir); From 22d665019a3770148929b7504c73bcdbe025ec12 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Thu, 5 Dec 2013 11:51:54 -0500 Subject: [PATCH 03/36] builtins.storePath: Try to substitute the path if it is not yet valid Signed-off-by: Shea Levy --- src/libexpr/primops.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 9c9d202eda..6d09bb7b13 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -582,8 +582,8 @@ static void prim_storePath(EvalState & state, Value * * args, Value & v) if (!isInStore(path)) throw EvalError(format("path `%1%' is not in the Nix store") % path); Path path2 = toStorePath(path); - if (!store->isValidPath(path2)) - throw EvalError(format("store path `%1%' is not valid") % path2); + if (!settings.readOnlyMode) + store->ensurePath(path2); context.insert(path2); mkString(v, path, context); } From e36229d27f9ab508e0abf1892f3e8c263d2f8c58 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Thu, 5 Dec 2013 12:07:05 -0500 Subject: [PATCH 04/36] Bump language version for new storePath feature This will allow e.g. channel expressions to use builtins.storePath IFF it is safe to do so without knowing if the path is valid yet. Signed-off-by: Shea Levy --- src/libexpr/primops.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 6d09bb7b13..4f836e2794 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1242,7 +1242,7 @@ void EvalState::createBaseEnv() language feature gets added. It's not necessary to increase it when primops get added, because you can just use `builtins ? primOp' to check. */ - mkInt(v, 1); + mkInt(v, 2); addConstant("__langVersion", v); // Miscellaneous From 7b0d8fb23d9c71f1efb119c1f267124349c82742 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 17 Dec 2013 18:16:04 +0100 Subject: [PATCH 05/36] nix-shell --pure: Keep $TERM --- scripts/nix-build.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/nix-build.in b/scripts/nix-build.in index 8ac95e90e5..aaab0ce4db 100755 --- a/scripts/nix-build.in +++ b/scripts/nix-build.in @@ -183,7 +183,7 @@ foreach my $expr (@exprs) { # Set the environment. if ($pure) { foreach my $name (keys %ENV) { - next if $name eq "HOME" || $name eq "USER" || $name eq "LOGNAME" || $name eq "DISPLAY" || $name eq "PATH"; + next if $name eq "HOME" || $name eq "USER" || $name eq "LOGNAME" || $name eq "DISPLAY" || $name eq "PATH" || $name eq "TERM"; delete $ENV{$name}; } # NixOS hack: prevent /etc/bashrc from sourcing /etc/profile. From 65a64522403f353880a501b02aca10fb96ea1c26 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 20 Dec 2013 13:10:14 +0100 Subject: [PATCH 06/36] nix-shell: Handle --option correctly Fixes #181. --- scripts/nix-build.in | 2 +- src/nix-store/nix-store.cc | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/nix-build.in b/scripts/nix-build.in index aaab0ce4db..b3f12b7062 100755 --- a/scripts/nix-build.in +++ b/scripts/nix-build.in @@ -177,7 +177,7 @@ foreach my $expr (@exprs) { # Build or fetch all dependencies of the derivation. my @inputDrvs = grep { my $x = $_; (grep { $x =~ $_ } @envExclude) == 0 } @{$drv->{inputDrvs}}; - system("$Nix::Config::binDir/nix-store -r @buildArgs @inputDrvs @{$drv->{inputSrcs}} > /dev/null") == 0 + system("$Nix::Config::binDir/nix-store", "-r", "--no-output", @buildArgs, @inputDrvs, @{$drv->{inputSrcs}}) == 0 or die "$0: failed to build all dependencies\n"; # Set the environment. diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index 5bd8558361..017c2c6df7 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -35,6 +35,7 @@ void printHelp() static Path gcRoot; static int rootNr = 0; static bool indirectRoot = false; +static bool noOutput = true; LocalStore & ensureLocalStore() @@ -139,8 +140,9 @@ static void opRealise(Strings opFlags, Strings opArgs) if (!ignoreUnknown) foreach (Paths::iterator, i, paths) { PathSet paths = realisePath(*i, false); - foreach (PathSet::iterator, j, paths) - cout << format("%1%\n") % *j; + if (!noOutput) + foreach (PathSet::iterator, j, paths) + cout << format("%1%\n") % *j; } } @@ -900,6 +902,8 @@ void run(Strings args) } else if (arg == "--indirect") indirectRoot = true; + else if (arg == "--no-output") + noOutput = true; else if (arg[0] == '-') { opFlags.push_back(arg); if (arg == "--max-freed" || arg == "--max-links" || arg == "--max-atime") { /* !!! hack */ From 0c1198cf08576f16633b2344dc6513cefb567cfc Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 20 Dec 2013 13:11:41 +0100 Subject: [PATCH 07/36] nix-shell: Set $IN_NIX_SHELL before evaluation This has some hacky applications. --- scripts/nix-build.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/nix-build.in b/scripts/nix-build.in index b3f12b7062..60e8afec8e 100755 --- a/scripts/nix-build.in +++ b/scripts/nix-build.in @@ -175,6 +175,8 @@ foreach my $expr (@exprs) { $drvPath = readlink $drvPath or die "cannot read symlink `$drvPath'" if -l $drvPath; my $drv = derivationFromPath($drvPath); + $ENV{'IN_NIX_SHELL'} = 1; + # Build or fetch all dependencies of the derivation. my @inputDrvs = grep { my $x = $_; (grep { $x =~ $_ } @envExclude) == 0 } @{$drv->{inputDrvs}}; system("$Nix::Config::binDir/nix-store", "-r", "--no-output", @buildArgs, @inputDrvs, @{$drv->{inputSrcs}}) == 0 @@ -193,8 +195,6 @@ foreach my $expr (@exprs) { $ENV{'NIX_STORE'} = $Nix::Config::storeDir; $ENV{$_} = $drv->{env}->{$_} foreach keys %{$drv->{env}}; - $ENV{'IN_NIX_SHELL'} = 1; - # Run a shell using the derivation's environment. For # convenience, source $stdenv/setup to setup additional # environment variables and shell functions. Also don't lose From 769f66216504cd882ac7b6bdfa0dd1ff26f3efe5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 20 Dec 2013 12:19:10 +0000 Subject: [PATCH 08/36] nix-shell: Don't warn about the lack of a GC root --- scripts/nix-build.in | 2 +- src/libmain/shared.cc | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/nix-build.in b/scripts/nix-build.in index 60e8afec8e..fe22058f62 100755 --- a/scripts/nix-build.in +++ b/scripts/nix-build.in @@ -179,7 +179,7 @@ foreach my $expr (@exprs) { # Build or fetch all dependencies of the derivation. my @inputDrvs = grep { my $x = $_; (grep { $x =~ $_ } @envExclude) == 0 } @{$drv->{inputDrvs}}; - system("$Nix::Config::binDir/nix-store", "-r", "--no-output", @buildArgs, @inputDrvs, @{$drv->{inputSrcs}}) == 0 + system("$Nix::Config::binDir/nix-store", "-r", "--no-output", "--no-gc-warning", @buildArgs, @inputDrvs, @{$drv->{inputSrcs}}) == 0 or die "$0: failed to build all dependencies\n"; # Set the environment. diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index b0b69f7f61..999b502380 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -35,8 +35,11 @@ static void sigintHandler(int signo) } +static bool gcWarning = true; + void printGCWarning() { + if (!gcWarning) return; static bool haveWarned = false; warnOnce(haveWarned, "you did not specify `--add-root'; " @@ -212,6 +215,8 @@ static void initAndRun(int argc, char * * argv) settings.useBuildHook = false; else if (arg == "--show-trace") settings.showTrace = true; + else if (arg == "--no-gc-warning") + gcWarning = false; else if (arg == "--option") { ++i; if (i == args.end()) throw UsageError("`--option' requires two arguments"); string name = *i; From 194e3374b89b8b2dec6296923877304bdb5c6ae2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 20 Dec 2013 13:31:31 +0100 Subject: [PATCH 09/36] Scan /proc//cmdline for GC roots --- scripts/find-runtime-roots.pl.in | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/find-runtime-roots.pl.in b/scripts/find-runtime-roots.pl.in index e1a2dde556..2e04a635e8 100755 --- a/scripts/find-runtime-roots.pl.in +++ b/scripts/find-runtime-roots.pl.in @@ -15,8 +15,6 @@ sub readProc { my $process = "/proc/$name"; - #print STDERR "=== $process\n"; - my $target; print "$target\n" if $target = readlink "$process/exe"; print "$target\n" if $target = readlink "$process/cwd"; @@ -38,11 +36,17 @@ sub readProc { } # Get all store paths that appear in the environment of this process. + my $re = "\Q$Nix::Config::storeDir\E\/[0-9a-z]+[0-9a-zA-Z\+\-\._\?=]*"; eval { my $env = Nix::Utils::readFile "$process/environ"; - my @matches = $env =~ /\Q$Nix::Config::storeDir\E\/[0-9a-z]+[0-9a-zA-Z\+\-\._\?=]*/g; + my @matches = $env =~ /$re/g; print "$_\n" foreach @matches; - } + }; + eval { + my $cmdline = Nix::Utils::readFile "$process/cmdline"; + my @matches = $cmdline =~ /$re/g; + print "$_\n" foreach @matches; + }; } closedir DIR; From 7d203faff6d74d839324cd082236381d20444d8e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 20 Dec 2013 13:56:42 +0100 Subject: [PATCH 10/36] nix-env --set-flag: Barf if a selector doesn't match any installed package Fixes #184. --- src/nix-env/nix-env.cc | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index cf74747dac..b42fe97eb4 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -233,6 +233,15 @@ static bool isPrebuilt(EvalState & state, DrvInfo & elem) } +static void checkSelectorUse(DrvNames & selectors) +{ + /* Check that all selectors have been used. */ + foreach (DrvNames::iterator, i, selectors) + if (i->hits == 0 && i->fullName != "*") + throw Error(format("selector `%1%' matches no derivations") % i->fullName); +} + + static DrvInfos filterBySelector(EvalState & state, const DrvInfos & allElems, const Strings & args, bool newestOnly) { @@ -315,11 +324,7 @@ static DrvInfos filterBySelector(EvalState & state, const DrvInfos & allElems, } } - /* Check that all selectors have been used. */ - foreach (DrvNames::iterator, i, selectors) - if (i->hits == 0 && i->fullName != "*") - throw Error(format("selector `%1%' matches no derivations") - % i->fullName); + checkSelectorUse(selectors); return elems; } @@ -673,11 +678,14 @@ static void opSetFlag(Globals & globals, foreach (DrvNames::iterator, j, selectors) if (j->matches(drvName)) { printMsg(lvlInfo, format("setting flag on `%1%'") % i->name); + j->hits++; setMetaFlag(globals.state, *i, flagName, flagValue); break; } } + checkSelectorUse(selectors); + /* Write the new user environment. */ if (createUserEnv(globals.state, installedElems, globals.profile, settings.envKeepDerivations, lockToken)) break; From f1e5dedb611d39ecc600fccb4eba4b0de730c5fc Mon Sep 17 00:00:00 2001 From: Petr Rockai Date: Sun, 24 Nov 2013 21:22:23 +0100 Subject: [PATCH 11/36] perl: Call loadConfFile() in doInit to avoid screwing sqlite journal mode. If the database is opened through perl bindings (and even though nix.conf has use-sqlite-wal set to false), the database is automatically converted into WAL mode. This makes the next nix process to access the database convert it back to "truncate". If the database is still open at the time in wal mode by the perl program, this fails and crashes the nix doing the wal -> truncate conversion. --- perl/lib/Nix/Store.xs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/perl/lib/Nix/Store.xs b/perl/lib/Nix/Store.xs index c449ed5241..d46af57e64 100644 --- a/perl/lib/Nix/Store.xs +++ b/perl/lib/Nix/Store.xs @@ -20,6 +20,8 @@ void doInit() if (!store) { try { settings.processEnvironment(); + settings.loadConfFile(); + settings.update(); settings.lockCPU = false; store = openStore(); } catch (Error & e) { From 8931bf7168cbbc6a078bed6486b8081652663836 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 20 Dec 2013 13:09:12 +0000 Subject: [PATCH 12/36] Doh --- src/nix-store/nix-store.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index 017c2c6df7..69a98fe472 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -35,7 +35,7 @@ void printHelp() static Path gcRoot; static int rootNr = 0; static bool indirectRoot = false; -static bool noOutput = true; +static bool noOutput = false; LocalStore & ensureLocalStore() From b352fe2775d09993add893ebff8c0c4c8369182a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 20 Dec 2013 14:18:24 +0100 Subject: [PATCH 13/36] Revert "Scan /proc//cmdline for GC roots" This reverts commit 194e3374b89b8b2dec6296923877304bdb5c6ae2. Checking the command line for GC roots means that $ nix-store --delete $path will fail because $path is now a root because it's mentioned on the command line. --- scripts/find-runtime-roots.pl.in | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/scripts/find-runtime-roots.pl.in b/scripts/find-runtime-roots.pl.in index 2e04a635e8..e1a2dde556 100755 --- a/scripts/find-runtime-roots.pl.in +++ b/scripts/find-runtime-roots.pl.in @@ -15,6 +15,8 @@ sub readProc { my $process = "/proc/$name"; + #print STDERR "=== $process\n"; + my $target; print "$target\n" if $target = readlink "$process/exe"; print "$target\n" if $target = readlink "$process/cwd"; @@ -36,17 +38,11 @@ sub readProc { } # Get all store paths that appear in the environment of this process. - my $re = "\Q$Nix::Config::storeDir\E\/[0-9a-z]+[0-9a-zA-Z\+\-\._\?=]*"; eval { my $env = Nix::Utils::readFile "$process/environ"; - my @matches = $env =~ /$re/g; + my @matches = $env =~ /\Q$Nix::Config::storeDir\E\/[0-9a-z]+[0-9a-zA-Z\+\-\._\?=]*/g; print "$_\n" foreach @matches; - }; - eval { - my $cmdline = Nix::Utils::readFile "$process/cmdline"; - my @matches = $cmdline =~ /$re/g; - print "$_\n" foreach @matches; - }; + } } closedir DIR; From 5ba5993470a6ad532fc8e842084a574a88876b0a Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Mon, 30 Dec 2013 07:58:14 -0500 Subject: [PATCH 14/36] nix-shell --pure: Don't clear IN_NIX_SHELL Signed-off-by: Shea Levy --- scripts/nix-build.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/nix-build.in b/scripts/nix-build.in index fe22058f62..8b66e38a9a 100755 --- a/scripts/nix-build.in +++ b/scripts/nix-build.in @@ -185,7 +185,7 @@ foreach my $expr (@exprs) { # Set the environment. if ($pure) { foreach my $name (keys %ENV) { - next if $name eq "HOME" || $name eq "USER" || $name eq "LOGNAME" || $name eq "DISPLAY" || $name eq "PATH" || $name eq "TERM"; + next if $name eq "HOME" || $name eq "USER" || $name eq "LOGNAME" || $name eq "DISPLAY" || $name eq "PATH" || $name eq "TERM" || $name eq "IN_NIX_SHELL"; delete $ENV{$name}; } # NixOS hack: prevent /etc/bashrc from sourcing /etc/profile. From 136f2f7046dfed18fde0b5f9933ddfafc1518ef5 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Fri, 13 Sep 2013 16:55:33 -0400 Subject: [PATCH 15/36] Add the ExprBuiltin Expr type to the AST Certain desugaring schemes may require the parser to use some builtin function to do some of the work (e.g. currently `throw` is used to lazily cause an error if a `<>`-style path is not in the search path) Unfortunately, these names are not reserved keywords, so an expression that uses such a syntactic sugar will not see the expected behavior (see tests/lang/eval-okay-redefine-builtin.nix for an example). This adds the ExprBuiltin AST type, which when evaluated uses the value from the rootmost variable scope (which of course is initialized internally and can't shadow any of the builtins). Signed-off-by: Shea Levy --- src/libexpr/eval.cc | 11 +++++++++++ src/libexpr/nixexpr.cc | 9 +++++++++ src/libexpr/nixexpr.hh | 7 +++++++ src/libexpr/parser.y | 18 +++++++++--------- tests/lang/eval-okay-redefine-builtin.exp | 1 + tests/lang/eval-okay-redefine-builtin.nix | 3 +++ 6 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 tests/lang/eval-okay-redefine-builtin.exp create mode 100644 tests/lang/eval-okay-redefine-builtin.nix diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 3db4bb66f4..538325601b 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -908,6 +908,17 @@ void ExprOpNot::eval(EvalState & state, Env & env, Value & v) } +void ExprBuiltin::eval(EvalState & state, Env & env, Value & v) +{ + // Not a hot path at all, but would be nice to access state.baseEnv directly + Env *baseEnv = &env; + while (baseEnv->up) baseEnv = baseEnv->up; + Bindings::iterator binding = baseEnv->values[0]->attrs->find(name); + assert(binding != baseEnv->values[0]->attrs->end()); + v = *binding->value; +} + + void ExprOpEq::eval(EvalState & state, Env & env, Value & v) { Value v1; e1->eval(state, env, v1); diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index f4b4295e29..c7ac967968 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -121,6 +121,11 @@ void ExprOpNot::show(std::ostream & str) str << "! " << *e; } +void ExprBuiltin::show(std::ostream & str) +{ + str << "builtins." << name; +} + void ExprConcatStrings::show(std::ostream & str) { bool first = true; @@ -314,6 +319,10 @@ void ExprOpNot::bindVars(const StaticEnv & env) e->bindVars(env); } +void ExprBuiltin::bindVars(const StaticEnv & env) +{ +} + void ExprConcatStrings::bindVars(const StaticEnv & env) { foreach (vector::iterator, i, *es) diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 61eb81fab1..2ae2e2e96f 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -248,6 +248,13 @@ struct ExprOpNot : Expr COMMON_METHODS }; +struct ExprBuiltin : Expr +{ + Symbol name; + ExprBuiltin(Symbol name) : name(name) { }; + COMMON_METHODS +}; + #define MakeBinOp(name, s) \ struct Expr##name : Expr \ { \ diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 7699cf502b..d30882ac84 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -311,13 +311,13 @@ expr_if expr_op : '!' expr_op %prec NOT { $$ = new ExprOpNot($2); } -| '-' expr_op %prec NEGATE { $$ = new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__sub")), new ExprInt(0)), $2); } +| '-' expr_op %prec NEGATE { $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("sub")), new ExprInt(0)), $2); } | expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); } | expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); } - | expr_op '<' expr_op { $$ = new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__lessThan")), $1), $3); } - | expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__lessThan")), $3), $1)); } - | expr_op '>' expr_op { $$ = new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__lessThan")), $3), $1); } - | expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__lessThan")), $1), $3)); } + | expr_op '<' expr_op { $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("lessThan")), $1), $3); } + | expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("lessThan")), $3), $1)); } + | expr_op '>' expr_op { $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("lessThan")), $3), $1); } + | expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("lessThan")), $1), $3)); } | expr_op AND expr_op { $$ = new ExprOpAnd($1, $3); } | expr_op OR expr_op { $$ = new ExprOpOr($1, $3); } | expr_op IMPL expr_op { $$ = new ExprOpImpl($1, $3); } @@ -329,9 +329,9 @@ expr_op l->push_back($3); $$ = new ExprConcatStrings(false, l); } - | expr_op '-' expr_op { $$ = new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__sub")), $1), $3); } - | expr_op '*' expr_op { $$ = new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__mul")), $1), $3); } - | expr_op '/' expr_op { $$ = new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__div")), $1), $3); } + | expr_op '-' expr_op { $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("sub")), $1), $3); } + | expr_op '*' expr_op { $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("mul")), $1), $3); } + | expr_op '/' expr_op { $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("div")), $1), $3); } | expr_op CONCAT expr_op { $$ = new ExprOpConcatLists($1, $3); } | expr_app ; @@ -381,7 +381,7 @@ expr_simple ‘throw’. */ $$ = path2 == "" ? (Expr * ) new ExprApp( - new ExprVar(noPos, data->symbols.create("throw")), + new ExprBuiltin(data->symbols.create("throw")), new ExprString(data->symbols.create( (format("file `%1%' was not found in the Nix search path (add it using $NIX_PATH or -I)") % path).str()))) : (Expr * ) new ExprPath(path2); diff --git a/tests/lang/eval-okay-redefine-builtin.exp b/tests/lang/eval-okay-redefine-builtin.exp new file mode 100644 index 0000000000..c508d5366f --- /dev/null +++ b/tests/lang/eval-okay-redefine-builtin.exp @@ -0,0 +1 @@ +false diff --git a/tests/lang/eval-okay-redefine-builtin.nix b/tests/lang/eval-okay-redefine-builtin.nix new file mode 100644 index 0000000000..df9fc3f37d --- /dev/null +++ b/tests/lang/eval-okay-redefine-builtin.nix @@ -0,0 +1,3 @@ +let + throw = abort "Error!"; +in (builtins.tryEval ).success From 18fefacf7df570444b332a8a0c2dc4f9d98d4344 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Fri, 20 Sep 2013 23:25:30 -0400 Subject: [PATCH 16/36] Dynamic attrs This adds new syntax for attribute names: * attrs."${name}" => getAttr name attrs * attrs ? "${name}" => isAttrs attrs && hasAttr attrs name * attrs."${name}" or def => if attrs ? "${name}" then attrs."${name}" else def * { "${name}" = value; } => listToAttrs [{ inherit name value; }] Of course, it's a bit more complicated than that. The attribute chains can be arbitrarily long and contain combinations of static and dynamic parts (e.g. attrs."${foo}".bar."${baz}" or qux), which is relatively straightforward for the getAttrs/hasAttrs cases but is more complex for the listToAttrs case due to rules about duplicate attribute definitions. For attribute sets with dynamic attribute names, duplicate static attributes are detected at parse time while duplicate dynamic attributes are detected when the attribute set is forced. So, for example, { a = null; a.b = null; "${"c"}" = true; } will be a parse-time error, while { a = {}; "${"a"}".b = null; c = true; } will be an eval-time error (technically that case could theoretically be detected at parse time, but the general case would require full evaluation). Moreover, duplicate dynamic attributes are not allowed even in cases where they would be with static attributes ({ a.b.d = true; a.b.c = false; } is legal, but { a."${"b"}".d = true; a."${"b"}".c = false; } is not). This restriction might be relaxed in the future in cases where the static variant would not be an error, but it is not obvious that that is desirable. Finally, recursive attribute sets with dynamic attributes have the static attributes in scope but not the dynamic ones. So rec { a = true; "${"b"}" = a; } is equivalent to { a = true; b = true; } but rec { "${"a"}" = true; b = a; } would be an error or use a from the surrounding scope if it exists. Note that the getAttr, getAttr or default, and hasAttr are all implemented purely in the parser as syntactic sugar, while attribute sets with dynamic attribute names required changes to the AST to be implemented cleanly. This is an alternative solution to and closes #167 Signed-off-by: Shea Levy --- src/libexpr/eval.cc | 24 ++- src/libexpr/nixexpr.cc | 9 + src/libexpr/nixexpr.hh | 10 +- src/libexpr/parser.y | 218 ++++++++++++++++++++++--- tests/lang/eval-okay-dynamic-attrs.exp | 1 + tests/lang/eval-okay-dynamic-attrs.nix | 17 ++ 6 files changed, 256 insertions(+), 23 deletions(-) create mode 100644 tests/lang/eval-okay-dynamic-attrs.exp create mode 100644 tests/lang/eval-okay-dynamic-attrs.nix diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 538325601b..cd3edecaa7 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -247,6 +247,11 @@ LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, con throw EvalError(format(s) % s2 % s3); } +LocalNoInlineNoReturn(void throwEvalError(const char * s, const Symbol & sym, const Pos & p1, const Pos & p2)) +{ + throw EvalError(format(s) % sym % p1 % p2); +} + LocalNoInlineNoReturn(void throwTypeError(const char * s)) { throw TypeError(s); @@ -557,12 +562,14 @@ void ExprPath::eval(EvalState & state, Env & env, Value & v) void ExprAttrs::eval(EvalState & state, Env & env, Value & v) { state.mkAttrs(v, attrs.size()); + Env *dynamicEnv = &env; if (recursive) { /* Create a new environment that contains the attributes in this `rec'. */ Env & env2(state.allocEnv(attrs.size())); env2.up = &env; + dynamicEnv = &env2; AttrDefs::iterator overrides = attrs.find(state.sOverrides); bool hasOverrides = overrides != attrs.end(); @@ -605,9 +612,24 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) } } - else { + else foreach (AttrDefs::iterator, i, attrs) v.attrs->push_back(Attr(i->first, i->second.e->maybeThunk(state, env), &i->second.pos)); + + /* dynamic attrs apply *after* rec and __overrides */ + foreach (DynamicAttrDefs::iterator, i, dynamicAttrs) { + Value nameVal; + i->nameExpr->eval(state, *dynamicEnv, nameVal); + state.forceStringNoCtx(nameVal); + Symbol nameSym = state.symbols.create(nameVal.string.s); + Bindings::iterator j = v.attrs->find(nameSym); + if (j != v.attrs->end()) + throwEvalError("dynamic attribute `%1%' at %2% already defined at %3%", nameSym, i->pos, *j->pos); + + i->valueExpr->setName(nameSym); + /* Keep sorted order so find can catch duplicates */ + v.attrs->insert(lower_bound(v.attrs->begin(), v.attrs->end(), Attr(nameSym, 0)), + Attr(nameSym, i->valueExpr->maybeThunk(state, *dynamicEnv), &i->pos)); } } diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index c7ac967968..a7ce58c4d9 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -61,6 +61,8 @@ void ExprAttrs::show(std::ostream & str) str << "inherit " << i->first << " " << "; "; else str << i->first << " = " << *i->second.e << "; "; + foreach (DynamicAttrDefs::iterator, i, dynamicAttrs) + str << "\"${" << *i->nameExpr << "}\" = " << *i->valueExpr << "; "; str << "}"; } @@ -227,8 +229,10 @@ void ExprOpHasAttr::bindVars(const StaticEnv & env) void ExprAttrs::bindVars(const StaticEnv & env) { + const StaticEnv *dynamicEnv = &env; if (recursive) { StaticEnv newEnv(false, &env); + dynamicEnv = &newEnv; unsigned int displ = 0; foreach (AttrDefs::iterator, i, attrs) @@ -241,6 +245,11 @@ void ExprAttrs::bindVars(const StaticEnv & env) else foreach (AttrDefs::iterator, i, attrs) i->second.e->bindVars(env); + + foreach (DynamicAttrDefs::iterator, i, dynamicAttrs) { + i->nameExpr->bindVars(*dynamicEnv); + i->valueExpr->bindVars(*dynamicEnv); + } } void ExprList::bindVars(const StaticEnv & env) diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 2ae2e2e96f..92c2ca8dc5 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -163,6 +163,14 @@ struct ExprAttrs : Expr }; typedef std::map AttrDefs; AttrDefs attrs; + struct DynamicAttrDef { + Expr * nameExpr; + Expr * valueExpr; + Pos pos; + DynamicAttrDef(Expr * nameExpr, Expr * valueExpr, const Pos & pos) : nameExpr(nameExpr), valueExpr(valueExpr), pos(pos) { }; + }; + typedef std::vector DynamicAttrDefs; + DynamicAttrDefs dynamicAttrs; ExprAttrs() : recursive(false) { }; COMMON_METHODS }; @@ -251,7 +259,7 @@ struct ExprOpNot : Expr struct ExprBuiltin : Expr { Symbol name; - ExprBuiltin(Symbol name) : name(name) { }; + ExprBuiltin(const Symbol & name) : name(name) { }; COMMON_METHODS }; diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index d30882ac84..aa08e1a63e 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -39,6 +39,15 @@ namespace nix { { }; }; + struct AttrName + { + Symbol symbol; + Expr *expr; + AttrName(const Symbol & s) : symbol(s) {}; + AttrName(Expr *e) : expr(e) {}; + }; + + typedef std::vector AttrNames; } #define YY_DECL int yylex \ @@ -243,7 +252,8 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err char * id; // !!! -> Symbol char * path; char * uri; - std::vector * attrNames; + std::vector * attrpath; + std::vector * attrlist; std::vector * string_parts; } @@ -253,8 +263,10 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err %type binds %type formals %type formal -%type attrs attrpath -%type string_parts ind_string_parts +%type attrpath +%type attrs +%type string_parts_interpolated ind_string_parts +%type string_parts string_attr %type attr %token ID ATTRPATH %token STR IND_STR @@ -300,7 +312,11 @@ expr_function | WITH expr ';' expr_function { $$ = new ExprWith(CUR_POS, $2, $4); } | LET binds IN expr_function - { $$ = new ExprLet($2, $4); } + { if (!$2->dynamicAttrs.empty()) + throw ParseError(format("dynamic attributes not allowed in let at %1%") + % CUR_POS); + $$ = new ExprLet($2, $4); + } | expr_if ; @@ -322,7 +338,39 @@ expr_op | expr_op OR expr_op { $$ = new ExprOpOr($1, $3); } | expr_op IMPL expr_op { $$ = new ExprOpImpl($1, $3); } | expr_op UPDATE expr_op { $$ = new ExprOpUpdate($1, $3); } - | expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); } + | expr_op '?' attrpath + { AttrPath path; + vector::iterator i; + $$ = $1; + // All attrpaths have at least one attr + assert(!$3->empty()); + for (i = $3->begin(); i + 1 != $3->end(); i++) { + if (i->symbol.set()) { + path.push_back(i->symbol); + } else { + if (!path.empty()) { + $$ = new ExprSelect($$, path, new ExprAttrs()); + path.clear(); + } + $$ = new ExprIf( + new ExprOpAnd( + new ExprApp(new ExprBuiltin(data->symbols.create("isAttrs")), $$), + new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("hasAttr")), i->expr), $$)), + new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("getAttr")), i->expr), $$), + new ExprAttrs()); + } + } + if (i->symbol.set()) { + path.push_back(i->symbol); + $$ = new ExprOpHasAttr($$, path); + } else { + if (!path.empty()) + $$ = new ExprSelect($$, path, new ExprAttrs()); + $$ = new ExprOpAnd( + new ExprApp(new ExprBuiltin(data->symbols.create("isAttrs")), $$), + new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("hasAttr")), i->expr), $$)); + } + } | expr_op '+' expr_op { vector * l = new vector; l->push_back($1); @@ -344,9 +392,58 @@ expr_app expr_select : expr_simple '.' attrpath - { $$ = new ExprSelect($1, *$3, 0); } + { AttrPath path; + $$ = $1; + foreach (vector::iterator, i, *$3) { + if (i->symbol.set()) { + path.push_back(i->symbol); + } else { + if (!path.empty()) { + $$ = new ExprSelect($$, path, 0); + path.clear(); + } + $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("getAttr")), i->expr), $$); + } + } + if (!path.empty()) + $$ = new ExprSelect($$, path, 0); + } | expr_simple '.' attrpath OR_KW expr_select - { $$ = new ExprSelect($1, *$3, $5); } + { AttrPath path; + vector::iterator i; + $$ = $1; + // All attrpaths have at least one attr + assert(!$3->empty()); + for (i = $3->begin(); i + 1 != $3->end(); i++) { + if (i->symbol.set()) { + path.push_back(i->symbol); + } else { + if (!path.empty()) { + $$ = new ExprSelect($$, path, new ExprAttrs()); + path.clear(); + } + $$ = new ExprIf( + new ExprOpAnd( + new ExprApp(new ExprBuiltin(data->symbols.create("isAttrs")), $$), + new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("hasAttr")), i->expr), $$)), + new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("getAttr")), i->expr), $$), + new ExprAttrs()); + } + } + if (i->symbol.set()) { + path.push_back(i->symbol); + $$ = new ExprSelect($$, path, $5); + } else { + if (!path.empty()) + $$ = new ExprSelect($$, path, new ExprAttrs()); + $$ = new ExprIf( + new ExprOpAnd( + new ExprApp(new ExprBuiltin(data->symbols.create("isAttrs")), $$), + new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("hasAttr")), i->expr), $$)), + new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("getAttr")), i->expr), $$), + $5); + } + } | /* Backwards compatibility: because Nixpkgs has a rarely used function named ‘or’, allow stuff like ‘map or [...]’. */ expr_simple OR_KW @@ -362,12 +459,7 @@ expr_simple $$ = new ExprVar(CUR_POS, data->symbols.create($1)); } | INT { $$ = new ExprInt($1); } - | '"' string_parts '"' { - /* For efficiency, and to simplify parse trees a bit. */ - if ($2->empty()) $$ = new ExprString(data->symbols.create("")); - else if ($2->size() == 1 && dynamic_cast($2->front())) $$ = $2->front(); - else $$ = new ExprConcatStrings(true, $2); - } + | '"' string_parts '"' { $$ = $2; } | IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE { $$ = stripIndentation(data->symbols, *$2); } @@ -400,9 +492,27 @@ expr_simple ; string_parts - : string_parts STR { $$ = $1; $1->push_back($2); } - | string_parts DOLLAR_CURLY expr '}' { backToString(scanner); $$ = $1; $1->push_back($3); } - | { $$ = new vector; } + : STR + | string_parts_interpolated { $$ = new ExprConcatStrings(true, $1); } + | { $$ = new ExprString(data->symbols.create("")) } + ; + +string_parts_interpolated + : string_parts_interpolated STR { $$ = $1; $1->push_back($2); } + | string_parts_interpolated DOLLAR_CURLY expr '}' { backToString(scanner); $$ = $1; $1->push_back($3); } + | STR DOLLAR_CURLY expr '}' + { + backToString(scanner); + $$ = new vector; + $$->push_back($1); + $$->push_back($3); + } + | DOLLAR_CURLY expr '}' + { + backToString(scanner); + $$ = new vector; + $$->push_back($2); + } ; ind_string_parts @@ -412,7 +522,43 @@ ind_string_parts ; binds - : binds attrpath '=' expr ';' { $$ = $1; addAttr($$, *$2, $4, makeCurPos(@2, data)); } + : binds attrpath '=' expr ';' + { + ExprAttrs *curAttrs = $1; + AttrPath path; + vector::iterator i; + // All attrpaths have at least one attr + assert(!$2->empty()); + for (i = $2->begin(); i + 1 < $2->end(); i++) { + if (i->symbol.set()) { + path.push_back(i->symbol); + } else { + ExprAttrs *temp; + if (!path.empty()) { + temp = curAttrs; + curAttrs = new ExprAttrs; + addAttr(temp, path, curAttrs, makeCurPos(@2, data)); + } + path.clear(); + + temp = curAttrs; + curAttrs = new ExprAttrs; + temp->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, curAttrs, makeCurPos(@2, data))); + } + } + if (i->symbol.set()) { + path.push_back(i->symbol); + addAttr(curAttrs, path, $4, makeCurPos(@2, data)); + } else { + if (!path.empty()) { + ExprAttrs *temp = curAttrs; + curAttrs = new ExprAttrs; + addAttr(temp, path, curAttrs, makeCurPos(@2, data)); + } + curAttrs->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, $4, makeCurPos(@2, data))); + } + $$ = $1; + } | binds INHERIT attrs ';' { $$ = $1; foreach (AttrPath::iterator, i, *$3) { @@ -436,19 +582,49 @@ binds attrs : attrs attr { $$ = $1; $1->push_back(data->symbols.create($2)); /* !!! dangerous */ } + | attrs string_attr + { $$ = $1; + ExprString *str = dynamic_cast($2); + if (str) { + $$->push_back(str->s); + delete str; + } else + throw ParseError(format("dynamic attributes not allowed in inherit at %1%") + % makeCurPos(@2, data)); + } | { $$ = new vector; } ; attrpath - : attrpath '.' attr { $$ = $1; $1->push_back(data->symbols.create($3)); } - | attr { $$ = new vector; $$->push_back(data->symbols.create($1)); } + : attrpath '.' attr { $$ = $1; $1->push_back(AttrName(data->symbols.create($3))); } + | attrpath '.' string_attr + { $$ = $1; + ExprString *str = dynamic_cast($3); + if (str) { + $$->push_back(AttrName(str->s)); + delete str; + } else + $$->push_back(AttrName($3)); + } + | attr { $$ = new vector; $$->push_back(AttrName(data->symbols.create($1))); } + | string_attr + { $$ = new vector; + ExprString *str = dynamic_cast($1); + if (str) { + $$->push_back(AttrName(str->s)); + delete str; + } else + $$->push_back(AttrName($1)); + } ; attr : ID { $$ = $1; } | OR_KW { $$ = "or"; } - | '"' STR '"' - { $$ = strdup(((string) ((ExprString *) $2)->s).c_str()); delete $2; } + ; + +string_attr + : '"' string_parts '"' { $$ = $2; } ; expr_list diff --git a/tests/lang/eval-okay-dynamic-attrs.exp b/tests/lang/eval-okay-dynamic-attrs.exp new file mode 100644 index 0000000000..df8750afc0 --- /dev/null +++ b/tests/lang/eval-okay-dynamic-attrs.exp @@ -0,0 +1 @@ +{ binds = true; hasAttrs = true; multiAttrs = true; recBinds = true; selectAttrs = true; selectOrAttrs = true; } diff --git a/tests/lang/eval-okay-dynamic-attrs.nix b/tests/lang/eval-okay-dynamic-attrs.nix new file mode 100644 index 0000000000..ee02ac7e65 --- /dev/null +++ b/tests/lang/eval-okay-dynamic-attrs.nix @@ -0,0 +1,17 @@ +let + aString = "a"; + + bString = "b"; +in { + hasAttrs = { a.b = null; } ? "${aString}".b; + + selectAttrs = { a.b = true; }.a."${bString}"; + + selectOrAttrs = { }."${aString}" or true; + + binds = { "${aString}"."${bString}c" = true; }.a.bc; + + recBinds = rec { "${bString}" = a; a = true; }.b; + + multiAttrs = { "${aString}" = true; "${bString}" = false; }.a; +} From 6f3a51809a2603574a16573bd46b95e4ff5233bd Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Tue, 31 Dec 2013 17:57:10 -0500 Subject: [PATCH 17/36] Fold dynamic binds handling into addAttr Since addAttr has to iterate through the AttrPath we pass it, it makes more sense to just iterate through the AttrNames in addAttr instead. As an added bonus, this allows attrsets where two dynamic attribute paths have the same static leading part (see added test case for an example that failed previously). Signed-off-by: Shea Levy --- src/libexpr/parser.y | 90 +++++++++--------------- tests/lang/eval-okay-dynamic-attrs-2.exp | 1 + tests/lang/eval-okay-dynamic-attrs-2.nix | 1 + 3 files changed, 37 insertions(+), 55 deletions(-) create mode 100644 tests/lang/eval-okay-dynamic-attrs-2.exp create mode 100644 tests/lang/eval-okay-dynamic-attrs-2.nix diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index aa08e1a63e..3bee3b010c 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -89,31 +89,47 @@ static void dupAttr(Symbol attr, const Pos & pos, const Pos & prevPos) } -static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, +static void addAttr(ExprAttrs * attrs, AttrNames & attrNames, Expr * e, const Pos & pos) { - unsigned int n = 0; - foreach (AttrPath::const_iterator, i, attrPath) { - n++; - ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(*i); - if (j != attrs->attrs.end()) { - if (!j->second.inherited) { - ExprAttrs * attrs2 = dynamic_cast(j->second.e); - if (!attrs2 || n == attrPath.size()) dupAttr(attrPath, pos, j->second.pos); - attrs = attrs2; - } else - dupAttr(attrPath, pos, j->second.pos); - } else { - if (n == attrPath.size()) - attrs->attrs[*i] = ExprAttrs::AttrDef(e, pos); - else { + AttrPath path; + AttrNames::iterator i; + // All attrpaths have at least one attr + assert(!attrNames.empty()); + for (i = attrNames.begin(); i + 1 < attrNames.end(); i++) { + if (i->symbol.set()) { + path.push_back(i->symbol); + ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol); + if (j != attrs->attrs.end()) { + if (!j->second.inherited) { + ExprAttrs * attrs2 = dynamic_cast(j->second.e); + if (!attrs2) dupAttr(path, pos, j->second.pos); + attrs = attrs2; + } else + dupAttr(path, pos, j->second.pos); + } else { + path.clear(); ExprAttrs * nested = new ExprAttrs; - attrs->attrs[*i] = ExprAttrs::AttrDef(nested, pos); + attrs->attrs[i->symbol] = ExprAttrs::AttrDef(nested, pos); attrs = nested; } + } else { + ExprAttrs *nested = new ExprAttrs; + attrs->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, nested, pos)); + attrs = nested; } } - e->setName(attrPath.back()); + if (i->symbol.set()) { + ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol); + if (j != attrs->attrs.end()) { + dupAttr(path, pos, j->second.pos); + } else { + attrs->attrs[i->symbol] = ExprAttrs::AttrDef(e, pos); + e->setName(i->symbol); + } + } else { + attrs->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, e, pos)); + } } @@ -522,43 +538,7 @@ ind_string_parts ; binds - : binds attrpath '=' expr ';' - { - ExprAttrs *curAttrs = $1; - AttrPath path; - vector::iterator i; - // All attrpaths have at least one attr - assert(!$2->empty()); - for (i = $2->begin(); i + 1 < $2->end(); i++) { - if (i->symbol.set()) { - path.push_back(i->symbol); - } else { - ExprAttrs *temp; - if (!path.empty()) { - temp = curAttrs; - curAttrs = new ExprAttrs; - addAttr(temp, path, curAttrs, makeCurPos(@2, data)); - } - path.clear(); - - temp = curAttrs; - curAttrs = new ExprAttrs; - temp->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, curAttrs, makeCurPos(@2, data))); - } - } - if (i->symbol.set()) { - path.push_back(i->symbol); - addAttr(curAttrs, path, $4, makeCurPos(@2, data)); - } else { - if (!path.empty()) { - ExprAttrs *temp = curAttrs; - curAttrs = new ExprAttrs; - addAttr(temp, path, curAttrs, makeCurPos(@2, data)); - } - curAttrs->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, $4, makeCurPos(@2, data))); - } - $$ = $1; - } + : binds attrpath '=' expr ';' { $$ = $1; addAttr($$, *$2, $4, makeCurPos(@2, data)); } | binds INHERIT attrs ';' { $$ = $1; foreach (AttrPath::iterator, i, *$3) { diff --git a/tests/lang/eval-okay-dynamic-attrs-2.exp b/tests/lang/eval-okay-dynamic-attrs-2.exp new file mode 100644 index 0000000000..27ba77ddaf --- /dev/null +++ b/tests/lang/eval-okay-dynamic-attrs-2.exp @@ -0,0 +1 @@ +true diff --git a/tests/lang/eval-okay-dynamic-attrs-2.nix b/tests/lang/eval-okay-dynamic-attrs-2.nix new file mode 100644 index 0000000000..6d57bf8549 --- /dev/null +++ b/tests/lang/eval-okay-dynamic-attrs-2.nix @@ -0,0 +1 @@ +{ a."${"b"}" = true; a."${"c"}" = false; }.a.b From cd49fe4f9b338242e1e404fd4dbb0a3ebc1c3a12 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Tue, 31 Dec 2013 23:56:26 +0000 Subject: [PATCH 18/36] Don't use any syntactic sugar for dynamic attrs This doesn't change any functionality but moves some behavior out of the parser and into the evaluator in order to simplify the code. Signed-off-by: Shea Levy --- src/libexpr/eval.cc | 20 +++++- src/libexpr/nixexpr.cc | 21 ++++-- src/libexpr/nixexpr.hh | 13 +++- src/libexpr/parser.y | 142 +++++++---------------------------------- 4 files changed, 68 insertions(+), 128 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index cd3edecaa7..3d8ee99340 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -129,6 +129,18 @@ string showType(const Value & v) } +Symbol getName(const AttrName & name, EvalState & state, Env & env) { + if (name.symbol.set()) { + return name.symbol; + } else { + Value nameValue; + name.expr->eval(state, env, nameValue); + state.forceStringNoCtx(nameValue); + return state.symbols.create(nameValue.string.s); + } +} + + EvalState::EvalState() : sWith(symbols.create("")) , sOutPath(symbols.create("outPath")) @@ -683,17 +695,18 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) foreach (AttrPath::const_iterator, i, attrPath) { nrLookups++; Bindings::iterator j; + Symbol name = getName(*i, state, env); if (def) { state.forceValue(*vAttrs); if (vAttrs->type != tAttrs || - (j = vAttrs->attrs->find(*i)) == vAttrs->attrs->end()) + (j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) { def->eval(state, env, v); return; } } else { state.forceAttrs(*vAttrs); - if ((j = vAttrs->attrs->find(*i)) == vAttrs->attrs->end()) + if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) throwEvalError("attribute `%1%' missing", showAttrPath(attrPath)); } vAttrs = j->value; @@ -724,8 +737,9 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v) foreach (AttrPath::const_iterator, i, attrPath) { state.forceValue(*vAttrs); Bindings::iterator j; + Symbol name = getName(*i, state, env); if (vAttrs->type != tAttrs || - (j = vAttrs->attrs->find(*i)) == vAttrs->attrs->end()) + (j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) { mkBool(v, false); return; diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index a7ce58c4d9..9f0bc2630d 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -155,12 +155,19 @@ std::ostream & operator << (std::ostream & str, const Pos & pos) string showAttrPath(const AttrPath & attrPath) { - string s; + std::ostringstream out; + bool first = true; foreach (AttrPath::const_iterator, i, attrPath) { - if (!s.empty()) s += '.'; - s += *i; + if (!first) + out << '.'; + else + first = false; + if (i->symbol.set()) + out << i->symbol; + else + out << "\"${" << *i->expr << "}\""; } - return s; + return out.str(); } @@ -220,11 +227,17 @@ void ExprSelect::bindVars(const StaticEnv & env) { e->bindVars(env); if (def) def->bindVars(env); + foreach (AttrPath::iterator, i, attrPath) + if (!i->symbol.set()) + i->expr->bindVars(env); } void ExprOpHasAttr::bindVars(const StaticEnv & env) { e->bindVars(env); + foreach (AttrPath::iterator, i, attrPath) + if (!i->symbol.set()) + i->expr->bindVars(env); } void ExprAttrs::bindVars(const StaticEnv & env) diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 92c2ca8dc5..bc6993477c 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -50,10 +50,19 @@ struct Env; struct Value; struct EvalState; struct StaticEnv; +struct Expr; /* An attribute path is a sequence of attribute names. */ -typedef vector AttrPath; +struct AttrName +{ + Symbol symbol; + Expr *expr; + AttrName(const Symbol & s) : symbol(s) {}; + AttrName(Expr *e) : expr(e) {}; +}; + +typedef std::vector AttrPath; string showAttrPath(const AttrPath & attrPath); @@ -138,7 +147,7 @@ struct ExprSelect : Expr Expr * e, * def; AttrPath attrPath; ExprSelect(Expr * e, const AttrPath & attrPath, Expr * def) : e(e), def(def), attrPath(attrPath) { }; - ExprSelect(Expr * e, const Symbol & name) : e(e), def(0) { attrPath.push_back(name); }; + ExprSelect(Expr * e, const Symbol & name) : e(e), def(0) { attrPath.push_back(AttrName(name)); }; COMMON_METHODS }; diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 3bee3b010c..28972cf724 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -39,15 +39,6 @@ namespace nix { { }; }; - struct AttrName - { - Symbol symbol; - Expr *expr; - AttrName(const Symbol & s) : symbol(s) {}; - AttrName(Expr *e) : expr(e) {}; - }; - - typedef std::vector AttrNames; } #define YY_DECL int yylex \ @@ -83,32 +74,28 @@ static void dupAttr(const AttrPath & attrPath, const Pos & pos, const Pos & prev static void dupAttr(Symbol attr, const Pos & pos, const Pos & prevPos) { - AttrPath attrPath; attrPath.push_back(attr); throw ParseError(format("attribute `%1%' at %2% already defined at %3%") - % showAttrPath(attrPath) % pos % prevPos); + % attr % pos % prevPos); } -static void addAttr(ExprAttrs * attrs, AttrNames & attrNames, +static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, Expr * e, const Pos & pos) { - AttrPath path; - AttrNames::iterator i; + AttrPath::iterator i; // All attrpaths have at least one attr - assert(!attrNames.empty()); - for (i = attrNames.begin(); i + 1 < attrNames.end(); i++) { + assert(!attrPath.empty()); + for (i = attrPath.begin(); i + 1 < attrPath.end(); i++) { if (i->symbol.set()) { - path.push_back(i->symbol); ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol); if (j != attrs->attrs.end()) { if (!j->second.inherited) { ExprAttrs * attrs2 = dynamic_cast(j->second.e); - if (!attrs2) dupAttr(path, pos, j->second.pos); + if (!attrs2) dupAttr(attrPath, pos, j->second.pos); attrs = attrs2; } else - dupAttr(path, pos, j->second.pos); + dupAttr(attrPath, pos, j->second.pos); } else { - path.clear(); ExprAttrs * nested = new ExprAttrs; attrs->attrs[i->symbol] = ExprAttrs::AttrDef(nested, pos); attrs = nested; @@ -122,7 +109,7 @@ static void addAttr(ExprAttrs * attrs, AttrNames & attrNames, if (i->symbol.set()) { ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol); if (j != attrs->attrs.end()) { - dupAttr(path, pos, j->second.pos); + dupAttr(attrPath, pos, j->second.pos); } else { attrs->attrs[i->symbol] = ExprAttrs::AttrDef(e, pos); e->setName(i->symbol); @@ -268,8 +255,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err char * id; // !!! -> Symbol char * path; char * uri; - std::vector * attrpath; - std::vector * attrlist; + std::vector * attrNames; std::vector * string_parts; } @@ -279,8 +265,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err %type binds %type formals %type formal -%type attrpath -%type attrs +%type attrs attrpath %type string_parts_interpolated ind_string_parts %type string_parts string_attr %type attr @@ -354,39 +339,7 @@ expr_op | expr_op OR expr_op { $$ = new ExprOpOr($1, $3); } | expr_op IMPL expr_op { $$ = new ExprOpImpl($1, $3); } | expr_op UPDATE expr_op { $$ = new ExprOpUpdate($1, $3); } - | expr_op '?' attrpath - { AttrPath path; - vector::iterator i; - $$ = $1; - // All attrpaths have at least one attr - assert(!$3->empty()); - for (i = $3->begin(); i + 1 != $3->end(); i++) { - if (i->symbol.set()) { - path.push_back(i->symbol); - } else { - if (!path.empty()) { - $$ = new ExprSelect($$, path, new ExprAttrs()); - path.clear(); - } - $$ = new ExprIf( - new ExprOpAnd( - new ExprApp(new ExprBuiltin(data->symbols.create("isAttrs")), $$), - new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("hasAttr")), i->expr), $$)), - new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("getAttr")), i->expr), $$), - new ExprAttrs()); - } - } - if (i->symbol.set()) { - path.push_back(i->symbol); - $$ = new ExprOpHasAttr($$, path); - } else { - if (!path.empty()) - $$ = new ExprSelect($$, path, new ExprAttrs()); - $$ = new ExprOpAnd( - new ExprApp(new ExprBuiltin(data->symbols.create("isAttrs")), $$), - new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("hasAttr")), i->expr), $$)); - } - } + | expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); } | expr_op '+' expr_op { vector * l = new vector; l->push_back($1); @@ -408,58 +361,9 @@ expr_app expr_select : expr_simple '.' attrpath - { AttrPath path; - $$ = $1; - foreach (vector::iterator, i, *$3) { - if (i->symbol.set()) { - path.push_back(i->symbol); - } else { - if (!path.empty()) { - $$ = new ExprSelect($$, path, 0); - path.clear(); - } - $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("getAttr")), i->expr), $$); - } - } - if (!path.empty()) - $$ = new ExprSelect($$, path, 0); - } + { $$ = new ExprSelect($1, *$3, 0); } | expr_simple '.' attrpath OR_KW expr_select - { AttrPath path; - vector::iterator i; - $$ = $1; - // All attrpaths have at least one attr - assert(!$3->empty()); - for (i = $3->begin(); i + 1 != $3->end(); i++) { - if (i->symbol.set()) { - path.push_back(i->symbol); - } else { - if (!path.empty()) { - $$ = new ExprSelect($$, path, new ExprAttrs()); - path.clear(); - } - $$ = new ExprIf( - new ExprOpAnd( - new ExprApp(new ExprBuiltin(data->symbols.create("isAttrs")), $$), - new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("hasAttr")), i->expr), $$)), - new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("getAttr")), i->expr), $$), - new ExprAttrs()); - } - } - if (i->symbol.set()) { - path.push_back(i->symbol); - $$ = new ExprSelect($$, path, $5); - } else { - if (!path.empty()) - $$ = new ExprSelect($$, path, new ExprAttrs()); - $$ = new ExprIf( - new ExprOpAnd( - new ExprApp(new ExprBuiltin(data->symbols.create("isAttrs")), $$), - new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("hasAttr")), i->expr), $$)), - new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("getAttr")), i->expr), $$), - $5); - } - } + { $$ = new ExprSelect($1, *$3, $5); } | /* Backwards compatibility: because Nixpkgs has a rarely used function named ‘or’, allow stuff like ‘map or [...]’. */ expr_simple OR_KW @@ -542,37 +446,37 @@ binds | binds INHERIT attrs ';' { $$ = $1; foreach (AttrPath::iterator, i, *$3) { - if ($$->attrs.find(*i) != $$->attrs.end()) - dupAttr(*i, makeCurPos(@3, data), $$->attrs[*i].pos); + if ($$->attrs.find(i->symbol) != $$->attrs.end()) + dupAttr(i->symbol, makeCurPos(@3, data), $$->attrs[i->symbol].pos); Pos pos = makeCurPos(@3, data); - $$->attrs[*i] = ExprAttrs::AttrDef(new ExprVar(CUR_POS, *i), pos, true); + $$->attrs[i->symbol] = ExprAttrs::AttrDef(new ExprVar(CUR_POS, i->symbol), pos, true); } } | binds INHERIT '(' expr ')' attrs ';' { $$ = $1; /* !!! Should ensure sharing of the expression in $4. */ - foreach (vector::iterator, i, *$6) { - if ($$->attrs.find(*i) != $$->attrs.end()) - dupAttr(*i, makeCurPos(@6, data), $$->attrs[*i].pos); - $$->attrs[*i] = ExprAttrs::AttrDef(new ExprSelect($4, *i), makeCurPos(@6, data)); + foreach (AttrPath::iterator, i, *$6) { + if ($$->attrs.find(i->symbol) != $$->attrs.end()) + dupAttr(i->symbol, makeCurPos(@6, data), $$->attrs[i->symbol].pos); + $$->attrs[i->symbol] = ExprAttrs::AttrDef(new ExprSelect($4, i->symbol), makeCurPos(@6, data)); } } | { $$ = new ExprAttrs; } ; attrs - : attrs attr { $$ = $1; $1->push_back(data->symbols.create($2)); /* !!! dangerous */ } + : attrs attr { $$ = $1; $1->push_back(AttrName(data->symbols.create($2))); } | attrs string_attr { $$ = $1; ExprString *str = dynamic_cast($2); if (str) { - $$->push_back(str->s); + $$->push_back(AttrName(str->s)); delete str; } else throw ParseError(format("dynamic attributes not allowed in inherit at %1%") % makeCurPos(@2, data)); } - | { $$ = new vector; } + | { $$ = new AttrPath; } ; attrpath From 4252b5a645138e84fa8916dfc3f8d6af8e74fc28 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 6 Jan 2014 11:32:22 +0100 Subject: [PATCH 19/36] Disable the tail call test On i686-linux, GCC stubbornly refuses to do tail-call optimisation. Don't know why. http://hydra.nixos.org/build/7300170 --- ...al-okay-tail-call-1.exp => eval-okay-tail-call-1.exp-disabled} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/lang/{eval-okay-tail-call-1.exp => eval-okay-tail-call-1.exp-disabled} (100%) diff --git a/tests/lang/eval-okay-tail-call-1.exp b/tests/lang/eval-okay-tail-call-1.exp-disabled similarity index 100% rename from tests/lang/eval-okay-tail-call-1.exp rename to tests/lang/eval-okay-tail-call-1.exp-disabled From fe23e28f12f5eedf387c974c73813f6de8320b21 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 6 Jan 2014 11:34:05 +0100 Subject: [PATCH 20/36] Disable FreeBSD tests for now The FreeBSD machines in the build farm are currently unreachable. --- release.nix | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/release.nix b/release.nix index 11ed7a1c30..b559024375 100644 --- a/release.nix +++ b/release.nix @@ -6,7 +6,7 @@ let pkgs = import {}; - systems = [ "x86_64-linux" "i686-linux" "x86_64-darwin" "x86_64-freebsd" "i686-freebsd" ]; + systems = [ "x86_64-linux" "i686-linux" "x86_64-darwin" /* "x86_64-freebsd" "i686-freebsd" */ ]; jobs = rec { @@ -212,15 +212,15 @@ let meta.description = "Release-critical builds"; constituents = [ tarball - build.i686-freebsd + #build.i686-freebsd build.i686-linux build.x86_64-darwin - build.x86_64-freebsd + #build.x86_64-freebsd build.x86_64-linux - binaryTarball.i686-freebsd + #binaryTarball.i686-freebsd binaryTarball.i686-linux binaryTarball.x86_64-darwin - binaryTarball.x86_64-freebsd + #binaryTarball.x86_64-freebsd binaryTarball.x86_64-linux deb_debian7i386 deb_debian7x86_64 From 485f4740ee3ba4228ba3345873eb530466a8f42d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Domen=20Ko=C5=BEar?= Date: Wed, 1 Jan 2014 18:10:48 +0100 Subject: [PATCH 21/36] wording --- src/libstore/build.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstore/build.cc b/src/libstore/build.cc index 0a8bdbaf5e..dcd7343394 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -1011,7 +1011,7 @@ void DerivationGoal::outputsSubstituted() trace("all outputs substituted (maybe)"); if (nrFailed > 0 && nrFailed > nrNoSubstituters + nrIncompleteClosure && !settings.tryFallback) - throw Error(format("some substitutes for the outputs of derivation `%1%' failed; try `--fallback'") % drvPath); + throw Error(format("some substitutes for the outputs of derivation `%1%' failed (usually happens due to networking issues); try `--fallback' to build derivation from source ") % drvPath); /* If the substitutes form an incomplete closure, then we should build the dependencies of this derivation, but after that, we From 405434e084fa994cc957249db7787731e9311fa8 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 6 Jan 2014 17:38:04 +0100 Subject: [PATCH 22/36] Revert "nix-shell: Set $IN_NIX_SHELL before evaluation" This reverts commit 0c1198cf08576f16633b2344dc6513cefb567cfc. --- scripts/nix-build.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/nix-build.in b/scripts/nix-build.in index 8b66e38a9a..8e6f43a373 100755 --- a/scripts/nix-build.in +++ b/scripts/nix-build.in @@ -175,8 +175,6 @@ foreach my $expr (@exprs) { $drvPath = readlink $drvPath or die "cannot read symlink `$drvPath'" if -l $drvPath; my $drv = derivationFromPath($drvPath); - $ENV{'IN_NIX_SHELL'} = 1; - # Build or fetch all dependencies of the derivation. my @inputDrvs = grep { my $x = $_; (grep { $x =~ $_ } @envExclude) == 0 } @{$drv->{inputDrvs}}; system("$Nix::Config::binDir/nix-store", "-r", "--no-output", "--no-gc-warning", @buildArgs, @inputDrvs, @{$drv->{inputSrcs}}) == 0 @@ -195,6 +193,8 @@ foreach my $expr (@exprs) { $ENV{'NIX_STORE'} = $Nix::Config::storeDir; $ENV{$_} = $drv->{env}->{$_} foreach keys %{$drv->{env}}; + $ENV{'IN_NIX_SHELL'} = 1; + # Run a shell using the derivation's environment. For # convenience, source $stdenv/setup to setup additional # environment variables and shell functions. Also don't lose From 0fdf4da0e979f992db75cc17376e455ddc5a96d8 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 Jan 2014 15:23:41 +0100 Subject: [PATCH 23/36] Support cryptographically signed binary caches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NAR info files in binary caches can now have a cryptographic signature that Nix will verify before using the corresponding NAR file. To create a private/public key pair for signing and verifying a binary cache, do: $ openssl genrsa -out ./cache-key.sec 2048 $ openssl rsa -in ./cache-key.sec -pubout > ./cache-key.pub You should also come up with a symbolic name for the key, such as "cache.example.org-1". This will be used by clients to look up the public key. (It's a good idea to number keys, in case you ever need to revoke/replace one.) To create a binary cache signed with the private key: $ nix-push --dest /path/to/binary-cache --key ./cache-key.sec --key-name cache.example.org-1 The public key (cache-key.pub) should be distributed to the clients. They should have a nix.conf should contain something like: signed-binary-caches = * binary-cache-public-key-cache.example.org-1 = /path/to/cache-key.pub If all works well, then if Nix fetches something from the signed binary cache, you will see a message like: *** Downloading ‘http://cache.example.org/nar/7dppcj5sc1nda7l54rjc0g5l1hamj09j-subversion-1.7.11’ (signed by ‘cache.example.org-1’) to ‘/nix/store/7dppcj5sc1nda7l54rjc0g5l1hamj09j-subversion-1.7.11’... On the other hand, if the signature is wrong, you get a message like NAR info file `http://cache.example.org/7dppcj5sc1nda7l54rjc0g5l1hamj09j.narinfo' has an invalid signature; ignoring Signatures are implemented as a single line appended to the NAR info file, which looks like this: Signature: 1;cache.example.org-1;HQ9Xzyanq9iV...muQ== Thus the signature has 3 fields: a version (currently "1"), the ID of key, and the base64-encoded signature of the SHA-256 hash of the contents of the NAR info file up to but not including the Signature line. Issue #75. --- perl/Makefile.am | 2 +- perl/lib/Nix/Config.pm.in | 3 +- perl/lib/Nix/Crypto.pm | 42 ++++++++++++++++++++++++ perl/lib/Nix/Manifest.pm | 40 ++++++++++++++++++++-- scripts/download-from-binary-cache.pl.in | 24 ++++++++++---- scripts/nix-push.in | 18 +++++++++- substitute.mk | 1 + tests/binary-cache.sh | 8 +++++ 8 files changed, 126 insertions(+), 12 deletions(-) create mode 100644 perl/lib/Nix/Crypto.pm diff --git a/perl/Makefile.am b/perl/Makefile.am index aaf76bbc8f..b8e60bc2dc 100644 --- a/perl/Makefile.am +++ b/perl/Makefile.am @@ -1,4 +1,4 @@ -PERL_MODULES = lib/Nix/Store.pm lib/Nix/Manifest.pm lib/Nix/GeneratePatches.pm lib/Nix/SSH.pm lib/Nix/CopyClosure.pm lib/Nix/Config.pm.in lib/Nix/Utils.pm +PERL_MODULES = lib/Nix/Store.pm lib/Nix/Manifest.pm lib/Nix/GeneratePatches.pm lib/Nix/SSH.pm lib/Nix/CopyClosure.pm lib/Nix/Config.pm.in lib/Nix/Utils.pm lib/Nix/Crypto.pm all: $(PERL_MODULES:.in=) diff --git a/perl/lib/Nix/Config.pm.in b/perl/lib/Nix/Config.pm.in index 8c902ab6ed..4f1f38ddd3 100644 --- a/perl/lib/Nix/Config.pm.in +++ b/perl/lib/Nix/Config.pm.in @@ -13,6 +13,7 @@ $storeDir = $ENV{"NIX_STORE_DIR"} || "@storedir@"; $bzip2 = "@bzip2@"; $xz = "@xz@"; $curl = "@curl@"; +$openssl = "@openssl@"; $useBindings = "@perlbindings@" eq "yes"; @@ -32,7 +33,7 @@ sub readConfig { open CONFIG, "<$config" or die "cannot open `$config'"; while () { - /^\s*([\w|-]+)\s*=\s*(.*)$/ or next; + /^\s*([\w\-\.]+)\s*=\s*(.*)$/ or next; $config{$1} = $2; } close CONFIG; diff --git a/perl/lib/Nix/Crypto.pm b/perl/lib/Nix/Crypto.pm new file mode 100644 index 0000000000..0286e88d3d --- /dev/null +++ b/perl/lib/Nix/Crypto.pm @@ -0,0 +1,42 @@ +package Nix::Crypto; + +use strict; +use MIME::Base64; +use Nix::Store; +use Nix::Config; +use IPC::Open2; + +our @ISA = qw(Exporter); +our @EXPORT = qw(signString isValidSignature); + +sub signString { + my ($privateKeyFile, $s) = @_; + my $hash = hashString("sha256", 0, $s); + my ($from, $to); + my $pid = open2($from, $to, $Nix::Config::openssl, "rsautl", "-sign", "-inkey", $privateKeyFile); + print $to $hash; + close $to; + local $/ = undef; + my $sig = <$from>; + close $from; + waitpid($pid, 0); + die "$0: OpenSSL returned exit code $? while signing hash\n" if $? != 0; + my $sig64 = encode_base64($sig, ""); + return $sig64; +} + +sub isValidSignature { + my ($publicKeyFile, $sig64, $s) = @_; + my ($from, $to); + my $pid = open2($from, $to, $Nix::Config::openssl, "rsautl", "-verify", "-inkey", $publicKeyFile, "-pubin"); + print $to decode_base64($sig64); + close $to; + my $decoded = <$from>; + close $from; + waitpid($pid, 0); + return 0 if $? != 0; + my $hash = hashString("sha256", 0, $s); + return $decoded eq $hash; +} + +1; diff --git a/perl/lib/Nix/Manifest.pm b/perl/lib/Nix/Manifest.pm index 04c699b43e..015c928356 100644 --- a/perl/lib/Nix/Manifest.pm +++ b/perl/lib/Nix/Manifest.pm @@ -8,6 +8,7 @@ use File::stat; use File::Path; use Fcntl ':flock'; use Nix::Config; +use Nix::Crypto; our @ISA = qw(Exporter); our @EXPORT = qw(readManifest writeManifest updateManifestDB addPatch deleteOldManifests parseNARInfo); @@ -394,9 +395,10 @@ sub deleteOldManifests { # Parse a NAR info file. sub parseNARInfo { - my ($storePath, $content) = @_; + my ($storePath, $content, $requireValidSig, $location) = @_; - my ($storePath2, $url, $fileHash, $fileSize, $narHash, $narSize, $deriver, $system); + my ($storePath2, $url, $fileHash, $fileSize, $narHash, $narSize, $deriver, $system, $sig); + my $signedData = ""; my $compression = "bzip2"; my @refs; @@ -412,11 +414,13 @@ sub parseNARInfo { elsif ($1 eq "References") { @refs = split / /, $2; } elsif ($1 eq "Deriver") { $deriver = $2; } elsif ($1 eq "System") { $system = $2; } + elsif ($1 eq "Signature") { $sig = $2; last; } + $signedData .= "$line\n"; } return undef if $storePath ne $storePath2 || !defined $url || !defined $narHash; - return + my $res = { url => $url , compression => $compression , fileHash => $fileHash @@ -427,6 +431,36 @@ sub parseNARInfo { , deriver => $deriver , system => $system }; + + if ($requireValidSig) { + if (!defined $sig) { + warn "NAR info file `$location' lacks a signature; ignoring\n"; + return undef; + } + my ($sigVersion, $keyName, $sig64) = split ";", $sig; + $sigVersion //= 0; + if ($sigVersion != 1) { + warn "NAR info file `$location' has unsupported version $sigVersion; ignoring\n"; + return undef; + } + return undef unless defined $keyName && defined $sig64; + my $publicKeyFile = $Nix::Config::config{"binary-cache-public-key-$keyName"}; + if (!defined $publicKeyFile) { + warn "NAR info file `$location' is signed by unknown key `$keyName'; ignoring\n"; + return undef; + } + if (! -f $publicKeyFile) { + die "binary cache public key file `$publicKeyFile' does not exist\n"; + return undef; + } + if (!isValidSignature($publicKeyFile, $sig64, $signedData)) { + warn "NAR info file `$location' has an invalid signature; ignoring\n"; + return undef; + } + $res->{signedBy} = $keyName; + } + + return $res; } diff --git a/scripts/download-from-binary-cache.pl.in b/scripts/download-from-binary-cache.pl.in index 950bcd1785..e6925d7316 100644 --- a/scripts/download-from-binary-cache.pl.in +++ b/scripts/download-from-binary-cache.pl.in @@ -42,6 +42,8 @@ my $caBundle = $ENV{"CURL_CA_BUNDLE"} // $ENV{"OPENSSL_X509_CERT_FILE"}; my $userName = getpwuid($<) or die "cannot figure out user name"; +my $requireSignedBinaryCaches = ($Nix::Config::config{"signed-binary-caches"} // "0") ne "0"; + sub addRequest { my ($storePath, $url, $head) = @_; @@ -120,9 +122,10 @@ sub processRequests { sub initCache { - my $dbPath = "$Nix::Config::stateDir/binary-cache-v2.sqlite"; + my $dbPath = "$Nix::Config::stateDir/binary-cache-v3.sqlite"; unlink "$Nix::Config::stateDir/binary-cache-v1.sqlite"; + unlink "$Nix::Config::stateDir/binary-cache-v2.sqlite"; # Open/create the database. $dbh = DBI->connect("dbi:SQLite:dbname=$dbPath", "", "") @@ -159,7 +162,7 @@ EOF narSize integer, refs text, deriver text, - system text, + signedBy text, timestamp integer not null, primary key (cache, storePath), foreign key (cache) references BinaryCaches(id) on delete cascade @@ -183,7 +186,7 @@ EOF $insertNAR = $dbh->prepare( "insert or replace into NARs(cache, storePath, url, compression, fileHash, fileSize, narHash, " . - "narSize, refs, deriver, system, timestamp) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") or die; + "narSize, refs, deriver, signedBy, timestamp) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") or die; $queryNAR = $dbh->prepare("select * from NARs where cache = ? and storePath = ?") or die; @@ -309,14 +312,16 @@ sub processNARInfo { return undef; } - my $narInfo = parseNARInfo($storePath, $request->{content}); + my $narInfo = parseNARInfo($storePath, $request->{content}, $requireSignedBinaryCaches, $request->{url}); return undef unless defined $narInfo; + die if $requireSignedBinaryCaches && !defined $narInfo->{signedBy}; + # Cache the result. $insertNAR->execute( $cache->{id}, basename($storePath), $narInfo->{url}, $narInfo->{compression}, $narInfo->{fileHash}, $narInfo->{fileSize}, $narInfo->{narHash}, $narInfo->{narSize}, - join(" ", @{$narInfo->{refs}}), $narInfo->{deriver}, $narInfo->{system}, time()) + join(" ", @{$narInfo->{refs}}), $narInfo->{deriver}, $narInfo->{signedBy}, time()) if shouldCache $request->{url}; return $narInfo; @@ -330,6 +335,10 @@ sub getCachedInfoFrom { my $res = $queryNAR->fetchrow_hashref(); return undef unless defined $res; + # We may previously have cached this info when signature checking + # was disabled. In that case, ignore the cached info. + return undef if $requireSignedBinaryCaches && !defined $res->{signedBy}; + return { url => $res->{url} , compression => $res->{compression} @@ -339,6 +348,7 @@ sub getCachedInfoFrom { , narSize => $res->{narSize} , refs => [ split " ", $res->{refs} ] , deriver => $res->{deriver} + , signedBy => $res->{signedBy} } if defined $res; } @@ -522,7 +532,8 @@ sub downloadBinary { next; } my $url = "$cache->{url}/$info->{url}"; # FIXME: handle non-relative URLs - print STDERR "\n*** Downloading ‘$url’ to ‘$storePath’...\n"; + die if $requireSignedBinaryCaches && !defined $info->{signedBy}; + print STDERR "\n*** Downloading ‘$url’ ", ($requireSignedBinaryCaches ? "(signed by ‘$info->{signedBy}’) " : ""), "to ‘$storePath’...\n"; checkURL $url; if (system("$Nix::Config::curl --fail --location --insecure '$url' $decompressor | $Nix::Config::binDir/nix-store --restore $destPath") != 0) { warn "download of `$url' failed" . ($! ? ": $!" : "") . "\n"; @@ -530,6 +541,7 @@ sub downloadBinary { } # Tell Nix about the expected hash so it can verify it. + die unless defined $info->{narHash} && $info->{narHash} ne ""; print "$info->{narHash}\n"; print STDERR "\n"; diff --git a/scripts/nix-push.in b/scripts/nix-push.in index 2c392c4155..bdd128a6f5 100755 --- a/scripts/nix-push.in +++ b/scripts/nix-push.in @@ -10,6 +10,7 @@ use Nix::Config; use Nix::Store; use Nix::Manifest; use Nix::Utils; +use Nix::Crypto; my $tmpDir = tempdir("nix-push.XXXXXX", CLEANUP => 1, TMPDIR => 1) or die "cannot create a temporary directory"; @@ -25,6 +26,8 @@ my $writeManifest = 0; my $manifestPath; my $archivesURL; my $link = 0; +my $privateKeyFile; +my $keyName; my @roots; for (my $n = 0; $n < scalar @ARGV; $n++) { @@ -57,6 +60,14 @@ for (my $n = 0; $n < scalar @ARGV; $n++) { $archivesURL = $ARGV[$n]; } elsif ($arg eq "--link") { $link = 1; + } elsif ($arg eq "--key") { + $n++; + die "$0: `$arg' requires an argument\n" unless $n < scalar @ARGV; + $privateKeyFile = $ARGV[$n]; + } elsif ($arg eq "--key-name") { + $n++; + die "$0: `$arg' requires an argument\n" unless $n < scalar @ARGV; + $keyName = $ARGV[$n]; } elsif (substr($arg, 0, 1) eq "-") { die "$0: unknown flag `$arg'\n"; } else { @@ -99,7 +110,7 @@ foreach my $storePath (@storePaths) { my $pathHash = substr(basename($storePath), 0, 32); my $narInfoFile = "$destDir/$pathHash.narinfo"; if (-e $narInfoFile) { - my $narInfo = parseNARInfo($storePath, readFile($narInfoFile)); + my $narInfo = parseNARInfo($storePath, readFile($narInfoFile), 0, $narInfoFile) or die "cannot read `$narInfoFile'\n"; my $narFile = "$destDir/$narInfo->{url}"; if (-e $narFile) { print STDERR "skipping existing $storePath\n"; @@ -245,6 +256,11 @@ for (my $n = 0; $n < scalar @storePaths2; $n++) { } } + if (defined $privateKeyFile && defined $keyName) { + my $sig = signString($privateKeyFile, $info); + $info .= "Signature: 1;$keyName;$sig\n"; + } + my $pathHash = substr(basename($storePath), 0, 32); $dst = "$destDir/$pathHash.narinfo"; diff --git a/substitute.mk b/substitute.mk index 967ad257b6..d90bded78a 100644 --- a/substitute.mk +++ b/substitute.mk @@ -35,6 +35,7 @@ -e "s^@version\@^$(VERSION)^g" \ -e "s^@perlbindings\@^$(perlbindings)^g" \ -e "s^@testPath\@^$(coreutils):$$(dirname $$(type -p expr))^g" \ + -e "s^@openssl\@^$(openssl_prog)^g" \ < $< > $@ || rm $@ if test -x $<; then chmod +x $@; fi diff --git a/tests/binary-cache.sh b/tests/binary-cache.sh index eb2ebbff82..ed947a6620 100644 --- a/tests/binary-cache.sh +++ b/tests/binary-cache.sh @@ -40,6 +40,14 @@ nix-store --check-validity $outPath nix-store -qR $outPath | grep input-2 +# Test whether this unsigned cache is rejected if the user requires signed caches. +clearStore + +rm -f $NIX_STATE_DIR/binary-cache* + +nix-store --option binary-caches "file://$cacheDir" --option signed-binary-caches 1 -r $outPath + + # Test whether fallback works if we have cached info but the # corresponding NAR has disappeared. clearStore From 11cb4bfb257f18c906ef1d6f14ed450be8fa49fe Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 Jan 2014 17:32:40 +0100 Subject: [PATCH 24/36] Fix checking of NAR hashes *headdesk* *headdesk* *headdesk* So since commit 22144afa8d9f8968da351618a1347072a93bd8aa, Nix hasn't actually checked whether the content of a downloaded NAR matches the hash specified in the manifest / NAR info file. Urghhh... --- src/libstore/build.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libstore/build.cc b/src/libstore/build.cc index dcd7343394..4329d9a22b 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -2749,6 +2749,7 @@ void SubstitutionGoal::finished() logPipe.readSide.close(); /* Get the hash info from stdout. */ + string dummy = readLine(outPipe.readSide); string expectedHashStr = statusOk(status) ? readLine(outPipe.readSide) : ""; outPipe.readSide.close(); From ea38e39a203a96451b1d0469103b737de5a85105 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 Jan 2014 17:56:30 +0100 Subject: [PATCH 25/36] Test whether Nix correctly checks the hash of downloaded NARs --- tests/binary-cache.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/binary-cache.sh b/tests/binary-cache.sh index ed947a6620..71ede03c83 100644 --- a/tests/binary-cache.sh +++ b/tests/binary-cache.sh @@ -40,6 +40,20 @@ nix-store --check-validity $outPath nix-store -qR $outPath | grep input-2 +# Test whether Nix notices if the NAR doesn't match the hash in the NAR info. +clearStore + +nar=$(ls $cacheDir/*.nar.xz | head -n1) +mv $nar $nar.good +mkdir -p $TEST_ROOT/empty +nix-store --dump $TEST_ROOT/empty | xz > $nar + +nix-build --option binary-caches "file://$cacheDir" dependencies.nix -o $TEST_ROOT/result 2>&1 | tee $TEST_ROOT/log +grep -q "hash mismatch in downloaded path" $TEST_ROOT/log + +mv $nar.good $nar + + # Test whether this unsigned cache is rejected if the user requires signed caches. clearStore From f4013b6189af731af42f99684ed7721076a54a0d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 Jan 2014 17:56:58 +0100 Subject: [PATCH 26/36] Fix signed-binary-caches test --- tests/binary-cache.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/binary-cache.sh b/tests/binary-cache.sh index 71ede03c83..9bd4fecb6d 100644 --- a/tests/binary-cache.sh +++ b/tests/binary-cache.sh @@ -59,7 +59,10 @@ clearStore rm -f $NIX_STATE_DIR/binary-cache* -nix-store --option binary-caches "file://$cacheDir" --option signed-binary-caches 1 -r $outPath +if nix-store --option binary-caches "file://$cacheDir" --option signed-binary-caches '*' -r $outPath; then + echo "unsigned binary cache incorrectly accepted" + exit 1 +fi # Test whether fallback works if we have cached info but the From ea59f39326c8e9dc42dfed4bcbf597fbce58797c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 13 Jan 2014 13:42:29 +0100 Subject: [PATCH 27/36] nix-shell: Set $IN_NIX_SHELL before evaluating --- scripts/nix-build.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/nix-build.in b/scripts/nix-build.in index 8e6f43a373..5d57692466 100755 --- a/scripts/nix-build.in +++ b/scripts/nix-build.in @@ -152,6 +152,8 @@ for (my $n = 0; $n < scalar @ARGV; $n++) { @exprs = ("./default.nix") if scalar @exprs == 0; +$ENV{'IN_NIX_SHELL'} = 1 if $runEnv; + foreach my $expr (@exprs) { @@ -193,8 +195,6 @@ foreach my $expr (@exprs) { $ENV{'NIX_STORE'} = $Nix::Config::storeDir; $ENV{$_} = $drv->{env}->{$_} foreach keys %{$drv->{env}}; - $ENV{'IN_NIX_SHELL'} = 1; - # Run a shell using the derivation's environment. For # convenience, source $stdenv/setup to setup additional # environment variables and shell functions. Also don't lose From f1357059a441a588b9a2b78d3500d7068238b478 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 13 Jan 2014 13:46:44 +0100 Subject: [PATCH 28/36] nix-shell: Don't set NIX_INDENT_MAKE It generally is not useful in interactive environments (and messes up some non-ANSI-compliant terminals). --- scripts/nix-build.in | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/nix-build.in b/scripts/nix-build.in index 5d57692466..07752f144f 100755 --- a/scripts/nix-build.in +++ b/scripts/nix-build.in @@ -211,6 +211,7 @@ foreach my $expr (@exprs) { 'set +e; ' . '[ -n "$PS1" ] && PS1="\n\[\033[1;32m\][nix-shell:\w]$\[\033[0m\] "; ' . 'unset NIX_ENFORCE_PURITY; ' . + 'unset NIX_INDENT_MAKE; ' . 'shopt -u nullglob; ' . $envCommand); $ENV{BASH_ENV} = $rcfile; From e640d671443e291b3ca5cc0575919d6fcf14a157 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 13 Jan 2014 13:50:12 +0100 Subject: [PATCH 29/36] Document nulls --- doc/manual/writing-nix-expressions.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/manual/writing-nix-expressions.xml b/doc/manual/writing-nix-expressions.xml index 4b6574288e..3e6cd73790 100644 --- a/doc/manual/writing-nix-expressions.xml +++ b/doc/manual/writing-nix-expressions.xml @@ -782,6 +782,9 @@ stdenv.mkDerivation { true and false. + The null value, denoted as + null. + From f9913f4422d1317af3c6b5aff37ad18b78083eb5 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Mon, 6 Jan 2014 10:27:26 -0500 Subject: [PATCH 30/36] Allow "bare" dynamic attrs Now, in addition to a."${b}".c, you can write a.${b}.c (applicable wherever dynamic attributes are valid). Signed-off-by: Shea Levy --- src/libexpr/lexer.l | 2 ++ src/libexpr/parser.y | 1 + tests/lang/eval-okay-dynamic-attrs-bare.exp | 1 + tests/lang/eval-okay-dynamic-attrs-bare.nix | 17 +++++++++++++++++ 4 files changed, 21 insertions(+) create mode 100644 tests/lang/eval-okay-dynamic-attrs-bare.exp create mode 100644 tests/lang/eval-okay-dynamic-attrs-bare.nix diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l index 5d0360401d..911850cc5b 100644 --- a/src/libexpr/lexer.l +++ b/src/libexpr/lexer.l @@ -117,6 +117,8 @@ or { return OR_KW; } return INT; } +\$\{ { return DOLLAR_CURLY; } + \" { BEGIN(STRING); return '"'; } ([^\$\"\\]|\$[^\{\"]|\\.)+ { /* !!! Not quite right: we want a follow restriction on diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 28972cf724..2305843882 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -509,6 +509,7 @@ attr string_attr : '"' string_parts '"' { $$ = $2; } + | DOLLAR_CURLY expr '}' { $$ = $2; } ; expr_list diff --git a/tests/lang/eval-okay-dynamic-attrs-bare.exp b/tests/lang/eval-okay-dynamic-attrs-bare.exp new file mode 100644 index 0000000000..df8750afc0 --- /dev/null +++ b/tests/lang/eval-okay-dynamic-attrs-bare.exp @@ -0,0 +1 @@ +{ binds = true; hasAttrs = true; multiAttrs = true; recBinds = true; selectAttrs = true; selectOrAttrs = true; } diff --git a/tests/lang/eval-okay-dynamic-attrs-bare.nix b/tests/lang/eval-okay-dynamic-attrs-bare.nix new file mode 100644 index 0000000000..0dbe15e638 --- /dev/null +++ b/tests/lang/eval-okay-dynamic-attrs-bare.nix @@ -0,0 +1,17 @@ +let + aString = "a"; + + bString = "b"; +in { + hasAttrs = { a.b = null; } ? ${aString}.b; + + selectAttrs = { a.b = true; }.a.${bString}; + + selectOrAttrs = { }.${aString} or true; + + binds = { ${aString}."${bString}c" = true; }.a.bc; + + recBinds = rec { ${bString} = a; a = true; }.b; + + multiAttrs = { ${aString} = true; ${bString} = false; }.a; +} From f5e5793cd2f32bc0f0d072b38cda742830f40f25 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Mon, 6 Jan 2014 13:53:57 -0500 Subject: [PATCH 31/36] Bare dynamic attrs: Match interpolation semantics Signed-off-by: Shea Levy --- src/libexpr/parser.y | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 2305843882..55a42fcaba 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -509,7 +509,7 @@ attr string_attr : '"' string_parts '"' { $$ = $2; } - | DOLLAR_CURLY expr '}' { $$ = $2; } + | DOLLAR_CURLY expr '}' { $$ = new ExprConcatStrings(true, new vector(1, $2)); } ; expr_list From bf0ad8aabca67b4faabe3a1ac3c57884ae9924f4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 15 Jan 2014 14:34:49 +0100 Subject: [PATCH 32/36] nix-profile.sh: Add the Nixpkgs channel to $NIX_PATH --- scripts/nix-profile.sh.in | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/nix-profile.sh.in b/scripts/nix-profile.sh.in index 16eb754c5d..06e7bdb7b8 100644 --- a/scripts/nix-profile.sh.in +++ b/scripts/nix-profile.sh.in @@ -8,10 +8,15 @@ if test -n "$HOME"; then @coreutils@/ln -s "$_NIX_DEF_LINK" "$NIX_LINK" fi + export PATH=$NIX_LINK/bin:$PATH + # Subscribe the root user to the Nixpkgs channel by default. if [ ! -e $HOME/.nix-channels ]; then echo "http://nixos.org/channels/nixpkgs-unstable nixpkgs" > $HOME/.nix-channels fi - export PATH=$NIX_LINK/bin:$PATH + # Append ~/.nix-defexpr/channels/nixpkgs to $NIX_PATH so that + # paths work when the user has fetched the Nixpkgs + # channel. + export NIX_PATH=${NIX_PATH:+$NIX_PATH:}nixpkgs=$HOME/.nix-defexpr/channels/nixpkgs fi From 0f2f44bb0ff8aafc160d8b236201ce510ca0b876 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 20 Jan 2014 14:22:59 +0100 Subject: [PATCH 33/36] Build Fedora 20 RPMs --- release.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/release.nix b/release.nix index b559024375..33b3db5673 100644 --- a/release.nix +++ b/release.nix @@ -175,6 +175,8 @@ let rpm_fedora18x86_64 = makeRPM_x86_64 (diskImageFunsFun: diskImageFunsFun.fedora18x86_64) 60; rpm_fedora19i386 = makeRPM_i686 (diskImageFuns: diskImageFuns.fedora19i386) 70; rpm_fedora19x86_64 = makeRPM_x86_64 (diskImageFunsFun: diskImageFunsFun.fedora19x86_64) 70; + rpm_fedora20i386 = makeRPM_i686 (diskImageFuns: diskImageFuns.fedora20i386) 70; + rpm_fedora20x86_64 = makeRPM_x86_64 (diskImageFunsFun: diskImageFunsFun.fedora20x86_64) 70; deb_debian60i386 = makeDeb_i686 (diskImageFuns: diskImageFuns.debian60i386) 50; @@ -230,6 +232,8 @@ let deb_ubuntu1310x86_64 rpm_fedora19i386 rpm_fedora19x86_64 + rpm_fedora20i386 + rpm_fedora20x86_64 tests.remote_builds tests.nix_copy_closure ]; From 0e2ca268187e0a1c17f2ba58ce53f59682df2fc4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 20 Jan 2014 14:23:07 +0100 Subject: [PATCH 34/36] nix.spec: Remove "make check" since it's a no-op --- nix.spec.in | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nix.spec.in b/nix.spec.in index d091405626..66e55ba158 100644 --- a/nix.spec.in +++ b/nix.spec.in @@ -152,10 +152,6 @@ cp -p misc/emacs/nix-mode.elc $RPM_BUILD_ROOT%{_emacs_sitelispdir}/ rm $RPM_BUILD_ROOT%{_defaultdocdir}/%{name}-doc-%{version}/README -%check -make check - - %clean rm -rf $RPM_BUILD_ROOT From c8fff6a77fb63dc8043a7a468feea37b41bfec06 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 21 Jan 2014 15:09:59 +0100 Subject: [PATCH 35/36] Fix evaluation --- release.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release.nix b/release.nix index 33b3db5673..6708719094 100644 --- a/release.nix +++ b/release.nix @@ -23,7 +23,7 @@ let inherit officialRelease; buildInputs = - [ curl bison flex2535 perl libxml2 libxslt w3m bzip2 + [ curl bison flex perl libxml2 libxslt w3m bzip2 tetex dblatex nukeReferences pkgconfig sqlite git ]; From 5ef8508a92997dbd7f8aa501b64fd283fb1c7bb8 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 21 Jan 2014 15:11:57 +0100 Subject: [PATCH 36/36] Remove unused type --- src/nix-env/nix-env.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index b42fe97eb4..e2781e540b 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -895,7 +895,6 @@ static void queryJSON(Globals & globals, vector & elems) static void opQuery(Globals & globals, Strings args, Strings opFlags, Strings opArgs) { - typedef vector< map > ResultSet; Strings remaining; string attrPath;