From e3daf561d1b1fa15bf787fe4e7003571e177a2ff Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Wed, 23 Sep 2026 20:13:44 +0200 Subject: [PATCH] fix(tooling): cargo_binary rebuilds a stale binary, not only a missing one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit core.process.cargo_binary built the Rust binary only when it was absent, a behaviour inherited from the three bash wrappers it replaced (tooling/atlas, generate-brands, generate-corporations). After an edit to the Rust source it went on running the old binary. For generate_brands this breaks the one promise the systems.db stamp makes. `make regen-db` stamps the SHA of the NEW brand sources onto generated_brands.toml produced by the OLD code, so the gate reports fresh and the output is stale. It had already happened: the committed binary wrote "Re-run: reach generate brands" while main.rs still said tooling/generate-brands, so what was on disk had not been built from the committed source. It now always runs `cargo build --bin ` and lets cargo decide. An up-to-date build is incremental and fast; a real rebuild is announced as an event, since a silent ninety-second compile reads as a hang. Proven on generate_brands (T-1253): with the old header restored, the rebuilt binary reproduces generated_brands.toml byte-for-byte (sha256 e748531…). With the new header it differs by exactly that one line. Co-Authored-By: Claude Opus 5.5 --- tooling/core/process.py | 43 +++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/tooling/core/process.py b/tooling/core/process.py index 5304fa299..e2948a166 100644 --- a/tooling/core/process.py +++ b/tooling/core/process.py @@ -147,34 +147,39 @@ def run( def cargo_binary(name: str, *args: str, capture: bool = True) -> str: - """Run a Rust binary from `server/`, building it first if it is absent. + """Run a Rust binary from `server/`, building it first if it is stale. Three scripts wrote this by hand — `tooling/atlas`, `generate-brands`, `generate-corporations` — each ~24 lines of identical bash: check for the debug binary, `cargo build --bin` it if missing, `exec` it with every argument. Three copies of one idea is a substrate, so it lives here. - Build-if-missing is kept because it is genuinely useful on a cold checkout, - but it is announced rather than silent: a first run that takes ninety - seconds with no explanation reads as a hang. + It ALWAYS asks cargo to build, and lets cargo decide whether anything is + stale. The copies all built only when the binary was MISSING, so an edit + to the Rust source ran the old binary — and for generate_brands that meant + `make regen-db` stamped the new source SHA onto output the old code + produced, the one thing the stamp exists to prevent (found T-1253: a header + change to main.rs did not reach generated_brands.toml). An up-to-date build + is incremental and fast; a real rebuild is announced, since ninety silent + seconds reads as a hang. """ - binary = config.path("server", "target", "debug", name) - if not binary.is_file(): - from tooling.core import console + from tooling.core import console - console.event(f"building the {name} binary (first run)", level="warn") - build = run( - ["cargo", "build", "--bin", name], - cwd=config.path("server"), - check=False, - missing_fix="install Rust — make setup-rust", + binary = config.path("server", "target", "debug", name) + build = run( + ["cargo", "build", "--bin", name], + cwd=config.path("server"), + check=False, + missing_fix="install Rust — make setup-rust", + ) + if build.returncode != 0: + raise ReachError( + f"could not build {name}\n{(build.stderr or '').strip()}", + fix=f"cd server && cargo build --bin {name} — for the full error", + exit_code=build.returncode, ) - if build.returncode != 0: - raise ReachError( - f"could not build {name}\n{(build.stderr or '').strip()}", - fix=f"cd server && cargo build --bin {name} — for the full error", - exit_code=build.returncode, - ) + if "Compiling" in (build.stderr or ""): + console.event(f"rebuilt the {name} binary — its source changed since the last build", level="warn") result = run([str(binary), *args], check=False, capture=capture) if result.returncode != 0: