aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/manual/src/release-notes/rl-next.md10
-rw-r--r--src/libcmd/installables.cc48
-rw-r--r--src/libcmd/installables.hh2
-rw-r--r--src/libexpr/flake/flakeref.cc11
-rw-r--r--src/libexpr/flake/flakeref.hh8
-rw-r--r--src/libstore/path-with-outputs.cc56
-rw-r--r--src/libstore/path-with-outputs.hh22
-rw-r--r--src/libstore/tests/path-with-outputs.cc46
-rw-r--r--src/libutil/experimental-features.cc6
-rw-r--r--src/libutil/experimental-features.hh4
-rw-r--r--src/nix/bundle.cc4
-rw-r--r--src/nix/develop.cc1
-rw-r--r--src/nix/flake.cc2
-rw-r--r--src/nix/nix.md45
-rw-r--r--src/nix/profile-install.md7
-rw-r--r--src/nix/profile.cc29
-rw-r--r--tests/build.sh41
-rw-r--r--tests/multiple-outputs.nix7
-rw-r--r--tests/nix-profile.sh19
-rw-r--r--tests/shell-hello.nix11
-rw-r--r--tests/shell.sh4
21 files changed, 354 insertions, 29 deletions
diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md
index 7b3ad4e58..efd893662 100644
--- a/doc/manual/src/release-notes/rl-next.md
+++ b/doc/manual/src/release-notes/rl-next.md
@@ -14,3 +14,13 @@
* `nix build` has a new `--print-out-paths` flag to print the resulting output paths.
This matches the default behaviour of `nix-build`.
+
+* You can now specify which outputs of a derivation `nix` should
+ operate on using the syntax `installable^outputs`,
+ e.g. `nixpkgs#glibc^dev,static` or `nixpkgs#glibc^*`. By default,
+ `nix` will use the outputs specified by the derivation's
+ `meta.outputsToInstall` attribute if it exists, or all outputs
+ otherwise.
+
+ Selecting derivation outputs using the attribute selection syntax
+ (e.g. `nixpkgs#glibc.dev`) no longer works.
diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc
index e2ee47dea..55a5e91e9 100644
--- a/src/libcmd/installables.cc
+++ b/src/libcmd/installables.cc
@@ -464,9 +464,19 @@ struct InstallableAttrPath : InstallableValue
SourceExprCommand & cmd;
RootValue v;
std::string attrPath;
-
- InstallableAttrPath(ref<EvalState> state, SourceExprCommand & cmd, Value * v, const std::string & attrPath)
- : InstallableValue(state), cmd(cmd), v(allocRootValue(v)), attrPath(attrPath)
+ OutputsSpec outputsSpec;
+
+ InstallableAttrPath(
+ ref<EvalState> state,
+ SourceExprCommand & cmd,
+ Value * v,
+ const std::string & attrPath,
+ OutputsSpec outputsSpec)
+ : InstallableValue(state)
+ , cmd(cmd)
+ , v(allocRootValue(v))
+ , attrPath(attrPath)
+ , outputsSpec(std::move(outputsSpec))
{ }
std::string what() const override { return attrPath; }
@@ -495,9 +505,15 @@ std::vector<InstallableValue::DerivationInfo> InstallableAttrPath::toDerivations
auto drvPath = drvInfo.queryDrvPath();
if (!drvPath)
throw Error("'%s' is not a derivation", what());
+
std::set<std::string> outputsToInstall;
- for (auto & output : drvInfo.queryOutputs(false, true))
- outputsToInstall.insert(output.first);
+
+ if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
+ outputsToInstall = *outputNames;
+ else
+ for (auto & output : drvInfo.queryOutputs(false, std::get_if<DefaultOutputs>(&outputsSpec)))
+ outputsToInstall.insert(output.first);
+
res.push_back(DerivationInfo {
.drvPath = *drvPath,
.outputsToInstall = std::move(outputsToInstall)
@@ -578,6 +594,7 @@ InstallableFlake::InstallableFlake(
ref<EvalState> state,
FlakeRef && flakeRef,
std::string_view fragment,
+ OutputsSpec outputsSpec,
Strings attrPaths,
Strings prefixes,
const flake::LockFlags & lockFlags)
@@ -585,6 +602,7 @@ InstallableFlake::InstallableFlake(
flakeRef(flakeRef),
attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment}),
prefixes(fragment == "" ? Strings{} : prefixes),
+ outputsSpec(std::move(outputsSpec)),
lockFlags(lockFlags)
{
if (cmd && cmd->getAutoArgs(*state)->size())
@@ -609,14 +627,19 @@ std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableF
for (auto & s : aOutputsToInstall->getListOfStrings())
outputsToInstall.insert(s);
- if (outputsToInstall.empty())
+ if (outputsToInstall.empty() || std::get_if<AllOutputs>(&outputsSpec)) {
+ outputsToInstall.clear();
if (auto aOutputs = attr->maybeGetAttr(state->sOutputs))
for (auto & s : aOutputs->getListOfStrings())
outputsToInstall.insert(s);
+ }
if (outputsToInstall.empty())
outputsToInstall.insert("out");
+ if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
+ outputsToInstall = *outputNames;
+
auto drvInfo = DerivationInfo {
.drvPath = std::move(drvPath),
.outputsToInstall = std::move(outputsToInstall),
@@ -742,8 +765,14 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
state->eval(e, *vFile);
}
- for (auto & s : ss)
- result.push_back(std::make_shared<InstallableAttrPath>(state, *this, vFile, s == "." ? "" : s));
+ for (auto & s : ss) {
+ auto [prefix, outputsSpec] = parseOutputsSpec(s);
+ result.push_back(
+ std::make_shared<InstallableAttrPath>(
+ state, *this, vFile,
+ prefix == "." ? "" : prefix,
+ outputsSpec));
+ }
} else {
@@ -762,12 +791,13 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
}
try {
- auto [flakeRef, fragment] = parseFlakeRefWithFragment(s, absPath("."));
+ auto [flakeRef, fragment, outputsSpec] = parseFlakeRefWithFragmentAndOutputsSpec(s, absPath("."));
result.push_back(std::make_shared<InstallableFlake>(
this,
getEvalState(),
std::move(flakeRef),
fragment,
+ outputsSpec,
getDefaultFlakeAttrPaths(),
getDefaultFlakeAttrPathPrefixes(),
lockFlags));
diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh
index 3c2c33549..1a5a96153 100644
--- a/src/libcmd/installables.hh
+++ b/src/libcmd/installables.hh
@@ -156,6 +156,7 @@ struct InstallableFlake : InstallableValue
FlakeRef flakeRef;
Strings attrPaths;
Strings prefixes;
+ OutputsSpec outputsSpec;
const flake::LockFlags & lockFlags;
mutable std::shared_ptr<flake::LockedFlake> _lockedFlake;
@@ -164,6 +165,7 @@ struct InstallableFlake : InstallableValue
ref<EvalState> state,
FlakeRef && flakeRef,
std::string_view fragment,
+ OutputsSpec outputsSpec,
Strings attrPaths,
Strings prefixes,
const flake::LockFlags & lockFlags);
diff --git a/src/libexpr/flake/flakeref.cc b/src/libexpr/flake/flakeref.cc
index c1eae413f..1dcc4555a 100644
--- a/src/libexpr/flake/flakeref.cc
+++ b/src/libexpr/flake/flakeref.cc
@@ -238,4 +238,15 @@ std::pair<fetchers::Tree, FlakeRef> FlakeRef::fetchTree(ref<Store> store) const
return {std::move(tree), FlakeRef(std::move(lockedInput), subdir)};
}
+std::tuple<FlakeRef, std::string, OutputsSpec> parseFlakeRefWithFragmentAndOutputsSpec(
+ const std::string & url,
+ const std::optional<Path> & baseDir,
+ bool allowMissing,
+ bool isFlake)
+{
+ auto [prefix, outputsSpec] = parseOutputsSpec(url);
+ auto [flakeRef, fragment] = parseFlakeRefWithFragment(prefix, baseDir, allowMissing, isFlake);
+ return {std::move(flakeRef), fragment, outputsSpec};
+}
+
}
diff --git a/src/libexpr/flake/flakeref.hh b/src/libexpr/flake/flakeref.hh
index 1fddfd9a0..a9182f4bf 100644
--- a/src/libexpr/flake/flakeref.hh
+++ b/src/libexpr/flake/flakeref.hh
@@ -3,6 +3,7 @@
#include "types.hh"
#include "hash.hh"
#include "fetchers.hh"
+#include "path-with-outputs.hh"
#include <variant>
@@ -79,4 +80,11 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment(
const std::string & url, const std::optional<Path> & baseDir = {});
+std::tuple<FlakeRef, std::string, OutputsSpec> parseFlakeRefWithFragmentAndOutputsSpec(
+ const std::string & url,
+ const std::optional<Path> & baseDir = {},
+ bool allowMissing = false,
+ bool isFlake = true);
+
+
}
diff --git a/src/libstore/path-with-outputs.cc b/src/libstore/path-with-outputs.cc
index 078c117bd..d6d67ea05 100644
--- a/src/libstore/path-with-outputs.cc
+++ b/src/libstore/path-with-outputs.cc
@@ -1,5 +1,8 @@
#include "path-with-outputs.hh"
#include "store-api.hh"
+#include "nlohmann/json.hpp"
+
+#include <regex>
namespace nix {
@@ -68,4 +71,57 @@ StorePathWithOutputs followLinksToStorePathWithOutputs(const Store & store, std:
return StorePathWithOutputs { store.followLinksToStorePath(path), std::move(outputs) };
}
+std::pair<std::string, OutputsSpec> parseOutputsSpec(const std::string & s)
+{
+ static std::regex regex(R"((.*)\^((\*)|([a-z]+(,[a-z]+)*)))");
+
+ std::smatch match;
+ if (!std::regex_match(s, match, regex))
+ return {s, DefaultOutputs()};
+
+ if (match[3].matched)
+ return {match[1], AllOutputs()};
+
+ return {match[1], tokenizeString<OutputNames>(match[4].str(), ",")};
+}
+
+std::string printOutputsSpec(const OutputsSpec & outputsSpec)
+{
+ if (std::get_if<DefaultOutputs>(&outputsSpec))
+ return "";
+
+ if (std::get_if<AllOutputs>(&outputsSpec))
+ return "^*";
+
+ if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
+ return "^" + concatStringsSep(",", *outputNames);
+
+ assert(false);
+}
+
+void to_json(nlohmann::json & json, const OutputsSpec & outputsSpec)
+{
+ if (std::get_if<DefaultOutputs>(&outputsSpec))
+ json = nullptr;
+
+ else if (std::get_if<AllOutputs>(&outputsSpec))
+ json = std::vector<std::string>({"*"});
+
+ else if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
+ json = *outputNames;
+}
+
+void from_json(const nlohmann::json & json, OutputsSpec & outputsSpec)
+{
+ if (json.is_null())
+ outputsSpec = DefaultOutputs();
+ else {
+ auto names = json.get<OutputNames>();
+ if (names == OutputNames({"*"}))
+ outputsSpec = AllOutputs();
+ else
+ outputsSpec = names;
+ }
+}
+
}
diff --git a/src/libstore/path-with-outputs.hh b/src/libstore/path-with-outputs.hh
index 4c4023dcb..0cb5eb223 100644
--- a/src/libstore/path-with-outputs.hh
+++ b/src/libstore/path-with-outputs.hh
@@ -4,6 +4,7 @@
#include "path.hh"
#include "derived-path.hh"
+#include "nlohmann/json_fwd.hpp"
namespace nix {
@@ -32,4 +33,25 @@ StorePathWithOutputs parsePathWithOutputs(const Store & store, std::string_view
StorePathWithOutputs followLinksToStorePathWithOutputs(const Store & store, std::string_view pathWithOutputs);
+typedef std::set<std::string> OutputNames;
+
+struct AllOutputs {
+ bool operator < (const AllOutputs & _) const { return false; }
+};
+
+struct DefaultOutputs {
+ bool operator < (const DefaultOutputs & _) const { return false; }
+};
+
+typedef std::variant<DefaultOutputs, AllOutputs, OutputNames> OutputsSpec;
+
+/* Parse a string of the form 'prefix^output1,...outputN' or
+ 'prefix^*', returning the prefix and the outputs spec. */
+std::pair<std::string, OutputsSpec> parseOutputsSpec(const std::string & s);
+
+std::string printOutputsSpec(const OutputsSpec & outputsSpec);
+
+void to_json(nlohmann::json &, const OutputsSpec &);
+void from_json(const nlohmann::json &, OutputsSpec &);
+
}
diff --git a/src/libstore/tests/path-with-outputs.cc b/src/libstore/tests/path-with-outputs.cc
new file mode 100644
index 000000000..350ea7ffd
--- /dev/null
+++ b/src/libstore/tests/path-with-outputs.cc
@@ -0,0 +1,46 @@
+#include "path-with-outputs.hh"
+
+#include <gtest/gtest.h>
+
+namespace nix {
+
+TEST(parseOutputsSpec, basic)
+{
+ {
+ auto [prefix, outputsSpec] = parseOutputsSpec("foo");
+ ASSERT_EQ(prefix, "foo");
+ ASSERT_TRUE(std::get_if<DefaultOutputs>(&outputsSpec));
+ }
+
+ {
+ auto [prefix, outputsSpec] = parseOutputsSpec("foo^*");
+ ASSERT_EQ(prefix, "foo");
+ ASSERT_TRUE(std::get_if<AllOutputs>(&outputsSpec));
+ }
+
+ {
+ auto [prefix, outputsSpec] = parseOutputsSpec("foo^out");
+ ASSERT_EQ(prefix, "foo");
+ ASSERT_TRUE(std::get<OutputNames>(outputsSpec) == OutputNames({"out"}));
+ }
+
+ {
+ auto [prefix, outputsSpec] = parseOutputsSpec("foo^out,bin");
+ ASSERT_EQ(prefix, "foo");
+ ASSERT_TRUE(std::get<OutputNames>(outputsSpec) == OutputNames({"out", "bin"}));
+ }
+
+ {
+ auto [prefix, outputsSpec] = parseOutputsSpec("foo^bar^out,bin");
+ ASSERT_EQ(prefix, "foo^bar");
+ ASSERT_TRUE(std::get<OutputNames>(outputsSpec) == OutputNames({"out", "bin"}));
+ }
+
+ {
+ auto [prefix, outputsSpec] = parseOutputsSpec("foo^&*()");
+ ASSERT_EQ(prefix, "foo^&*()");
+ ASSERT_TRUE(std::get_if<DefaultOutputs>(&outputsSpec));
+ }
+}
+
+}
diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc
index df37edf57..9326cd08c 100644
--- a/src/libutil/experimental-features.cc
+++ b/src/libutil/experimental-features.cc
@@ -58,11 +58,13 @@ std::ostream & operator <<(std::ostream & str, const ExperimentalFeature & featu
return str << showExperimentalFeature(feature);
}
-void to_json(nlohmann::json& j, const ExperimentalFeature& feature) {
+void to_json(nlohmann::json & j, const ExperimentalFeature & feature)
+{
j = showExperimentalFeature(feature);
}
-void from_json(const nlohmann::json& j, ExperimentalFeature& feature) {
+void from_json(const nlohmann::json & j, ExperimentalFeature & feature)
+{
const std::string input = j;
const auto parsed = parseExperimentalFeature(input);
diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh
index a6d080094..57512830c 100644
--- a/src/libutil/experimental-features.hh
+++ b/src/libutil/experimental-features.hh
@@ -55,7 +55,7 @@ public:
* Semi-magic conversion to and from json.
* See the nlohmann/json readme for more details.
*/
-void to_json(nlohmann::json&, const ExperimentalFeature&);
-void from_json(const nlohmann::json&, ExperimentalFeature&);
+void to_json(nlohmann::json &, const ExperimentalFeature &);
+void from_json(const nlohmann::json &, ExperimentalFeature &);
}
diff --git a/src/nix/bundle.cc b/src/nix/bundle.cc
index 2421adf4e..2e48e4c74 100644
--- a/src/nix/bundle.cc
+++ b/src/nix/bundle.cc
@@ -75,10 +75,10 @@ struct CmdBundle : InstallableCommand
auto val = installable->toValue(*evalState).first;
- auto [bundlerFlakeRef, bundlerName] = parseFlakeRefWithFragment(bundler, absPath("."));
+ auto [bundlerFlakeRef, bundlerName, outputsSpec] = parseFlakeRefWithFragmentAndOutputsSpec(bundler, absPath("."));
const flake::LockFlags lockFlags{ .writeLockFile = false };
InstallableFlake bundler{this,
- evalState, std::move(bundlerFlakeRef), bundlerName,
+ evalState, std::move(bundlerFlakeRef), bundlerName, outputsSpec,
{"bundlers." + settings.thisSystem.get() + ".default",
"defaultBundler." + settings.thisSystem.get()
},
diff --git a/src/nix/develop.cc b/src/nix/develop.cc
index 7fc74d34e..1190b8348 100644
--- a/src/nix/develop.cc
+++ b/src/nix/develop.cc
@@ -507,6 +507,7 @@ struct CmdDevelop : Common, MixEnvironment
state,
installable->nixpkgsFlakeRef(),
"bashInteractive",
+ DefaultOutputs(),
Strings{},
Strings{"legacyPackages." + settings.thisSystem.get() + "."},
nixpkgsLockFlags);
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index 6a34ca67b..1938ce4e6 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -724,7 +724,7 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
auto [templateFlakeRef, templateName] = parseFlakeRefWithFragment(templateUrl, absPath("."));
auto installable = InstallableFlake(nullptr,
- evalState, std::move(templateFlakeRef), templateName,
+ evalState, std::move(templateFlakeRef), templateName, DefaultOutputs(),
defaultTemplateAttrPaths,
defaultTemplateAttrPathsPrefixes,
lockFlags);
diff --git a/src/nix/nix.md b/src/nix/nix.md
index 0dacadee6..d48682a94 100644
--- a/src/nix/nix.md
+++ b/src/nix/nix.md
@@ -146,6 +146,51 @@ For most commands, if no installable is specified, the default is `.`,
i.e. Nix will operate on the default flake output attribute of the
flake in the current directory.
+## Derivation output selection
+
+Derivations can have multiple outputs, each corresponding to a
+different store path. For instance, a package can have a `bin` output
+that contains programs, and a `dev` output that provides development
+artifacts like C/C++ header files. The outputs on which `nix` commands
+operate are determined as follows:
+
+* You can explicitly specify the desired outputs using the syntax
+ *installable*`^`*output1*`,`*...*`,`*outputN*. For example, you can
+ obtain the `dev` and `static` outputs of the `glibc` package:
+
+ ```console
+ # nix build 'nixpkgs#glibc^dev,static'
+ # ls ./result-dev/include/ ./result-static/lib/
+ …
+ ```
+
+* 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:
+
+ ```console
+ # nix path-info -S --eval-store auto --store https://cache.nixos.org 'nixpkgs#glibc^*'
+ /nix/store/g02b1lpbddhymmcjb923kf0l7s9nww58-glibc-2.33-123 33208200
+ /nix/store/851dp95qqiisjifi639r0zzg5l465ny4-glibc-2.33-123-bin 36142896
+ /nix/store/kdgs3q6r7xdff1p7a9hnjr43xw2404z7-glibc-2.33-123-debug 155787312
+ /nix/store/n4xa8h6pbmqmwnq0mmsz08l38abb06zc-glibc-2.33-123-static 42488328
+ /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:
+
+ ```console
+ # nix eval 'nixpkgs#libxml2.meta.outputsToInstall'
+ [ "bin" "man" ]
+ ```
+
+ a command like `nix shell nixpkgs#libxml2` will provide only those
+ two outputs by default.
+
+* Otherwise, Nix will use all outputs of the derivation.
+
# Nix stores
Most `nix` subcommands operate on a *Nix store*.
diff --git a/src/nix/profile-install.md b/src/nix/profile-install.md
index e3009491e..aed414963 100644
--- a/src/nix/profile-install.md
+++ b/src/nix/profile-install.md
@@ -20,6 +20,13 @@ R""(
# nix profile install nixpkgs/d73407e8e6002646acfdef0e39ace088bacc83da#hello
```
+* Install a specific output of a package:
+
+ ```console
+ # nix profile install nixpkgs#bash^man
+ ```
+
+
# Description
This command adds *installables* to a Nix profile.
diff --git a/src/nix/profile.cc b/src/nix/profile.cc
index 52c918016..685776bec 100644
--- a/src/nix/profile.cc
+++ b/src/nix/profile.cc
@@ -22,13 +22,13 @@ struct ProfileElementSource
// FIXME: record original attrpath.
FlakeRef resolvedRef;
std::string attrPath;
- // FIXME: output names
+ OutputsSpec outputs;
bool operator < (const ProfileElementSource & other) const
{
return
- std::pair(originalRef.to_string(), attrPath) <
- std::pair(other.originalRef.to_string(), other.attrPath);
+ std::tuple(originalRef.to_string(), attrPath, outputs) <
+ std::tuple(other.originalRef.to_string(), other.attrPath, other.outputs);
}
};
@@ -42,7 +42,7 @@ struct ProfileElement
std::string describe() const
{
if (source)
- return fmt("%s#%s", source->originalRef, source->attrPath);
+ return fmt("%s#%s%s", source->originalRef, source->attrPath, printOutputsSpec(source->outputs));
StringSet names;
for (auto & path : storePaths)
names.insert(DrvName(path.name()).name);
@@ -98,7 +98,7 @@ struct ProfileManifest
auto version = json.value("version", 0);
std::string sUrl;
std::string sOriginalUrl;
- switch(version){
+ switch (version) {
case 1:
sUrl = "uri";
sOriginalUrl = "originalUri";
@@ -116,11 +116,12 @@ struct ProfileManifest
for (auto & p : e["storePaths"])
element.storePaths.insert(state.store->parseStorePath((std::string) p));
element.active = e["active"];
- if (e.value(sUrl,"") != "") {
- element.source = ProfileElementSource{
+ if (e.value(sUrl, "") != "") {
+ element.source = ProfileElementSource {
parseFlakeRef(e[sOriginalUrl]),
parseFlakeRef(e[sUrl]),
- e["attrPath"]
+ e["attrPath"],
+ e["outputs"].get<OutputsSpec>()
};
}
elements.emplace_back(std::move(element));
@@ -156,6 +157,7 @@ struct ProfileManifest
obj["originalUrl"] = element.source->originalRef.to_string();
obj["url"] = element.source->resolvedRef.to_string();
obj["attrPath"] = element.source->attrPath;
+ obj["outputs"] = element.source->outputs;
}
array.push_back(obj);
}
@@ -283,10 +285,11 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
if (auto installable2 = std::dynamic_pointer_cast<InstallableFlake>(installable)) {
// FIXME: make build() return this?
auto [attrPath, resolvedRef, drv] = installable2->toDerivation();
- element.source = ProfileElementSource{
+ element.source = ProfileElementSource {
installable2->flakeRef,
resolvedRef,
attrPath,
+ installable2->outputsSpec
};
}
@@ -443,6 +446,7 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
getEvalState(),
FlakeRef(element.source->originalRef),
"",
+ element.source->outputs,
Strings{element.source->attrPath},
Strings{},
lockFlags);
@@ -454,10 +458,11 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
printInfo("upgrading '%s' from flake '%s' to '%s'",
element.source->attrPath, element.source->resolvedRef, resolvedRef);
- element.source = ProfileElementSource{
+ element.source = ProfileElementSource {
installable->flakeRef,
resolvedRef,
attrPath,
+ installable->outputsSpec
};
installables.push_back(installable);
@@ -513,8 +518,8 @@ struct CmdProfileList : virtual EvalCommand, virtual StoreCommand, MixDefaultPro
for (size_t i = 0; i < manifest.elements.size(); ++i) {
auto & element(manifest.elements[i]);
logger->cout("%d %s %s %s", i,
- element.source ? element.source->originalRef.to_string() + "#" + element.source->attrPath : "-",
- element.source ? element.source->resolvedRef.to_string() + "#" + element.source->attrPath : "-",
+ element.source ? element.source->originalRef.to_string() + "#" + element.source->attrPath + printOutputsSpec(element.source->outputs) : "-",
+ element.source ? element.source->resolvedRef.to_string() + "#" + element.source->attrPath + printOutputsSpec(element.source->outputs) : "-",
concatStringsSep(" ", store->printStorePathSet(element.storePaths)));
}
}
diff --git a/tests/build.sh b/tests/build.sh
index 339155991..ff16d1603 100644
--- a/tests/build.sh
+++ b/tests/build.sh
@@ -2,6 +2,8 @@ source common.sh
clearStore
+set -o pipefail
+
# Make sure that 'nix build' returns all outputs by default.
nix build -f multiple-outputs.nix --json a b --no-link | jq --exit-status '
(.[0] |
@@ -15,6 +17,45 @@ nix build -f multiple-outputs.nix --json a b --no-link | jq --exit-status '
(.outputs.out | match(".*multiple-outputs-b")))
'
+# Test output selection using the '^' syntax.
+nix build -f multiple-outputs.nix --json a^first --no-link | jq --exit-status '
+ (.[0] |
+ (.drvPath | match(".*multiple-outputs-a.drv")) and
+ (.outputs | keys == ["first"]))
+'
+
+nix build -f multiple-outputs.nix --json a^second,first --no-link | jq --exit-status '
+ (.[0] |
+ (.drvPath | match(".*multiple-outputs-a.drv")) and
+ (.outputs | keys == ["first", "second"]))
+'
+
+nix build -f multiple-outputs.nix --json 'a^*' --no-link | jq --exit-status '
+ (.[0] |
+ (.drvPath | match(".*multiple-outputs-a.drv")) and
+ (.outputs | keys == ["first", "second"]))
+'
+
+# Test that 'outputsToInstall' is respected by default.
+nix build -f multiple-outputs.nix --json e --no-link | jq --exit-status '
+ (.[0] |
+ (.drvPath | match(".*multiple-outputs-e.drv")) and
+ (.outputs | keys == ["a", "b"]))
+'
+
+# But not when it's overriden.
+nix build -f multiple-outputs.nix --json e^a --no-link | jq --exit-status '
+ (.[0] |
+ (.drvPath | match(".*multiple-outputs-e.drv")) and
+ (.outputs | keys == ["a"]))
+'
+
+nix build -f multiple-outputs.nix --json 'e^*' --no-link | jq --exit-status '
+ (.[0] |
+ (.drvPath | match(".*multiple-outputs-e.drv")) and
+ (.outputs | keys == ["a", "b", "c"]))
+'
+
testNormalization () {
clearStore
outPath=$(nix-build ./simple.nix --no-out-link)
diff --git a/tests/multiple-outputs.nix b/tests/multiple-outputs.nix
index b915493f7..624a5dade 100644
--- a/tests/multiple-outputs.nix
+++ b/tests/multiple-outputs.nix
@@ -80,4 +80,11 @@ rec {
'';
}).a;
+ e = mkDerivation {
+ name = "multiple-outputs-e";
+ outputs = [ "a" "b" "c" ];
+ meta.outputsToInstall = [ "a" "b" ];
+ buildCommand = "mkdir $a $b $c";
+ };
+
}
diff --git a/tests/nix-profile.sh b/tests/nix-profile.sh
index 814192252..f8da3d929 100644
--- a/tests/nix-profile.sh
+++ b/tests/nix-profile.sh
@@ -101,3 +101,22 @@ printf Utrecht > $flake1Dir/who
nix profile install $flake1Dir
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello Utrecht" ]]
[[ $(nix path-info --json $(realpath $TEST_HOME/.nix-profile/bin/hello) | jq -r .[].ca) =~ fixed:r:sha256: ]]
+
+# Override the outputs.
+nix profile remove 0 1
+nix profile install "$flake1Dir^*"
+[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello Utrecht" ]]
+[ -e $TEST_HOME/.nix-profile/share/man ]
+[ -e $TEST_HOME/.nix-profile/include ]
+
+printf Nix > $flake1Dir/who
+nix profile upgrade 0
+[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello Nix" ]]
+[ -e $TEST_HOME/.nix-profile/share/man ]
+[ -e $TEST_HOME/.nix-profile/include ]
+
+nix profile remove 0
+nix profile install "$flake1Dir^man"
+(! [ -e $TEST_HOME/.nix-profile/bin/hello ])
+[ -e $TEST_HOME/.nix-profile/share/man ]
+(! [ -e $TEST_HOME/.nix-profile/include ])
diff --git a/tests/shell-hello.nix b/tests/shell-hello.nix
index 77dcbd2a9..3fdd3501d 100644
--- a/tests/shell-hello.nix
+++ b/tests/shell-hello.nix
@@ -3,15 +3,24 @@ with import ./config.nix;
{
hello = mkDerivation {
name = "hello";
+ outputs = [ "out" "dev" ];
+ meta.outputsToInstall = [ "out" ];
buildCommand =
''
- mkdir -p $out/bin
+ mkdir -p $out/bin $dev/bin
+
cat > $out/bin/hello <<EOF
#! ${shell}
who=\$1
echo "Hello \''${who:-World} from $out/bin/hello"
EOF
chmod +x $out/bin/hello
+
+ cat > $dev/bin/hello2 <<EOF
+ #! ${shell}
+ echo "Hello2"
+ EOF
+ chmod +x $dev/bin/hello2
'';
};
}
diff --git a/tests/shell.sh b/tests/shell.sh
index 2b85bb337..6a80e8385 100644
--- a/tests/shell.sh
+++ b/tests/shell.sh
@@ -6,6 +6,10 @@ clearCache
nix shell -f shell-hello.nix hello -c hello | grep 'Hello World'
nix shell -f shell-hello.nix hello -c hello NixOS | grep 'Hello NixOS'
+# Test output selection.
+nix shell -f shell-hello.nix hello^dev -c hello2 | grep 'Hello2'
+nix shell -f shell-hello.nix 'hello^*' -c hello2 | grep 'Hello2'
+
if ! canUseSandbox; then exit 99; fi
chmod -R u+w $TEST_ROOT/store0 || true