diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index b0dddd18bffd..126be39709ea 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1596,6 +1596,50 @@ static RegisterPrimOp primop_readDir({ .fun = prim_readDir, }); +/* Extend single element string context with another output. */ +static void prim_outputOf(EvalState & state, const Pos & pos, Value * * args, Value & v) +{ + PathSet context; + Path path = state.coerceToPath(pos, *args[0], context); + + std::string_view outputName = state.forceStringNoCtx(*args[1], pos); + + Path path2; + try { + auto p = DownstreamPlaceholder::parse(path); + path2 = DownstreamPlaceholder { p, outputName }.render(); + } catch (BadHash &) { + path2 = DownstreamPlaceholder { state.store->parseStorePath(path), outputName }.render(); + } + + if (context.size() != 1) + throw EvalError({ + .msg = hintfmt("path '%s' should have exactly one item in string context, but has", context.size()), + .errPos = pos + }); + auto [ctxS, outputNames] = decodeContext(*context.begin()); + outputNames.push_back(std::string { outputName }); + PathSet context2 = { "!" + concatStringsSep("!", outputNames) + "!" + ctxS }; + + v.mkString(path2, context2); +} + +static RegisterPrimOp primop_outputOf({ + .name = "__outputOf", + .args = {"drv path", "output name"}, + .doc = R"( + Return path (actually placeholder path) to the output of the given drv. + The drv path may itself be a placeholder, which permits chaining this primop. + + For instance, `builtins.outputOf (builtins.outputOf myDrv "out) "out"` + will return a placeholder for the output of the output of `myDrv`, + interpreted as a derivation. + + It may help to compare to the `->` operator in C, which can also by + chained. E.g. compare the above example to `drv->out->out`. + )", + .fun = prim_outputOf, +}); /************************************************************* * Creating files diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index fdc681bd724a..21244a16b740 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -737,6 +737,14 @@ std::string DownstreamPlaceholder::render() const } +DownstreamPlaceholder DownstreamPlaceholder::parse(std::string_view s) +{ + assert(s[0] == '/'); + s = s.substr(1); + return { Hash::parseNonSRIUnprefixed(s, htSHA256) }; +} + + inline Hash DownstreamPlaceholder::worker1(const StorePath & drvPath, std::string_view outputName) { auto drvNameWithExtension = drvPath.name(); diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh index b59bde84d62e..61aa4786cf3e 100644 --- a/src/libstore/derivations.hh +++ b/src/libstore/derivations.hh @@ -257,6 +257,8 @@ public: dependency which is a CA derivation. */ std::string render() const; + static DownstreamPlaceholder parse(std::string_view); + // For CA derivations DownstreamPlaceholder(const StorePath & drvPath, std::string_view outputName); @@ -264,6 +266,7 @@ public: DownstreamPlaceholder(const DownstreamPlaceholder & placeholder, std::string_view outputName); private: + DownstreamPlaceholder(Hash && h) : hash(std::move(h)) {} static inline Hash worker1(const StorePath & drvPath, std::string_view outputName); static inline Hash worker2(const DownstreamPlaceholder & placeholder, std::string_view outputName); }; diff --git a/tests/dyn-drv/dep-built-drv.sh b/tests/dyn-drv/dep-built-drv.sh new file mode 100644 index 000000000000..b63484592503 --- /dev/null +++ b/tests/dyn-drv/dep-built-drv.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +source common.sh + +feats=(--experimental-features 'nix-command ca-derivations') + +out1=$(nix-build "${feats[@]}" ./text-hashed-output.nix -A root --no-out-link) + +clearStore + +out2=$(nix-build "${feats[@]}" ./text-hashed-output.nix -A wrapper --no-out-link) + +diff -r $out1 $out2 diff --git a/tests/dyn-drv/eval-outputOf.sh b/tests/dyn-drv/eval-outputOf.sh new file mode 100644 index 000000000000..6d51708b8ec8 --- /dev/null +++ b/tests/dyn-drv/eval-outputOf.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +source common.sh + +feats=(--experimental-features 'nix-command ca-derivations') + +nix eval "${feats[@]}" --impure --expr \ + 'with (import ./text-hashed-output.nix); let + a = root.outPath; + b = builtins.outputOf root.drvPath "out"; + in builtins.trace a + (builtins.trace b + (assert a == b; null))' + +nix eval "${feats[@]}" --impure --expr \ + 'with (import ./text-hashed-output.nix); let + a = dependent.outPath; + b = builtins.outputOf dependent.drvPath "out"; + in builtins.trace a + (builtins.trace b + (assert a == b; null))' + +nix eval "${feats[@]}" --impure --expr \ + 'with (import ./text-hashed-output.nix); let + a = builtins.outputOf dependent.outPath "out"; + b = builtins.outputOf dependent.out "out"; + in builtins.trace a + (builtins.trace b + (assert a == b; null))' + +nix eval "${feats[@]}" --impure --expr \ + 'with (import ./text-hashed-output.nix); let + a = builtins.outputOf dependent.out "out"; + b = builtins.outputOf (builtins.outputOf dependent.drvPath "out") "out"; + in builtins.trace a + (builtins.trace b + (assert a == b; null))' diff --git a/tests/dyn-drv/text-hashed-output.nix b/tests/dyn-drv/text-hashed-output.nix index 653124091531..338455d60985 100644 --- a/tests/dyn-drv/text-hashed-output.nix +++ b/tests/dyn-drv/text-hashed-output.nix @@ -26,4 +26,14 @@ rec { outputHashMode = "text"; outputHashAlgo = "sha256"; }; + wrapper = mkDerivation { + name = "put-it-all-together"; + buildCommand = '' + echo "Copying the output of the dynamic derivation" + cp -r ${builtins.outputOf dependent.out "out"} $out + ''; + __contentAddressed = true; + outputHashMode = "recursive"; + outputHashAlgo = "sha256"; + }; } diff --git a/tests/local.mk b/tests/local.mk index 53fbc33b4e7e..a0a7e076c943 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -83,6 +83,8 @@ nix_tests = \ ca/import-derivation.sh \ dyn-drv/text-hashed-output.sh \ dyn-drv/build-built-drv.sh \ + dyn-drv/eval-outputOf.sh \ + dyn-drv/dep-built-drv.sh \ nix_path.sh \ case-hack.sh \ placeholders.sh \