build: fix a target that does not compile, and advice that discards a build tree (fixes #604, fixes #592) - #606
Merged
Yaraslaut merged 2 commits intoSep 20, 2026
Conversation
fixes #604) bank_gui_qml_tests compiles examples/common/testkit/testkit_main.cpp, whose `#include <testkit/log_level.hpp>` resolves against exactly one directory in the tree -- the repository's tests/ -- and the target reached it through nothing. It links Catch2::Catch2 directly, where its sibling bank_gui_tests, one target above, links morph_test_main and gets the path publicly from morph_test_log_level. That divergence is the defect; the two targets diverge for a real reason (this one owns its own QGuiApplication-holding main, so it cannot link a library that carries a `main` of its own), but the reason covers only the `main`, not the include path. So link morph_test_log_level alongside Catch2::Catch2 -- the same split, for the same reason, that morph_qt_tests already uses in tests/qt/CMakeLists.txt. That keeps the siblings consistent on what they get from the testkit while leaving each one's `main` where it belongs. Reproduced on 26bfdb8 with the linux-everything preset, which is the only configuration in the repository that sets MORPH_BUILD_BANK_GUI=ON: FAILED: examples/bank/CMakeFiles/bank_gui_qml_tests.dir/__/common/testkit/testkit_main.cpp.o .../examples/common/testkit/testkit_main.cpp:21:10: fatal error: 'testkit/log_level.hpp' file not found 21 | #include <testkit/log_level.hpp> | ^~~~~~~~~~~~~~~~~~~~~~~ 1 error generated. and after the change, on the same tree: [5/10] Building CXX object examples/bank/CMakeFiles/bank_gui_qml_tests.dir/__/common/testkit/testkit_main.cpp.o [6/10] Building CXX object examples/bank/CMakeFiles/bank_gui_qml_tests.dir/tests/gui/test_bank_gui_qml_behaviour.cpp.o [7/10] Linking CXX executable examples/bank/bank_gui_qml_tests $ QT_QPA_PLATFORM=offscreen ./examples/bank/bank_gui_qml_tests All tests passed (32 assertions in 2 test cases) The linked library is doing more than satisfying an include: the --log-level option that log_level.hpp declares is live on the binary, which is what tells the two apart -- $ ./examples/bank/bank_gui_qml_tests --log-level nonsense error: --log-level: unknown log level 'nonsense'; expected one of debug, info, warn, error, off No CI job configures MORPH_BUILD_BANK_GUI, which is why master carried a target that does not compile. That gap is #605 and is deliberately not addressed here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GS5K2vqZtC4xbRiGJHT7jH
#592) When a launcher is already in the CMake cache, CompileCache.cmake returns early and told the reader to "reconfigure with --fresh to let this module choose instead". Reconfiguring the same build directory with cmake -B <dir> -UCMAKE_C_COMPILER_LAUNCHER -UCMAKE_CXX_COMPILER_LAUNCHER removes exactly the two entries the guard reads, falls through to the selection below, and leaves the directory standing. This is a wording change only: --fresh still works, is still the right answer when a full reconfigure is genuinely wanted, and is kept -- with what it actually costs, and with the one case where it does not work at all. Measured on an isolated harness that does nothing but include() this module, with a trivial C target so there is an object to lose. 1. -U reaches the selection and keeps the tree: $ cmake -S src -B b -G Ninja -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -- [cache] Compiler launcher already set externally (C='ccache', CXX='ccache'); leaving it untouched. $ cmake --build b # object inode 39761163 $ cmake -S src -B b -UCMAKE_C_COMPILER_LAUNCHER -UCMAKE_CXX_COMPILER_LAUNCHER -- [cache] Enabling fastcache-cc at 127.0.0.1:6674 (...) for C/C++ compilation $ stat -c '%i' b/CMakeFiles/harness.dir/main.c.o 39761163 $ grep CMAKE_GENERATOR: b/CMakeCache.txt CMAKE_GENERATOR:INTERNAL=Ninja 2. --fresh deletes CMakeFiles/ and takes the generator with it unless it is re-passed -- the same "generator/flag drift" recorded in docs/superpowers/progress/2026-08-19-ledger-rung5-progress.md: $ cmake -S src -B b --fresh $ stat -c '%i' b/CMakeFiles/harness.dir/main.c.o stat: cannot statx '.../b/CMakeFiles/harness.dir/main.c.o': No such file or directory $ grep CMAKE_GENERATOR: b/CMakeCache.txt CMAKE_GENERATOR:INTERNAL=Unix Makefiles # a stale build.ninja is still in the directory 3. And for the "a preset" source the message itself names, --fresh does not work at all -- the preset re-applies its cacheVariables on the very reconfigure --fresh triggers, so the tree is gone and the pin remains: $ cmake -S src --preset pinned --fresh -- [cache] Compiler launcher already set externally (C='ccache', CXX='ccache'); leaving it untouched. -- [cache] That value comes from the CMake cache (a -D, a preset, or an older configure); ... while -U on a plain reconfigure of that same directory does clear it, until the preset is next run. One thing the new text does not claim, because it is not true: -U does not save the compiling. A launcher is part of every C/C++ compile command, so the next build recompiles those TUs by either route (measured -- the harness object was rebuilt after the -U reconfigure). What -U saves is the configure state: the populated _deps tree, the generator, generated sources, cached find_ results. The comment says so in those words. Swept the rest of the tree for the same advice: `grep -rn -- --fresh` outside build/ and .git/ finds only this line and one historical incident report in docs/superpowers/progress/, which records a --fresh going wrong rather than recommending one and is left alone. docs/, CONTRIBUTING.md and README.md contain no reconfigure or launcher advice at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GS5K2vqZtC4xbRiGJHT7jH
Yaraslaut
force-pushed
the
fix/604-bank-qml-tests-include-592-fresh-advice
branch
from
September 20, 2026 05:12
00b07c6 to
88a1048
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two build-configuration tickets, neither touching library source, batched onto one branch so they share a CI cycle. Separate commits; they can be read independently.
675943ab— build: bank_gui_qml_tests does not compile -- testkit_main.cpp's <testkit/log_level.hpp> is not on its include path #604:bank_gui_qml_testsdoes not compile onmaster.88a10481— CompileCache.cmake tells you to use --fresh to change launcher, discarding the build tree, when -U does it for free #592:CompileCache.cmakeadvises--freshwhere-Udoes the job.#604 —
bank_gui_qml_testsdid not compileReproduced, on
26bfdb8f, with thelinux-everythingpreset (the only configuration in the repository that setsMORPH_BUILD_BANK_GUI=ON; the option defaults toOFF, which is why an ordinary build never sees this):<testkit/log_level.hpp>exists at exactly one path in the tree —tests/testkit/log_level.hpp, so it resolves only against the repository'stests/directory — and the target's two include directories (examples/bank/tests,examples/common) reach neither it nor anything that carries it.The fix, and why this one
The sibling
bank_gui_tests, one target above, linksmorph_test_main, whichPUBLIC-linksmorph_test_log_level, whoseINTERFACEinclude directory is the repository'stests/.bank_gui_qml_testslinks neither. The divergence between the two siblings is what produced the defect, so the fix closes the divergence rather than papering over it with a baretarget_include_directories(... ${PROJECT_SOURCE_DIR}/tests).It cannot simply link
morph_test_mainthe way its sibling does — that library's only symbol is amain, and this target has its own (testkit_main.cpp, which must own theQGuiApplication, because Qt Quick cannot instantiate an item without a platform integration andBankClient'sQtExecutorcapturesQCoreApplication::instance()at construction). So it links the half of it that is wanted:That is precisely the split
morph_qt_testsalready uses (tests/qt/CMakeLists.txt:49-52), for precisely the same reason, and its comment there already says so. After this, the two bank GUI test targets get the same thing from the testkit and differ only where they must — in who ownsmain.Evidence
Same tree, after the change:
Registered with
ctestand passing:Would the check still pass if the linked library did nothing? No — and that is worth showing rather than asserting, because "the include resolves" is a weak claim about a library whose actual job is a command-line option.
log_level.hppdeclares the--log-levelhelpertestkit_main.cppcalls, and it is live on the binary:A bare include-directory fix would have satisfied the compiler and left the binary's
maincalling a helper it had no library relationship with; this one links the thing that owns it.The whole
linux-everythingtreeBuilt to completion with
-k 0, so one failure would not have hidden others (three bounded foreground passes; ninja resumes). ZeroFAILEDlines across all three build logs, final pass exiting 0.bank_gui_qml_testswas the only broken target behind the gate, not the first of several. Measured on this branch, clang 22.1.8 / Ninja / Linux; I did not run the fullctestsuite for this tree, only the three bank GUI tests above.This fix alone leaves the hole open
Nothing in CI configures
MORPH_BUILD_BANK_GUI, so nothing would catch this target breaking again tomorrow — including this fix. The job named "all optional features" enables 5 of the repository's 14MORPH_BUILD_*options, and this is not among them. That is #605, deliberately not addressed here: it is a gate-coverage decision with real CI-cost implications, and it should be decided on its own terms rather than smuggled in behind a one-line link fix. No CI job is added or changed by this PR.#592 —
--freshadvice, where-Udoes the jobcmake/CompileCache.cmakereturns early when a launcher is already pinned in the CMake cache, and told the reader to "reconfigure with--freshto let this module choose instead". Documentation change only — no behaviour changes,--freshis not removed, and it is still the right answer when a full reconfigure is genuinely wanted.All three findings below were measured, on an isolated harness that does nothing but
include()the module, with a trivial C target so there is an object to lose.1.
-Ureaches the same selection and leaves the directory standing. The guard reads exactly two cache entries, and-Udeletes exactly those two:2.
--freshdeletesCMakeFiles/and takes the generator with it unless it is re-passed:— on a directory configured
-G Ninja, now a Makefile tree with a stalebuild.ninjastill sitting in it. This is the same "generator/flag drift" already recorded indocs/superpowers/progress/2026-08-19-ledger-rung5-progress.md:65, where an implementer's owncmake --freshdesynced a build tree badly enough to block a task. morph's presets pin"generator": "Ninja", socmake --preset X --freshis safe; a plaincmake -S . -B build --freshis not.3. For a preset-pinned launcher,
--freshdoes not work at all. "A preset" is one of the three sources the message itself names, and it is the one case where the advice loops — the preset re-applies itscacheVariableson the very reconfigure--freshtriggers:The tree is gone and the launcher is still pinned.
-Uon a plain reconfigure of that same directory does clear it (until the preset is next run, which the new text says). This one is not a cost argument at all — it is the advice being wrong — and it is the reason the ordering matters rather than merely being cheaper.A finding that cuts against the ticket's framing
The ticket is titled "advice that costs a whole build tree", and the headline cost is partly overstated for this particular scenario. A launcher is part of every C/C++ compile command, so changing it invalidates every C/C++ object by either route — measured: the harness object was recompiled on the first build after the
-Ureconfigure.-Udoes not save the compiling.What it does save is the configure state: the populated
_depstree, the generator, generated sources, cachedfind_results — which, per finding 2, is where--freshhas actually hurt this repository before. The new comment says this in those words rather than claiming the objects are preserved, and the rendered message says "leaves the build directory standing", not "keeps your objects". Stated as measured; I have not measured the wall-clock difference on a real morph tree, and do not claim one.The rendered message
Verified by running it, and the advice verified by following it verbatim (finding 1), not by reasoning about it.
Sweep, wider than the one line
grep -rn -- "--fresh"over the tree, excludingbuild/and.git/, finds two occurrences:cmake/CompileCache.cmake:63— the advisory line, fixed here.docs/superpowers/progress/2026-08-19-ledger-rung5-progress.md:65— a historical incident report that records a--freshgoing wrong rather than recommending one. Left alone; progress docs are a dated record, not live guidance.docs/,CONTRIBUTING.mdandREADME.mdcontain no reconfigure or launcher advice at all (grep -niE "reconfigur|stuck|launcher"over them: no matches). No test or CI job asserts on the message text (grep -rln "already set externally"overscripts/and.github/: no matches), so nothing downstream breaks on the rewording.Review notes
Copilot review is unavailable on this org, so the review reasoning is here.
morph_test_log_levelis available at that point in the configure. It is defined in the rootCMakeLists.txtunderif(MORPH_BUILD_TESTS), afteradd_subdirectory(examples/bank). That is fine and deliberate —target_link_libraries()resolution is deferred to generate time, and the rootCMakeLists.txtcomment at the definition site says so explicitly, namingexamples/{concepts,bank,vetted_hmac}as the directories relying on it. The siblingbank_gui_testsalready linksmorph_test_mainfrom the same block, three lines away, so this adds no new ordering assumption. Confirmed by the configure succeeding.bank_gui_qml_testslives insideif(MORPH_BUILD_TESTS)(examples/bank/CMakeLists.txt:164), the same guard that createsmorph_test_log_level. There is no configuration where the target exists and the library does not.morph_test_log_levelis anINTERFACElibrary carrying an include directory plusmorph::morphandCatch2::Catch2— both of which this target already had transitively viabank_gui_liband directly. It adds an include path and no objects.Catch2::Catch2is kept explicitly rather than left to arrive throughmorph_test_log_level. A target that names its own Catch2 dependency does not silently break if the testkit's own link line is ever reorganised, and it matchesmorph_qt_tests, which names both.--freshtext is preserved, not deleted, as the ticket asked — with its cost and its one failure case attached. A reader who wants a full reconfigure still finds it in the message.scripts/branch_partial_allowlist.jsonwas touched; no entry references either file.CHANGELOG.mdentries are additive.Fixes #604. Fixes #592. Related: #605 (the gate that would have caught #604 — deliberately out of scope).
🤖 Generated with Claude Code
https://claude.ai/code/session_01GS5K2vqZtC4xbRiGJHT7jH