diff options
-rw-r--r-- | doc/manual/src/release-notes/rl-next.md | 2 | ||||
-rw-r--r-- | src/libcmd/installables.cc | 77 | ||||
-rw-r--r-- | src/libstore/derived-path.cc | 17 | ||||
-rw-r--r-- | src/libstore/derived-path.hh | 2 | ||||
-rw-r--r-- | src/nix/nix.md | 21 | ||||
-rw-r--r-- | tests/build-explicit-output.sh | 43 | ||||
-rw-r--r-- | tests/build.sh | 12 | ||||
-rw-r--r-- | tests/local.mk | 1 |
8 files changed, 133 insertions, 42 deletions
diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 2069e4578..9a13a808e 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -10,3 +10,5 @@ This avoids a lot of spurious errors where some benign strings end-up having a context just because they are read from a store path ([#7260](https://github.com/NixOS/nix/pull/7260)). + +* Allow explicitly selecting outputs with *store derivations* installable syntax too.
\ No newline at end of file diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index d6e62e775..176d655be 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -399,44 +399,56 @@ static StorePath getDeriver( struct InstallableStorePath : Installable { ref<Store> store; - StorePath storePath; + DerivedPath req; InstallableStorePath(ref<Store> store, StorePath && storePath) - : store(store), storePath(std::move(storePath)) { } + : store(store), + req(storePath.isDerivation() + ? (DerivedPath) DerivedPath::Built { + .drvPath = std::move(storePath), + .outputs = {}, + } + : (DerivedPath) DerivedPath::Opaque { + .path = std::move(storePath), + }) + { } + + InstallableStorePath(ref<Store> store, DerivedPath && req) + : store(store), req(std::move(req)) + { } - std::string what() const override { return store->printStorePath(storePath); } + std::string what() const override + { + return req.to_string(*store); + } DerivedPaths toDerivedPaths() override { - if (storePath.isDerivation()) { - auto drv = store->readDerivation(storePath); - return { - DerivedPath::Built { - .drvPath = storePath, - .outputs = drv.outputNames(), - } - }; - } else { - return { - DerivedPath::Opaque { - .path = storePath, - } - }; - } + return { req }; } StorePathSet toDrvPaths(ref<Store> store) override { - if (storePath.isDerivation()) { - return {storePath}; - } else { - return {getDeriver(store, *this, storePath)}; - } + return std::visit(overloaded { + [&](const DerivedPath::Built & bfd) -> StorePathSet { + return { bfd.drvPath }; + }, + [&](const DerivedPath::Opaque & bo) -> StorePathSet { + return { getDeriver(store, *this, bo.path) }; + }, + }, req.raw()); } std::optional<StorePath> getStorePath() override { - return storePath; + return std::visit(overloaded { + [&](const DerivedPath::Built & bfd) { + return bfd.drvPath; + }, + [&](const DerivedPath::Opaque & bo) { + return bo.path; + }, + }, req.raw()); } }; @@ -802,7 +814,22 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables( for (auto & s : ss) { std::exception_ptr ex; - if (s.find('/') != std::string::npos) { + auto found = s.rfind('^'); + if (found != std::string::npos) { + try { + result.push_back(std::make_shared<InstallableStorePath>( + store, + DerivedPath::Built::parse(*store, s.substr(0, found), s.substr(found + 1)))); + continue; + } catch (BadStorePath &) { + } catch (...) { + if (!ex) + ex = std::current_exception(); + } + } + + found = s.find('/'); + if (found != std::string::npos) { try { result.push_back(std::make_shared<InstallableStorePath>(store, store->followLinksToStorePath(s))); continue; diff --git a/src/libstore/derived-path.cc b/src/libstore/derived-path.cc index 88b59f615..7fe797aa1 100644 --- a/src/libstore/derived-path.cc +++ b/src/libstore/derived-path.cc @@ -78,24 +78,25 @@ DerivedPath::Opaque DerivedPath::Opaque::parse(const Store & store, std::string_ return {store.parseStorePath(s)}; } -DerivedPath::Built DerivedPath::Built::parse(const Store & store, std::string_view s) +DerivedPath::Built DerivedPath::Built::parse(const Store & store, std::string_view drvS, std::string_view outputsS) { - size_t n = s.find("!"); - assert(n != s.npos); - auto drvPath = store.parseStorePath(s.substr(0, n)); - auto outputsS = s.substr(n + 1); + auto drvPath = store.parseStorePath(drvS); std::set<std::string> outputs; - if (outputsS != "*") + if (outputsS != "*") { outputs = tokenizeString<std::set<std::string>>(outputsS, ","); + if (outputs.empty()) + throw Error( + "Explicit list of wanted outputs '%s' must not be empty. Consider using '*' as a wildcard meaning all outputs if no output in particular is wanted.", outputsS); + } return {drvPath, outputs}; } DerivedPath DerivedPath::parse(const Store & store, std::string_view s) { - size_t n = s.find("!"); + size_t n = s.rfind("!"); return n == s.npos ? (DerivedPath) DerivedPath::Opaque::parse(store, s) - : (DerivedPath) DerivedPath::Built::parse(store, s); + : (DerivedPath) DerivedPath::Built::parse(store, s.substr(0, n), s.substr(n + 1)); } RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const diff --git a/src/libstore/derived-path.hh b/src/libstore/derived-path.hh index 878696136..706e5dcb4 100644 --- a/src/libstore/derived-path.hh +++ b/src/libstore/derived-path.hh @@ -47,7 +47,7 @@ struct DerivedPathBuilt { std::set<std::string> outputs; std::string to_string(const Store & store) const; - static DerivedPathBuilt parse(const Store & store, std::string_view); + static DerivedPathBuilt parse(const Store & store, std::string_view, std::string_view); nlohmann::json toJSON(ref<Store> store) const; bool operator < (const DerivedPathBuilt & b) const diff --git a/src/nix/nix.md b/src/nix/nix.md index d48682a94..811936024 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -164,6 +164,13 @@ operate are determined as follows: … ``` + and likewise, using a store path to a "drv" file to specify the derivation: + + ```console + # nix build '/nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^dev,static' + … + ``` + * You can also specify that *all* outputs should be used using the syntax *installable*`^*`. For example, the following shows the size of all outputs of the `glibc` package in the binary cache: @@ -177,9 +184,17 @@ operate are determined as follows: /nix/store/q6580lr01jpcsqs4r5arlh4ki2c1m9rv-glibc-2.33-123-dev 44200560 ``` -* If you didn't specify the desired outputs, but the derivation has an - attribute `meta.outputsToInstall`, Nix will use those outputs. For - example, since the package `nixpkgs#libxml2` has this attribute: + and likewise, again using a store path to a "drv" file to specify the derivation: + + ```console + # nix path-info -S --eval-store auto --store https://cache.nixos.org '/nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^*' + … + ``` + +* If you didn't specify the desired outputs, but the derivation comes + from an expression which has an attribute `meta.outputsToInstall`, Nix + will use those outputs. For example, since the package + `nixpkgs#libxml2` has this attribute: ```console # nix eval 'nixpkgs#libxml2.meta.outputsToInstall' diff --git a/tests/build-explicit-output.sh b/tests/build-explicit-output.sh new file mode 100644 index 000000000..45320d6e3 --- /dev/null +++ b/tests/build-explicit-output.sh @@ -0,0 +1,43 @@ +source common.sh + +set -o pipefail + +drv=$(nix eval -f multiple-outputs.nix --raw a.drvPath) +if nix build "$drv^not-an-output" --no-link --json; then + fail "'not-an-output' should fail to build" +fi + +if nix build "$drv^" --no-link --json; then + fail "'empty outputs list' should fail to build" +fi + +if nix build "$drv^*nope" --no-link --json; then + fail "'* must be entire string' should fail to build" +fi + +nix build "$drv^first" --no-link --json | jq --exit-status ' + (.[0] | + (.drvPath | match(".*multiple-outputs-a.drv")) and + (.outputs | + (keys | length == 1) and + (.first | match(".*multiple-outputs-a-first")) and + (has("second") | not))) +' + +nix build "$drv^first,second" --no-link --json | jq --exit-status ' + (.[0] | + (.drvPath | match(".*multiple-outputs-a.drv")) and + (.outputs | + (keys | length == 2) and + (.first | match(".*multiple-outputs-a-first")) and + (.second | match(".*multiple-outputs-a-second")))) +' + +nix build "$drv^*" --no-link --json | jq --exit-status ' + (.[0] | + (.drvPath | match(".*multiple-outputs-a.drv")) and + (.outputs | + (keys | length == 2) and + (.first | match(".*multiple-outputs-a-first")) and + (.second | match(".*multiple-outputs-a-second")))) +' diff --git a/tests/build.sh b/tests/build.sh index c7db039b4..3a3d773b1 100644 --- a/tests/build.sh +++ b/tests/build.sh @@ -8,13 +8,15 @@ set -o pipefail nix build -f multiple-outputs.nix --json a b --no-link | jq --exit-status ' (.[0] | (.drvPath | match(".*multiple-outputs-a.drv")) and - (.outputs | keys | length == 2) and - (.outputs.first | match(".*multiple-outputs-a-first")) and - (.outputs.second | match(".*multiple-outputs-a-second"))) + (.outputs | + (keys | length == 2) and + (.first | match(".*multiple-outputs-a-first")) and + (.second | match(".*multiple-outputs-a-second")))) and (.[1] | (.drvPath | match(".*multiple-outputs-b.drv")) and - (.outputs | keys | length == 1) and - (.outputs.out | match(".*multiple-outputs-b"))) + (.outputs | + (keys | length == 1) and + (.out | match(".*multiple-outputs-b")))) ' # Test output selection using the '^' syntax. diff --git a/tests/local.mk b/tests/local.mk index 340817ec3..aff595d3b 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -98,6 +98,7 @@ nix_tests = \ ssh-relay.sh \ plugins.sh \ build.sh \ + build-explicit-output.sh \ ca/nix-run.sh \ selfref-gc.sh ca/selfref-gc.sh \ db-migration.sh \ |