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 {
2425struct FetchedFlake
2526{
2627 FlakeRef lockedRef;
27- StorePath storePath ;
28+ ref<SourceAccessor> accessor ;
2829};
2930
3031typedef 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
89103static 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
111160static 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
243273static 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+
345395static 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