fix(tooling): cargo_binary rebuilds a stale binary, not only a missing one

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 <name>` 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 <noreply@anthropic.com>
This commit is contained in:
2026-09-23 20:13:44 +02:00
co-authored by Claude Opus 5.5
parent 5a01450585
commit e3daf561d1
+24 -19
View File
@@ -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: