Unit 7.9 · about 5 h
50 / 324Repeatable CMake builds and test targets
A safe native change must survive a clean build, not just a working terminal session. Target-based builds keep dependencies and diagnostic options attached to the code that needs them.
- implement
You can now
- Describe a library, executable and native test as CMake targets with explicit usage requirements.
- Configure independent debug, optimized and sanitizer builds and inspect their actual compile and link commands.
- Run registered tests from a fresh build tree and install the intended artifact without installing test executables.
Covers
- Repeatable CMake configure, build and test targets
Study, in this order
4 steps
- Targets, libraries and linkingCMake tutorial
Step 1: Background; Exercise 1 - Building an Executable; Exercise 2 - Building a Library; Exercise 3 - Linking Together Libraries and Executables
Depth · Use the tutorial source archive linked from the v4.2 tutorial index. Stop before Exercise 4 if your project does not need subdirectories.
- Target features, warnings and link optionsCMake tutorial
Step 4: Background; Exercise 1 - Features and Definitions; Exercise 2 - Compile and Link Options
Depth · Read the scoped-command examples. Skip vendor-library import and precompiled headers; apply the commands to your own targets rather than completing all preceding steps.
- Testing and CTestCMake tutorial
Step 8: Background and Exercise 1 - Adding Tests, all seven completion markers, including
TestMathFunctionsand the four registered operations - Install the intended artifactsCMake tutorial
Step 9: Background and Exercise 1 - Installing Artifacts
Depth · Use install(TARGETS) and a local install prefix. Stop before exporting targets and version files.
Focus on
- Model dependencies with
target_link_librariesand target-scoped sources, includes and features.PRIVATEaffects the target,INTERFACEits consumers, andPUBLICboth. target_compile_features(... cxx_std_20)specifies at least C++20, not necessarily an exact-std=c++20flag. Record verbose build commands and disable language extensions when strict standard mode is intended.- For single-config generators such as Unix Makefiles, configure
CMAKE_BUILD_TYPEin separate trees. For multi-config generators, choose--configat build time and-Cfor CTest. - CTest runs registered commands; it does not automatically build them. Sanitizer instrumentation needs compile options and runtime linkage on the executable, and release tests must not disappear under
NDEBUG.
Reproduce closed-book
enable_testing() and add_test(NAME native COMMAND native_tests). Configure with cmake -S . -B build-debug -G 'Unix Makefiles' -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE=Debug, build with cmake --build build-debug --verbose, and run ctest --test-dir build-debug --output-on-failure. Repeat in a new build-release tree with Release. Inspect the commands for g++ 15.2, language mode and optimization rather than assuming exact preset flags. Add an opt-in sanitizer configuration that applies -fsanitize=address,undefined to compilation and final linking, plus -fno-sanitize-recover=undefined and -fno-omit-frame-pointer to compilation; verify it in a new clang++ 21.1 build tree. Finally use install(TARGETS) for the executable and cmake --install build-release --prefix install-stage, and check that tests are not installed. A deliberately false test condition must make CTest fail in both Debug and Release.Traps
- Changing the compiler inside an existing cached tree is not equivalent to a fresh configure. Use a new build directory for a different toolchain.
- Passing sanitizer flags only when compiling can leave the final link without its runtime libraries.
- No tests found is not evidence that tests passed. Check discovery and use explicit failure checks that survive optimized builds.
Practice
CMake Tutorial: Building and linking targets
Step 1, Exercises 1 through 3: Building an Executable, Building a Library, Linking Together Libraries and Executables
Complete the target declarations and link the provided program. Then carry the same target structure into the section mini-project.
Step 8, Exercise 1, all seven completion markers; register TestMathFunctions for add, mul, sqrt and sub
Start from the supplied Step8 directory, which includes earlier solutions. Its public executable checks one fixed example per operation and reports failure via its exit code; it is not a broad numerical test suite. Run all four with CTest, then add your own boundary cases in the mini-project.
Self-test
-
A library's public header uses
std::span. Should its C++20 requirement bePRIVATEorPUBLIC?PUBLIC, because both the library and consumers compiling that header need the feature. -
Why can CTest fail to run a newly registered executable?
The target may not have been built. Configure registers the test; build creates its executable; CTest runs it. -
Does
CMAKE_BUILD_TYPE=Releaseselect the configuration for every generator?No. Multi-config generators select it with--configwhen building and-Cwhen running CTest. -
Why can an
assert-only regression pass inReleasedespite a defect?NDEBUGcan remove the assertions entirely. Use test checks that report failure independently ofassert.
Completion criterion
Stop when
The named tutorial targets and four tests work, your own clean debug, release and sanitizer trees run the registered regression, an injected test failure is detected in Debug and Release, and installation excludes tests.
Mark passed, then open 1.1 Only mark this when you can do it without notes.Leads to
- 5.8 Dependency locks, run manifests and quality gates Capture the compiler, interpreter and test configuration as reproducible evidence.
- stage 06 · Systems Foundations Build larger native components from explicit dependency contracts.
Comes after
- 7.1 Compiling, linking, loading, ABI and C linkageEncode the pipeline as repeatable targets.
- 7.6 Undefined behavior, sanitizers and debugger evidenceKeep diagnostic builds and regression tests repeatable.
- 7.8 Python and native code: dtype, stride, ownership and GIL contractsBuild and test the binding against the intended interpreter.
Resources for this unit
- CMake tutorial Use the introductory steps for targets, libraries, testing and install rules. Produce one clean debug build and one optimized build without copied one-off commands.
Also in this section
- AlgoMaster C++ Use the sections on compilation, pointers and references, memory, RAII and smart pointers, copy and move, STL containers, templates, errors, concurrency and paired exercises. Skip competitive-programming breadth already covered in Algorithms.
- C++ Core Guidelines Use the Philosophy, Interfaces, Resource management, Expressions, Containers and concurrency rule groups to review ownership and safety decisions in the native component.
- cppreference Consult language lifetime, object model, value categories, memory model, atomics, containers and iterator invalidation while reading or modifying code.
- Clang AddressSanitizer and UndefinedBehaviorSanitizer Compile seeded memory defects with AddressSanitizer and UndefinedBehaviorSanitizer, preserve the reports, repair the root cause and keep regression tests.
- pybind11 documentation Follow First steps and NumPy support to expose one small native function with explicit shape, dtype, stride, ownership, GIL and exception behavior.
- Algorithmica - AoS and SoA Read the AoS and SoA experiment for a measured case where keeping each record together wins, the counterpart to a single-field scan.
- `ld.so`: dynamic linker/loader Read DESCRIPTION, Dynamic string tokens and
LD_DEBUGto distinguish link-time symbols from runtime shared-library discovery on Linux. - Memory Layout Transformations Read Overview and Topics through AOS Form and SOA Form for field-wise access and locality trade-offs. Skip Intel array-notation syntax and the LIBOR example.
- GDB manual: Backtraces Use section 8.2 for frame numbers,
bt,bt -fulland optimized-out arguments when inspecting a native failure.