diff --git a/doc/manual/release-notes.xml b/doc/manual/release-notes.xml index 426078b8..36e345ec 100644 --- a/doc/manual/release-notes.xml +++ b/doc/manual/release-notes.xml @@ -11,6 +11,20 @@ TODO + + + Derivations can specify the new special attribute + allowedRequisites, which has a similar meaning to + allowedReferences. But instead of only enforcing + to explicitly specify the immediate references, it requires the + derivation to specify all the dependencies recursively (hence the + name, requisites) that are used by the resulting output. This is + used in NixOS when rebuilding the stdenv on Linux to ensure that the + resulting stdenv doesn't have any surprising dependency, e.g. on + bootstrapTools. + + + diff --git a/doc/manual/writing-nix-expressions.xml b/doc/manual/writing-nix-expressions.xml index 0470625f..2c9b4a63 100644 --- a/doc/manual/writing-nix-expressions.xml +++ b/doc/manual/writing-nix-expressions.xml @@ -1569,6 +1569,25 @@ allowedReferences = []; + allowedRequisites + + This attribute is similar to + allowedReferences, but it specifies the legal + requisites of the whole closure, so all the dependencies + recursively. For example, + + +allowedReferences = [ foobar ]; + + + enforces that the output of a derivation cannot have any other + runtime dependency than foobar, and in addition + it enforces that foobar itself doesn't + introduce any other dependency itself. This is used in NixOS when + rebuilding the stdenv on Linux to ensure that the resulting stdenv + doesn't have any surprising dependency, e.g. on bootstrapTools. + + exportReferencesGraph diff --git a/src/libstore/build.cc b/src/libstore/build.cc index c547a5cb..6390a748 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -2326,7 +2326,24 @@ void DerivationGoal::registerOutputs() PathSet allowed = parseReferenceSpecifiers(drv, get(drv.env, "allowedReferences")); foreach (PathSet::iterator, i, references) if (allowed.find(*i) == allowed.end()) - throw BuildError(format("output is not allowed to refer to path ‘%1%’") % *i); + throw BuildError(format("output (‘%1%’) is not allowed to refer to path ‘%2%’") % actualPath % *i); + } + + /* If the derivation specifies an `allowedRequisites' + attribute (containing a list of paths that the output may + refer to), check that all requisites are in that list. !!! + allowedRequisites should really be per-output. */ + if (drv.env.find("allowedRequisites") != drv.env.end()) { + PathSet allowed = parseReferenceSpecifiers(drv, get(drv.env, "allowedRequisites")); + PathSet requisites; + /* Our requisites are the union of the closures of our references. */ + foreach (PathSet::iterator, i, references) + /* Don't call computeFSClosure on ourselves. */ + if (actualPath != *i) + computeFSClosure(worker.store, *i, requisites); + foreach (PathSet::iterator, i, requisites) + if (allowed.find(*i) == allowed.end()) + throw BuildError(format("output (‘%1%’) is not allowed to refer to requisite path ‘%2%’") % actualPath % *i); } worker.store.optimisePath(path); // FIXME: combine with scanForReferences() diff --git a/tests/check-reqs.nix b/tests/check-reqs.nix new file mode 100644 index 00000000..5af8ea6f --- /dev/null +++ b/tests/check-reqs.nix @@ -0,0 +1,43 @@ +with import ./config.nix; + +rec { + dep1 = mkDerivation { + name = "check-reqs-dep1"; + builder = builtins.toFile "builder.sh" "mkdir $out; touch $out/file1"; + }; + + dep2 = mkDerivation { + name = "check-reqs-dep2"; + builder = builtins.toFile "builder.sh" "mkdir $out; touch $out/file2"; + }; + + deps = mkDerivation { + name = "check-reqs-deps"; + dep1 = dep1; + dep2 = dep2; + builder = builtins.toFile "builder.sh" '' + mkdir $out + ln -s $dep1/file1 $out/file1 + ln -s $dep2/file2 $out/file2 + ''; + }; + + makeTest = nr: allowreqs: mkDerivation { + name = "check-reqs-" + toString nr; + inherit deps; + builder = builtins.toFile "builder.sh" '' + mkdir $out + ln -s $deps $out/depdir1 + ''; + allowedRequisites = allowreqs; + }; + + # When specifying all the requisites, the build succeeds. + test1 = makeTest 1 [ dep1 dep2 deps ]; + + # But missing anything it fails. + test2 = makeTest 2 [ dep2 deps ]; + test3 = makeTest 3 [ dep1 deps ]; + test4 = makeTest 4 [ deps ]; + test5 = makeTest 5 []; +} diff --git a/tests/check-reqs.sh b/tests/check-reqs.sh new file mode 100644 index 00000000..643c2d0c --- /dev/null +++ b/tests/check-reqs.sh @@ -0,0 +1,12 @@ +source common.sh + +RESULT=$TEST_ROOT/result + +# test1 should succeed. +nix-build -o $RESULT check-reqs.nix -A test1 + +# test{2,3,4,5} should fail. +(! nix-build -o $RESULT check-reqs.nix -A test2) +(! nix-build -o $RESULT check-reqs.nix -A test3) +(! nix-build -o $RESULT check-reqs.nix -A test4) +(! nix-build -o $RESULT check-reqs.nix -A test5) diff --git a/tests/local.mk b/tests/local.mk index 65aa1263..69a22749 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -10,7 +10,8 @@ nix_tests = \ remote-store.sh export.sh export-graph.sh negative-caching.sh \ binary-patching.sh timeout.sh secure-drv-outputs.sh nix-channel.sh \ multiple-outputs.sh import-derivation.sh fetchurl.sh optimise-store.sh \ - binary-cache.sh nix-profile.sh repair.sh dump-db.sh case-hack.sh + binary-cache.sh nix-profile.sh repair.sh dump-db.sh case-hack.sh \ + check-reqs.sh # parallel.sh install-tests += $(foreach x, $(nix_tests), tests/$(x))