Skip to content

Commit 92bf150

Browse files
authored
Merge pull request #12421 from DeterminateSystems/self-input-attrs
Add `inputs.self.submodules` flake attribute
2 parents 1f485b6 + 2819d8b commit 92bf150

File tree

9 files changed

+222
-75
lines changed

9 files changed

+222
-75
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
synopsis: "`inputs.self.submodules` flake attribute"
3+
prs: [12421]
4+
---
5+
6+
Flakes in Git repositories can now declare that they need Git submodules to be enabled:
7+
```
8+
{
9+
inputs.self.submodules = true;
10+
}
11+
```
12+
Thus, it's no longer needed for the caller of the flake to pass `submodules = true`.

src/libcmd/common-eval-args.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ EvalSettings evalSettings {
3434
// FIXME `parseFlakeRef` should take a `std::string_view`.
3535
auto flakeRef = parseFlakeRef(fetchSettings, std::string { rest }, {}, true, false);
3636
debug("fetching flake search path element '%s''", rest);
37-
auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first;
37+
auto [accessor, lockedRef] = flakeRef.resolve(state.store).lazyFetch(state.store);
38+
auto storePath = nix::fetchToStore(*state.store, SourcePath(accessor), FetchMode::Copy, lockedRef.input.getName());
39+
state.allowPath(storePath);
3840
return state.rootPath(state.store->toRealPath(storePath));
3941
},
4042
},
@@ -183,7 +185,9 @@ SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * bas
183185
else if (hasPrefix(s, "flake:")) {
184186
experimentalFeatureSettings.require(Xp::Flakes);
185187
auto flakeRef = parseFlakeRef(fetchSettings, std::string(s.substr(6)), {}, true, false);
186-
auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first;
188+
auto [accessor, lockedRef] = flakeRef.resolve(state.store).lazyFetch(state.store);
189+
auto storePath = nix::fetchToStore(*state.store, SourcePath(accessor), FetchMode::Copy, lockedRef.input.getName());
190+
state.allowPath(storePath);
187191
return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
188192
}
189193

src/libfetchers/fetchers.cc

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ bool Input::contains(const Input & other) const
186186
return false;
187187
}
188188

189+
// FIXME: remove
189190
std::pair<StorePath, Input> Input::fetchToStore(ref<Store> store) const
190191
{
191192
if (!scheme)
@@ -200,10 +201,6 @@ std::pair<StorePath, Input> Input::fetchToStore(ref<Store> store) const
200201
auto narHash = store->queryPathInfo(storePath)->narHash;
201202
result.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
202203

203-
// FIXME: we would like to mark inputs as final in
204-
// getAccessorUnchecked(), but then we can't add
205-
// narHash. Or maybe narHash should be excluded from the
206-
// concept of "final" inputs?
207204
result.attrs.insert_or_assign("__final", Explicit<bool>(true));
208205

209206
assert(result.isFinal());
@@ -284,6 +281,8 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessor(ref<Store> store) const
284281
try {
285282
auto [accessor, result] = getAccessorUnchecked(store);
286283

284+
result.attrs.insert_or_assign("__final", Explicit<bool>(true));
285+
287286
checkLocks(*this, result);
288287

289288
return {accessor, std::move(result)};

src/libflake/flake/flake.cc

Lines changed: 137 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "flake/settings.hh"
1313
#include "value-to-json.hh"
1414
#include "local-fs-store.hh"
15+
#include "fetch-to-store.hh"
1516

1617
#include <nlohmann/json.hpp>
1718

@@ -24,7 +25,7 @@ namespace flake {
2425
struct FetchedFlake
2526
{
2627
FlakeRef lockedRef;
27-
StorePath storePath;
28+
ref<SourceAccessor> accessor;
2829
};
2930

3031
typedef std::map<FlakeRef, FetchedFlake> FlakeCache;
@@ -40,7 +41,7 @@ static std::optional<FetchedFlake> lookupInFlakeCache(
4041
return i->second;
4142
}
4243

43-
static std::tuple<StorePath, FlakeRef, FlakeRef> fetchOrSubstituteTree(
44+
static std::tuple<ref<SourceAccessor>, FlakeRef, FlakeRef> fetchOrSubstituteTree(
4445
EvalState & state,
4546
const FlakeRef & originalRef,
4647
bool useRegistries,
@@ -51,8 +52,8 @@ static std::tuple<StorePath, FlakeRef, FlakeRef> fetchOrSubstituteTree(
5152

5253
if (!fetched) {
5354
if (originalRef.input.isDirect()) {
54-
auto [storePath, lockedRef] = originalRef.fetchTree(state.store);
55-
fetched.emplace(FetchedFlake{.lockedRef = lockedRef, .storePath = storePath});
55+
auto [accessor, lockedRef] = originalRef.lazyFetch(state.store);
56+
fetched.emplace(FetchedFlake{.lockedRef = lockedRef, .accessor = accessor});
5657
} else {
5758
if (useRegistries) {
5859
resolvedRef = originalRef.resolve(
@@ -64,8 +65,8 @@ static std::tuple<StorePath, FlakeRef, FlakeRef> fetchOrSubstituteTree(
6465
});
6566
fetched = lookupInFlakeCache(flakeCache, originalRef);
6667
if (!fetched) {
67-
auto [storePath, lockedRef] = resolvedRef.fetchTree(state.store);
68-
fetched.emplace(FetchedFlake{.lockedRef = lockedRef, .storePath = storePath});
68+
auto [accessor, lockedRef] = resolvedRef.lazyFetch(state.store);
69+
fetched.emplace(FetchedFlake{.lockedRef = lockedRef, .accessor = accessor});
6970
}
7071
flakeCache.insert_or_assign(resolvedRef, *fetched);
7172
}
@@ -76,14 +77,27 @@ static std::tuple<StorePath, FlakeRef, FlakeRef> fetchOrSubstituteTree(
7677
flakeCache.insert_or_assign(originalRef, *fetched);
7778
}
7879

79-
debug("got tree '%s' from '%s'",
80-
state.store->printStorePath(fetched->storePath), fetched->lockedRef);
80+
debug("got tree '%s' from '%s'", fetched->accessor, fetched->lockedRef);
8181

82-
state.allowPath(fetched->storePath);
82+
return {fetched->accessor, resolvedRef, fetched->lockedRef};
83+
}
84+
85+
static StorePath copyInputToStore(
86+
EvalState & state,
87+
fetchers::Input & input,
88+
const fetchers::Input & originalInput,
89+
ref<SourceAccessor> accessor)
90+
{
91+
auto storePath = fetchToStore(*state.store, accessor, FetchMode::Copy, input.getName());
92+
93+
state.allowPath(storePath);
94+
95+
auto narHash = state.store->queryPathInfo(storePath)->narHash;
96+
input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
8397

84-
assert(!originalRef.input.getNarHash() || fetched->storePath == originalRef.input.computeStorePath(*state.store));
98+
assert(!originalInput.getNarHash() || storePath == originalInput.computeStorePath(*state.store));
8599

86-
return {fetched->storePath, resolvedRef, fetched->lockedRef};
100+
return storePath;
87101
}
88102

89103
static void forceTrivialValue(EvalState & state, Value & value, const PosIdx pos)
@@ -101,12 +115,47 @@ static void expectType(EvalState & state, ValueType type,
101115
showType(type), showType(value.type()), state.positions[pos]);
102116
}
103117

104-
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
118+
static std::pair<std::map<FlakeId, FlakeInput>, fetchers::Attrs> parseFlakeInputs(
105119
EvalState & state,
106120
Value * value,
107121
const PosIdx pos,
108122
const InputAttrPath & lockRootAttrPath,
109-
const SourcePath & flakeDir);
123+
const SourcePath & flakeDir,
124+
bool allowSelf);
125+
126+
static void parseFlakeInputAttr(
127+
EvalState & state,
128+
const Attr & attr,
129+
fetchers::Attrs & attrs)
130+
{
131+
// Allow selecting a subset of enum values
132+
#pragma GCC diagnostic push
133+
#pragma GCC diagnostic ignored "-Wswitch-enum"
134+
switch (attr.value->type()) {
135+
case nString:
136+
attrs.emplace(state.symbols[attr.name], attr.value->c_str());
137+
break;
138+
case nBool:
139+
attrs.emplace(state.symbols[attr.name], Explicit<bool> { attr.value->boolean() });
140+
break;
141+
case nInt: {
142+
auto intValue = attr.value->integer().value;
143+
if (intValue < 0)
144+
state.error<EvalError>("negative value given for flake input attribute %1%: %2%", state.symbols[attr.name], intValue).debugThrow();
145+
attrs.emplace(state.symbols[attr.name], uint64_t(intValue));
146+
break;
147+
}
148+
default:
149+
if (attr.name == state.symbols.create("publicKeys")) {
150+
experimentalFeatureSettings.require(Xp::VerifiedFetches);
151+
NixStringContext emptyContext = {};
152+
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, attr.pos, emptyContext).dump());
153+
} else
154+
state.error<TypeError>("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
155+
state.symbols[attr.name], showType(*attr.value)).debugThrow();
156+
}
157+
#pragma GCC diagnostic pop
158+
}
110159

111160
static FlakeInput parseFlakeInput(
112161
EvalState & state,
@@ -149,44 +198,14 @@ static FlakeInput parseFlakeInput(
149198
expectType(state, nBool, *attr.value, attr.pos);
150199
input.isFlake = attr.value->boolean();
151200
} else if (attr.name == sInputs) {
152-
input.overrides = parseFlakeInputs(state, attr.value, attr.pos, lockRootAttrPath, flakeDir);
201+
input.overrides = parseFlakeInputs(state, attr.value, attr.pos, lockRootAttrPath, flakeDir, false).first;
153202
} else if (attr.name == sFollows) {
154203
expectType(state, nString, *attr.value, attr.pos);
155204
auto follows(parseInputAttrPath(attr.value->c_str()));
156205
follows.insert(follows.begin(), lockRootAttrPath.begin(), lockRootAttrPath.end());
157206
input.follows = follows;
158-
} else {
159-
// Allow selecting a subset of enum values
160-
#pragma GCC diagnostic push
161-
#pragma GCC diagnostic ignored "-Wswitch-enum"
162-
switch (attr.value->type()) {
163-
case nString:
164-
attrs.emplace(state.symbols[attr.name], attr.value->c_str());
165-
break;
166-
case nBool:
167-
attrs.emplace(state.symbols[attr.name], Explicit<bool> { attr.value->boolean() });
168-
break;
169-
case nInt: {
170-
auto intValue = attr.value->integer().value;
171-
172-
if (intValue < 0) {
173-
state.error<EvalError>("negative value given for flake input attribute %1%: %2%", state.symbols[attr.name], intValue).debugThrow();
174-
}
175-
176-
attrs.emplace(state.symbols[attr.name], uint64_t(intValue));
177-
break;
178-
}
179-
default:
180-
if (attr.name == state.symbols.create("publicKeys")) {
181-
experimentalFeatureSettings.require(Xp::VerifiedFetches);
182-
NixStringContext emptyContext = {};
183-
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, emptyContext).dump());
184-
} else
185-
state.error<TypeError>("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
186-
state.symbols[attr.name], showType(*attr.value)).debugThrow();
187-
}
188-
#pragma GCC diagnostic pop
189-
}
207+
} else
208+
parseFlakeInputAttr(state, attr, attrs);
190209
} catch (Error & e) {
191210
e.addTrace(
192211
state.positions[attr.pos],
@@ -216,28 +235,39 @@ static FlakeInput parseFlakeInput(
216235
return input;
217236
}
218237

219-
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
238+
static std::pair<std::map<FlakeId, FlakeInput>, fetchers::Attrs> parseFlakeInputs(
220239
EvalState & state,
221240
Value * value,
222241
const PosIdx pos,
223242
const InputAttrPath & lockRootAttrPath,
224-
const SourcePath & flakeDir)
243+
const SourcePath & flakeDir,
244+
bool allowSelf)
225245
{
226246
std::map<FlakeId, FlakeInput> inputs;
247+
fetchers::Attrs selfAttrs;
227248

228249
expectType(state, nAttrs, *value, pos);
229250

230251
for (auto & inputAttr : *value->attrs()) {
231-
inputs.emplace(state.symbols[inputAttr.name],
232-
parseFlakeInput(state,
233-
state.symbols[inputAttr.name],
234-
inputAttr.value,
235-
inputAttr.pos,
236-
lockRootAttrPath,
237-
flakeDir));
252+
auto inputName = state.symbols[inputAttr.name];
253+
if (inputName == "self") {
254+
if (!allowSelf)
255+
throw Error("'self' input attribute not allowed at %s", state.positions[inputAttr.pos]);
256+
expectType(state, nAttrs, *inputAttr.value, inputAttr.pos);
257+
for (auto & attr : *inputAttr.value->attrs())
258+
parseFlakeInputAttr(state, attr, selfAttrs);
259+
} else {
260+
inputs.emplace(inputName,
261+
parseFlakeInput(state,
262+
inputName,
263+
inputAttr.value,
264+
inputAttr.pos,
265+
lockRootAttrPath,
266+
flakeDir));
267+
}
238268
}
239269

240-
return inputs;
270+
return {inputs, selfAttrs};
241271
}
242272

243273
static Flake readFlake(
@@ -269,8 +299,11 @@ static Flake readFlake(
269299

270300
auto sInputs = state.symbols.create("inputs");
271301

272-
if (auto inputs = vInfo.attrs()->get(sInputs))
273-
flake.inputs = parseFlakeInputs(state, inputs->value, inputs->pos, lockRootAttrPath, flakeDir);
302+
if (auto inputs = vInfo.attrs()->get(sInputs)) {
303+
auto [flakeInputs, selfAttrs] = parseFlakeInputs(state, inputs->value, inputs->pos, lockRootAttrPath, flakeDir, true);
304+
flake.inputs = std::move(flakeInputs);
305+
flake.selfAttrs = std::move(selfAttrs);
306+
}
274307

275308
auto sOutputs = state.symbols.create("outputs");
276309

@@ -301,10 +334,10 @@ static Flake readFlake(
301334
state.symbols[setting.name],
302335
std::string(state.forceStringNoCtx(*setting.value, setting.pos, "")));
303336
else if (setting.value->type() == nPath) {
304-
NixStringContext emptyContext = {};
337+
auto storePath = fetchToStore(*state.store, setting.value->path(), FetchMode::Copy);
305338
flake.config.settings.emplace(
306339
state.symbols[setting.name],
307-
state.coerceToString(setting.pos, *setting.value, emptyContext, "", false, true, true).toOwned());
340+
state.store->toRealPath(storePath));
308341
}
309342
else if (setting.value->type() == nInt)
310343
flake.config.settings.emplace(
@@ -342,16 +375,54 @@ static Flake readFlake(
342375
return flake;
343376
}
344377

378+
static FlakeRef applySelfAttrs(
379+
const FlakeRef & ref,
380+
const Flake & flake)
381+
{
382+
auto newRef(ref);
383+
384+
std::set<std::string> allowedAttrs{"submodules"};
385+
386+
for (auto & attr : flake.selfAttrs) {
387+
if (!allowedAttrs.contains(attr.first))
388+
throw Error("flake 'self' attribute '%s' is not supported", attr.first);
389+
newRef.input.attrs.insert_or_assign(attr.first, attr.second);
390+
}
391+
392+
return newRef;
393+
}
394+
345395
static Flake getFlake(
346396
EvalState & state,
347397
const FlakeRef & originalRef,
348398
bool useRegistries,
349399
FlakeCache & flakeCache,
350400
const InputAttrPath & lockRootAttrPath)
351401
{
352-
auto [storePath, resolvedRef, lockedRef] = fetchOrSubstituteTree(
402+
// Fetch a lazy tree first.
403+
auto [accessor, resolvedRef, lockedRef] = fetchOrSubstituteTree(
353404
state, originalRef, useRegistries, flakeCache);
354405

406+
// Parse/eval flake.nix to get at the input.self attributes.
407+
auto flake = readFlake(state, originalRef, resolvedRef, lockedRef, {accessor}, lockRootAttrPath);
408+
409+
// Re-fetch the tree if necessary.
410+
auto newLockedRef = applySelfAttrs(lockedRef, flake);
411+
412+
if (lockedRef != newLockedRef) {
413+
debug("refetching input '%s' due to self attribute", newLockedRef);
414+
// FIXME: need to remove attrs that are invalidated by the changed input attrs, such as 'narHash'.
415+
newLockedRef.input.attrs.erase("narHash");
416+
auto [accessor2, resolvedRef2, lockedRef2] = fetchOrSubstituteTree(
417+
state, newLockedRef, false, flakeCache);
418+
accessor = accessor2;
419+
lockedRef = lockedRef2;
420+
}
421+
422+
// Copy the tree to the store.
423+
auto storePath = copyInputToStore(state, lockedRef.input, originalRef.input, accessor);
424+
425+
// Re-parse flake.nix from the store.
355426
return readFlake(state, originalRef, resolvedRef, lockedRef, state.rootPath(state.store->toRealPath(storePath)), lockRootAttrPath);
356427
}
357428

@@ -707,8 +778,12 @@ LockedFlake lockFlake(
707778
if (auto resolvedPath = resolveRelativePath()) {
708779
return {*resolvedPath, *input.ref};
709780
} else {
710-
auto [storePath, resolvedRef, lockedRef] = fetchOrSubstituteTree(
781+
auto [accessor, resolvedRef, lockedRef] = fetchOrSubstituteTree(
711782
state, *input.ref, useRegistries, flakeCache);
783+
784+
// FIXME: allow input to be lazy.
785+
auto storePath = copyInputToStore(state, lockedRef.input, input.ref->input, accessor);
786+
712787
return {state.rootPath(state.store->toRealPath(storePath)), lockedRef};
713788
}
714789
}();

0 commit comments

Comments
 (0)