Fix CMake/CTest configuration issues - #29
Merged
Merged
Conversation
Salvaged from the abandoned claude/review-cmake-config branch, which is
otherwise fully superseded by main. CMakeUserPresets.json is gitignored, so
there was no template for developers building natively instead of in the dev
container -- mainly macOS, where Homebrew LLVM and the ARM GNU Toolchain live
at paths CMake will not find on its own.
Only the example file is taken. That branch also rewrote host-clang.cmake to
drop the -18 version suffixes and call bare `clang`, which would undo the
toolchain version pinning main relies on.
The presets as written on that branch did not actually work against main. The
`default` preset sets CMAKE_PRESET to ${presetName}, which resolves to the
derived preset's own name, so `if(CMAKE_PRESET STREQUAL "host")` in
CMakeLists.txt was false and googletest/cppzmq/nlohmann_json were never
fetched -- configure failed on missing targets. Each host-derived preset now
pins CMAKE_PRESET explicitly. The ARM presets are unaffected: nothing is gated
on the cm4 path they inherit.
Verified end to end: `cmake --preset=host-linux` configures, builds, and passes
29/29 tests. Also refreshed the stale /Applications ARM toolchain version and
moved the setup caveats into preset descriptions, since preset JSON permits no
comments.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
README and CLAUDE.md listed `cmake --workflow --preset=stm32f3_discovery-release`
under "ARM targets" as though it runs today. It cannot: src/libs/mcu/CMakeLists.txt
does add_subdirectory(${EMBEDDED_CPP_MCU}) and only the `host` implementation
exists, so configure fails on the missing arm_cm4/ directory. Verified against the
stock preset with no user presets present.
This is planned sequencing rather than a defect -- host build and emulation first,
hardware platforms after -- and docs/PROJECT_PLAN.md already lists STM32F3/F7 as in
progress. The build instructions were the only place that read as working, and they
contradicted the README's own Implementation Status table.
Note the ARM toolchain files and board directories are deliberately in place ahead
of the MCU layer; nothing here changes that scaffolding.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds nine skills under .claude/skills/ covering the development lifecycle: idea-refine raw idea -> sharp concept spec-driven-development spec before code planning-and-task-breakdown spec -> ordered, verifiable tasks test-driven-development failing test first; repro test before bug fix incremental-implementation thin vertical slices that stay green debugging-and-error-recovery scientific-method root-cause analysis code-review-and-quality multi-axis review before merge git-workflow-and-versioning atomic commits, short-lived branches context-engineering agent context setup and recovery These are committed rather than left local so the workflow is shared rather than per-machine, consistent with .claude/settings.json. Personal overrides still belong in .claude/settings.local.json, which stays gitignored. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
COMMON_COMPILE_OPTIONS appended -Os and -g after the per-config flags. The last -O on the command line wins, so Release compiled as -O3 -DNDEBUG -Os -g and was actually size-optimized, while Debug compiled as -g -Os -g and was optimized rather than debuggable. Confirmed against a vectorizable loop: -O3 gives 243 bytes of .text, -Os gives 107, and -O3 -Os gives 107. Both flags are redundant with the per-config settings. cmake/toolchain/armgcc.cmake already sets CMAKE_C_FLAGS_DEBUG_INIT "-O0" and CMAKE_C_FLAGS_RELEASE_INIT "-Os -flto" plus -g3, so the ARM path is unaffected. The host build now uses CMake's defaults: Debug -g, Release -O3 -DNDEBUG, RelWithDebInfo -O2 -g -DNDEBUG. Note Release no longer carries -g; use RelWithDebInfo for an optimized build with symbols. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
All three set_tests_properties calls on host_emulator_test were no-ops: - The first set DEPENDS "blinky;uart_echo;i2c_demo", then the second set DEPENDS host_emulator_venv. set_tests_properties replaces rather than appends, so the first was silently discarded -- the generated CTestTestfile.cmake showed only the second. - DEPENDS names tests, not targets. blinky/uart_echo/i2c_demo are executables, and host_emulator_venv is created at configure time by execute_process in cmake/python_venv.cmake, so it is not in the test registry at all. Neither form ever ordered anything. - FIXTURES_SETUP declares that this test *is* a setup fixture for other tests. Nothing declared a matching FIXTURES_REQUIRED, so it did nothing. The likely intent was to ensure the apps are built before the integration test runs, but ctest does not build. Verified by deleting build/host/bin and running the ctest preset alone: all 28 unit tests report "Not Run" and host_emulator_test fails. No test property can fix that -- the workflow presets exist for it, and they are what the docs, CI and docker-compose already use. Replaced with TIMEOUT 300 (the test takes ~6s; previously it could hang until CTest's 1500s default) and LABELS "integration", enabling `ctest -L integration`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Hide `default` (configure and build): it is a base for host/arm and cannot configure standalone -- EMBEDDED_CPP_BOARD/_MCU are unset, so `cmake --preset=default` failed with "add_subdirectory called with incorrect number of arguments". Hide the `host` build and test presets and make them inheritance bases. The test preset was identical to host-debug, and the build preset carried no configuration, so both relied on a trailing -C/--config to mean anything. Now there is one explicit preset per configuration. The hidden `default` test preset already held the output/execution settings but host/host-debug/host-release duplicated them verbatim; chaining default -> host -> per-config removes that duplication. Add host-relwithdebinfo build, test and workflow presets. RelWithDebInfo is in CMAKE_CONFIGURATION_TYPES and the generator emits build-RelWithDebInfo.ninja, but CMakePresets.json did not mention it once, so it was only reachable via a bare --config. It matters now that Release no longer carries -g: RelWithDebInfo is the supported optimized-with-symbols build. Update the documented commands accordingly. Also fix `-R test_zmq_transport`, which matched no tests -- gtest_discover_tests registers ZmqTransportTest.SendReceive, named for the fixture rather than the source file, and noTestsAction:error made it fail. Now -R ZmqTransportTest. docker-compose.yml and CI are unaffected; both use --workflow --preset host-debug. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fe03b14 moved the dev dependencies from [project.optional-dependencies] to a PEP 735 [dependency-groups] table but left CLAUDE.md documenting `uv run --extra dev`. That now fails outright: error: Extra `dev` is not defined in the project's `optional-dependencies` table uv syncs dependency groups by default, which was the point of the move, so the flag is not just wrong but unnecessary -- plain `uv run pytest`, `uv run ruff` and `uv run mypy` all work. Verified by running each documented command verbatim. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every other FetchContent_Declare pins a tag; cmake-scripts alone tracked `main`. It supplies clang_tidy(), reset_clang_tidy(), add_code_coverage_all_targets() and target_code_coverage(), so upstream changes could alter lint enforcement or coverage instrumentation with no commit in this repo -- and silently, since a FetchContent GIT_TAG branch is only re-resolved on a fresh fetch. 25.08 is the newest tag; the tree in use was 12 commits past it. Verified against a clean build directory: the tag fetches, clang-tidy is found, coverage still instruments 30 translation units, all 136 ccov targets generate, ccov-all produces its HTML report, and the suite passes 29/29. No fallback to a SHA pin was needed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add LABELS "unit" to the five gtest_discover_tests calls. With the "integration" label added in 4b74acc, the suite now partitions cleanly: `ctest -L unit` selects 28, `ctest -L integration` selects 1, 29 total. Previously there was no way to run the fast C++ tests without also spinning up the Python emulator. Remove cmake_minimum_required from every subdirectory CMakeLists.txt. Only the top-level call has effect -- the rest were copy-paste noise, and one used a FATAL_ERROR argument the others did not, implying a distinction that never existed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each unit test was declared in three separate locations: the executable plus its
compile options and link libraries, a gtest_discover_tests call, and a
target_code_coverage call inside the CODE_COVERAGE block. Nothing tied them
together, so a test added to one list but not the others failed silently -- no
error, just a test that never gets labelled or never gets instrumented.
Collapse all three into add_host_unit_test(name source [libs...]). Adding a test
is now a single line that cannot omit either.
`source` is a parameter rather than being derived as ${name}.cpp because
test_host_transport is built from test_zmq_transport.cpp; a convention-based
helper would silently break that target.
Behaviour is unchanged, verified by comparison rather than a passing build alone:
29/29 tests, 28 unit and 1 integration by label, 30 instrumented translation
units, and 136 ccov targets -- all identical to before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
nehalkpatel
force-pushed
the
chore/cmake-user-presets-example
branch
from
August 27, 2026 04:59
a85ef24 to
0007564
Compare
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.
Summary
An audit of the CMake/CTest setup found six issues, each verified empirically against the build rather than inferred. The most serious: Release builds were not actually
-O3. Also adds the missing RelWithDebInfo presets, pins the last unpinned dependency, and collapses the three-places-per-test declaration pattern into a single helper.Every change is verified from a wiped
build/directory. All three workflows pass 29/29.Release was silently size-optimized
COMMON_COMPILE_OPTIONSappended-Os -gafter the per-config flags, and the last-Oon the command line wins:-g -Os -g-g-O3 -DNDEBUG -Os -g-O3 -DNDEBUG-O2 -g -DNDEBUGProven with a vectorizable loop:
-O3→ 243 bytes of.text,-Os→ 107,-O3 -Os→ 107. So Release was size-optimized and Debug was optimized rather than debuggable.Both flags were redundant anyway —
cmake/toolchain/armgcc.cmakealready setsCMAKE_C_FLAGS_DEBUG_INIT "-O0"andCMAKE_C_FLAGS_RELEASE_INIT "-Os -flto"plus-g3, so the ARM path is unaffected.Every CTest property on the integration test was inert
All three
set_tests_propertiescalls did nothing:DEPENDS "blinky;uart_echo;i2c_demo", the second setDEPENDS host_emulator_venv.set_tests_propertiesreplaces rather than appends, so the first was silently discarded — the generatedCTestTestfile.cmakeshowed only the second.DEPENDSnames tests, not targets.blinkyet al. are executables, andhost_emulator_venvis created at configure time byexecute_process, so it isn't in the test registry at all.FIXTURES_SETUPdeclares the test is a fixture for others; nothing declared a matchingFIXTURES_REQUIRED.The intent was presumably "build the apps first", but ctest does not build — verified by deleting
build/host/binand running the ctest preset alone: 28 tests "Not Run", integration test failed. The workflow presets exist for this and are what the docs, CI and docker-compose already use.Replaced with
TIMEOUT 300(previously a hang would block until CTest's 1500s default) andLABELS "integration".Presets
defaultwas user-visible but unbuildable —cmake --preset=defaultfailed withadd_subdirectory called with incorrect number of arguments(EMBEDDED_CPP_BOARD/_MCUunset). Now hidden, matchingarm/arm-cm4/arm-cm7.CMAKE_CONFIGURATION_TYPESand havingbuild-RelWithDebInfo.ninjagenerated. It was reachable only via a bare--config. Now has build/test/workflow presets — which matters more since Release no longer carries-g.hostbuild and test presets are now hidden inheritance bases. The test preset was identical tohost-debug; the build preset carried no configuration. Both relied on a trailing-C/--configto mean anything. The hiddendefaulttest preset already held theoutput/executionsettings, but the host presets duplicated them verbatim — chainingdefault → host → per-configremoves that.Also fixed
-R test_zmq_transportmatched no tests.gtest_discover_testsregistersZmqTransportTest.SendReceive— named for the fixture, not the source file — andnoTestsAction: errormade the documented command fail outright. Now-R ZmqTransportTest.uv run --extra devwas broken.fe03b14moved dev deps to a PEP 735[dependency-groups]table but left CLAUDE.md documenting the old flag, which now errors:Extra 'dev' is not defined in the project's optional-dependencies table. uv syncs groups by default, so plainuv runis correct.cmake-scripts tracked
main. It suppliesclang_tidy(),reset_clang_tidy(),add_code_coverage_all_targets()andtarget_code_coverage(), so upstream drift could change lint enforcement or coverage silently. Pinned to25.08, verified from a clean build directory.Test declaration collapsed into one helper
Each unit test was declared in three separate places — executable+options+libs,
gtest_discover_tests, andtarget_code_coverage. Nothing tied them together, so a test added to one list but not the others failed silently.add_host_unit_test(name source [libs...])makes it one line.sourceis a parameter rather than derived as${name}.cppbecausetest_host_transportis built fromtest_zmq_transport.cpp; a convention-based helper would break that target.Behaviour verified identical by comparison, not just a passing build: 30 instrumented TUs and 136 ccov targets before and after.
Test plan
From a wiped
build/:host-debug,host-release,host-relwithdebinfo— 29/29 eachLABELS "integration"andTIMEOUT "300"present in generatedCTestTestfile.cmakectest -L unit→ 28,-L integration→ 1, total 2925.08; clang-tidy found;ccov-allgenerates its HTML reportNotes
ctest --preset=hostandcmake --build --preset=hostno longer resolve. All documented commands are updated;docker-compose.ymland CI are unaffected (both use--workflow --preset host-debug).src/libs/mcu/arm_cm4/doesn't exist yet. Expected, and the docs say so.ccov-allemitswarning: 70 functions have mismatched data. Pre-existing, reproduces from a wiped tree with a single Debug build and no changes. Cause is header-defined inline/template functions (themcu/board/errorINTERFACE libraries) instantiated into all five test binaries, then merged. Benign — the report still generates.