Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Input: Replace 'locked' bool by isLocked() method #10024

Merged
merged 2 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 37 additions & 24 deletions src/libexpr/flake/call-flake.nix
Original file line number Diff line number Diff line change
@@ -1,20 +1,52 @@
lockFileStr: rootSrc: rootSubdir:
# This is a helper to callFlake() to lazily fetch flake inputs.

# The contents of the lock file, in JSON format.
lockFileStr:

# A mapping of lock file node IDs to { sourceInfo, subdir } attrsets,
# with sourceInfo.outPath providing an InputAccessor to a previously
# fetched tree. This is necessary for possibly unlocked inputs, in
# particular the root input, but also --override-inputs pointing to
# unlocked trees.
overrides:

let

lockFile = builtins.fromJSON lockFileStr;

# Resolve a input spec into a node name. An input spec is
# either a node name, or a 'follows' path from the root
# node.
resolveInput = inputSpec:
if builtins.isList inputSpec
then getInputByPath lockFile.root inputSpec
else inputSpec;

# Follow an input path (e.g. ["dwarffs" "nixpkgs"]) from the
# root node, returning the final node.
getInputByPath = nodeName: path:
if path == []
then nodeName
else
getInputByPath
# Since this could be a 'follows' input, call resolveInput.
(resolveInput lockFile.nodes.${nodeName}.inputs.${builtins.head path})
(builtins.tail path);

allNodes =
builtins.mapAttrs
(key: node:
let

sourceInfo =
if key == lockFile.root
then rootSrc
else fetchTree (node.info or {} // removeAttrs node.locked ["dir"]);
if overrides ? ${key}
then
overrides.${key}.sourceInfo
else
# FIXME: remove obsolete node.info.
fetchTree (node.info or {} // removeAttrs node.locked ["dir"]);

subdir = if key == lockFile.root then rootSubdir else node.locked.dir or "";
subdir = overrides.${key}.dir or node.locked.dir or "";

outPath = sourceInfo + ((if subdir == "" then "" else "/") + subdir);

Expand All @@ -24,25 +56,6 @@ let
(inputName: inputSpec: allNodes.${resolveInput inputSpec})
(node.inputs or {});

# Resolve a input spec into a node name. An input spec is
# either a node name, or a 'follows' path from the root
# node.
resolveInput = inputSpec:
if builtins.isList inputSpec
then getInputByPath lockFile.root inputSpec
else inputSpec;

# Follow an input path (e.g. ["dwarffs" "nixpkgs"]) from the
# root node, returning the final node.
getInputByPath = nodeName: path:
if path == []
then nodeName
else
getInputByPath
# Since this could be a 'follows' input, call resolveInput.
(resolveInput lockFile.nodes.${nodeName}.inputs.${builtins.head path})
(builtins.tail path);

outputs = flake.outputs (inputs // { self = result; });

result =
Expand Down
84 changes: 53 additions & 31 deletions src/libexpr/flake/flake.cc
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ LockedFlake lockFlake(
std::map<InputPath, FlakeInput> overrides;
std::set<InputPath> explicitCliOverrides;
std::set<InputPath> overridesUsed, updatesUsed;
std::map<ref<Node>, StorePath> nodePaths;

for (auto & i : lockFlags.inputOverrides) {
overrides.insert_or_assign(i.first, FlakeInput { .ref = i.second });
Expand Down Expand Up @@ -535,11 +536,13 @@ LockedFlake lockFlake(
}
}

computeLocks(
mustRefetch
? getFlake(state, oldLock->lockedRef, false, flakeCache, inputPath).inputs
: fakeInputs,
childNode, inputPath, oldLock, lockRootPath, parentPath, !mustRefetch);
if (mustRefetch) {
auto inputFlake = getFlake(state, oldLock->lockedRef, false, flakeCache, inputPath);
nodePaths.emplace(childNode, inputFlake.storePath);
computeLocks(inputFlake.inputs, childNode, inputPath, oldLock, lockRootPath, parentPath, false);
} else {
computeLocks(fakeInputs, childNode, inputPath, oldLock, lockRootPath, parentPath, true);
}

} else {
/* We need to create a new lock file entry. So fetch
Expand Down Expand Up @@ -584,6 +587,7 @@ LockedFlake lockFlake(
flake. Also, unless we already have this flake
in the top-level lock file, use this flake's
own lock file. */
nodePaths.emplace(childNode, inputFlake.storePath);
computeLocks(
inputFlake.inputs, childNode, inputPath,
oldLock
Expand All @@ -596,11 +600,13 @@ LockedFlake lockFlake(
}

else {
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
auto [storePath, resolvedRef, lockedRef] = fetchOrSubstituteTree(
state, *input.ref, useRegistries, flakeCache);

auto childNode = make_ref<LockedNode>(lockedRef, ref, false);

nodePaths.emplace(childNode, storePath);

node->inputs.insert_or_assign(id, childNode);
}
}
Expand All @@ -615,6 +621,8 @@ LockedFlake lockFlake(
// Bring in the current ref for relative path resolution if we have it
auto parentPath = canonPath(state.store->toRealPath(flake.storePath) + "/" + flake.lockedRef.subdir, true);

nodePaths.emplace(newLockFile.root, flake.storePath);

computeLocks(
flake.inputs,
newLockFile.root,
Expand Down Expand Up @@ -707,14 +715,6 @@ LockedFlake lockFlake(
flake.lockedRef.input.getRev() &&
prevLockedRef.input.getRev() != flake.lockedRef.input.getRev())
warn("committed new revision '%s'", flake.lockedRef.input.getRev()->gitRev());

/* Make sure that we picked up the change,
i.e. the tree should usually be dirty
now. Corner case: we could have reverted from a
dirty to a clean tree! */
if (flake.lockedRef.input == prevLockedRef.input
&& !flake.lockedRef.input.isLocked())
throw Error("'%s' did not change after I updated its 'flake.lock' file; is 'flake.lock' under version control?", flake.originalRef);
}
} else
throw Error("cannot write modified lock file of flake '%s' (use '--no-write-lock-file' to ignore)", topRef);
Expand All @@ -724,7 +724,11 @@ LockedFlake lockFlake(
}
}

return LockedFlake { .flake = std::move(flake), .lockFile = std::move(newLockFile) };
return LockedFlake {
.flake = std::move(flake),
.lockFile = std::move(newLockFile),
.nodePaths = std::move(nodePaths)
};

} catch (Error & e) {
e.addTrace({}, "while updating the lock file of flake '%s'", flake.lockedRef.to_string());
Expand All @@ -736,30 +740,48 @@ void callFlake(EvalState & state,
const LockedFlake & lockedFlake,
Value & vRes)
{
auto vLocks = state.allocValue();
auto vRootSrc = state.allocValue();
auto vRootSubdir = state.allocValue();
auto vTmp1 = state.allocValue();
auto vTmp2 = state.allocValue();
experimentalFeatureSettings.require(Xp::Flakes);

auto [lockFileStr, keyMap] = lockedFlake.lockFile.to_string();

vLocks->mkString(lockedFlake.lockFile.to_string());
auto overrides = state.buildBindings(lockedFlake.nodePaths.size());

emitTreeAttrs(
state,
lockedFlake.flake.storePath,
lockedFlake.flake.lockedRef.input,
*vRootSrc,
false,
lockedFlake.flake.forceDirty);
for (auto & [node, storePath] : lockedFlake.nodePaths) {
auto override = state.buildBindings(2);

vRootSubdir->mkString(lockedFlake.flake.lockedRef.subdir);
auto & vSourceInfo = override.alloc(state.symbols.create("sourceInfo"));

auto lockedNode = node.dynamic_pointer_cast<const LockedNode>();

emitTreeAttrs(
state,
storePath,
lockedNode ? lockedNode->lockedRef.input : lockedFlake.flake.lockedRef.input,
vSourceInfo,
false,
!lockedNode && lockedFlake.flake.forceDirty);

auto key = keyMap.find(node);
assert(key != keyMap.end());

override
.alloc(state.symbols.create("dir"))
.mkString(lockedNode ? lockedNode->lockedRef.subdir : lockedFlake.flake.lockedRef.subdir);

overrides.alloc(state.symbols.create(key->second)).mkAttrs(override);
}

auto & vOverrides = state.allocValue()->mkAttrs(overrides);

auto vCallFlake = state.allocValue();
state.evalFile(state.callFlakeInternal, *vCallFlake);

auto vTmp1 = state.allocValue();
auto vLocks = state.allocValue();
vLocks->mkString(lockFileStr);
state.callFunction(*vCallFlake, *vLocks, *vTmp1, noPos);
state.callFunction(*vTmp1, *vRootSrc, *vTmp2, noPos);
state.callFunction(*vTmp2, *vRootSubdir, vRes, noPos);

state.callFunction(*vTmp1, vOverrides, vRes, noPos);
}

static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, Value & v)
Expand Down
7 changes: 7 additions & 0 deletions src/libexpr/flake/flake.hh
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ struct LockedFlake
Flake flake;
LockFile lockFile;

/**
* Store paths of nodes that have been fetched in
* lockFlake(); in particular, the root node and the overriden
* inputs.
*/
std::map<ref<Node>, StorePath> nodePaths;

Fingerprint getFingerprint() const;
};

Expand Down
17 changes: 9 additions & 8 deletions src/libexpr/flake/lockfile.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ LockedNode::LockedNode(const nlohmann::json & json)
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
{
if (!lockedRef.input.isLocked())
throw Error("lock file contains mutable lock '%s'",
throw Error("lock file contains unlocked input '%s'",
fetchers::attrsToJSON(lockedRef.input.toAttrs()));
}

Expand Down Expand Up @@ -134,10 +134,10 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path)
// a bit since we don't need to worry about cycles.
}

nlohmann::json LockFile::toJSON() const
std::pair<nlohmann::json, LockFile::KeyMap> LockFile::toJSON() const
{
nlohmann::json nodes;
std::unordered_map<std::shared_ptr<const Node>, std::string> nodeKeys;
KeyMap nodeKeys;
std::unordered_set<std::string> keys;

std::function<std::string(const std::string & key, ref<const Node> node)> dumpNode;
Expand Down Expand Up @@ -194,12 +194,13 @@ nlohmann::json LockFile::toJSON() const
json["root"] = dumpNode("root", root);
json["nodes"] = std::move(nodes);

return json;
return {json, std::move(nodeKeys)};
}

std::string LockFile::to_string() const
std::pair<std::string, LockFile::KeyMap> LockFile::to_string() const
{
return toJSON().dump(2);
auto [json, nodeKeys] = toJSON();
return {json.dump(2), std::move(nodeKeys)};
}

LockFile LockFile::read(const Path & path)
Expand All @@ -210,7 +211,7 @@ LockFile LockFile::read(const Path & path)

std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile)
{
stream << lockFile.toJSON().dump(2);
stream << lockFile.toJSON().first.dump(2);
return stream;
}

Expand Down Expand Up @@ -243,7 +244,7 @@ std::optional<FlakeRef> LockFile::isUnlocked() const
bool LockFile::operator ==(const LockFile & other) const
{
// FIXME: slow
return toJSON() == other.toJSON();
return toJSON().first == other.toJSON().first;
}

bool LockFile::operator !=(const LockFile & other) const
Expand Down
7 changes: 4 additions & 3 deletions src/libexpr/flake/lockfile.hh
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,15 @@ struct LockFile

typedef std::map<ref<const Node>, std::string> KeyMap;

nlohmann::json toJSON() const;
std::pair<nlohmann::json, KeyMap> toJSON() const;

std::string to_string() const;
std::pair<std::string, KeyMap> to_string() const;

static LockFile read(const Path & path);

/**
* Check whether this lock file has any unlocked inputs.
* Check whether this lock file has any unlocked inputs. If so,
* return one.
*/
std::optional<FlakeRef> isUnlocked() const;

Expand Down
6 changes: 2 additions & 4 deletions src/libexpr/primops/fetchTree.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ void emitTreeAttrs(
bool emptyRevFallback,
bool forceDirty)
{
assert(input.isLocked());

auto attrs = state.buildBindings(100);

state.mkStorePathString(storePath, attrs.alloc(state.sOutPath));
Expand Down Expand Up @@ -176,8 +174,8 @@ static void fetchTree(
fetcher = "fetchGit";

state.error<EvalError>(
"in pure evaluation mode, %s requires a locked input",
fetcher
"in pure evaluation mode, '%s' will not fetch unlocked input '%s'",
fetcher, input.to_string()
).atPos(pos).debugThrow();
}

Expand Down
11 changes: 5 additions & 6 deletions src/libfetchers/fetchers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,8 @@ static void fixupInput(Input & input)
// Check common attributes.
input.getType();
input.getRef();
if (input.getRev())
input.locked = true;
input.getRevCount();
input.getLastModified();
if (input.getNarHash())
input.locked = true;
}

Input Input::fromURL(const ParsedURL & url, bool requireTree)
Expand Down Expand Up @@ -140,6 +136,11 @@ bool Input::isDirect() const
return !scheme || scheme->isDirect(*this);
}

bool Input::isLocked() const
{
return scheme && scheme->isLocked(*this);
}

Attrs Input::toAttrs() const
{
return attrs;
Expand Down Expand Up @@ -222,8 +223,6 @@ std::pair<StorePath, Input> Input::fetch(ref<Store> store) const
input.to_string(), *prevRevCount);
}

input.locked = true;

return {std::move(storePath), input};
}

Expand Down
Loading
Loading