3
3
#include " source-path.hh"
4
4
#include " fetch-to-store.hh"
5
5
#include " json-utils.hh"
6
+ #include " store-path-accessor.hh"
6
7
7
8
#include < nlohmann/json.hpp>
8
9
@@ -100,7 +101,7 @@ Input Input::fromAttrs(const Settings & settings, Attrs && attrs)
100
101
auto allowedAttrs = inputScheme->allowedAttrs ();
101
102
102
103
for (auto & [name, _] : attrs)
103
- if (name != " type" && allowedAttrs.count (name) == 0 )
104
+ if (name != " type" && name != " __final " && allowedAttrs.count (name) == 0 )
104
105
throw Error (" input attribute '%s' not supported by scheme '%s'" , name, schemeName);
105
106
106
107
auto res = inputScheme->inputFromAttrs (settings, attrs);
@@ -145,6 +146,11 @@ bool Input::isLocked() const
145
146
return scheme && scheme->isLocked (*this );
146
147
}
147
148
149
+ bool Input::isFinal () const
150
+ {
151
+ return maybeGetBoolAttr (attrs, " __final" ).value_or (false );
152
+ }
153
+
148
154
Attrs Input::toAttrs () const
149
155
{
150
156
return attrs;
@@ -172,16 +178,24 @@ std::pair<StorePath, Input> Input::fetchToStore(ref<Store> store) const
172
178
173
179
auto [storePath, input] = [&]() -> std::pair<StorePath, Input> {
174
180
try {
175
- auto [accessor, final ] = getAccessorUnchecked (store);
181
+ auto [accessor, result ] = getAccessorUnchecked (store);
176
182
177
- auto storePath = nix::fetchToStore (*store, SourcePath (accessor), FetchMode::Copy, final .getName ());
183
+ auto storePath = nix::fetchToStore (*store, SourcePath (accessor), FetchMode::Copy, result .getName ());
178
184
179
185
auto narHash = store->queryPathInfo (storePath)->narHash ;
180
- final .attrs .insert_or_assign (" narHash" , narHash.to_string (HashFormat::SRI, true ));
186
+ result .attrs .insert_or_assign (" narHash" , narHash.to_string (HashFormat::SRI, true ));
181
187
182
- scheme->checkLocks (*this , final );
188
+ // FIXME: we would like to mark inputs as final in
189
+ // getAccessorUnchecked(), but then we can't add
190
+ // narHash. Or maybe narHash should be excluded from the
191
+ // concept of "final" inputs?
192
+ result.attrs .insert_or_assign (" __final" , Explicit<bool >(true ));
183
193
184
- return {storePath, final };
194
+ assert (result.isFinal ());
195
+
196
+ checkLocks (*this , result);
197
+
198
+ return {storePath, result};
185
199
} catch (Error & e) {
186
200
e.addTrace ({}, " while fetching the input '%s'" , to_string ());
187
201
throw ;
@@ -191,46 +205,73 @@ std::pair<StorePath, Input> Input::fetchToStore(ref<Store> store) const
191
205
return {std::move (storePath), input};
192
206
}
193
207
194
- void InputScheme::checkLocks (const Input & specified, const Input & final ) const
195
- {
208
+ void Input::checkLocks (Input specified, Input & result)
209
+ {
210
+ /* If the original input is final, then we just return the
211
+ original attributes, dropping any new fields returned by the
212
+ fetcher. However, any fields that are in both the specified and
213
+ result input must be identical. */
214
+ if (specified.isFinal ()) {
215
+
216
+ /* Backwards compatibility hack: we had some lock files in the
217
+ past that 'narHash' fields with incorrect base-64
218
+ formatting (lacking the trailing '=', e.g. 'sha256-ri...Mw'
219
+ instead of ''sha256-ri...Mw='). So fix that. */
220
+ if (auto prevNarHash = specified.getNarHash ())
221
+ specified.attrs .insert_or_assign (" narHash" , prevNarHash->to_string (HashFormat::SRI, true ));
222
+
223
+ for (auto & field : specified.attrs ) {
224
+ auto field2 = result.attrs .find (field.first );
225
+ if (field2 != result.attrs .end () && field.second != field2->second )
226
+ throw Error (" mismatch in field '%s' of input '%s', got '%s'" ,
227
+ field.first ,
228
+ attrsToJSON (specified.attrs ),
229
+ attrsToJSON (result.attrs ));
230
+ }
231
+
232
+ result.attrs = specified.attrs ;
233
+
234
+ return ;
235
+ }
236
+
196
237
if (auto prevNarHash = specified.getNarHash ()) {
197
- if (final .getNarHash () != prevNarHash) {
198
- if (final .getNarHash ())
238
+ if (result .getNarHash () != prevNarHash) {
239
+ if (result .getNarHash ())
199
240
throw Error ((unsigned int ) 102 , " NAR hash mismatch in input '%s', expected '%s' but got '%s'" ,
200
- specified.to_string (), prevNarHash->to_string (HashFormat::SRI, true ), final .getNarHash ()->to_string (HashFormat::SRI, true ));
241
+ specified.to_string (), prevNarHash->to_string (HashFormat::SRI, true ), result .getNarHash ()->to_string (HashFormat::SRI, true ));
201
242
else
202
243
throw Error ((unsigned int ) 102 , " NAR hash mismatch in input '%s', expected '%s' but got none" ,
203
244
specified.to_string (), prevNarHash->to_string (HashFormat::SRI, true ));
204
245
}
205
246
}
206
247
207
248
if (auto prevLastModified = specified.getLastModified ()) {
208
- if (final .getLastModified () != prevLastModified)
209
- throw Error (" 'lastModified' attribute mismatch in input '%s', expected %d" ,
210
- final .to_string (), *prevLastModified);
249
+ if (result .getLastModified () != prevLastModified)
250
+ throw Error (" 'lastModified' attribute mismatch in input '%s', expected %d, got %d " ,
251
+ result .to_string (), *prevLastModified, result. getLastModified (). value_or (- 1 ) );
211
252
}
212
253
213
254
if (auto prevRev = specified.getRev ()) {
214
- if (final .getRev () != prevRev)
255
+ if (result .getRev () != prevRev)
215
256
throw Error (" 'rev' attribute mismatch in input '%s', expected %s" ,
216
- final .to_string (), prevRev->gitRev ());
257
+ result .to_string (), prevRev->gitRev ());
217
258
}
218
259
219
260
if (auto prevRevCount = specified.getRevCount ()) {
220
- if (final .getRevCount () != prevRevCount)
261
+ if (result .getRevCount () != prevRevCount)
221
262
throw Error (" 'revCount' attribute mismatch in input '%s', expected %d" ,
222
- final .to_string (), *prevRevCount);
263
+ result .to_string (), *prevRevCount);
223
264
}
224
265
}
225
266
226
267
std::pair<ref<SourceAccessor>, Input> Input::getAccessor (ref<Store> store) const
227
268
{
228
269
try {
229
- auto [accessor, final ] = getAccessorUnchecked (store);
270
+ auto [accessor, result ] = getAccessorUnchecked (store);
230
271
231
- scheme-> checkLocks (*this , final );
272
+ checkLocks (*this , result );
232
273
233
- return {accessor, std::move (final )};
274
+ return {accessor, std::move (result )};
234
275
} catch (Error & e) {
235
276
e.addTrace ({}, " while fetching the input '%s'" , to_string ());
236
277
throw ;
@@ -244,12 +285,42 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
244
285
if (!scheme)
245
286
throw Error (" cannot fetch unsupported input '%s'" , attrsToJSON (toAttrs ()));
246
287
247
- auto [accessor, final ] = scheme->getAccessor (store, *this );
288
+ /* The tree may already be in the Nix store, or it could be
289
+ substituted (which is often faster than fetching from the
290
+ original source). So check that. We only do this for final
291
+ inputs, otherwise there is a risk that we don't return the
292
+ same attributes (like `lastModified`) that the "real" fetcher
293
+ would return.
294
+
295
+ FIXME: add a setting to disable this.
296
+ FIXME: substituting may be slower than fetching normally,
297
+ e.g. for fetchers like Git that are incremental!
298
+ */
299
+ if (isFinal () && getNarHash ()) {
300
+ try {
301
+ auto storePath = computeStorePath (*store);
302
+
303
+ store->ensurePath (storePath);
304
+
305
+ debug (" using substituted/cached input '%s' in '%s'" ,
306
+ to_string (), store->printStorePath (storePath));
307
+
308
+ auto accessor = makeStorePathAccessor (store, storePath);
309
+
310
+ accessor->fingerprint = scheme->getFingerprint (store, *this );
311
+
312
+ return {accessor, *this };
313
+ } catch (Error & e) {
314
+ debug (" substitution of input '%s' failed: %s" , to_string (), e.what ());
315
+ }
316
+ }
317
+
318
+ auto [accessor, result] = scheme->getAccessor (store, *this );
248
319
249
320
assert (!accessor->fingerprint );
250
- accessor->fingerprint = scheme->getFingerprint (store, final );
321
+ accessor->fingerprint = scheme->getFingerprint (store, result );
251
322
252
- return {accessor, std::move (final )};
323
+ return {accessor, std::move (result )};
253
324
}
254
325
255
326
Input Input::applyOverrides (
0 commit comments