Skip to content

feat(cli): add 'nix derivation source-origins' subcommand - #2

Merged
ethancedwards8 merged 11 commits into
mainfrom
exa/source-origins
Jul 2, 2026
Merged

feat(cli): add 'nix derivation source-origins' subcommand#2
ethancedwards8 merged 11 commits into
mainfrom
exa/source-origins

Conversation

@ethancedwards8

Copy link
Copy Markdown

Summary

Ports the nix derivation source-origins command (9 commits, authorship and messages preserved) from exa-labs/nix onto this repo's Determinate 3.21.2 base.

The command evaluates installables and, for each derivation in the build closure (recursive by default, --no-recursive to disable), prints a JSON mapping of every inputSrc store path back to the original filesystem path that was copied into the store during evaluation. When a source is a filtered directory (cleanSourceWith / builtins.path), sourceFiles enumerates the store path contents for file-level precision. This lets build-graph tooling discover which working-tree paths feed each derivation.

Implementation: a storeToSrc reverse map populated in copyPathToStore(), a sourceStoreToOriginalPath map populated in mountInput() (git/path input schemes tag accessors with originalRootPath), and recordPathOrigin() called from addPath() for filtered sources. Both maps use boost::concurrent_flat_map for parallel eval safety.

Port notes (3.18.0 → 3.21.2 adaptations):

  • EvalState::srcToStore no longer exists here (the source→store cache moved into fetchers::Settings / fetchToStore()), so storeToSrc is recorded directly in copyPathToStore() after fetching; a follow-up commit drops the stale srcToStore cross-reference from the command docs.
  • Everything else applied as-is (identical hunks, only line offsets).

Test plan

  • nix build .#default on aarch64-darwin: all components compile; the only functional-test failures (remote-store, bash-profile, impure-env) also fail identically on plain main in the same local sandbox (unresolvable build-user name), i.e. pre-existing and environment-specific
  • Smoke test on a flake using builtins.path with a filter: sourcePath resolves to the original filesystem paths and sourceFiles lists exactly the files that passed the filter; --no-recursive, --pretty, and --help work
  • CI: build + checks on the 3 target platforms

Generated with Devin

jld-adriano and others added 10 commits July 2, 2026 11:24
Maps inputSrc store paths back to original filesystem source paths.

During evaluation, nix copies source paths into the store via
copyPathToStore(). This commit:

1. Adds a reverse mapping (storeToSrc) to EvalState that is populated
   alongside the existing srcToStore cache.

2. Exposes getSourceOrigin(StorePath) and getSourceOrigins() accessors
   on EvalState to query this mapping after evaluation.

3. Adds a new 'nix derivation source-origins' command that evaluates
   installables and, for each resulting derivation, prints a JSON
   mapping of each inputSrc store path to its original source path.

This enables build-system tooling to determine which working-tree
directories contributed to a derivation's build inputs - useful for
monorepo CI to know which parts of the repo are affected by a change.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Add sourceStoreToOriginalPath map to EvalState for tracking
  store path → original filesystem path mappings
- Set originalRootPath on accessors in git.cc (git root) and path.cc
  (flake dir) input schemes so mountInput() can record mappings
- Implement recordPathOrigin() with 3-strategy resolution for
  builtins.path/cleanSourceWith sources that bypass storeToSrc
- Modify addPath() to call recordPathOrigin() in both cached and
  new store path branches
- Auto-disable eval cache in source-origins command
- Use git+file:// scheme for correct monorepo root resolution

Tested on diverse monorepo flakes: rust (atlas-ops, atlas, queue-urls),
python (atlas), typescript (clank-stank, exa-deploy), go (readability_parser),
and flakes/graph. All correctly resolve local source paths.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…e-level precision

When a store path was created by cleanSourceWith with a broad source root
(e.g. src = ../../../.), the sourcePath would report the monorepo root,
making every file change trigger a match. Now we enumerate the actual
store path contents (BFS) and report individual files as sourceFiles[].

The store path IS the filtered result — its contents are exactly what
passed the cleanSourceWith filter. Each file is mapped back to its
original source location using the sourcePath as the base.

This gives file-level precision for all cleanSourceWith/builtins.path
cases, making dependency graph filtering dramatically more precise.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…:// examples, and implementation details

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
source-origins now traces inputDrvs → inputSrcs through the entire
build closure, not just the top-level derivation. This lets
nix-dep-graph discover dependencies like uv path deps that are
inputSrcs of derivations deep in the transitive closure.

The old non-recursive behavior is available via --no-recursive.

Fixes GIGA-2286.

Co-Authored-By: Michael Fine <mfine15@gmail.com>
When recursing through the full derivation closure, most transitive
derivations have no source origin mapping (they come from substituters
or previous builds). The JSON value() call was throwing because the
key exists with a null type, not a string. Use is_string() check
instead.

Co-Authored-By: Michael Fine <mfine15@gmail.com>
EvalState performs concurrent evaluation, so the sourceStoreToOriginalPath
cache (written from mountInput and recordPathOrigin, read from
getOriginalPath) must use the same concurrent container that the sibling
caches (srcToStore, storeToSrc, etc.) use. Previously it was a plain
std::map, which races with concurrent eval.

Strategy 3 in recordPathOrigin iterated the map and inserted into it in
the same loop; snapshot the entries via cvisit_all first, then insert
after, to avoid deadlocking on the concurrent map's shared-lock.
* git.cc: propagate `originalRootPath` to the outer `MountedSourceAccessor`
  for git repos with submodules. Without this, the wrapper accessor returned
  to callers has no `originalRootPath`, and `mountInput` fails to register
  the source-to-filesystem mapping for those flakes.

* eval.cc: drop the `snapshot.size() == 1` shortcut in Strategy 3 of
  `recordPathOrigin`. The shortcut attributed any root-path `SourcePath` to
  the sole registered source regardless of whether they are actually related,
  which could produce incorrect provenance when only one source has been
  registered so far. Always require a mount-identity match via `storeFS`.
directory_iterator with is_directory() follows symlinks, which hangs
indefinitely on circular symlinks like 'link -> .' and can enumerate
files outside the store path via symlinks to external directories.
Treat symlinks as leaf entries instead.
In this codebase the srcToStore cache lives in fetchers::Settings
(used inside fetchToStore()) rather than EvalState, so the doc's
"reverse of srcToStore" wording no longer matches the code.
storeToSrc is populated directly in copyPathToStore().

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@exa-guardian

exa-guardian Bot commented Jul 2, 2026

Copy link
Copy Markdown

🛡️ Guardian Review

⚠️ global-review-orchestrator-guardian — inconclusive · session

Error: Guardian global-review-orchestrator-guardian terminal session produced no agent output after 3 nudge attempts

⚠️ fallback-observability-guardian — inconclusive · session

Error: Guardian fallback-observability-guardian terminal session produced no agent output after 3 nudge attempts

Guardian - Joint Required Check: ✅ PASSED (0/2 approved)

PR #2

Mechanical reformat of the ported series to satisfy this repo's
pre-commit clang-format hook (nix develop -c ./maintainers/format.sh).
No functional changes.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@ethancedwards8
ethancedwards8 merged commit 6e8640c into main Jul 2, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants