Skip to content

Commit

Permalink
Merge pull request #10024 from edolstra/remove-locked-flag
Browse files Browse the repository at this point in the history
Input: Replace 'locked' bool by isLocked() method
  • Loading branch information
edolstra authored Feb 21, 2024
2 parents 9ae665b + 09d76e5 commit 3f5d7af
Show file tree
Hide file tree
Showing 16 changed files with 159 additions and 85 deletions.
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

0 comments on commit 3f5d7af

Please sign in to comment.