1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-12-13 04:21:04 +01:00

Restore input substitution

The ability to substitute inputs was removed in #10612 because it was
broken: with user-specified inputs containing a `narHash` attribute,
substitution resulted in an input that lacked the attributes returned
by the real fetcher (such as `lastModified`).

To fix this, we introduce a new input attribute `final`. If `final =
true`, fetching the input cannot add or change any attributes.

We only attempt to substitute inputs that have `final = true`. This is
implied by lock file entries; we only write a lock file if all its
entries are "final".

The user can specified `final = true` in `fetchTree`, in which case it
is their responsibility to ensure that all attributes returned by the
fetcher are included in the `fetchTree` call. For example,

  nix eval --impure --expr 'builtins.fetchTree { type = "github"; owner = "NixOS"; repo = "patchelf"; final = true; narHash = "sha256-FSoxTcRZMGHNJh8dNtKOkcUtjhmhU6yQXcZZfUPLhQM="; }'

succeeds in a store path with the specified NAR hash exists or is
substitutable, but fails with

  error: fetching final input '{"final":true,"narHash":"sha256-FSoxTcRZMGHNJh8dNtKOkcUtjhmhU6yQXcZZfUPLhQM=","owner":"NixOS","repo":"patchelf","type":"github"}' resulted in different input '{"final":true,"lastModified":1718457448,"narHash":"sha256-FSoxTcRZMGHNJh8dNtKOkcUtjhmhU6yQXcZZfUPLhQM=","owner":"NixOS","repo":"patchelf","rev":"a0f54334df36770b335c051e540ba40afcbf8378","type":"github"}'
This commit is contained in:
Eelco Dolstra 2024-10-15 20:55:05 +02:00
parent 806a91f7bf
commit 188d97e1f1
7 changed files with 79 additions and 21 deletions

View file

@ -3,6 +3,7 @@
#include "source-path.hh"
#include "fetch-to-store.hh"
#include "json-utils.hh"
#include "store-path-accessor.hh"
#include <nlohmann/json.hpp>
@ -100,7 +101,7 @@ Input Input::fromAttrs(const Settings & settings, Attrs && attrs)
auto allowedAttrs = inputScheme->allowedAttrs();
for (auto & [name, _] : attrs)
if (name != "type" && allowedAttrs.count(name) == 0)
if (name != "type" && name != "final" && allowedAttrs.count(name) == 0)
throw Error("input attribute '%s' not supported by scheme '%s'", name, schemeName);
auto res = inputScheme->inputFromAttrs(settings, attrs);
@ -145,6 +146,11 @@ bool Input::isLocked() const
return scheme && scheme->isLocked(*this);
}
bool Input::isFinal() const
{
return maybeGetBoolAttr(attrs, "final").value_or(false);
}
Attrs Input::toAttrs() const
{
return attrs;
@ -221,6 +227,12 @@ void InputScheme::checkLocks(const Input & specified, const Input & final) const
throw Error("'revCount' attribute mismatch in input '%s', expected %d",
final.to_string(), *prevRevCount);
}
assert(final.isFinal());
if (specified.isFinal() && specified.attrs != final.attrs)
throw Error("fetching final input '%s' resulted in different input '%s'",
attrsToJSON(specified.attrs), attrsToJSON(final.attrs));
}
std::pair<ref<SourceAccessor>, Input> Input::getAccessor(ref<Store> store) const
@ -244,11 +256,43 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
if (!scheme)
throw Error("cannot fetch unsupported input '%s'", attrsToJSON(toAttrs()));
/* The tree may already be in the Nix store, or it could be
substituted (which is often faster than fetching from the
original source). So check that. We only do this for final
inputs, otherwise there is a risk that we don't return the
same attributes (like `lastModified`) that the "real" fetcher
would return.
FIXME: add a setting to disable this.
FIXME: substituting may be slower than fetching normally,
e.g. for fetchers like that Git that are incremental!
*/
if (isFinal() && getNarHash()) {
try {
auto storePath = computeStorePath(*store);
store->ensurePath(storePath);
debug("using substituted/cached input '%s' in '%s'",
to_string(), store->printStorePath(storePath));
auto accessor = makeStorePathAccessor(store, storePath);
accessor->fingerprint = scheme->getFingerprint(store, *this);
return {accessor, *this};
} catch (Error & e) {
debug("substitution of input '%s' failed: %s", to_string(), e.what());
}
}
auto [accessor, final] = scheme->getAccessor(store, *this);
assert(!accessor->fingerprint);
accessor->fingerprint = scheme->getFingerprint(store, final);
final.attrs.insert_or_assign("final", Explicit<bool>(true));
return {accessor, std::move(final)};
}