nix: Respect meta.outputsToInstall, and use all outputs by default

'nix profile install' will now install all outputs listed in the
package's meta.outputsToInstall attribute, or all outputs if that
attribute doesn't exist. This makes it behave consistently with
nix-env. Fixes #6385.

Furthermore, for consistency, all other 'nix' commands do this as
well. E.g. 'nix build' will build and symlink the outputs in
meta.outputsToInstall, defaulting to all outputs. Previously, it only
built/symlinked the first output. Note that this means that selecting
a specific output using attrpath selection (e.g. 'nix build
nixpkgs#libxml2.dev') no longer works. A subsequent PR will add a way
to specify the desired outputs explicitly.
This commit is contained in:
Eelco Dolstra 2022-04-20 16:39:47 +02:00
parent a81622c21d
commit 1ddabe1a01
10 changed files with 96 additions and 27 deletions

View file

@ -440,9 +440,7 @@ DerivedPaths InstallableValue::toDerivedPaths()
// Group by derivation, helps with .all in particular
for (auto & drv : toDerivations()) {
auto outputName = drv.outputName;
if (outputName == "")
throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(drv.drvPath));
for (auto & outputName : drv.outputsToInstall)
drvsToOutputs[drv.drvPath].insert(outputName);
drvsToCopy.insert(drv.drvPath);
}
@ -497,7 +495,13 @@ std::vector<InstallableValue::DerivationInfo> InstallableAttrPath::toDerivations
auto drvPath = drvInfo.queryDrvPath();
if (!drvPath)
throw Error("'%s' is not a derivation", what());
res.push_back({ *drvPath, drvInfo.queryOutputName() });
std::set<std::string> outputsToInstall;
for (auto & output : drvInfo.queryOutputs(false, true))
outputsToInstall.insert(output.first);
res.push_back(DerivationInfo {
.drvPath = *drvPath,
.outputsToInstall = std::move(outputsToInstall)
});
}
return res;
@ -598,9 +602,24 @@ std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableF
auto drvPath = attr->forceDerivation();
std::set<std::string> outputsToInstall;
if (auto aMeta = attr->maybeGetAttr(state->sMeta))
if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall"))
for (auto & s : aOutputsToInstall->getListOfStrings())
outputsToInstall.insert(s);
if (outputsToInstall.empty())
if (auto aOutputs = attr->maybeGetAttr(state->sOutputs))
for (auto & s : aOutputs->getListOfStrings())
outputsToInstall.insert(s);
if (outputsToInstall.empty())
outputsToInstall.insert("out");
auto drvInfo = DerivationInfo {
std::move(drvPath),
attr->getAttr(state->sOutputName)->getString()
.drvPath = std::move(drvPath),
.outputsToInstall = std::move(outputsToInstall),
};
return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)};

View file

@ -141,7 +141,7 @@ struct InstallableValue : Installable
struct DerivationInfo
{
StorePath drvPath;
std::string outputName;
std::set<std::string> outputsToInstall;
};
virtual std::vector<DerivationInfo> toDerivations() = 0;

View file

@ -175,6 +175,24 @@ struct AttrDb
});
}
AttrId setListOfStrings(
AttrKey key,
const std::vector<std::string> & l)
{
return doSQLite([&]()
{
auto state(_state->lock());
state->insertAttribute.use()
(key.first)
(symbols[key.second])
(AttrType::ListOfStrings)
(concatStringsSep("\t", l)).exec();
return state->db.getLastInsertedRowId();
});
}
AttrId setPlaceholder(AttrKey key)
{
return doSQLite([&]()
@ -269,6 +287,8 @@ struct AttrDb
}
case AttrType::Bool:
return {{rowId, queryAttribute.getInt(2) != 0}};
case AttrType::ListOfStrings:
return {{rowId, tokenizeString<std::vector<std::string>>(queryAttribute.getStr(2), "\t")}};
case AttrType::Missing:
return {{rowId, missing_t()}};
case AttrType::Misc:
@ -385,7 +405,7 @@ std::string AttrCursor::getAttrPathStr(Symbol name) const
Value & AttrCursor::forceValue()
{
debug("evaluating uncached attribute %s", getAttrPathStr());
debug("evaluating uncached attribute '%s'", getAttrPathStr());
auto & v = getValue();
@ -601,6 +621,38 @@ bool AttrCursor::getBool()
return v.boolean;
}
std::vector<std::string> AttrCursor::getListOfStrings()
{
if (root->db) {
if (!cachedValue)
cachedValue = root->db->getAttr(getKey());
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
if (auto l = std::get_if<std::vector<std::string>>(&cachedValue->second)) {
debug("using cached list of strings attribute '%s'", getAttrPathStr());
return *l;
} else
throw TypeError("'%s' is not a list of strings", getAttrPathStr());
}
}
debug("evaluating uncached attribute '%s'", getAttrPathStr());
auto & v = getValue();
root->state.forceValue(v, noPos);
if (v.type() != nList)
throw TypeError("'%s' is not a list", getAttrPathStr());
std::vector<std::string> res;
for (auto & elem : v.listItems())
res.push_back(std::string(root->state.forceStringNoCtx(*elem)));
cachedValue = {root->db->setListOfStrings(getKey(), res), res};
return res;
}
std::vector<Symbol> AttrCursor::getAttrs()
{
if (root->db) {

View file

@ -44,6 +44,7 @@ enum AttrType {
Misc = 4,
Failed = 5,
Bool = 6,
ListOfStrings = 7,
};
struct placeholder_t {};
@ -61,7 +62,8 @@ typedef std::variant<
missing_t,
misc_t,
failed_t,
bool
bool,
std::vector<std::string>
> AttrValue;
class AttrCursor : public std::enable_shared_from_this<AttrCursor>
@ -114,6 +116,8 @@ public:
bool getBool();
std::vector<std::string> getListOfStrings();
std::vector<Symbol> getAttrs();
bool isDerivation();

View file

@ -89,7 +89,7 @@ UnresolvedApp Installable::toApp(EvalState & state)
auto outputName = cursor->getAttr(state.sOutputName)->getString();
auto name = cursor->getAttr(state.sName)->getString();
auto aPname = cursor->maybeGetAttr("pname");
auto aMeta = cursor->maybeGetAttr("meta");
auto aMeta = cursor->maybeGetAttr(state.sMeta);
auto aMainProgram = aMeta ? aMeta->maybeGetAttr("mainProgram") : nullptr;
auto mainProgram =
aMainProgram

View file

@ -1015,8 +1015,8 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
auto name = visitor.getAttr(state->sName)->getString();
if (json) {
std::optional<std::string> description;
if (auto aMeta = visitor.maybeGetAttr("meta")) {
if (auto aDescription = aMeta->maybeGetAttr("description"))
if (auto aMeta = visitor.maybeGetAttr(state->sMeta)) {
if (auto aDescription = aMeta->maybeGetAttr(state->sDescription))
description = aDescription->getString();
}
j.emplace("type", "derivation");

View file

@ -93,10 +93,10 @@ struct CmdSearch : InstallableCommand, MixJSON
};
if (cursor.isDerivation()) {
DrvName name(cursor.getAttr("name")->getString());
DrvName name(cursor.getAttr(state->sName)->getString());
auto aMeta = cursor.maybeGetAttr("meta");
auto aDescription = aMeta ? aMeta->maybeGetAttr("description") : nullptr;
auto aMeta = cursor.maybeGetAttr(state->sMeta);
auto aDescription = aMeta ? aMeta->maybeGetAttr(state->sDescription) : nullptr;
auto description = aDescription ? aDescription->getString() : "";
std::replace(description.begin(), description.end(), '\n', ' ');
auto attrPath2 = concatStringsSep(".", attrPathS);

View file

@ -2,15 +2,8 @@ source common.sh
clearStore
# Make sure that 'nix build' only returns the outputs we asked for.
nix build -f multiple-outputs.nix --json a --no-link | jq --exit-status '
(.[0] |
(.drvPath | match(".*multiple-outputs-a.drv")) and
(.outputs | keys | length == 1) and
(.outputs.first | match(".*multiple-outputs-a-first")))
'
nix build -f multiple-outputs.nix --json a.all b.all --no-link | jq --exit-status '
# 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] |
(.drvPath | match(".*multiple-outputs-a.drv")) and
(.outputs | keys | length == 2) and

View file

@ -25,7 +25,8 @@ buildDrvs --substitute --substituters $REMOTE_STORE --no-require-sigs -j0 transi
# Check that the thing weve just substituted has its realisation stored
nix realisation info --file ./content-addressed.nix transitivelyDependentCA
# Check that its dependencies have it too
nix realisation info --file ./content-addressed.nix dependentCA rootCA
nix realisation info --file ./content-addressed.nix dependentCA
# nix realisation info --file ./content-addressed.nix rootCA --outputs out
# Same thing, but
# 1. With non-ca derivations