mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-24 14:56:15 +02:00
Improve subflake handling
Relative 'path:' inputs are now handled correctly in call-flake.nix. This does require the lock file to record what the 'parent' is relative to which a node's source should be fetched.
This commit is contained in:
parent
0d14ffbcba
commit
5c2603d9c7
4 changed files with 107 additions and 53 deletions
|
@ -4,6 +4,25 @@ 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:
|
||||
|
@ -12,35 +31,26 @@ let
|
|||
sourceInfo =
|
||||
if key == lockFile.root
|
||||
then rootSrc
|
||||
else fetchTree (node.info or {} // removeAttrs node.locked ["dir"]);
|
||||
else if node.locked.type == "path" && builtins.substring 0 1 node.locked.path != "/"
|
||||
then
|
||||
let
|
||||
parentNode = allNodes.${getInputByPath lockFile.root node.parent};
|
||||
in parentNode.sourceInfo // {
|
||||
outPath = parentNode.sourceInfo.outPath + ("/" + node.locked.path);
|
||||
}
|
||||
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 "";
|
||||
|
||||
flake = import (sourceInfo + (if subdir != "" then "/" else "") + subdir + "/flake.nix");
|
||||
flake =
|
||||
import (sourceInfo.outPath + ((if subdir != "" then "/" else "") + subdir + "/flake.nix"));
|
||||
|
||||
inputs = builtins.mapAttrs
|
||||
(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 = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; };
|
||||
|
|
|
@ -307,11 +307,16 @@ LockedFlake lockFlake(
|
|||
debug("old lock file: %s", oldLockFile);
|
||||
|
||||
// FIXME: check whether all overrides are used.
|
||||
std::map<InputPath, FlakeInput> overrides;
|
||||
std::map<InputPath, std::tuple<FlakeInput, SourcePath, std::optional<InputPath>>> overrides;
|
||||
std::set<InputPath> overridesUsed, updatesUsed;
|
||||
|
||||
for (auto & i : lockFlags.inputOverrides)
|
||||
overrides.insert_or_assign(i.first, FlakeInput { .ref = i.second });
|
||||
overrides.emplace(
|
||||
i.first,
|
||||
std::make_tuple(
|
||||
FlakeInput { .ref = i.second },
|
||||
state.rootPath("/"),
|
||||
std::nullopt));
|
||||
|
||||
LockFile newLockFile;
|
||||
|
||||
|
@ -322,18 +327,29 @@ LockedFlake lockFlake(
|
|||
std::shared_ptr<Node> node,
|
||||
const InputPath & inputPathPrefix,
|
||||
std::shared_ptr<const Node> oldNode,
|
||||
const InputPath & lockRootPath,
|
||||
const SourcePath & parentPath,
|
||||
const InputPath & followsPrefix,
|
||||
const SourcePath & sourcePath,
|
||||
bool trustLock)>
|
||||
computeLocks;
|
||||
|
||||
computeLocks = [&](
|
||||
/* The inputs of this node, either from flake.nix or
|
||||
flake.lock */
|
||||
const FlakeInputs & flakeInputs,
|
||||
/* The node whose locks are to be updated.*/
|
||||
std::shared_ptr<Node> node,
|
||||
/* The path to this node in the lock file graph. */
|
||||
const InputPath & inputPathPrefix,
|
||||
/* The old node, if any, from which locks can be
|
||||
copied. */
|
||||
std::shared_ptr<const Node> oldNode,
|
||||
const InputPath & lockRootPath,
|
||||
const SourcePath & parentPath,
|
||||
/* The prefix relative to which 'follows' should be
|
||||
interpreted. When a node is initially locked, it's
|
||||
relative to the node's flake; when it's already locked,
|
||||
it's relative to the root of the lock file. */
|
||||
const InputPath & followsPrefix,
|
||||
/* The source path of this node's flake. */
|
||||
const SourcePath & sourcePath,
|
||||
bool trustLock)
|
||||
{
|
||||
debug("computing lock file node '%s'", printInputPath(inputPathPrefix));
|
||||
|
@ -345,7 +361,8 @@ LockedFlake lockFlake(
|
|||
auto inputPath(inputPathPrefix);
|
||||
inputPath.push_back(id);
|
||||
inputPath.push_back(idOverride);
|
||||
overrides.insert_or_assign(inputPath, inputOverride);
|
||||
overrides.emplace(inputPath,
|
||||
std::make_tuple(inputOverride, sourcePath, inputPathPrefix));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -364,13 +381,18 @@ LockedFlake lockFlake(
|
|||
ancestors? */
|
||||
auto i = overrides.find(inputPath);
|
||||
bool hasOverride = i != overrides.end();
|
||||
if (hasOverride) {
|
||||
if (hasOverride)
|
||||
overridesUsed.insert(inputPath);
|
||||
// Respect the “flakeness” of the input even if we
|
||||
// override it
|
||||
i->second.isFlake = input2.isFlake;
|
||||
}
|
||||
auto & input = hasOverride ? i->second : input2;
|
||||
auto input = hasOverride ? std::get<0>(i->second) : input2;
|
||||
|
||||
/* Resolve relative 'path:' inputs relative to
|
||||
the source path of the overrider. */
|
||||
auto overridenSourcePath = hasOverride ? std::get<1>(i->second) : sourcePath;
|
||||
|
||||
/* Respect the "flakeness" of the input even if we
|
||||
override it. */
|
||||
if (hasOverride)
|
||||
input.isFlake = input2.isFlake;
|
||||
|
||||
/* Resolve 'follows' later (since it may refer to an input
|
||||
path we haven't processed yet. */
|
||||
|
@ -386,17 +408,20 @@ LockedFlake lockFlake(
|
|||
|
||||
assert(input.ref);
|
||||
|
||||
auto overridenParentPath =
|
||||
input.ref->input.isRelative()
|
||||
? std::optional<InputPath>(hasOverride ? std::get<2>(i->second) : inputPathPrefix)
|
||||
: std::nullopt;
|
||||
|
||||
/* Get the input flake, resolve 'path:./...'
|
||||
flakerefs relative to the parent flake. */
|
||||
auto getInputFlake = [&]()
|
||||
{
|
||||
if (input.ref->input.isRelative()) {
|
||||
SourcePath inputSourcePath {
|
||||
parentPath.accessor,
|
||||
CanonPath(*input.ref->input.getSourcePath(), *parentPath.path.parent())
|
||||
overridenSourcePath.accessor,
|
||||
CanonPath(*input.ref->input.getSourcePath(), *overridenSourcePath.path.parent())
|
||||
};
|
||||
// FIXME: we need to record in the lock
|
||||
// file what the parent flake is.
|
||||
return readFlake(state, *input.ref, *input.ref, *input.ref, inputSourcePath, inputPath);
|
||||
} else
|
||||
return getFlake(state, *input.ref, useRegistries, inputPath);
|
||||
|
@ -415,6 +440,7 @@ LockedFlake lockFlake(
|
|||
|
||||
if (oldLock
|
||||
&& oldLock->originalRef == *input.ref
|
||||
&& oldLock->parentPath == overridenParentPath
|
||||
&& !hasOverride)
|
||||
{
|
||||
debug("keeping existing input '%s'", inputPathS);
|
||||
|
@ -423,7 +449,8 @@ LockedFlake lockFlake(
|
|||
didn't change and there is no override from a
|
||||
higher level flake. */
|
||||
auto childNode = std::make_shared<LockedNode>(
|
||||
oldLock->lockedRef, oldLock->originalRef, oldLock->isFlake);
|
||||
oldLock->lockedRef, oldLock->originalRef, oldLock->isFlake,
|
||||
oldLock->parentPath);
|
||||
|
||||
node->inputs.insert_or_assign(id, childNode);
|
||||
|
||||
|
@ -451,7 +478,7 @@ LockedFlake lockFlake(
|
|||
.isFlake = (*lockedNode)->isFlake,
|
||||
});
|
||||
} else if (auto follows = std::get_if<1>(&i.second)) {
|
||||
if (! trustLock) {
|
||||
if (!trustLock) {
|
||||
// It is possible that the flake has changed,
|
||||
// so we must confirm all the follows that are in the lockfile are also in the flake.
|
||||
auto overridePath(inputPath);
|
||||
|
@ -466,7 +493,7 @@ LockedFlake lockFlake(
|
|||
break;
|
||||
}
|
||||
}
|
||||
auto absoluteFollows(lockRootPath);
|
||||
auto absoluteFollows(followsPrefix);
|
||||
absoluteFollows.insert(absoluteFollows.end(), follows->begin(), follows->end());
|
||||
fakeInputs.emplace(i.first, FlakeInput {
|
||||
.follows = absoluteFollows,
|
||||
|
@ -477,13 +504,14 @@ LockedFlake lockFlake(
|
|||
|
||||
if (mustRefetch) {
|
||||
auto inputFlake = getInputFlake();
|
||||
computeLocks(inputFlake.inputs, childNode, inputPath, oldLock, lockRootPath, inputFlake.path, !mustRefetch);
|
||||
computeLocks(inputFlake.inputs, childNode, inputPath, oldLock, followsPrefix,
|
||||
inputFlake.path, !mustRefetch);
|
||||
} else {
|
||||
// FIXME: parentPath is wrong here, we
|
||||
// FIXME: sourcePath is wrong here, we
|
||||
// should pass a lambda that lazily
|
||||
// fetches the parent flake if needed
|
||||
// (i.e. getInputFlake()).
|
||||
computeLocks(fakeInputs, childNode, inputPath, oldLock, lockRootPath, parentPath, !mustRefetch);
|
||||
computeLocks(fakeInputs, childNode, inputPath, oldLock, followsPrefix, sourcePath, !mustRefetch);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
@ -506,7 +534,9 @@ LockedFlake lockFlake(
|
|||
if (input.isFlake) {
|
||||
auto inputFlake = getInputFlake();
|
||||
|
||||
auto childNode = std::make_shared<LockedNode>(inputFlake.lockedRef, ref);
|
||||
auto childNode = std::make_shared<LockedNode>(
|
||||
inputFlake.lockedRef, ref, true,
|
||||
overridenParentPath);
|
||||
|
||||
node->inputs.insert_or_assign(id, childNode);
|
||||
|
||||
|
@ -526,7 +556,7 @@ LockedFlake lockFlake(
|
|||
oldLock
|
||||
? std::dynamic_pointer_cast<const Node>(oldLock)
|
||||
: readLockFile(inputFlake).root,
|
||||
oldLock ? lockRootPath : inputPath,
|
||||
oldLock ? followsPrefix : inputPath,
|
||||
inputFlake.path,
|
||||
false);
|
||||
}
|
||||
|
@ -537,7 +567,7 @@ LockedFlake lockFlake(
|
|||
auto [accessor, lockedRef] = resolvedRef.lazyFetch(state.store);
|
||||
|
||||
node->inputs.insert_or_assign(id,
|
||||
std::make_shared<LockedNode>(lockedRef, ref, false));
|
||||
std::make_shared<LockedNode>(lockedRef, ref, false, overridenParentPath));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -549,8 +579,13 @@ LockedFlake lockFlake(
|
|||
};
|
||||
|
||||
computeLocks(
|
||||
flake->inputs, newLockFile.root, {},
|
||||
lockFlags.recreateLockFile ? nullptr : oldLockFile.root, {}, flake->path, false);
|
||||
flake->inputs,
|
||||
newLockFile.root,
|
||||
{},
|
||||
lockFlags.recreateLockFile ? nullptr : oldLockFile.root,
|
||||
{},
|
||||
flake->path,
|
||||
false);
|
||||
|
||||
for (auto & i : lockFlags.inputOverrides)
|
||||
if (!overridesUsed.count(i.first))
|
||||
|
|
|
@ -31,9 +31,10 @@ FlakeRef getFlakeRef(
|
|||
}
|
||||
|
||||
LockedNode::LockedNode(const nlohmann::json & json)
|
||||
: lockedRef(getFlakeRef(json, "locked", "info"))
|
||||
: lockedRef(getFlakeRef(json, "locked", "info")) // FIXME: remove "info"
|
||||
, originalRef(getFlakeRef(json, "original", nullptr))
|
||||
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
|
||||
, parentPath(json.find("parent") != json.end() ? (std::optional<InputPath>) json["parent"] : std::nullopt)
|
||||
{
|
||||
if (!lockedRef.input.isLocked() && !lockedRef.input.isRelative())
|
||||
throw Error("lock file contains unlocked input '%s'",
|
||||
|
@ -164,7 +165,10 @@ nlohmann::json LockFile::toJSON() const
|
|||
if (auto lockedNode = std::dynamic_pointer_cast<const LockedNode>(node)) {
|
||||
n["original"] = fetchers::attrsToJSON(lockedNode->originalRef.toAttrs());
|
||||
n["locked"] = fetchers::attrsToJSON(lockedNode->lockedRef.toAttrs());
|
||||
if (!lockedNode->isFlake) n["flake"] = false;
|
||||
if (!lockedNode->isFlake)
|
||||
n["flake"] = false;
|
||||
if (lockedNode->parentPath)
|
||||
n["parent"] = *lockedNode->parentPath;
|
||||
}
|
||||
|
||||
nodes[key] = std::move(n);
|
||||
|
|
|
@ -33,11 +33,16 @@ struct LockedNode : Node
|
|||
FlakeRef lockedRef, originalRef;
|
||||
bool isFlake = true;
|
||||
|
||||
/* The node relative to which relative source paths
|
||||
(e.g. 'path:../foo') are interpreted. */
|
||||
std::optional<InputPath> parentPath;
|
||||
|
||||
LockedNode(
|
||||
const FlakeRef & lockedRef,
|
||||
const FlakeRef & originalRef,
|
||||
bool isFlake = true)
|
||||
: lockedRef(lockedRef), originalRef(originalRef), isFlake(isFlake)
|
||||
bool isFlake = true,
|
||||
std::optional<InputPath> parentPath = {})
|
||||
: lockedRef(lockedRef), originalRef(originalRef), isFlake(isFlake), parentPath(parentPath)
|
||||
{ }
|
||||
|
||||
LockedNode(const nlohmann::json & json);
|
||||
|
|
Loading…
Reference in a new issue