flake lock: fix relative inputs in nested flakes#15982
Conversation
847272b to
fde583a
Compare
Excuse me? At the very least this bit is completely hallucinated, as is the code comment. |
I did do some testing locally to come up with that result, but I didn't post the benchmarks. I have another change that would introduce performance testing for Flakes. And sorry I forgot the regression test. I'm force-pushing that now. |
When computeLocks() keeps an existing flake input without refetching it, it recurses into that flake's inputs using the parent flake's source path. That value is only used to resolve relative 'path:' inputs, so it is harmless unless the kept flake has one. When it does (e.g. a flake with a "path:./sub" input that is itself a transitive input), the relative input is resolved against the parent flake's tree instead of the kept flake's, and locking then fails to find the input's flake.nix. Force a refetch of the kept flake when it has a relative input, so its real source path is available to resolve that input. Flakes without relative inputs stay on the lazy path unchanged. Fixes NixOS#14762.
fde583a to
3337ecd
Compare
Patch 0024 makes computeLocks() treat the child's own flake.lock as authoritative for the subtree of a relative path flake input instead of reusing the possibly stale nodes copied into the parent's lock. A relative input lives inside the parent's tree, so re-reading it is free and its content is already pinned; an unchanged child reproduces byte-identical locks, a changed child refreshes without a manual `nix flake update <input>`. Scoped first step of the approved sparseNodes plan (NixOS/nix#7730, no upstream implementation); also carries the kept-flake relative-input refetch of open PR NixOS/nix#15982 so nested relative inputs resolve against the right tree. Fixes the 2026-07-19 production failure where a submodule bump added an input to index and the workstation config failed to evaluate ("function 'outputs' called without required argument 'git-src'"), and the silent stale-pin variant of the same. Site update: packages/site/src/lib/updates/nix-sparse-locks-relative-inputs.svx Closes #3627 (PR authored by an AI agent via Claude Code, Claude Opus 4.5)
Motivation
nix flake update/nix flake lock(and any command that locks the flake) fails for a flake that transitively depends on a flake which itself has a relativepath:input, once the nesting is at least three levels deep. Locking aborts with an error like:The relative input is resolved against the wrong flake's source tree.
Context
Closes: #14762
Reproducer
Using the reporter's minimal repo (in #14762):
The graph is
user → library-that-depends-on-library → library → subflake, wheresubflakeislibrary's relativepath:./subflakeinput.Root cause
When
computeLocks()(src/libflake/flake.cc) keeps an existing flake input without refetching it, it recurses into that flake's inputs using the parent flake's source path. That value is only consulted to resolve relativepath:inputs, so it is harmless unless the kept flake actually has one. When it does (library'spath:./subflake), the relative input is resolved against the parent flake's tree (library-that-depends-on-library) instead of the kept flake's (library), and locking fails.This only surfaces at ≥ 3 levels, because at shallower depth the flake holding the relative input is the one being fetched, so the source path is already correct.
The fix
Force a refetch of a kept flake when it has a relative input, so its real source path is available to resolve that input. Flakes without relative inputs stay on the lazy path unchanged:
This mirrors the existing pattern a few lines down, where a disappeared override likewise sets
mustRefetch = true; break;. It is narrower than theif (true)workaround suggested in the issue, which would defeat the laziness optimization for every kept flake.Performance
nix flake update(which recreates the lock from scratch) is unaffected.Measured with a
nix-flake-benchmarksmicrobenchmark on the lock path: no measurable change without relative inputs; ~0.7 ms re-eval overhead for a tiny nested relative flake, scaling with source size for larger ones (≈ tens of ms for a multi-thousand-file tree). A fuller fix that also restores the lazy copy is described under "Future work" below.A new benchmark for flakes is added in pull request #15983, which should be merged before this pull request, since there is no performance benchmark for this change.Future work (out of scope)
A second, correctness-neutral quirk remains:
parentInputAttrPathis stored relative to each lock file's own root but compared against the outer lock's coordinate system, so the relative input is re-locked from scratch rather than copied from the existing lock. Rebasing that path when merging nested lock files would remove the residual refetch cost. Left out to keep this change minimal.The diagnosis and the pointer to the
mustRefetchbranch come from @yunfachi in #14762.Checklist:
tests/functional/flakes/relative-paths.shdoc/manual/rl-next/