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-17 12:13:30 +00:00 committed by GitHub
commit de2be8d6f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 88 additions and 22 deletions

View file

@ -44,7 +44,8 @@ let
overrides.${key}.sourceInfo
else
# 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 "";

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;
@ -179,6 +185,14 @@ std::pair<StorePath, Input> Input::fetchToStore(ref<Store> store) const
auto narHash = store->queryPathInfo(storePath)->narHash;
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);
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",
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
@ -244,6 +262,36 @@ 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 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);

View file

@ -84,11 +84,21 @@ public:
bool isDirect() const;
/**
* Check whether this is a "locked" input, that is,
* one that contains a commit hash or content hash.
* Check whether this is a "locked" input, that is, it has
* attributes like a Git revision or NAR hash that uniquely
* identify its contents.
*/
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 contains(const Input & other) const;
@ -144,6 +154,10 @@ public:
/**
* For locked inputs, return a string that uniquely specifies the
* 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;
};
@ -212,24 +226,15 @@ struct InputScheme
*/
virtual std::optional<ExperimentalFeature> experimentalFeature() const;
/// See `Input::isDirect()`.
virtual bool isDirect(const Input & input) const
{ return true; }
/**
* 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.
*/
/// See `Input::getFingerprint()`.
virtual std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const
{ return std::nullopt; }
/**
* 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.
*/
/// See `Input::isLocked()`.
virtual bool isLocked(const Input & input) const
{ return false; }

View file

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

View file

@ -384,7 +384,11 @@ struct TarballInputScheme : CurlInputScheme
input = immutableInput;
}
if (result.lastModified && !input.attrs.contains("lastModified"))
/* If we got a lastModified and the input is not final and
doesn't have one, then return it. Note that we don't do
this if the input is final for compatibility with old lock
files that didn't include lastModified. */
if (result.lastModified && !_input.isFinal() && !input.attrs.contains("lastModified"))
input.attrs.insert_or_assign("lastModified", uint64_t(result.lastModified));
input.attrs.insert_or_assign("narHash",

View file

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

View file

@ -46,6 +46,10 @@ LockedNode::LockedNode(
if (!lockedRef.input.isLocked())
throw Error("lock file contains unlocked input '%s'",
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
@ -53,7 +57,6 @@ StorePath LockedNode::computeStorePath(Store & store) const
return lockedRef.input.computeStorePath(store);
}
static std::shared_ptr<Node> doFind(const ref<Node> & root, const InputPath & path, std::vector<InputPath> & visited)
{
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>()) {
n["original"] = fetchers::attrsToJSON(lockedNode->originalRef.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)
n["flake"] = false;
}
@ -239,7 +247,7 @@ std::optional<FlakeRef> LockFile::isUnlocked() const
for (auto & i : nodes) {
if (i == ref<const Node>(root)) continue;
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;
}

View file

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