Skip to content

Add nix derivation source-origins command - #5

Open
ethancedwards8 wants to merge 1 commit into
exafrom
giga-3360/source-origins
Open

ethancedwards8 wants to merge 1 commit into
exafrom
giga-3360/source-origins

Conversation

@ethancedwards8

Copy link
Copy Markdown
Member

Summary

Part 2 of 3 for GIGA-3360 (feature from GIGA-2286 / nix-src PR #2). Single squashed commit re-implementing nix derivation source-origins [--no-recursive] INSTALLABLES... against upstream 2.36: for each derivation (recursing through inputDrvs by default) it maps every inputSrcs store path back to the working-tree path it was copied from during this evaluation, emitting JSON with storePath, sourcePath (null if not produced by this eval) and a sourceFiles enumeration for directory sources.

How it works on 2.36:

  • SourceAccessor::originalRootPath set by the git (workdir + submodule mount) and path: input schemes
  • EvalState gains reverse maps storeToSrc and sourceStoreToOriginalPath (concurrent_flat_map, like the existing caches), populated from copyPathToStore, addPath (builtins.path/filterSource/cleanSourceWith) and mountInput
  • The forward srcToStore cache now lives inside fetchToStore2 upstream, so the reverse map is maintained separately

Deviations from the nix-src version (all deliberate): origin resolution centralised in EvalState::recordPathOrigin (with a cheap early-return on repeat store paths since it's on the copy hot path); mount matching by accessor identity instead of an always-true path compare; recursion walks Derivation::inputs (SingleDerivedPath) since inputDrvs/inputSrcs no longer exist; sourceFiles uses the store's FS accessor so it works with chroot stores; MixPrintJSON has no --json flag upstream (output is always JSON).

This feature was silently dropped in the last consolidation, so it now has a functional testtests/functional/derivation-source-origins.sh covers -f, path: flake, git+file://…?dir=sub flake, builtins.path filter, null sourcePath for toFile, symlink leaves, --no-recursive, multiple installables and eval-cache bypass.

Test plan

  • Full meson build clean (aarch64-darwin); clang-format, meson-format, shellcheck pre-commit hooks pass
  • meson test derivation-source-origins OK
  • Regression: nix-expr-tests, nix-fetchers-tests, nix-util-tests, filter-source, fetchGit, fetchGitSubmodules, restricted, pure-eval, derivation-json, fetchTree-file, full flakes suite all OK
  • nix3-derivation-source-origins manpage builds with -Ddoc-gen=true
  • Linux build via exa-build.yml
  • Not exercised: lazy-trees mode, real submodule repo end-to-end

Generated with Devin

`nix derivation source-origins [--no-recursive] INSTALLABLES...`
evaluates the given installables and, for every derivation in their
build closure (walking `inputDrvs` via `Derivation::inputs`), prints a
JSON mapping from each `inputSrcs` store path back to the filesystem
path it was copied from during that evaluation. This lets tooling
(e.g. monorepo CI) determine which working-tree directories and files
contributed to a build.

The provenance is recorded in two reverse maps on `EvalState`
(`boost::concurrent_flat_map`s, like the sibling caches, since
evaluation may be concurrent):

* `storeToSrc` (StorePath -> SourcePath), populated by
  `copyPathToStore()` and by `addPath()` (`builtins.path`,
  `builtins.filterSource`, hence also `lib.cleanSourceWith`) via the
  new `recordPathOrigin()`.

* `sourceStoreToOriginalPath` (StorePath -> std::filesystem::path),
  populated by `mountInput()` for inputs whose accessor carries an
  `originalRootPath`, and by `recordPathOrigin()`, which resolves a
  source path to an original filesystem path by (1) mapping
  `/nix/store/<src>/rel` through the known source roots, (2) using the
  accessor's `originalRootPath`, (3) matching the accessor by identity
  against the mounts in `storeFS`, or (4) falling back to the physical
  path for plain filesystem paths.

`SourceAccessor` gains an `originalRootPath` field, which the `git`
input scheme sets to the repository root on workdir accessors (also on
the `MountedSourceAccessor` wrapper used when submodules are present)
and the `path` input scheme sets to the input path on the store
accessor it now returns. Using the repo root (rather than
`Input::getSourcePath()`, i.e. the flake directory) means paths in
`git+file://repo?dir=sub` flakes resolve correctly.

`sourcePath` is `null` for store paths not produced by the current
evaluation (substituted paths, `builtins.toFile`, ...). When a source is
a directory, `sourceFiles` enumerates the contents of the store object
(via the store's accessor, breadth-first, treating symlinks as leaves
so cycles like `link -> .` are not followed) mapped back under
`sourcePath`; since the store object is the *result* of any filtering,
this gives file-level precision for `cleanSourceWith` sources with a
broad root. The command disables the evaluation cache, since a cached
evaluation would never copy anything to the store.

Includes a functional test covering `--file`, `path:` and `git+file:`
evaluations, filtered sources, null origins, symlinks, `--no-recursive`
and eval-cache bypass, plus a manual page and release note.

Re-implementation of the exa-labs/nix-src series ee40e5e..d4ebfeb against
NixOS/nix 2.36 (GIGA-3360, GIGA-2286).

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

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

linear-code Bot commented Sep 7, 2026

Copy link
Copy Markdown

GIGA-3360

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.

1 participant