Skip to content

build: cargo dev aliases, mold on Linux, and warm worktree targets - #1211

Merged
ragnorc merged 9 commits into
ModernRelay:mainfrom
ragnorc:claude/build-throughput-f1f2fe
Nov 1, 2026
Merged

ragnorc merged 9 commits into
ModernRelay:mainfrom
ragnorc:claude/build-throughput-f1f2fe

Conversation

@ragnorc

@ragnorc ragnorc commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

What

Faster local and CI build loops, configuration and docs only; no engine or test code changes.

  • One feature set for every local loop. .cargo/config.toml adds the cargo dev-test, dev-nextest, dev-build-tests and dev-clippy aliases, all on the canonical CI graph (omnigraph-engine/failpoints,omnigraph-cluster/failpoints). Moving between them never recompiles a crate for a feature change; subsets are chosen at run time (cargo dev-nextest -E 'package(omnigraph-engine)').
  • mold on Linux. target.{x86_64,aarch64}-unknown-linux-gnu.linker = "scripts/cargo-linker.sh" adds -fuse-ld=mold when mold is on PATH and runs cc unchanged otherwise. Only linker is set (a target.*.rustflags entry would replace [build] rustflags), and the linker is not part of any fingerprint. The CI test, lint, nightly and DST jobs install mold; release and publish jobs don't, and link as before.
  • Warm worktrees. scripts/seed-target.sh seeds its target/ as an APFS copy-on-write clone (cp -c -R -p) of $OMNIGRAPH_WARM_TARGET or of the most recently built target/ among the repository's worktrees whose ancestor Cargo configs match. It does nothing when target/ exists, off macOS, or across volumes. It checks for the same APFS volume because cp -c silently falls back to a full byte copy. -p keeps mtimes so Cargo still rebuilds workspace crates whose sources differ. A checked-in .claude/settings.json SessionStart hook (matcher startup) runs it for Claude Code sessions; .gitignore now ignores .claude/* except that file, so .claude/worktrees/ stays ignored. Codex and other agents get the instruction from AGENTS.md.
  • Docs. New docs/dev/local-builds.md (linked from AGENTS.md, which also gains a one-line pointer in Build and test, the dev index and testing.md), a short note in ci.md, and optional shared sccache S3/R2 backend configuration. No buckets or credentials are created.

Findings

  • Integration-test consolidation (test: consolidate integration suites into shared binaries #945) and nextest scheduling (ci: schedule workspace tests with cargo nextest #930) were already on main. The standalone engine binaries left today need their own process (failpoints, current_exe re-exec, env mutation), apart from schema_apply, writes and policy_engine_chassis. Main's test-workspace-build-timings shows those compile in parallel after the engine lib (12.2s, 10.1s), while integration is already 22.3s on the critical path. Folding them in would trade link time for a longer serial step, so they stay as they are.

  • sccache shares only part of a cold build across worktrees: its Rust key hashes the compile's cwd (src/compiler/rust.rs, step 9, unchanged on main). Cargo compiles registry crates from their own directory, so those can hit, but compiles workspace crates from the workspace root, so they miss in every worktree. Proc-macros, links and build-script runs are never cached. SCCACHE_BASEDIRS is C/C++ only. The CoW target clone reuses all of it.

  • Worktrees nested in the main checkout (.claude/worktrees/*) build with --cfg tokio_unstable twice, because Cargo joins build.rustflags arrays from every ancestor .cargo/config.toml. Their artifact hashes therefore differ from the main checkout's, and a clone of the main checkout's target/ rebuilt all 646 units. The seed script only clones from a worktree with identical ancestor configs. A string-valued rustflags (which Cargo doesn't join) is not used, because Cargo refuses to merge a string with an array, so every checkout still on the array form would fail to load its config.

Measurements

cargo test --no-run -p omnigraph-engine --locked on this Mac (18 cores, Rust 1.98.0, sccache 0.18). Two other sessions were building at the same time (load 13–40), so absolute times are inflated; the ratios are what matter.

Scenario Wall CPU (user) Units compiled
Fresh worktree, empty target/ (sccache: 51% Rust hits) 2,581 s 955 s all
Fresh worktree, target/ cloned from the main checkout (config mismatch) 1,538 s 793 s 646
Fresh sibling worktree, seed-target.sh clone (3.9 s) 155 s 399 s 8 (workspace crates only)
Same worktree after touch crates/omnigraph/src/lib.rs 21 s 83 s engine only
  • Engine test binaries: 12, the same before and after (no test layout change).
  • target/ after the engine test build: 9.2 GB. The clone consumed about 0.3 GB of new disk (copy-on-write); the rebuilt workspace crates add the rest.
  • The mold linker path is Linux-only and is first exercised by this PR's CI.

ragnorc commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

Recommendation

Revise.

Why, what, and impact

A cold worktree, or a switch between feature sets, compiles the Lance and DataFusion stack again. Link time is a large part of each test binary. This pull request adds local build configuration so that cost is paid less often. It does not change queries, writes, publication, or the on-disk graph.

Four cargo aliases in .cargo/config.toml select the workspace test graph. scripts/cargo-linker.sh adds -fuse-ld=mold on Linux when mold is on PATH. scripts/seed-target.sh copies a warm target/ into a new macOS worktree with an APFS clone. Test and lint workflows install mold. A Claude Code session-start hook calls the seed script. The new page docs/dev/local-builds.md describes the three mechanisms.

Linux CI test jobs link with mold. I checked the fallback with mold removed from PATH. The binary comment is the same LLD 22.1.6 line that rustc 1.97.1 writes with no linker override. Release workflows do not install mold, and that fallback is what they will hit. macOS keeps the system linker. The observable engine result is unchanged.

Blocking

  1. dev-clippy is not on the feature set the comment and the new docs promise. The alias is clippy --workspace --all-targets --locked -- -D warnings -W clippy::dbg_macro. It does not pass --features omnigraph-engine/failpoints,omnigraph-cluster/failpoints. The comment on the alias table says a switch among test, nextest, clippy, and --no-run never rebuilds for a feature change. docs/dev/local-builds.md lists cargo dev-clippy in that same set.

    CI already lints two graphs in the Lint (clippy) job. The first graph uses default features. The second passes FAILPOINT_FEATURES. Both graphs contain code. crates/omnigraph/src/db/write_queue.rs and crates/omnigraph-core/src/seams.rs have #[cfg(not(feature = "failpoints"))] branches, so one clippy command cannot see both.

    Put the failpoint features on dev-clippy, so it shares artifacts with dev-test. Add a second alias for the default-features graph, and name that graph in the docs. Update the config comment and docs/dev/local-builds.md in the same change.

  2. scripts/seed-target.sh runs BSD stat -f before it tests for macOS. On Linux that stat is a different command. I ran sh scripts/seed-target.sh from the pull-request worktree while another worktree had target/debug. The script printed stat: cannot read file system information for '%m' and Illegal number, then exited 0. The header says every failure is silent. AGENTS.md tells every new worktree to run this script. Move the Darwin test above the worktree scan so Linux returns before stat.

Optional

  1. docs/dev/local-builds.md says that installing or removing mold relinks without a recompile. After a successful link, I installed mold and ran cargo build again. Cargo printed Fresh and did not link. The linker path is stable, so the fingerprint does not change. The next link that Cargo already needs will use mold. An existing binary stays as it is. Say that.

  2. The seed script closes the long race. It copies into target.cloning.<pid> and checks that target/ is still absent. The last step is still mv. If target/ appears in that gap, mv moves the clone inside target/ and returns 0, and the script prints success. I reproduced that mv result. rename(2) fails when the destination directory exists, so the script can delete the temp directory instead. This matters because a Claude SessionStart hook runs in the background and the user can start a build while the copy runs. The check after cp already covers that long window.

  3. Arguments to dev-clippy are clippy arguments. The alias already contains --, and Cargo appends user arguments after the alias text. cargo dev-clippy --help printed Clippy help. A trailing -p does not select a package. The other three aliases can take Cargo arguments. Say this next to the dev-clippy example.

  4. The hook matches only startup, and the command cds to $CLAUDE_PROJECT_DIR. I did not run Claude Code. The current Claude Code hook docs say that variable stays on the checkout where the session started when Claude later enters a worktree, and that cwd in the hook input is the worktree. They also list matcher fork for /branch. This hook does not use cwd or fork. A nested .claude/worktrees/* checkout, which the seed script is written to recognize, is then seeded only when someone runs the script there. AGENTS.md already says to do that. The hook docs should say the same limit.

Tradeoffs and liability

The linker script is the right single owner. One target.*.rustflags entry would replace [build] rustflags and drop --cfg tokio_unstable. I confirmed that merge rule on Cargo 1.97.1. A string rustflags value in one config and an array in a parent fails to load. The script avoids both traps. RUSTFLAGS still replaces [build] rustflags and does not clear target.*.linker. I built with RUSTFLAGS= empty. rustc still received -C linker=..., and with mold absent the binary comment matched the default LLD link. Release, publish, nix, and scripts/install-source.sh therefore use the wrapper. They link as before only while mold is absent. A machine that has mold will mold-link those builds too. That is a new PATH-dependent input to in-tree release binaries. CI release workflows do not install mold, so their result stays on LLD.

The seed script is the right owner for the warm directory. The ancestor checksum is a narrow copy of Cargo's config walk. It hashes parent .cargo/config.toml and .cargo/config files, not the worktree's own file. I confirmed the behavior it is guarding. On Cargo 1.97.1 a nested checkout joins parent and child build.rustflags arrays, so --cfg parent_flag --cfg child_flag both appear. Two sibling nested checkouts with the same child flags get the same joined list. The checksum matches that case. It will drift if Cargo's walk changes. That dependence should stay in this script, not in a second copy.

The aliases are a second copy of the canonical feature string. ci.yml already has FAILPOINT_FEATURES. AGENTS.md and docs/dev/testing.md repeat the workspace command. Five more aliases would copy it five more times. Prefer one list that the aliases and the CI env var both follow. The mold apt-get lines follow the existing protobuf lines. A shared install step would pay off only if that list keeps growing.

What this removes: repeated feature-unification rebuilds for the three test aliases, full cold builds for a matching macOS worktree, and link time on the CI jobs that install mold.

What this adds: a shell process on every Linux link, including release. A macOS-only clone script that Linux contributors are still told to run. A Claude hook. Another copy of the feature string. Docs that track Cargo fingerprint rules and the sccache hash. Those docs are accurate today. sccache 0.16.0 (the CI pin), 0.19.0, and current main all hash the compile cwd as input 9. Pull request mozilla/sccache#2678, which would let SCCACHE_BASEDIRS change the Rust key, is still open. I also checked rustc's cwd on 1.97.1. A workspace crate compiles from the workspace root. A registry crate compiles from its own directory. That part of docs/dev/local-builds.md matches this toolchain.

-p on the clone is load-bearing. I copied a built target/ without preserved mtimes, set the copied artifacts to the current time, and changed the source while leaving the source mtime in 2020. Cargo printed Finished in 0.00s and the binary still printed the old text. With preserved mtimes and a new source mtime, Cargo recompiled. I could not run cp -c here. This host is Linux.

No engine invariant is in play. There is no second publication door, no snapshot mix, and no Lance behavior change. The Lance 13.0.0 pin is untouched. Other open pull requests are product and RFC work. I do not see one that needs a different linker or a different alias set. A later toolchain bump still goes through this wrapper. The safe fallback is the one this script has now: forward rustc's arguments, including -fuse-ld=lld and the -B path to the self-contained gcc-ld directory, and append -fuse-ld=mold only when mold is present.

Validation

Head reviewed: b605208803d1d541e09293fe77f6c61a4b62517f. Merge base with current origin/main: 269299b757f9dcce1a9a45449ebbaefa054b4be3. Toolchain: rustc 1.97.1. No earlier review or review comment was on the pull request at post time.

GQT cannot see a linker or a Cargo alias. I did not add a .gqt case. The same library tests at the merge base and at the head are the behavior check. omnigraph-seams has no source diff between those commits.

  • Merge base, default linker: CARGO_TARGET_DIR=/tmp/og-mb-target cargo test -p omnigraph-seams --lib --offline --locked at 269299b. Result: 14 passed, 0 failed, 0 ignored. Binary comment: Linker: LLD 22.1.6.
  • Head, mold 2.30.0 on PATH: the same command with CARGO_TARGET_DIR=/tmp/og-head-target at b605208. Result: 14 passed, 0 failed, 0 ignored. Binary comment: mold 2.30.0.

Probe program, output link-ok 42 in each case:

  • No linker override. Comment: LLD 22.1.6.
  • Wrapper, mold absent. Comment: the same LLD 22.1.6 text. rustc passed -fuse-ld=lld and -B to the self-contained gcc-ld directory. The script forwarded those arguments to cc.
  • Wrapper, mold present. Comment: mold 2.30.0. Output still link-ok 42.
  • Install mold after a finished build. Next cargo build was Fresh.

Alias checks at the head:

  • cargo dev-test --help and cargo dev-build-tests --help exit 0.
  • cargo dev-test --offline --features definitely-not-a-feature exits 101. Cargo lists the workspace packages and does not list omnigraph-gqt, omnigraph-gqt-served, or omnigraph-dst. So the excludes are in force.
  • cargo dev-clippy --help prints Clippy help.
  • cargo dev-nextest --help exits 101 with no such command: nextest. nextest is not installed in this environment. The alias text matches the CI nextest invocation except --profile ci and --no-fail-fast, which are run options, not a feature set.

Other checks:

  • Nested build.rustflags arrays join on Cargo 1.97.1. A string value and a parent array fail with expected string, but found array.
  • Cargo resolves linker = "scripts/log-linker.sh" to an absolute path. A build from a subdirectory links.
  • git check-ignore leaves .claude/settings.json unignored and ignores .claude/settings.local.json and .claude/worktrees/example.
  • CI for this exact head is green, including Test Workspace, Lint (clippy), GQT (ordinary), GQT (dst), GQT (served), and DST pinned suite. Those jobs install mold before they build. Release workflows did not run.

The timing table in the pull-request description is the author's macOS measurement. I did not reproduce those wall times.

Limitations

I did not run cp -c on APFS, and I did not link aarch64-unknown-linux-gnu. I did not run the release, publish, nix, or source-install builds. I did not rebuild the full workspace locally. I did not run Claude Code. The seams tests cover one small crate, not the workspace suite. The workspace suite evidence is the green CI run at this head.

Inline comments

.cargo/config.toml line 71, side RIGHT:

dev-clippy omits --features omnigraph-engine/failpoints,omnigraph-cluster/failpoints. The comment above this alias says a switch among test, nextest, clippy, and --no-run does not rebuild for a feature change. docs/dev/local-builds.md lists cargo dev-clippy in that same set.

The Lint (clippy) job lints two graphs. The first uses default features. The second passes FAILPOINT_FEATURES. crates/omnigraph/src/db/write_queue.rs and crates/omnigraph-core/src/seams.rs contain #[cfg(not(feature = "failpoints"))] code, so one clippy command cannot see both graphs.

Give dev-clippy the same features as dev-test. Add a second alias for the default-features graph. Update this comment and docs/dev/local-builds.md together.

scripts/seed-target.sh line 42, side RIGHT:

This stat -f runs before the macOS test on line 49. On Linux, stat -f is not the BSD mtime form. I ran sh scripts/seed-target.sh here while another worktree had target/debug. The script printed stat: cannot read file system information for '%m' and Illegal number, then exited 0.

The header says every failure is silent. AGENTS.md tells every new worktree to run this script. Move [ "$(uname -s)" = Darwin ] || exit 0 above the worktree scan.

@ragnorc
ragnorc enabled auto-merge October 31, 2026 20:23
@ragnorc
ragnorc added this pull request to the merge queue Oct 31, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to no response for status checks Oct 31, 2026
@ragnorc
ragnorc merged commit f728f01 into ModernRelay:main Nov 1, 2026
32 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.

1 participant