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:
parent
a81622c21d
commit
1ddabe1a01
|
@ -440,9 +440,7 @@ DerivedPaths InstallableValue::toDerivedPaths()
|
||||||
|
|
||||||
// Group by derivation, helps with .all in particular
|
// Group by derivation, helps with .all in particular
|
||||||
for (auto & drv : toDerivations()) {
|
for (auto & drv : toDerivations()) {
|
||||||
auto outputName = drv.outputName;
|
for (auto & outputName : drv.outputsToInstall)
|
||||||
if (outputName == "")
|
|
||||||
throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(drv.drvPath));
|
|
||||||
drvsToOutputs[drv.drvPath].insert(outputName);
|
drvsToOutputs[drv.drvPath].insert(outputName);
|
||||||
drvsToCopy.insert(drv.drvPath);
|
drvsToCopy.insert(drv.drvPath);
|
||||||
}
|
}
|
||||||
|
@ -497,7 +495,13 @@ std::vector<InstallableValue::DerivationInfo> InstallableAttrPath::toDerivations
|
||||||
auto drvPath = drvInfo.queryDrvPath();
|
auto drvPath = drvInfo.queryDrvPath();
|
||||||
if (!drvPath)
|
if (!drvPath)
|
||||||
throw Error("'%s' is not a derivation", what());
|
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;
|
return res;
|
||||||
|
@ -598,9 +602,24 @@ std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableF
|
||||||
|
|
||||||
auto drvPath = attr->forceDerivation();
|
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 {
|
auto drvInfo = DerivationInfo {
|
||||||
std::move(drvPath),
|
.drvPath = std::move(drvPath),
|
||||||
attr->getAttr(state->sOutputName)->getString()
|
.outputsToInstall = std::move(outputsToInstall),
|
||||||
};
|
};
|
||||||
|
|
||||||
return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)};
|
return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)};
|
||||||
|
|
|
@ -141,7 +141,7 @@ struct InstallableValue : Installable
|
||||||
struct DerivationInfo
|
struct DerivationInfo
|
||||||
{
|
{
|
||||||
StorePath drvPath;
|
StorePath drvPath;
|
||||||
std::string outputName;
|
std::set<std::string> outputsToInstall;
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual std::vector<DerivationInfo> toDerivations() = 0;
|
virtual std::vector<DerivationInfo> toDerivations() = 0;
|
||||||
|
|
|
@ -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)
|
AttrId setPlaceholder(AttrKey key)
|
||||||
{
|
{
|
||||||
return doSQLite([&]()
|
return doSQLite([&]()
|
||||||
|
@ -269,6 +287,8 @@ struct AttrDb
|
||||||
}
|
}
|
||||||
case AttrType::Bool:
|
case AttrType::Bool:
|
||||||
return {{rowId, queryAttribute.getInt(2) != 0}};
|
return {{rowId, queryAttribute.getInt(2) != 0}};
|
||||||
|
case AttrType::ListOfStrings:
|
||||||
|
return {{rowId, tokenizeString<std::vector<std::string>>(queryAttribute.getStr(2), "\t")}};
|
||||||
case AttrType::Missing:
|
case AttrType::Missing:
|
||||||
return {{rowId, missing_t()}};
|
return {{rowId, missing_t()}};
|
||||||
case AttrType::Misc:
|
case AttrType::Misc:
|
||||||
|
@ -385,7 +405,7 @@ std::string AttrCursor::getAttrPathStr(Symbol name) const
|
||||||
|
|
||||||
Value & AttrCursor::forceValue()
|
Value & AttrCursor::forceValue()
|
||||||
{
|
{
|
||||||
debug("evaluating uncached attribute %s", getAttrPathStr());
|
debug("evaluating uncached attribute '%s'", getAttrPathStr());
|
||||||
|
|
||||||
auto & v = getValue();
|
auto & v = getValue();
|
||||||
|
|
||||||
|
@ -601,6 +621,38 @@ bool AttrCursor::getBool()
|
||||||
return v.boolean;
|
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()
|
std::vector<Symbol> AttrCursor::getAttrs()
|
||||||
{
|
{
|
||||||
if (root->db) {
|
if (root->db) {
|
||||||
|
|
|
@ -44,6 +44,7 @@ enum AttrType {
|
||||||
Misc = 4,
|
Misc = 4,
|
||||||
Failed = 5,
|
Failed = 5,
|
||||||
Bool = 6,
|
Bool = 6,
|
||||||
|
ListOfStrings = 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct placeholder_t {};
|
struct placeholder_t {};
|
||||||
|
@ -61,7 +62,8 @@ typedef std::variant<
|
||||||
missing_t,
|
missing_t,
|
||||||
misc_t,
|
misc_t,
|
||||||
failed_t,
|
failed_t,
|
||||||
bool
|
bool,
|
||||||
|
std::vector<std::string>
|
||||||
> AttrValue;
|
> AttrValue;
|
||||||
|
|
||||||
class AttrCursor : public std::enable_shared_from_this<AttrCursor>
|
class AttrCursor : public std::enable_shared_from_this<AttrCursor>
|
||||||
|
@ -114,6 +116,8 @@ public:
|
||||||
|
|
||||||
bool getBool();
|
bool getBool();
|
||||||
|
|
||||||
|
std::vector<std::string> getListOfStrings();
|
||||||
|
|
||||||
std::vector<Symbol> getAttrs();
|
std::vector<Symbol> getAttrs();
|
||||||
|
|
||||||
bool isDerivation();
|
bool isDerivation();
|
||||||
|
|
|
@ -89,7 +89,7 @@ UnresolvedApp Installable::toApp(EvalState & state)
|
||||||
auto outputName = cursor->getAttr(state.sOutputName)->getString();
|
auto outputName = cursor->getAttr(state.sOutputName)->getString();
|
||||||
auto name = cursor->getAttr(state.sName)->getString();
|
auto name = cursor->getAttr(state.sName)->getString();
|
||||||
auto aPname = cursor->maybeGetAttr("pname");
|
auto aPname = cursor->maybeGetAttr("pname");
|
||||||
auto aMeta = cursor->maybeGetAttr("meta");
|
auto aMeta = cursor->maybeGetAttr(state.sMeta);
|
||||||
auto aMainProgram = aMeta ? aMeta->maybeGetAttr("mainProgram") : nullptr;
|
auto aMainProgram = aMeta ? aMeta->maybeGetAttr("mainProgram") : nullptr;
|
||||||
auto mainProgram =
|
auto mainProgram =
|
||||||
aMainProgram
|
aMainProgram
|
||||||
|
|
|
@ -1015,8 +1015,8 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
||||||
auto name = visitor.getAttr(state->sName)->getString();
|
auto name = visitor.getAttr(state->sName)->getString();
|
||||||
if (json) {
|
if (json) {
|
||||||
std::optional<std::string> description;
|
std::optional<std::string> description;
|
||||||
if (auto aMeta = visitor.maybeGetAttr("meta")) {
|
if (auto aMeta = visitor.maybeGetAttr(state->sMeta)) {
|
||||||
if (auto aDescription = aMeta->maybeGetAttr("description"))
|
if (auto aDescription = aMeta->maybeGetAttr(state->sDescription))
|
||||||
description = aDescription->getString();
|
description = aDescription->getString();
|
||||||
}
|
}
|
||||||
j.emplace("type", "derivation");
|
j.emplace("type", "derivation");
|
||||||
|
|
|
@ -93,10 +93,10 @@ struct CmdSearch : InstallableCommand, MixJSON
|
||||||
};
|
};
|
||||||
|
|
||||||
if (cursor.isDerivation()) {
|
if (cursor.isDerivation()) {
|
||||||
DrvName name(cursor.getAttr("name")->getString());
|
DrvName name(cursor.getAttr(state->sName)->getString());
|
||||||
|
|
||||||
auto aMeta = cursor.maybeGetAttr("meta");
|
auto aMeta = cursor.maybeGetAttr(state->sMeta);
|
||||||
auto aDescription = aMeta ? aMeta->maybeGetAttr("description") : nullptr;
|
auto aDescription = aMeta ? aMeta->maybeGetAttr(state->sDescription) : nullptr;
|
||||||
auto description = aDescription ? aDescription->getString() : "";
|
auto description = aDescription ? aDescription->getString() : "";
|
||||||
std::replace(description.begin(), description.end(), '\n', ' ');
|
std::replace(description.begin(), description.end(), '\n', ' ');
|
||||||
auto attrPath2 = concatStringsSep(".", attrPathS);
|
auto attrPath2 = concatStringsSep(".", attrPathS);
|
||||||
|
|
|
@ -2,15 +2,8 @@ source common.sh
|
||||||
|
|
||||||
clearStore
|
clearStore
|
||||||
|
|
||||||
# Make sure that 'nix build' only returns the outputs we asked for.
|
# Make sure that 'nix build' returns all outputs by default.
|
||||||
nix build -f multiple-outputs.nix --json a --no-link | jq --exit-status '
|
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 == 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 '
|
|
||||||
(.[0] |
|
(.[0] |
|
||||||
(.drvPath | match(".*multiple-outputs-a.drv")) and
|
(.drvPath | match(".*multiple-outputs-a.drv")) and
|
||||||
(.outputs | keys | length == 2) and
|
(.outputs | keys | length == 2) and
|
||||||
|
|
|
@ -23,7 +23,7 @@ rec {
|
||||||
};
|
};
|
||||||
rootCA = mkCADerivation {
|
rootCA = mkCADerivation {
|
||||||
name = "rootCA";
|
name = "rootCA";
|
||||||
outputs = [ "out" "dev" "foo"];
|
outputs = [ "out" "dev" "foo" ];
|
||||||
buildCommand = ''
|
buildCommand = ''
|
||||||
echo "building a CA derivation"
|
echo "building a CA derivation"
|
||||||
echo "The seed is ${toString seed}"
|
echo "The seed is ${toString seed}"
|
||||||
|
|
|
@ -25,7 +25,8 @@ buildDrvs --substitute --substituters $REMOTE_STORE --no-require-sigs -j0 transi
|
||||||
# Check that the thing we’ve just substituted has its realisation stored
|
# Check that the thing we’ve just substituted has its realisation stored
|
||||||
nix realisation info --file ./content-addressed.nix transitivelyDependentCA
|
nix realisation info --file ./content-addressed.nix transitivelyDependentCA
|
||||||
# Check that its dependencies have it too
|
# 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
|
# Same thing, but
|
||||||
# 1. With non-ca derivations
|
# 1. With non-ca derivations
|
||||||
|
|
Loading…
Reference in a new issue