1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2024-10-18 00:16:11 -04:00
This commit is contained in:
Eelco Dolstra 2024-10-16 20:09:33 +02:00 committed by GitHub
commit eeff72184b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 83 additions and 21 deletions

View file

@ -44,7 +44,8 @@ let
overrides.${key}.sourceInfo overrides.${key}.sourceInfo
else else
# FIXME: remove obsolete node.info. # FIXME: remove obsolete node.info.
fetchTree (node.info or {} // removeAttrs node.locked ["dir"]); # Note: lock file entries are always final.
fetchTree (node.info or {} // removeAttrs node.locked ["dir"] // { final = true; });
subdir = overrides.${key}.dir or node.locked.dir or ""; subdir = overrides.${key}.dir or node.locked.dir or "";

View file

@ -3,6 +3,7 @@
#include "source-path.hh" #include "source-path.hh"
#include "fetch-to-store.hh" #include "fetch-to-store.hh"
#include "json-utils.hh" #include "json-utils.hh"
#include "store-path-accessor.hh"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@ -100,7 +101,7 @@ Input Input::fromAttrs(const Settings & settings, Attrs && attrs)
auto allowedAttrs = inputScheme->allowedAttrs(); auto allowedAttrs = inputScheme->allowedAttrs();
for (auto & [name, _] : attrs) 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); throw Error("input attribute '%s' not supported by scheme '%s'", name, schemeName);
auto res = inputScheme->inputFromAttrs(settings, attrs); auto res = inputScheme->inputFromAttrs(settings, attrs);
@ -145,6 +146,11 @@ bool Input::isLocked() const
return scheme && scheme->isLocked(*this); return scheme && scheme->isLocked(*this);
} }
bool Input::isFinal() const
{
return maybeGetBoolAttr(attrs, "final").value_or(false);
}
Attrs Input::toAttrs() const Attrs Input::toAttrs() const
{ {
return attrs; return attrs;
@ -179,6 +185,14 @@ std::pair<StorePath, Input> Input::fetchToStore(ref<Store> store) const
auto narHash = store->queryPathInfo(storePath)->narHash; auto narHash = store->queryPathInfo(storePath)->narHash;
final.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true)); final.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
// FIXME: we would like to mark inputs as final in
// getAccessorUnchecked(), but then we can't add
// narHash. Or maybe narHash should be excluded from the
// concept of "final" inputs?
final.attrs.insert_or_assign("final", Explicit<bool>(true));
assert(final.isFinal());
scheme->checkLocks(*this, final); scheme->checkLocks(*this, final);
return {storePath, final}; return {storePath, final};
@ -221,6 +235,10 @@ void InputScheme::checkLocks(const Input & specified, const Input & final) const
throw Error("'revCount' attribute mismatch in input '%s', expected %d", throw Error("'revCount' attribute mismatch in input '%s', expected %d",
final.to_string(), *prevRevCount); final.to_string(), *prevRevCount);
} }
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 std::pair<ref<SourceAccessor>, Input> Input::getAccessor(ref<Store> store) const
@ -244,6 +262,36 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
if (!scheme) if (!scheme)
throw Error("cannot fetch unsupported input '%s'", attrsToJSON(toAttrs())); 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 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); auto [accessor, final] = scheme->getAccessor(store, *this);
assert(!accessor->fingerprint); assert(!accessor->fingerprint);

View file

@ -84,11 +84,21 @@ public:
bool isDirect() const; bool isDirect() const;
/** /**
* Check whether this is a "locked" input, that is, * Check whether this is a "locked" input, that is, it has
* one that contains a commit hash or content hash. * attributes like a Git revision or NAR hash that uniquely
* identify its contents.
*/ */
bool isLocked() const; bool isLocked() const;
/**
* Check whether this is a "final" input, meaning that fetching it
* will not add or change any attributes. For instance, a Git
* input with a `rev` attribute but without a `lastModified`
* attribute is considered locked but not final. Only "final"
* inputs can be substituted from a binary cache.
*/
bool isFinal() const;
bool operator ==(const Input & other) const noexcept; bool operator ==(const Input & other) const noexcept;
bool contains(const Input & other) const; bool contains(const Input & other) const;
@ -144,6 +154,10 @@ public:
/** /**
* For locked inputs, return a string that uniquely specifies the * For locked inputs, return a string that uniquely specifies the
* content of the input (typically a commit hash or content hash). * content of the input (typically a commit hash or content hash).
*
* Only known-equivalent inputs should return the same fingerprint.
*
* This is not a stable identifier between Nix versions, but not guaranteed to change either.
*/ */
std::optional<std::string> getFingerprint(ref<Store> store) const; std::optional<std::string> getFingerprint(ref<Store> store) const;
}; };
@ -212,24 +226,15 @@ struct InputScheme
*/ */
virtual std::optional<ExperimentalFeature> experimentalFeature() const; virtual std::optional<ExperimentalFeature> experimentalFeature() const;
/// See `Input::isDirect()`.
virtual bool isDirect(const Input & input) const virtual bool isDirect(const Input & input) const
{ return true; } { return true; }
/** /// See `Input::getFingerprint()`.
* A sufficiently unique string that can be used as a cache key to identify the `input`.
*
* Only known-equivalent inputs should return the same fingerprint.
*
* This is not a stable identifier between Nix versions, but not guaranteed to change either.
*/
virtual std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const virtual std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const
{ return std::nullopt; } { return std::nullopt; }
/** /// See `Input::isLocked()`.
* Return `true` if this input is considered "locked", i.e. it has
* attributes like a Git revision or NAR hash that uniquely
* identify its contents.
*/
virtual bool isLocked(const Input & input) const virtual bool isLocked(const Input & input) const
{ return false; } { return false; }

View file

@ -72,6 +72,7 @@ struct PathInputScheme : InputScheme
auto query = attrsToQuery(input.attrs); auto query = attrsToQuery(input.attrs);
query.erase("path"); query.erase("path");
query.erase("type"); query.erase("type");
query.erase("final");
return ParsedURL { return ParsedURL {
.scheme = "path", .scheme = "path",
.path = getStrAttr(input.attrs, "path"), .path = getStrAttr(input.attrs, "path"),

View file

@ -85,7 +85,6 @@ static void forceTrivialValue(EvalState & state, Value & value, const PosIdx pos
state.forceValue(value, pos); state.forceValue(value, pos);
} }
static void expectType(EvalState & state, ValueType type, static void expectType(EvalState & state, ValueType type,
Value & value, const PosIdx pos) Value & value, const PosIdx pos)
{ {

View file

@ -46,6 +46,10 @@ LockedNode::LockedNode(
if (!lockedRef.input.isLocked()) if (!lockedRef.input.isLocked())
throw Error("lock file contains unlocked input '%s'", throw Error("lock file contains unlocked input '%s'",
fetchers::attrsToJSON(lockedRef.input.toAttrs())); fetchers::attrsToJSON(lockedRef.input.toAttrs()));
// For backward compatibility, lock file entries are implicitly final.
assert(!lockedRef.input.attrs.contains("final"));
lockedRef.input.attrs.insert_or_assign("final", Explicit<bool>(true));
} }
StorePath LockedNode::computeStorePath(Store & store) const StorePath LockedNode::computeStorePath(Store & store) const
@ -53,7 +57,6 @@ StorePath LockedNode::computeStorePath(Store & store) const
return lockedRef.input.computeStorePath(store); return lockedRef.input.computeStorePath(store);
} }
static std::shared_ptr<Node> doFind(const ref<Node> & root, const InputPath & path, std::vector<InputPath> & visited) static std::shared_ptr<Node> doFind(const ref<Node> & root, const InputPath & path, std::vector<InputPath> & visited)
{ {
auto pos = root; auto pos = root;
@ -191,6 +194,11 @@ std::pair<nlohmann::json, LockFile::KeyMap> LockFile::toJSON() const
if (auto lockedNode = node.dynamic_pointer_cast<const LockedNode>()) { if (auto lockedNode = node.dynamic_pointer_cast<const LockedNode>()) {
n["original"] = fetchers::attrsToJSON(lockedNode->originalRef.toAttrs()); n["original"] = fetchers::attrsToJSON(lockedNode->originalRef.toAttrs());
n["locked"] = fetchers::attrsToJSON(lockedNode->lockedRef.toAttrs()); n["locked"] = fetchers::attrsToJSON(lockedNode->lockedRef.toAttrs());
/* For backward compatibility, omit the "final"
attribute. We never allow non-final inputs in lock files
anyway. */
assert(lockedNode->lockedRef.input.isFinal());
n["locked"].erase("final");
if (!lockedNode->isFlake) if (!lockedNode->isFlake)
n["flake"] = false; n["flake"] = false;
} }
@ -239,7 +247,7 @@ std::optional<FlakeRef> LockFile::isUnlocked() const
for (auto & i : nodes) { for (auto & i : nodes) {
if (i == ref<const Node>(root)) continue; if (i == ref<const Node>(root)) continue;
auto node = i.dynamic_pointer_cast<const LockedNode>(); auto node = i.dynamic_pointer_cast<const LockedNode>();
if (node && !node->lockedRef.input.isLocked()) if (node && (!node->lockedRef.input.isLocked() || !node->lockedRef.input.isFinal()))
return node->lockedRef; return node->lockedRef;
} }

View file

@ -68,8 +68,8 @@ struct LockFile
std::pair<std::string, KeyMap> to_string() const; std::pair<std::string, KeyMap> to_string() const;
/** /**
* Check whether this lock file has any unlocked inputs. If so, * Check whether this lock file has any unlocked or non-final
* return one. * inputs. If so, return one.
*/ */
std::optional<FlakeRef> isUnlocked() const; std::optional<FlakeRef> isUnlocked() const;