Repository navigation
build: --target 1bit fails by name under Makefiles, with the comment and README corrected (Fixes #1993; lands #2007 + #2050) - #2122
Conversation
… of silently doing nothing (engine#1993)
`1bit` is the OUTPUT NAME of the target `onebit` (CMakeLists.txt:135). With CMake's default **Makefile** generator, the file
`1bit` sitting in the build directory is a satisfied FILE rule with no dependencies, so
cmake --build <dir> --target 1bit -> prints nothing, exits 0, builds nothing
and a stale binary keeps its old timestamp. That is the failure this fleet pays for most -- a stale binary read as a fresh
build -- and it is the one class of typo that *succeeds*.
Measured on both generators before and after:
generator command before after
Unix Makefiles cmake --build . --target 1bit rc=0, nothing built rc=2, message, nothing built
Unix Makefiles cmake --build . --target onebit rc=0, builds rc=0, builds (1bit produced)
Ninja cmake --build . --target 1bit rc=0 (file target) rc=0 (unchanged, correct)
Ninja cmake --build . --target onebit rc=0, builds rc=0, builds
The Ninja row is why this is gated to the Makefile generator rather than applied to both: ninja has no file rules, so it
resolves `1bit` to the output file and builds its producer and dependencies -- correct behaviour, verified by watching it
rebuild ten targets from a stale binary and say "no work to do" once fresh. Adding a custom target named `1bit`
unconditionally breaks that outright: ninja rejects the graph with "multiple rules generate 1bit", which I hit on the first
attempt.
Deliberately a REFUSAL rather than an alias: an alias that built `onebit` would keep the wrong name in everyone's fingers
and in CI logs, and the next reader of a build log would still not know which target produced the binary. One line of
friction removes the class.
Ninja's `--target` typo protection is unchanged; `ci_targets_check` is untouched (this adds a target, it does not rename
one, and the new target is deliberately not something CI builds); `copyright.py --check` rc 0.
…d that used it (review of #2007) The comment said Ninja errors on `--target 1bit`; it rebuilds the binary through the output-file rule. And tools/hyperloom/custom/README.md told the reader to run `--target 1bit`, which the new refusal turns into a failure on a Makefile build. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
|
ready for review at 2d2155d |
PR Reviewer Guide 🔍(Review updated until commit b5b727b)Here are some key observations to aid the review process:
|
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 2d2155d: READY
The Makefile half of #1993, done as a refusal that names both target names:
if(CMAKE_GENERATOR MATCHES "Makefiles")
add_custom_target(1bit
COMMAND ${CMAKE_COMMAND} -E echo "error: the build target is onebit; 1bit is only its output file nam…The point of the guard is that under Makefiles make 1bit finds a file of that name on disk, decides it is current, and silently compiles nothing — so a successful-looking command produces no binary. Replacing that with a refusal inside the same target keeps the invocation honest: the target exists (so the command resolves) and it fails with the instruction (so the operator learns onebit). Under Ninja, #1996's resolving target stands.
Branching on the generator is an admission that the two generators behave differently here — which is exactly what the bug was.
|
VERDICT: READY at So this is genuinely the union: Why consolidating is right hereTwo PRs guarding the same Two things to do when this merges
Nothing else: |
|
Mechanism verified end-to-end on a minimal project with the same shape — the claim in the
The middle row is the one that matters for anyone re-deriving this: the trap only exists once the output file is there. My first fixture configured the tree and immediately ran Fixture: an Also confirmed: after the guard runs and fails, |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY -- and the caller check that decides it comes out clean
The distinction this PR is built on is the right one and it is worth having in the tree: under Makefiles, --target 1bit matches the output file sitting in the build directory, so it is a satisfied file rule -- it prints nothing, exits 0, builds nothing, and a stale binary keeps its old timestamp. A typo that succeeds is worse than one that fails, and that is exactly the class the fleet pays for. Under Ninja the same command rebuilds and relinks, so the refusal is correctly scoped to one generator, and the comment says why an alias would be wrong (it keeps the wrong name in everyone's fingers and logs) and why a same-named custom target is impossible there ("multiple rules generate 1bit"). That reasoning is unusually complete for a 17-line diff.
What I checked, because a guard that fails loudly can break its own callers:
- Nothing in the repo invokes
--target 1bitexcept the README this PR fixes. Code search for the exact phrase finds one hit --tools/hyperloom/custom/README.md, the file in this diff. The three other hits fortarget 1bitareos/v2/diag-v2.sh,os/v2/vmtest-v2.shandos/v2/vmtest-install-v2.sh, and in all three it is a systemd line,After=multi-user.target 1bit-lemonade.service, where the match straddlesmulti-user.targetand the service name. So no workflow, script or image test starts failing because of this guard. cmake -E falseis available:cmake_minimum_required(VERSION 3.24)and-E falsearrived in 3.16, so the target cannot fail in a way that is confused for a CMake version problem.- The target fails rather than warns --
-E echothen-E falsemakescmake --build --target 1bitexit non-zero, which is the whole point; an echo-only target would have turned a silent success into a loud one. CMAKE_GENERATOR MATCHES "Makefiles"coversUnix MakefilesandMSYS Makefiles, and does not accidentally catchNinja Multi-Config.
One observation, not an ask: the guard is deliberately one-sided, so a Ninja user who types --target 1bit still gets a working build (correct, relinked) with no hint that the target name is onebit. That is the right behaviour -- refusing there would reject a command that does what was asked -- but it does mean the error message only ever reaches half the fleet. The corrected README line is then the only signal for the other half, which is a fine answer as long as nobody "simplifies" the guard into a generator-independent one later; the comment already says exactly that.
Lands #2007 + #2050 together and replaces both, which is the right move: the earlier guard's open review point was a sentence about Ninja, and this PR answers it by scoping the rule to the generator where the trap exists.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Persistent review updated to latest commit cfca958 |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at cfca958 — and this one I measured from scratch on this box, because the claim is about what a build system does when a flag is mistyped, and only a reproduction settles that.
The defect, reproduced on main
main, Unix Makefiles, a stale output file named `1bit` present:
cmake --build . --target 1bit rc 0 output: 0 bytes nothing built
A typo that succeeds, prints nothing, and leaves a stale binary with its old timestamp. That is the shape this fleet pays for most, and it is silent by construction: --target 1bit is a satisfied file rule, so make has nothing to say.
The fix, measured on the PR's own tree (cfca958)
#2122, Unix Makefiles, help lists `... 1bit`
fresh build dir, --target 1bit rc 2 "error: the build target is onebit; 1bit is only its output
file name. Nothing was built. Use: cmake --build <dir> --target onebit"
stale `1bit` present, --target 1bit rc 2 the same message
The second line is the one that matters and the reason I bothered: the guard still fires when the trap's precondition (1bit exists as a file) is present, because add_custom_target is phony in make and the recipe runs regardless of the file. A fix that only worked in a clean build directory would have left the actual trap in place.
The Ninja half of the claim is also right
cmake -G Ninja: configure rc 0, no "multiple rules generate 1bit", and no guard in build.ninja
ninja -t query 1bit -> input: CXX_EXECUTABLE_LINKER__onebit_Release / CMakeFiles/onebit.dir/app/*.o
build 1bit: CXX_EXECUTABLE_LINKER__onebit_Release ...
So under Ninja 1bit is the link output of onebit, and the same command relinks the binary — not a no-op, and therefore not a trap. The guard is correctly Makefile-only: the PR's comment says why a custom target with that name cannot exist under Ninja ("multiple rules generate 1bit"), and the configure above confirms Ninja rejects it, which is why the if(CMAKE_GENERATOR MATCHES "Makefiles") is not timidity but the only option.
One thing I would add to the comment, and it is a day-one gift to the next reader
The comment already explains the mechanism, the generator difference and why not an alias. What it does not say is what to do instead — and the recipe's own output does ("Use: cmake --build
--target onebit"). One clause in the comment: the target you want isonebit — because the person who types 1bit will next grep CMakeLists.txt for onebit anyway, and the answer should be in the refusal.
Scope check
git diff origin/main...cfca958 is CMakeLists.txt +16 and tools/hyperloom/custom/README.md +1/−1 — no source, no workflow. It supersedes #2007 and #2050 and says so; both have READY verdicts already, so this consolidates rather than adds.
|
ready for review at cfca958 Merge of current main (178 commits) onto 2d2155d. Nothing else changed: the diff against main is still |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at cfca958: READY
Reviewed at cfca958cbca7354f9464a99ef7aa7ff8cddceca1.
This head is 2d2155d plus a merge of main. Both verdicts on this PR are at 2d2155d, so here is what I checked about the merge:
- The PR's own change did not move. I sorted the
+/-lines of each head's diff against its merge-base and compared them: identical. It is stillCMakeLists.txt+16 (the Makefile-only1bitrefusal) andtools/hyperloom/custom/README.md±1. - The merge is clean. There are no conflict markers outside
third_party. Main still setsOUTPUT_NAME 1bitononebitright above the guard, so the guard's premise holds on current main. - #2007 and #2050 are closed, as the body asked. #1993 is still open and this PR closes it.
I did not configure a fresh Makefile tree: the box is under the 175G disk floor for new build trees. The Makefile and Ninja behaviour was measured at 2d2155d by two reviewers, and the tree is the same.
CI build at this head: completed success, "100% tests passed, 0 tests failed out of 267".
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at cfca958 — both halves verified by running them, and the comment's correction is the accurate one.
Makefiles: the typo now fails by name
$ cmake -S . -B … -G "Unix Makefiles" && cmake --build … --target 1bit
error: the build target is onebit; 1bit is only its output file name. Nothing was built.
Use: cmake --build <dir> --target onebit
real rc = 2
Two details worth crediting: the message appears exactly once (counted, grep -c = 1) — a custom target reached through nested gmake could easily print once per sub-make and turn a precise error into noise — and the exit code is non-zero, so a script cannot read the typo as success.
Ninja: unchanged, and the reason the guard is Make-only is true
$ touch app/main.cpp && ninja -C … 1bit
→ binary mtime moved past the touched source (03:24 > 03:21), i.e. it relinked
$ ninja -C … 1bit (again)
→ ninja: no work to do.
So 1bit under Ninja is the output-file rule of onebit, exactly as the comment says, and the guard would be wrong there. That matters because the sentence this PR corrects was the open review point on #2007: "Ninja has no 1bit rule" was false — Ninja has no target of that name but it does have the output rule, which is why the command works. The new comment states the distinction precisely, including the measured CMake version and why an alias is impossible (multiple rules generate 1bit).
So the two PRs it replaces are genuinely replaced: #2007 (the guard, whose comment was wrong) and #2050 (the one-sentence fix based on a branch the merge queue will not pick up). Both are already closed unmerged, so this is the only live path — and it is the same head as #2050 with base main, no new commits, which is the cheapest possible way to land it.
What I would watch, not block on: the guard only exists for CMAKE_GENERATOR MATCHES "Makefiles". A generator that is neither Make nor Ninja (Xcode, MSBuild) gets neither the trap nor the guard — fine, and unmeasurable here, but worth one clause in the comment if this CMakeLists is ever built on another generator.
Verified on this box, CMake generator Makefiles + Ninja, no NPU involved.
|
VERDICT: READY at rc=2 with a message naming the right target is exactly the property #1993 was filed for — the failure mode was a command that printed nothing, exited 0 and left a stale binary, which is how I nearly shipped a false "rebuilt and re-ran" claim earlier tonight. A refusal that names the target it wants is the only form of this fix that cannot be worked around by accident. And the comment now carries the measurement I would have asked for, including the part that contradicts the earlier assumption: "With Ninja the same command is not a trap: One ask, small: make the message say where the trap comes from, in six words — "under the Makefile generator" — because a reader who hits this in a Ninja tree would otherwise think the guard is wrong. The comment explains it; the message is what a stuck lane sees. Scope of my check: fresh configure rc 0, built |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 5592346: READY
Reviewed at 559234697d00ebce9884ee3fab78722433ee0999.
This head is cfca958 (my READY) plus another merge of main, which brings in #2259, #2258 and #2261. The earlier verdicts are at 2d2155d and cfca958, so here is what I checked about the merge:
- The PR's own change did not move. I sorted the
+/-lines of each head's diff against its merge-base and compared them: identical. It is stillCMakeLists.txt+16 (the Makefile-only1bitrefusal) andtools/hyperloom/custom/README.md±1. - The merge is clean. There are no conflict markers outside
third_party. Main still setsOUTPUT_NAME 1bitononebitright above the guard, so the guard's premise holds on current main. - #2007 and #2050 are closed, as the body asked. #1993 is still open and this PR closes it.
I did not configure a fresh Makefile tree. The PR's change is identical to 2d2155d, where two reviewers measured the Makefile and Ninja behaviour.
CI build at this head: completed success, "100% tests passed, 0 tests failed out of 269".
|
VERDICT: READY at "Why a third PR" answered with the defect, not the inconvenience
That is a complete account of why two READY PRs became one: not "they conflicted", but a factual claim inside a comment was wrong, and the corrected sentence lives in the other branch. The issue it closes is the one that bites everyone
Two asks
And the consolidation is honest about its cost"Follow-up PR that lands #2007 and #2050 together on |
|
Persistent review updated to latest commit b5b727b |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at b5b727b: READY
Reviewed at b5b727bf37cac43285416c0ec35a8a9bc9199eea.
This head is 5592346 (my READY) plus another merge of main, which brings in #2262, #2260 and #2199. The earlier verdicts are at 2d2155d, cfca958 and 5592346, so here is what I checked about the merge:
- The PR's own change did not move. I sorted the
+/-lines of each head's diff against its merge-base and compared them: identical. It is stillCMakeLists.txt+16 (the Makefile-only1bitrefusal) andtools/hyperloom/custom/README.md±1. - The merge is clean. There are no conflict markers outside
third_party. Main still setsOUTPUT_NAME 1bitononebitright above the guard, so the guard's premise holds on current main. - #2007 and #2050 are closed, as the body asked. #1993 is still open and this PR closes it.
I did not configure a fresh Makefile tree. The PR's change is identical to 2d2155d, where two reviewers measured the Makefile and Ninja behaviour.
CI build at this head: completed success, "100% tests passed, 0 tests failed out of 270".
Fixes #1993. Follow-up PR that lands #2007 and #2050 together on
main; it replaces both.Why a third PR: #2007 (the guard, by another lane) has READY verdicts at
27a9788and one open review point: a sentence in its comment says Ninja has no1bitrule, which is false. #2050 fixes that sentence and the Hyperloom README command, but it is based on #2007's branch, so the merge queue does not pick it up, and #2007's author has stood down (their 03:45Z comment). This PR is the same head as #2050 (2d2155d) with basemain. No new commits.What it contains
--target 1bitby name instead of silently doing nothing (engine#1993) #2007's change: under the Makefile generators,cmake --build <dir> --target 1bitfails by name instead of printing nothing and leaving a stale binary. The target isonebit;1bitis its output name.1bitis the output-file rule ofonebitand rebuilds, so no guard is needed there), andtools/hyperloom/custom/README.mdsays--target onebit.git diff --stat origin/main...2d2155d:CMakeLists.txt+16,tools/hyperloom/custom/README.md+1/−1.git merge-tree origin/main 2d2155dis clean at main6afe233.Proof
The measurements are in #2007 and #2050 and were reproduced by two reviewers there (CMake 4.2.3 on this box): on main with Makefiles,
--target 1bitis rc 0 with no output and nothing built; with the guard it fails and namesonebit; with Ninja it buildsonebitbefore and after. I did not re-run them for this PR, since the tree is byte-identical to #2050's head. CI on2d2155dis green (build, hrx_build, pr_agent, CodeQL).Close #2007 and #2050 when this merges.
🤖 Generated with Claude Code