mirror of
https://github.com/NixOS/nix
synced 2025-06-25 14:51:16 +02:00
Add inputs.self.submodules
flake attribute
This allows a flake to specify that it needs Git submodules to be enabled (or disabled, if we ever change the default) on the top-level flake. This requires the input to be refetched, but since the first fetch is lazy, this shouldn't be expensive. Currently the only attribute allowed by `inputs.self` is `submodules`, but more can be added in the future (e.g. a `lazy` attribute to opt in to lazy tree behaviour). Fixes #5312, #9842.
This commit is contained in:
parent
01598487b7
commit
25fcc8d1ab
3 changed files with 128 additions and 48 deletions
|
@ -116,12 +116,47 @@ static void expectType(EvalState & state, ValueType type,
|
||||||
showType(type), showType(value.type()), state.positions[pos]);
|
showType(type), showType(value.type()), state.positions[pos]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
|
static std::pair<std::map<FlakeId, FlakeInput>, fetchers::Attrs> parseFlakeInputs(
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
Value * value,
|
Value * value,
|
||||||
const PosIdx pos,
|
const PosIdx pos,
|
||||||
const InputAttrPath & lockRootAttrPath,
|
const InputAttrPath & lockRootAttrPath,
|
||||||
const SourcePath & flakeDir);
|
const SourcePath & flakeDir,
|
||||||
|
bool allowSelf);
|
||||||
|
|
||||||
|
static void parseFlakeInputAttr(
|
||||||
|
EvalState & state,
|
||||||
|
const Attr & attr,
|
||||||
|
fetchers::Attrs & attrs)
|
||||||
|
{
|
||||||
|
// Allow selecting a subset of enum values
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wswitch-enum"
|
||||||
|
switch (attr.value->type()) {
|
||||||
|
case nString:
|
||||||
|
attrs.emplace(state.symbols[attr.name], attr.value->c_str());
|
||||||
|
break;
|
||||||
|
case nBool:
|
||||||
|
attrs.emplace(state.symbols[attr.name], Explicit<bool> { attr.value->boolean() });
|
||||||
|
break;
|
||||||
|
case nInt: {
|
||||||
|
auto intValue = attr.value->integer().value;
|
||||||
|
if (intValue < 0)
|
||||||
|
state.error<EvalError>("negative value given for flake input attribute %1%: %2%", state.symbols[attr.name], intValue).debugThrow();
|
||||||
|
attrs.emplace(state.symbols[attr.name], uint64_t(intValue));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if (attr.name == state.symbols.create("publicKeys")) {
|
||||||
|
experimentalFeatureSettings.require(Xp::VerifiedFetches);
|
||||||
|
NixStringContext emptyContext = {};
|
||||||
|
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, attr.pos, emptyContext).dump());
|
||||||
|
} else
|
||||||
|
state.error<TypeError>("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
|
||||||
|
state.symbols[attr.name], showType(*attr.value)).debugThrow();
|
||||||
|
}
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
}
|
||||||
|
|
||||||
static FlakeInput parseFlakeInput(
|
static FlakeInput parseFlakeInput(
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
|
@ -166,44 +201,14 @@ static FlakeInput parseFlakeInput(
|
||||||
expectType(state, nBool, *attr.value, attr.pos);
|
expectType(state, nBool, *attr.value, attr.pos);
|
||||||
input.isFlake = attr.value->boolean();
|
input.isFlake = attr.value->boolean();
|
||||||
} else if (attr.name == sInputs) {
|
} else if (attr.name == sInputs) {
|
||||||
input.overrides = parseFlakeInputs(state, attr.value, attr.pos, lockRootAttrPath, flakeDir);
|
input.overrides = parseFlakeInputs(state, attr.value, attr.pos, lockRootAttrPath, flakeDir, false).first;
|
||||||
} else if (attr.name == sFollows) {
|
} else if (attr.name == sFollows) {
|
||||||
expectType(state, nString, *attr.value, attr.pos);
|
expectType(state, nString, *attr.value, attr.pos);
|
||||||
auto follows(parseInputAttrPath(attr.value->c_str()));
|
auto follows(parseInputAttrPath(attr.value->c_str()));
|
||||||
follows.insert(follows.begin(), lockRootAttrPath.begin(), lockRootAttrPath.end());
|
follows.insert(follows.begin(), lockRootAttrPath.begin(), lockRootAttrPath.end());
|
||||||
input.follows = follows;
|
input.follows = follows;
|
||||||
} else {
|
|
||||||
// Allow selecting a subset of enum values
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
#pragma GCC diagnostic ignored "-Wswitch-enum"
|
|
||||||
switch (attr.value->type()) {
|
|
||||||
case nString:
|
|
||||||
attrs.emplace(state.symbols[attr.name], attr.value->c_str());
|
|
||||||
break;
|
|
||||||
case nBool:
|
|
||||||
attrs.emplace(state.symbols[attr.name], Explicit<bool> { attr.value->boolean() });
|
|
||||||
break;
|
|
||||||
case nInt: {
|
|
||||||
auto intValue = attr.value->integer().value;
|
|
||||||
|
|
||||||
if (intValue < 0) {
|
|
||||||
state.error<EvalError>("negative value given for flake input attribute %1%: %2%", state.symbols[attr.name], intValue).debugThrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
attrs.emplace(state.symbols[attr.name], uint64_t(intValue));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if (attr.name == state.symbols.create("publicKeys")) {
|
|
||||||
experimentalFeatureSettings.require(Xp::VerifiedFetches);
|
|
||||||
NixStringContext emptyContext = {};
|
|
||||||
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, emptyContext).dump());
|
|
||||||
} else
|
} else
|
||||||
state.error<TypeError>("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
|
parseFlakeInputAttr(state, attr, attrs);
|
||||||
state.symbols[attr.name], showType(*attr.value)).debugThrow();
|
|
||||||
}
|
|
||||||
#pragma GCC diagnostic pop
|
|
||||||
}
|
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(
|
e.addTrace(
|
||||||
state.positions[attr.pos],
|
state.positions[attr.pos],
|
||||||
|
@ -233,28 +238,39 @@ static FlakeInput parseFlakeInput(
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
|
static std::pair<std::map<FlakeId, FlakeInput>, fetchers::Attrs> parseFlakeInputs(
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
Value * value,
|
Value * value,
|
||||||
const PosIdx pos,
|
const PosIdx pos,
|
||||||
const InputAttrPath & lockRootAttrPath,
|
const InputAttrPath & lockRootAttrPath,
|
||||||
const SourcePath & flakeDir)
|
const SourcePath & flakeDir,
|
||||||
|
bool allowSelf)
|
||||||
{
|
{
|
||||||
std::map<FlakeId, FlakeInput> inputs;
|
std::map<FlakeId, FlakeInput> inputs;
|
||||||
|
fetchers::Attrs selfAttrs;
|
||||||
|
|
||||||
expectType(state, nAttrs, *value, pos);
|
expectType(state, nAttrs, *value, pos);
|
||||||
|
|
||||||
for (auto & inputAttr : *value->attrs()) {
|
for (auto & inputAttr : *value->attrs()) {
|
||||||
inputs.emplace(state.symbols[inputAttr.name],
|
auto inputName = state.symbols[inputAttr.name];
|
||||||
|
if (inputName == "self") {
|
||||||
|
if (!allowSelf)
|
||||||
|
throw Error("'self' input attribute not allowed at %s", state.positions[inputAttr.pos]);
|
||||||
|
expectType(state, nAttrs, *inputAttr.value, inputAttr.pos);
|
||||||
|
for (auto & attr : *inputAttr.value->attrs())
|
||||||
|
parseFlakeInputAttr(state, attr, selfAttrs);
|
||||||
|
} else {
|
||||||
|
inputs.emplace(inputName,
|
||||||
parseFlakeInput(state,
|
parseFlakeInput(state,
|
||||||
state.symbols[inputAttr.name],
|
inputName,
|
||||||
inputAttr.value,
|
inputAttr.value,
|
||||||
inputAttr.pos,
|
inputAttr.pos,
|
||||||
lockRootAttrPath,
|
lockRootAttrPath,
|
||||||
flakeDir));
|
flakeDir));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return inputs;
|
return {inputs, selfAttrs};
|
||||||
}
|
}
|
||||||
|
|
||||||
static Flake readFlake(
|
static Flake readFlake(
|
||||||
|
@ -286,8 +302,11 @@ static Flake readFlake(
|
||||||
|
|
||||||
auto sInputs = state.symbols.create("inputs");
|
auto sInputs = state.symbols.create("inputs");
|
||||||
|
|
||||||
if (auto inputs = vInfo.attrs()->get(sInputs))
|
if (auto inputs = vInfo.attrs()->get(sInputs)) {
|
||||||
flake.inputs = parseFlakeInputs(state, inputs->value, inputs->pos, lockRootAttrPath, flakeDir);
|
auto [flakeInputs, selfAttrs] = parseFlakeInputs(state, inputs->value, inputs->pos, lockRootAttrPath, flakeDir, true);
|
||||||
|
flake.inputs = std::move(flakeInputs);
|
||||||
|
flake.selfAttrs = std::move(selfAttrs);
|
||||||
|
}
|
||||||
|
|
||||||
auto sOutputs = state.symbols.create("outputs");
|
auto sOutputs = state.symbols.create("outputs");
|
||||||
|
|
||||||
|
@ -361,6 +380,23 @@ static Flake readFlake(
|
||||||
return flake;
|
return flake;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static FlakeRef applySelfAttrs(
|
||||||
|
const FlakeRef & ref,
|
||||||
|
const Flake & flake)
|
||||||
|
{
|
||||||
|
auto newRef(ref);
|
||||||
|
|
||||||
|
std::set<std::string> allowedAttrs{"submodules"};
|
||||||
|
|
||||||
|
for (auto & attr : flake.selfAttrs) {
|
||||||
|
if (!allowedAttrs.contains(attr.first))
|
||||||
|
throw Error("flake 'self' attribute '%s' is not supported", attr.first);
|
||||||
|
newRef.input.attrs.insert_or_assign(attr.first, attr.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRef;
|
||||||
|
}
|
||||||
|
|
||||||
static Flake getFlake(
|
static Flake getFlake(
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
const FlakeRef & originalRef,
|
const FlakeRef & originalRef,
|
||||||
|
@ -372,6 +408,22 @@ static Flake getFlake(
|
||||||
auto [accessor, resolvedRef, lockedRef] = fetchOrSubstituteTree(
|
auto [accessor, resolvedRef, lockedRef] = fetchOrSubstituteTree(
|
||||||
state, originalRef, useRegistries, flakeCache);
|
state, originalRef, useRegistries, flakeCache);
|
||||||
|
|
||||||
|
// Parse/eval flake.nix to get at the input.self attributes.
|
||||||
|
auto flake = readFlake(state, originalRef, resolvedRef, lockedRef, {accessor}, lockRootAttrPath);
|
||||||
|
|
||||||
|
// Re-fetch the tree if necessary.
|
||||||
|
auto newLockedRef = applySelfAttrs(lockedRef, flake);
|
||||||
|
|
||||||
|
if (lockedRef != newLockedRef) {
|
||||||
|
debug("refetching input '%s' due to self attribute", newLockedRef);
|
||||||
|
// FIXME: need to remove attrs that are invalidated by the changed input attrs, such as 'narHash'.
|
||||||
|
newLockedRef.input.attrs.erase("narHash");
|
||||||
|
auto [accessor2, resolvedRef2, lockedRef2] = fetchOrSubstituteTree(
|
||||||
|
state, newLockedRef, false, flakeCache);
|
||||||
|
accessor = accessor2;
|
||||||
|
lockedRef = lockedRef2;
|
||||||
|
}
|
||||||
|
|
||||||
// Copy the tree to the store.
|
// Copy the tree to the store.
|
||||||
auto storePath = copyInputToStore(state, lockedRef.input, accessor);
|
auto storePath = copyInputToStore(state, lockedRef.input, accessor);
|
||||||
|
|
||||||
|
@ -492,6 +544,7 @@ LockedFlake lockFlake(
|
||||||
/* Get the overrides (i.e. attributes of the form
|
/* Get the overrides (i.e. attributes of the form
|
||||||
'inputs.nixops.inputs.nixpkgs.url = ...'). */
|
'inputs.nixops.inputs.nixpkgs.url = ...'). */
|
||||||
for (auto & [id, input] : flakeInputs) {
|
for (auto & [id, input] : flakeInputs) {
|
||||||
|
//if (id == "self") continue;
|
||||||
for (auto & [idOverride, inputOverride] : input.overrides) {
|
for (auto & [idOverride, inputOverride] : input.overrides) {
|
||||||
auto inputAttrPath(inputAttrPathPrefix);
|
auto inputAttrPath(inputAttrPathPrefix);
|
||||||
inputAttrPath.push_back(id);
|
inputAttrPath.push_back(id);
|
||||||
|
|
|
@ -79,24 +79,37 @@ struct Flake
|
||||||
* The original flake specification (by the user)
|
* The original flake specification (by the user)
|
||||||
*/
|
*/
|
||||||
FlakeRef originalRef;
|
FlakeRef originalRef;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* registry references and caching resolved to the specific underlying flake
|
* registry references and caching resolved to the specific underlying flake
|
||||||
*/
|
*/
|
||||||
FlakeRef resolvedRef;
|
FlakeRef resolvedRef;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the specific local store result of invoking the fetcher
|
* the specific local store result of invoking the fetcher
|
||||||
*/
|
*/
|
||||||
FlakeRef lockedRef;
|
FlakeRef lockedRef;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The path of `flake.nix`.
|
* The path of `flake.nix`.
|
||||||
*/
|
*/
|
||||||
SourcePath path;
|
SourcePath path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pretend that 'lockedRef' is dirty
|
* Pretend that `lockedRef` is dirty.
|
||||||
*/
|
*/
|
||||||
bool forceDirty = false;
|
bool forceDirty = false;
|
||||||
|
|
||||||
std::optional<std::string> description;
|
std::optional<std::string> description;
|
||||||
|
|
||||||
FlakeInputs inputs;
|
FlakeInputs inputs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attributes to be retroactively applied to the `self` input
|
||||||
|
* (such as `submodules = true`).
|
||||||
|
*/
|
||||||
|
fetchers::Attrs selfAttrs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 'nixConfig' attribute
|
* 'nixConfig' attribute
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -77,6 +77,20 @@ git -C "$rootRepo" commit -m "Add flake.nix"
|
||||||
storePath=$(nix flake prefetch --json "$rootRepo?submodules=1" | jq -r .storePath)
|
storePath=$(nix flake prefetch --json "$rootRepo?submodules=1" | jq -r .storePath)
|
||||||
[[ -e "$storePath/submodule" ]]
|
[[ -e "$storePath/submodule" ]]
|
||||||
|
|
||||||
|
# Test the use of inputs.self.
|
||||||
|
cat > "$rootRepo"/flake.nix <<EOF
|
||||||
|
{
|
||||||
|
inputs.self.submodules = true;
|
||||||
|
outputs = { self }: {
|
||||||
|
foo = self.outPath;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
git -C "$rootRepo" commit -a -m "Bla"
|
||||||
|
|
||||||
|
storePath=$(nix eval --raw "$rootRepo#foo")
|
||||||
|
[[ -e "$storePath/submodule" ]]
|
||||||
|
|
||||||
# The root repo may use the submodule repo as an input
|
# The root repo may use the submodule repo as an input
|
||||||
# through the relative path. This may change in the future;
|
# through the relative path. This may change in the future;
|
||||||
# see: https://discourse.nixos.org/t/57783 and #9708.
|
# see: https://discourse.nixos.org/t/57783 and #9708.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue