Skip to content

feat(scanner): Track Cargo build-script inputs - #127

Merged
JordanCoin merged 3 commits into
JordanCoin:mainfrom
reneleonhardt:feat/rust-build-script-input-dependencies
Aug 21, 2026
Merged

feat(scanner): Track Cargo build-script inputs#127
JordanCoin merged 3 commits into
JordanCoin:mainfrom
reneleonhardt:feat/rust-build-script-input-dependencies

Conversation

@reneleonhardt

@reneleonhardt reneleonhardt commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

  • Record file dependencies declared by literal println!("cargo:rerun-if-changed=...") and println!("cargo::rerun-if-changed=...") build-script directives, reusing the existing Rust syntax and string-literal scanner.
  • Resolve them conservatively through Cargo metadata: require the package's exact custom-build target and accept only one exact configured, indexed file under that package.
  • Leave everything else unresolved: comments, documentation strings, formatting arguments, malformed or control-containing values, globs, directories, missing files, absolute paths, escaping paths, ambiguous targets, self references, and directives outside the package build script.

Type of change

  • Bug fix
  • New feature
  • New language support
  • Documentation
  • Other (describe below)

Checklist

  • I've tested this locally with go build && ./codemap .
  • I've read CONTRIBUTING.md; this does not add a new language.
  • Documentation is unchanged because the dependency output is self-describing.

Additional notes

An installed-binary integration smoke test resolves app/build.rs as the importer of app/src/generated.rs.

Developed with carefully directed, manually reviewed AI assistance.

@JordanCoin

Copy link
Copy Markdown
Owner

Reviewed against real ground truth — built a Cargo workspace and used cargo check -vv's own freshness output to confirm which files cargo considers inputs, then compared codemap to that. The resolution is correct: both the old cargo: and new cargo:: prefixes work, raw strings work, paths resolve relative to the package root, and every non-literal form (format!, {ident} inline, loops, concat!, commented-out) produces no edge rather than a wrong one. With cargo off PATH it emits zero build-input edges — fails closed. The custom-build target guard is consistent with main's rustDependencyEligible model.

Two things I'd fix before landing.

1. println!($PATH) is unscoped — it matches every println! in every Rust file

scanner/sg-rules/rust.yml:41. parseRustBuildScriptInput then discards ~100% of them, but ast-grep has already matched, serialized, and buffered the lot. Measured on a synthetic 301-file crate with 30k println!s:

main this PR
ast-grep matches 30,000
JSON payload 39 MB
codemap --deps wall 0.31s 0.63s

All of it unmarshalled into []ScanMatch before filtering. The real hazard isn't the 2x — it's astGrepScanTimeout = 30s (scanner/astgrep.go:23). A println-heavy Rust monorepo pushed past that degrades the entire scan to ScanSourceTimeout, losing coverage for every language, not just this feature.

One-line fix, verified — 30,000 matches → 0 on the perf fixture, while the real fixture still yields all 4 genuine matches:

constraints:
  PATH:
    regex: 'cargo::?rerun-if-changed='

(A files: ["**/build.rs"] glob also works but would wrongly exclude crates using build = "custom_build.rs", so the constraint is the better lever.)

2. Directive paths leak into the imports array

scanner/astgrep.go:358-360 only excludes rust-path-imports, so extracted directive strings get appended to FileAnalysis.Imports:

{"path":"app/src/lib.rs","imports":["generated","schema.proto"]}

lib.rs imports nothing named schema.proto — it just contains a println! with that string in an ordinary function. Earlier fixture state also published ["../outside.proto","build.rs","missing.proto","protos"] for build.rs: unresolvable paths plus a self-reference, in the versioned codemap.analysis/v1 payload. Resolved graph edges are unaffected — only the raw array lies.

Worth noting #126 explicitly excludes its own kind (&& m.RuleID != "rust-askama-template-imports"), so #127 is the outlier here. Whoever merges second should make the two consistent rather than preserving both behaviors.

Finding 3 turned out to be a bug in main — filed as #130

Extension-less declared inputs can never resolve. cargo proves app/VERSION is an input; codemap says No files import app/VERSION. Root cause is in buildFileIndexContext: when a path has no extension, filepath.Ext returns "", so noExt == path and the same path is appended twice under the same key — meaning len(byExact[p]) == 2 forever, and the len(...) != 1 idiom rejects it permanently. Hits VERSION, Makefile, Dockerfile, LICENSE.

That's not yours to fix here — it's pre-existing, and the same three lines are also what lets #125 fabricate an edge to a nonexistent file. Filed as #130 with both halves; fixing it there makes this finding disappear without touching your PR.

Merge note

No signature changes, and rust-cargo-rerun-imports doesn't collide with #125's or #126's rule ids. I merged all four Rust PRs locally with union resolution and got a clean build and green go test ./...no repeat of the #117/#118 break. The one hazard for whoever resolves: scanner/astgrep.go:327 must union all three rule IDs. Taking either side of that one-line conflict silently disables a feature with fully green CI.

Nits: ExplicitTarget is set at astgrep.go:354 but resolveRustBuildScriptInput reads ref.Path, so it's dead for this kind; the test file constructs two scanners and never closes the one that actually scans; and rustbuildscript_test.go:97 hard-codes forward-slash expectations that would fail on Windows (latent — CI runs tests on ubuntu+macos only).

@reneleonhardt

Copy link
Copy Markdown
Contributor Author

Rebased

The astgrep conflict was resolved as a union — rust-cargo-rerun-imports joined the rule-ID disjunction and switch alongside the askama and include cases, and the resolver and rules file were unioned the same way.

Compliance

  • println! rule scoped with your constraint (PATH regex cargo::?rerun-if-changed=); a plain non-directive println! in the extraction fixture now yields zero matches.
  • Directive paths no longer leak into the imports array — rust-cargo-rerun-imports is excluded from the append, consistent with feat(scanner): Resolve Askama template dependencies #126's exclusion of its own kind.
  • All three nits addressed: the dead ExplicitTarget assignment is removed, the scanning test scanner is closed (t.Cleanup), and the resolved-edge expectations are Windows-neutral (filepath.ToSlash).

Where I didn't fully comply

Other changes the review forced (not in your suggestions)

  • Removing the dead ExplicitTarget assignment meant dropping the extraction test's ExplicitTarget expectations — the test was asserting the dead field.
  • Added a leak regression assertion: any analysis carrying rust-build-input references must have an empty imports array.
  • The union rebase also touched rustgraph.go (resolver case) and rust.yml (rule block) — the same shared-region collision you flagged; all rule IDs preserved, verified patch-equivalent and full-suite green.

Copy link
Copy Markdown
Owner

Reviewed the rebased branch — the review items are all addressed (the println! rule is scoped with the PATH regex constraint, directive paths no longer leak into imports with a regression assertion guarding it, and the three nits are fixed). I merged this branch with current main locally, ran go vet and the full go test ./... suite with a real ast-grep, and it is green.

This is approved and would have been merged, but #124 and #141 just landed and the branch is now conflicted — one hunk in scanner/astgrep.go, in the m.RuleID switch. The resolution is the union you already know: keep #124's rust-use brace-tree gate inside case "rust-use-imports": and this PR's case "rust-cargo-rerun-imports": block right after it. rustgraph.go and sg-rules/rust.yml auto-merge cleanly; the rule-ID disjunction and the imports-append exclusion already union correctly on your side.

Once you push the rebase (or merge main in), this merges as-is — no further review needed.


Generated by Claude Code

reneleonhardt and others added 3 commits August 22, 2026 00:02
Resolve literal Cargo rerun-if-changed directives through the existing Rust syntax scanner, authoritative custom-build ownership, and exact configured-file matching.

Co-Authored-By: GPT-5.6 Sol <codex@openai.com>
@reneleonhardt
reneleonhardt force-pushed the feat/rust-build-script-input-dependencies branch from 88b339a to 15fce34 Compare August 21, 2026 22:12
@reneleonhardt

Copy link
Copy Markdown
Contributor Author

Rebased as requested.

@JordanCoin
JordanCoin merged commit 5da4494 into JordanCoin:main Aug 21, 2026
12 checks passed
@reneleonhardt
reneleonhardt deleted the feat/rust-build-script-input-dependencies branch August 21, 2026 22:15
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