docs: document template workflows
cpp-template / format (push) Failing after 3m38s
cpp-template / debug (push) Failing after 5m18s
cpp-template / clang-tidy (push) Failing after 1m26s
cpp-template / release (push) Failing after 1m42s
cpp-template / cxx11-smoke (push) Failing after 1m43s
cpp-template / install-consumer (push) Failing after 1m37s
cpp-template / asan (push) Failing after 1m23s
cpp-template / tsan (push) Failing after 1m26s
cpp-template / fuzz-smoke (push) Failing after 1m15s
cpp-template / no-network-negative (push) Failing after 7m7s

This commit is contained in:
tqcq
2026-05-18 09:41:16 +08:00
parent 022e6d59bf
commit 05eabe7cd4
4 changed files with 1256 additions and 0 deletions
+264
View File
@@ -0,0 +1,264 @@
# C++ Project Template
A CMake-based C++ project template with vendored dependencies, offline builds, and developer checks.
## Prerequisites
| Tool | Minimum Version | Notes |
| ------------ | --------------- | ------------------------------- |
| CMake | 3.20 | Build system |
| Ninja | any | Generator (required by presets) |
| Python | 3.8+ | Helper scripts |
| C++ compiler | C++14 | GCC or Clang for default lanes |
| clang-format | 17+ | Format checking |
| clang-tidy | 14+ | Static analysis |
| Clang | C++17 capable | Fuzz lane only |
The fuzz preset requires Clang and C++17. All other presets work with any
C++14 compiler.
## Quick Start
After a fresh clone, all dependencies are already vendored in `3rd/archives/`.
No network access is needed for the default build.
```bash
# Configure and build
cmake --preset debug
cmake --build --preset debug
# Run tests
ctest --preset debug
# Run the full fast check (format, build, test, clang-tidy)
python3 scripts/dev_check.py --fast
```
## Daily Workflow
### First-time setup
```bash
python3 scripts/setup_hooks.py
cmake --preset debug
cmake --build --preset debug
```
The hook runs `python3 scripts/dev_check.py --fast` on every commit. Build at
least once so `compile_commands.json` exists for clang-tidy.
### Edit and test loop
```bash
# Build and run tests (pick one)
cmake --build --preset debug && ctest --preset debug --output-on-failure
cmake --build --preset debug --target run_tests
# Run benchmarks
cmake --build --preset debug --target run_benchmarks
```
`run_tests` and `run_benchmarks` are aggregate targets that drive all registered
test and benchmark executables. See `docs/cmake-api.md` for helper API details.
### Fast and full checks
```bash
# Format + build + test + clang-tidy
python3 scripts/dev_check.py --fast
# Individual verification subcommands
python3 scripts/dev_check.py cmake-helper-fixtures
python3 scripts/dev_check.py no-network-default
```
Run `python3 scripts/dev_check.py --help` for the full list. CI runs all
subcommands independently; see the Developer Checks table below for details.
### Sanitizer builds
```bash
cmake --preset asan && cmake --build --preset asan
ctest --preset asan --output-on-failure
cmake --preset tsan && cmake --build --preset tsan
ctest --preset tsan --output-on-failure
```
Sanitizer presets disable benchmarks to avoid false positives from
instrumented benchmark code.
### Fuzz smoke and long fuzzing
The fuzz preset requires Clang. Smoke tests run in deterministic unit-test mode:
```bash
cmake --preset fuzz && cmake --build --preset fuzz
ctest --preset fuzz --output-on-failure -L fuzz_smoke
```
Continuous fuzzing requires a separate build with `FUZZTEST_FUZZING_MODE=ON`
and runs the executable directly:
```bash
./build/fuzz/calc_fuzz --fuzz=CalcFuzz.AddNeverCrashes --fuzz_for=60s
```
### C++11 compatibility smoke
```bash
cmake --preset cxx11-smoke && cmake --build --preset cxx11-smoke
```
This verifies the project configures and compiles under C++11. No tests or
benchmarks run in this lane.
## Build Presets
| Preset | Standard | Type | Tests | Notes |
| ------------- | -------- | ------- | ----- | ------------------------ |
| `debug` | C++14 | Debug | yes | Default developer lane |
| `release` | C++14 | Release | yes | Optimized build |
| `asan` | C++14 | Debug | yes | AddressSanitizer |
| `tsan` | C++14 | Debug | yes | ThreadSanitizer |
| `fuzz` | C++17 | Debug | no | FuzzTest, requires Clang |
| `cxx11-smoke` | C++11 | Debug | no | Configure+build only |
Example:
```bash
cmake --preset release
cmake --build --preset release
ctest --preset release
```
## Offline Build Guarantee
Default build lanes (`debug`, `release`, `asan`, `tsan`, `cxx11-smoke`) and the
`fuzz` lane resolve all dependencies from committed archives in `3rd/archives/`.
CPM loads each dependency via `URL` pointing at the local tarball with
`URL_HASH SHA256=...` integrity verification. No remote fetch occurs during the
build.
This is enforced by the `no-network-default` and `no-network-fuzztest-lane`
checks, which run CMake under invalid HTTP/HTTPS proxies with an isolated CPM
source cache:
```bash
python3 scripts/dev_check.py no-network-default
python3 scripts/dev_check.py no-network-fuzztest-lane
```
There is no global offline mode or `CMAKE_DISABLE_FIND_PACKAGE` flag. The
offline guarantee comes from CPM archive mode, not from suppressing network
access at the CMake level.
See `3rd/README.md` for archive policy, the full inventory table, and update
instructions.
## Developer Checks
`scripts/dev_check.py` runs structured verification subcommands:
| Command | What it checks |
| ---------------------------- | -------------------------------------------------- |
| `--fast` | Format, debug configure/build/test, clang-tidy |
| `no-network-default` | Configure (optionally build) under invalid proxies |
| `no-network-missing-archive` | Negative check for missing archive detection |
| `no-network-fuzztest-lane` | Fuzz lane configure+build under invalid proxies |
| `gtest-isolation` | GTest version isolation (normal=1.16, fuzz=1.17) |
| `fuzztest-optional-features` | FuzzTest optional deps stay disabled |
| `install-consumer` | Install and consume as a subproject |
| `cmake-helper-fixtures` | CMake helper API fixture tests |
Dry-run any command to see the plan without executing:
```bash
python3 scripts/dev_check.py --fast --dry-run
```
## Dependency Management
`scripts/fetch_deps.py` manages the vendored archive inventory:
```bash
# List dependency status
python3 scripts/fetch_deps.py --list
# Verify all archives match recorded hashes (offline)
python3 scripts/fetch_deps.py --check
# Download a specific dependency
python3 scripts/fetch_deps.py --fetch spdlog
# Update pending hashes in versions.cmake
python3 scripts/fetch_deps.py --update-hashes
```
Hashes and URLs are defined in `cmake/deps/versions.cmake`. The full inventory
of 18 dependencies with SHA256 values is in `3rd/README.md`.
## Git Hooks
Opt-in pre-commit hooks run format checks and fast developer validation:
```bash
# Install hooks
python3 scripts/setup_hooks.py
# Check status
python3 scripts/setup_hooks.py --status
# Preview without installing
python3 scripts/setup_hooks.py --dry-run
```
## Troubleshooting
### Hash mismatch on configure
```
CMake Error: hash mismatch for ...
```
The archive doesn't match the SHA256 in `cmake/deps/versions.cmake`. Re-download:
```bash
python3 scripts/fetch_deps.py --fetch --force <name>
```
### Missing archive
```
Could not find archive: 3rd/archives/<name>.tar.gz
```
The tarball wasn't committed. Download it:
```bash
python3 scripts/fetch_deps.py --fetch <name>
```
### Unsupported compiler for fuzz lane
The fuzz preset requires C++17 and Clang. If you don't have Clang, skip the fuzz
preset. All other presets work with any C++14 compiler.
### clang-format not found
Format checks require clang-format 17 or newer. Install it and ensure it's on
your `PATH` as `clang-format`.
## Project Layout
```
├── cmake/ # CMake modules, dependency versions, helper API
├── scripts/ # Developer tooling (format, tidy, checks, hooks)
├── src/ # Application and library source
├── tests/ # Test sources
├── 3rd/
│ ├── archives/ # Vendored dependency tarballs (18 total)
│ └── patches/ # Per-dependency patches (FuzzTest)
├── CMakePresets.json # Build preset definitions
└── .githooks/ # Opt-in pre-commit hook
```
+472
View File
@@ -0,0 +1,472 @@
# CMake helper API manual
This manual documents the project helper API implemented under `cmake/`. The examples are intentionally close to the positive helper fixtures and current root usage, but paths and target names are illustrative unless a section explicitly says otherwise.
## Global conventions
- Call native `project(<name> LANGUAGES C CXX)` before `cc_project()`; the helper rejects CMake's implicit `project(Project)` default.
- The primary project standard is C++14. Fuzz lane targets require the fuzz preset or equivalent Clang + C++17 configuration.
- `INCS`, `DEPS`, and `DEFINES` use explicit visibility groups. Every item must follow `PUBLIC`, `PRIVATE`, or `INTERFACE`; unqualified items are rejected.
- Executable-like targets (`cc_executable`, `cc_test`, `cc_benchmark`, and `cc_fuzz`) do not support `INTERFACE` visibility because they are not consumed as link interfaces. Use `PUBLIC` or `PRIVATE` only.
- `cc_library(TYPE INTERFACE)` supports only `INTERFACE` visibility for `INCS`, `DEPS`, and `DEFINES`.
- Helpers do not acquire external dependencies. Define or load targets such as `GTest::gtest`, `benchmark::benchmark`, and `fuzztest::fuzztest` before calling the corresponding helper.
- There are aggregate targets `run_tests` and `run_benchmarks`, but no per-target `run_test_<name>`, `run_benchmark_<name>`, or `run_fuzz_<name>` wrappers.
## `cc_project()`
Signature:
```cmake
cc_project()
```
Use in the root `CMakeLists.txt` after native `project()` and after any cache variables that should influence helper options.
```cmake
cmake_minimum_required(VERSION 3.19)
project(my_package LANGUAGES C CXX)
include(cmake/cc_project.cmake)
cc_project()
```
Behavior:
- Sets C++ defaults: C++14 by default, required standard, extensions off.
- Enables `CMAKE_EXPORT_COMPILE_COMMANDS` unless the caller already set it.
- Enables position-independent code.
- Defines helper options:
- `CC_ENABLE_INSTALL`: ON by default for the top-level project, OFF for subprojects.
- `CC_ENABLE_TESTING`: ON by default.
- `CC_ENABLE_BENCHMARKS`: ON by default.
- `CC_ENABLE_FUZZTEST`: OFF by default; requires the fuzz lane when enabled.
- `CC_WARNINGS_AS_ERRORS`: OFF by default.
- Bridges existing lane options into helper options when present: `CPP_TEMPLATE_ENABLE_TESTS`, `CPP_TEMPLATE_ENABLE_BENCHMARKS`, and `CPP_TEMPLATE_FUZZ_LANE`.
- Creates top-level aggregate targets when enabled:
- `run_tests`: runs `ctest --output-on-failure`.
- `run_benchmarks`: aggregate target populated by `cc_benchmark()`.
- Defers `cc_finalize_install()` at generate time when the install module is included.
## `cc_library()`
Signature:
```cmake
cc_library(<target>
TYPE STATIC|SHARED|INTERFACE
[SRCS <src>...]
[HDRS <hdr>...]
[INCS [PUBLIC|PRIVATE|INTERFACE <dir>...]...]
[DEPS [PUBLIC|PRIVATE|INTERFACE <dep>...]...]
[DEFINES [PUBLIC|PRIVATE|INTERFACE <define>...]...]
[OPTIONS <option>...]
[FEATURES <feature>...]
[ALIAS <alias>]
[INSTALL]
)
```
Required contract:
- `TYPE` is mandatory and must be `STATIC`, `SHARED`, or `INTERFACE`.
- `OBJECT` libraries are not supported by this helper.
- `TYPE INTERFACE` must not provide `SRCS` or `HDRS`.
- `INCS`, `DEPS`, and `DEFINES` require explicit visibility for every item.
- `TYPE INTERFACE` only accepts `INTERFACE` visibility in visibility-grouped arguments.
- `ALIAS` creates a real build-tree library alias with `add_library(<alias> ALIAS <target>)` and also stores install/export metadata.
- `INSTALL` only marks the target. Install rules are produced later by `cc_finalize_install()` when install is enabled.
Static library example, mirroring the positive helper fixture pattern:
```cmake
include("${CC_PROJECT_ROOT}/cmake/cc_targets.cmake")
cc_library(my_static_lib
TYPE STATIC
SRCS lib.cpp
HDRS lib.h
INCS PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
DEPS PUBLIC m
DEFINES PUBLIC MY_STATIC_BUILD
OPTIONS -Wall
FEATURES cxx_std_14
ALIAS my_static_lib::my_static_lib
INSTALL
)
```
Interface library example:
```cmake
cc_library(my_headers
TYPE INTERFACE
INCS INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include
DEFINES INTERFACE MY_HEADERS_ONLY=1
FEATURES cxx_std_14
)
```
## `cc_executable()`
Signature:
```cmake
cc_executable(<target>
[SRCS <src>...]
[HDRS <hdr>...]
[INCS [PUBLIC|PRIVATE <dir>...]...]
[DEPS [PUBLIC|PRIVATE <dep>...]...]
[DEFINES [PUBLIC|PRIVATE <define>...]...]
[OPTIONS <option>...]
[FEATURES <feature>...]
[ALIAS <alias>]
[INSTALL]
)
```
Behavior:
- Creates an executable target. `SRCS` may be omitted, producing an initially empty executable target.
- Applies `OPTIONS` and `FEATURES` privately.
- Rejects `INTERFACE` visibility in `INCS`, `DEPS`, and `DEFINES`.
- `ALIAS` is install/export metadata only. CMake does not support `add_executable(<alias> ALIAS ...)`, so no build-tree executable alias is created.
- `INSTALL` marks the target for later install/export finalization.
Example:
```cmake
include("${CC_PROJECT_ROOT}/cmake/cc_targets.cmake")
cc_executable(hello_exe
SRCS main.cpp
DEPS PRIVATE my_static_lib
OPTIONS -Wall
FEATURES cxx_std_14
)
```
Installable executable example:
```cmake
cc_executable(my_tool
SRCS main.cpp
ALIAS my_ns::my_tool
INSTALL
)
```
## `cc_test()`
Signature:
```cmake
cc_test(<target>
[NO_MAIN]
SRCS <src>...
[HDRS <hdr>...]
[INCS [PUBLIC|PRIVATE <dir>...]...]
[DEPS [PUBLIC|PRIVATE <dep>...]...]
[DEFINES [PUBLIC|PRIVATE <define>...]...]
[OPTIONS <option>...]
[FEATURES <feature>...]
[ARGS <arg>...]
)
```
Behavior:
- Skips target creation when `CC_ENABLE_TESTING` is OFF.
- Requires `GTest::gtest`; if unavailable, warns and skips target creation.
- Requires non-empty `SRCS`.
- Registers `add_test(NAME <target> COMMAND <target> [<ARGS>...])`.
- Adds the test executable as a dependency of `run_tests` when that aggregate target exists.
- Without `NO_MAIN`, links `GTest::gtest_main`, which implies `GTest::gtest`.
- With `NO_MAIN`, links `GTest::gtest` only; use this when the test source or another dependency supplies `main()`.
- `GTest::gmock` may be listed in `DEPS` with or without `NO_MAIN`.
- `GTest::gmock_main` requires `NO_MAIN`; the helper rejects it otherwise because it conflicts with default `GTest::gtest_main`.
Basic example:
```cmake
include("${CC_PROJECT_ROOT}/cmake/cc_targets.cmake")
include("${CC_PROJECT_ROOT}/cmake/cc_testing.cmake")
enable_testing()
cc_test(calc_test
SRCS calc_test.cpp
DEPS PRIVATE calc
ARGS --gtest_filter=CalcTest.*
)
```
GMock custom-main example, mirroring the positive helper fixture pattern:
```cmake
cc_test(mock_test
SRCS test_mock.cpp
NO_MAIN
DEPS PRIVATE GTest::gmock
)
```
## `cc_benchmark()`
Signature:
```cmake
cc_benchmark(<target>
[NO_MAIN]
SRCS <src>...
[HDRS <hdr>...]
[INCS [PUBLIC|PRIVATE <dir>...]...]
[DEPS [PUBLIC|PRIVATE <dep>...]...]
[DEFINES [PUBLIC|PRIVATE <define>...]...]
[OPTIONS <option>...]
[FEATURES <feature>...]
[ARGS <arg>...]
)
```
Behavior:
- Skips target creation when `CC_ENABLE_BENCHMARKS` is OFF.
- Requires `benchmark::benchmark`; if unavailable, warns and skips target creation.
- Without `NO_MAIN`, also requires and links `benchmark::benchmark_main`.
- With `NO_MAIN`, links `benchmark::benchmark` only; use this when the benchmark source provides `main()`.
- Requires non-empty `SRCS`.
- Does not register the benchmark with default CTest.
- Adds the benchmark target as a dependency of `run_benchmarks` when that aggregate target exists.
- Adds a `POST_BUILD` command to `run_benchmarks` that directly executes the benchmark binary with `ARGS`.
- `ARGS` are native Google Benchmark runtime arguments. There is no helper-specific `OUTPUT` keyword; use native flags such as `--benchmark_out=<path>` and `--benchmark_out_format=json`.
Example with native benchmark arguments, mirroring the positive helper fixture pattern:
```cmake
include("${CC_PROJECT_ROOT}/cmake/cc_targets.cmake")
include("${CC_PROJECT_ROOT}/cmake/cc_benchmark.cmake")
add_custom_target(run_benchmarks
COMMENT "Running all benchmarks"
VERBATIM
)
cc_benchmark(args_bench
SRCS bench_args.cc
ARGS --benchmark_out=${CMAKE_BINARY_DIR}/bench_output.json
--benchmark_out_format=json
)
```
Custom-main example:
```cmake
cc_benchmark(no_main_bench
SRCS bench_no_main.cc
NO_MAIN
DEPS PRIVATE calc
)
```
## `cc_fuzz()`
Signature:
```cmake
cc_fuzz(<target>
[REQUIRED]
SRCS <src>...
[HDRS <hdr>...]
[INCS [PUBLIC|PRIVATE <dir>...]...]
[DEPS [PUBLIC|PRIVATE <dep>...]...]
[DEFINES [PUBLIC|PRIVATE <define>...]...]
[OPTIONS <option>...]
[FEATURES <feature>...]
[SMOKE_ARGS <arg>...]
[SMOKE_ENV <key=value>...]
[SMOKE_TIMEOUT <seconds>]
[ARGS <arg>...]
)
```
Behavior:
- Skips target creation when `CC_ENABLE_FUZZTEST` is OFF.
- Requires non-empty `SRCS`.
- Links `fuzztest::fuzztest` when available, otherwise `fuzztest`.
- Links the available FuzzTest GTest main provider when present.
- If FuzzTest is unavailable, warns and skips by default. With `REQUIRED`, configure fails with guidance to use the fuzz lane.
- Does not materialize FuzzTest dependencies and does not change compiler standard itself.
- Fuzz targets are intended for the fuzz lane: Clang and C++17 via `cmake --preset fuzz` or equivalent `CPP_TEMPLATE_FUZZ_LANE=ON` setup.
- Registers only a deterministic CTest smoke test named `<target>_smoke` with label `fuzz_smoke`.
- `SMOKE_ARGS` and `SMOKE_ENV` are for fast deterministic CI smoke execution; `SMOKE_TIMEOUT` defaults to 30 seconds.
- `ARGS` are stored as `_CC_FUZZ_ARGS` metadata for long-running direct executable invocation outside CTest.
- There are no `run_fuzz_<name>` wrapper targets.
Example based on current root usage:
```cmake
include(cmake/cc_targets.cmake)
include(cmake/cc_fuzz.cmake)
cc_fuzz(calc_fuzz
SRCS src/calc_fuzz.cc
DEPS PRIVATE calc
SMOKE_ENV FUZZTEST_PRNG_SEED=42
)
```
Example with explicit smoke and long-run arguments:
```cmake
cc_fuzz(parser_fuzz
REQUIRED
SRCS parser_fuzz.cc
DEPS PRIVATE parser
FEATURES cxx_std_17
SMOKE_ARGS --fuzz_for=1s
SMOKE_ENV FUZZTEST_PRNG_SEED=42
SMOKE_TIMEOUT 10
ARGS --fuzz_for=600s
)
```
## `cc_install()`
Signature:
```cmake
cc_install(<target> [DEPS <logical-name>...])
```
Behavior:
- Marks an existing target for install/export by setting `_CC_INSTALL TRUE`.
- Fails if the target does not exist.
- `DEPS` lists install-time dependency logical names registered with `cc_register_dependency()`.
- `DEPS` is not a link dependency list. Link dependencies belong in `cc_library(... DEPS ...)` or `cc_executable(... DEPS ...)`; install-time `DEPS` control generated `find_dependency()` calls and static private dependency validation.
- Installable targets must have explicit namespaced `ALIAS` metadata before finalization. The helpers do not auto-create aliases.
Example using explicit install marking instead of an `INSTALL` keyword:
```cmake
cc_library(core
TYPE STATIC
SRCS core.cpp
INCS PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
DEPS PRIVATE ZLIB::ZLIB
ALIAS mypkg::core
)
cc_register_dependency(zlib
PACKAGE ZLIB
TARGETS ZLIB::ZLIB
)
cc_install(core DEPS zlib)
```
## `cc_register_dependency()`
Signature:
```cmake
cc_register_dependency(<logical-name>
PACKAGE <PackageName>
[VERSION <version-or-range>]
[EXACT]
[COMPONENTS <component>...]
[OPTIONAL_COMPONENTS <component>...]
[TARGETS <target>...]
)
```
Behavior:
- Registers package metadata for generated package config files.
- `PACKAGE` is required and becomes the `find_dependency(<PackageName> ...)` package name.
- `VERSION`, `EXACT`, `COMPONENTS`, and `OPTIONAL_COMPONENTS` are forwarded to `find_dependency()`.
- `TARGETS` records the imported targets provided by the package. It is metadata used by install/export validation.
- Duplicate logical names are allowed only when all metadata matches exactly; conflicting re-registration fails at configure time.
- Reference registered logical names from `cc_install(<target> DEPS <logical-name>...)`.
Example:
```cmake
cc_register_dependency(fmt
PACKAGE fmt
VERSION 10.0
TARGETS fmt::fmt
)
cc_library(formatting
TYPE STATIC
SRCS formatting.cpp
DEPS PUBLIC fmt::fmt
ALIAS mypkg::formatting
INSTALL
)
cc_install(formatting DEPS fmt)
```
## `cc_finalize_install()`
Signature:
```cmake
cc_finalize_install()
```
Behavior:
- Idempotent; repeated calls after the first do nothing.
- Returns without producing install rules when `CC_ENABLE_INSTALL` is OFF.
- Uses only targets marked by `INSTALL` or `cc_install()`.
- Validates that every install target has an explicit namespaced alias of the form `namespace::name`.
- Requires all installed targets to use the same namespace.
- Sets `EXPORT_NAME` from the alias leaf and installs targets into a single export set named `<project>Targets`.
- Generates and installs `<project>Config.cmake` and `<project>ConfigVersion.cmake`.
- Emits `find_dependency()` calls for logical dependencies declared with `cc_install(... DEPS ...)`.
- Installs public headers from `include/<project>/` when that directory exists.
- `cc_project()` schedules this function with `cmake_language(DEFER)` for top-level projects, so most projects should not need to call it manually.
Manual finalization example for a narrow fixture or custom project layout:
```cmake
include(cmake/cc_project.cmake)
include(cmake/cc_targets.cmake)
include(cmake/cc_install.cmake)
cc_project()
cc_library(core
TYPE STATIC
SRCS core.cpp
ALIAS mypkg::core
INSTALL
)
cc_finalize_install()
```
## Unsupported helper patterns
Do not use these patterns through the helper API:
```cmake
# Unsupported: OBJECT library type.
cc_library(obj TYPE OBJECT SRCS obj.cpp)
# Unsupported: missing visibility before dependency.
cc_library(core TYPE STATIC SRCS core.cpp DEPS fmt::fmt)
# Unsupported: executable build-tree aliases.
add_executable(mypkg::tool ALIAS tool)
# Unsupported: helper-specific benchmark output keyword.
cc_benchmark(core_bench SRCS bench.cc OUTPUT bench.json)
# Unsupported: expected per-target run wrappers; use run_fuzz_tests or ctest labels instead.
# Do not run: cmake --build build --target run_fuzz_core_fuzz
```
+316
View File
@@ -0,0 +1,316 @@
# Install/export and dependency policy
This project exposes install/export support through the CMake helpers in
`cmake/cc_targets.cmake` and `cmake/cc_install.cmake`. Install/export is explicit:
only targets marked with `INSTALL` or `cc_install(<target>)` are installed, and
external packages are rediscovered by consumers rather than bundled into the install
prefix.
## Install/export entry points
Install/export is enabled when `CC_ENABLE_INSTALL` is true. The top-level project
uses install/export by default; subprojects can disable it through the usual project
configuration.
Targets enter the export set in one of two ways:
```cmake
cc_library(my_lib
TYPE STATIC
SRCS my_lib.cc
ALIAS MyPackage::my_lib
INSTALL
)
cc_library(other_lib
TYPE STATIC
SRCS other_lib.cc
ALIAS MyPackage::other_lib
)
cc_install(other_lib)
cc_finalize_install()
```
`cc_finalize_install()` is idempotent and normally runs at generate time. It creates
install rules for marked targets, exports `<project>Targets.cmake`, generates
`<project>Config.cmake`, generates `<project>ConfigVersion.cmake`, and installs public
headers from `include/<project>/` when that directory exists.
Unmarked targets are not installed automatically. This is intentional: a private or
internal build target stays out of the installed package unless it is explicitly marked
for install.
## Required install metadata
Every installed target needs a valid export identity:
- Provide `ALIAS namespace::name` on `cc_library(... INSTALL)` or
`cc_executable(... INSTALL)` targets.
- For targets installed by direct `cc_install()`, set the `_CC_ALIAS` target property
yourself if the target was not created by a helper that accepts `ALIAS`.
- The alias must contain exactly one `::` separator.
- Namespace and target name must both be non-empty.
- All targets in one generated package must share the same namespace.
Good:
```cmake
cc_library(core
TYPE STATIC
SRCS core.cc
ALIAS MyPackage::core
INSTALL
)
```
Bad:
```cmake
cc_library(core TYPE STATIC SRCS core.cc INSTALL) # missing ALIAS
cc_library(core TYPE STATIC SRCS core.cc ALIAS core INSTALL) # not namespaced
cc_library(core TYPE STATIC SRCS core.cc ALIAS a::b::c INSTALL) # too many separators
```
During finalization the alias leaf is used as `EXPORT_NAME`, so a raw target such as
`core_impl` can be exported as `MyPackage::core` by setting `ALIAS MyPackage::core`.
## Header install policy
`cc_finalize_install()` installs headers only from:
```text
include/<project>/
```
The destination is:
```text
<prefix>/include/<project>/
```
Only `*.h` and `*.hpp` files under that directory are installed by this helper. Headers
stored elsewhere are not automatically discovered or installed by `cc_finalize_install()`.
If a package needs a different layout, add explicit install rules and keep the exported
include directories consistent with the installed tree.
The install-consumer fixture uses this convention with headers such as:
```cpp
#include "ConsumerTestLib/math_utils.h"
#include "ConsumerTestLib/str_utils.h"
```
Its package fixture also adds install-tree include usage requirements:
```cmake
target_include_directories(consumer_test_math PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
```
Do not assume the helper rewrites arbitrary source-tree include directories into
install-tree paths. Keep installed public headers under `include/<project>/` and set
explicit `BUILD_INTERFACE` / `INSTALL_INTERFACE` include directories when consumer
usage requires them.
## Dependency registry and generated `find_dependency()`
External install-time dependencies are declared with `cc_register_dependency()` and
referenced from `cc_install(... DEPS ...)`:
```cmake
cc_register_dependency(fmt
PACKAGE fmt
VERSION 9.0
TARGETS fmt::fmt
)
cc_register_dependency(spdlog
PACKAGE spdlog
VERSION 1.12
COMPONENTS fmt
TARGETS spdlog::spdlog
)
cc_library(dep_lib
TYPE STATIC
SRCS dep_lib.cc
ALIAS MyPackage::dep_lib
INSTALL
)
cc_install(dep_lib DEPS fmt spdlog)
```
For every referenced logical dependency, `cc_finalize_install()` validates that the
logical name was registered. It then writes `include(CMakeFindDependencyMacro)` and
`find_dependency(...)` calls into the generated package config before including the
exported targets file. The example above generates lines equivalent to:
```cmake
include(CMakeFindDependencyMacro)
find_dependency(fmt 9.0)
find_dependency(spdlog 1.12 COMPONENTS fmt)
include("${CMAKE_CURRENT_LIST_DIR}/MyPackageTargets.cmake")
```
Supported registration metadata:
- `PACKAGE <PackageName>`: required. This is the name passed to `find_dependency()`.
- `VERSION <version-or-range>`: optional.
- `EXACT`: optional exact version requirement.
- `COMPONENTS <component>...`: optional required components.
- `OPTIONAL_COMPONENTS <component>...`: optional components.
- `TARGETS <imported-target>...`: metadata used by static private dependency
validation.
Duplicate logical names are allowed only when every metadata field matches the previous
registration exactly.
External dependencies are not copied, vendored, bundled, or installed into this package's
prefix by these helpers. Consumers must have those dependencies available to `find_package()`
through their normal package manager, `CMAKE_PREFIX_PATH`, toolchain file, or other CMake
package discovery mechanism.
## Static `PRIVATE` dependency policy
Installed static libraries can leak `PRIVATE` link dependencies into their exported
usage requirements as link-only dependencies. The helpers therefore validate static
libraries marked for install.
For a `PRIVATE` project-target dependency:
```cmake
cc_library(private_dep
TYPE STATIC
SRCS private_dep.cc
ALIAS MyPackage::private_dep
INSTALL
)
cc_library(installed_lib
TYPE STATIC
SRCS installed_lib.cc
DEPS PRIVATE private_dep
ALIAS MyPackage::installed_lib
INSTALL
)
```
This is valid only because `private_dep` is also installed/exported and has a valid
namespaced alias. If `private_dep` is not installed, finalization rejects the package.
Fix one of these ways:
- install/export the project dependency too and give it a valid namespaced alias;
- make the dependency `PUBLIC` or `INTERFACE` if consumers are intended to link it;
- for true external packages, use `cc_register_dependency()` plus `cc_install(... DEPS ...)`.
For a `PRIVATE` imported dependency:
```cmake
add_library(ExternalPkg::external INTERFACE IMPORTED)
cc_register_dependency(external_pkg
PACKAGE ExternalPkg
TARGETS ExternalPkg::external
)
cc_library(installed_lib
TYPE STATIC
SRCS installed_lib.cc
DEPS PRIVATE ExternalPkg::external
ALIAS MyPackage::installed_lib
INSTALL
)
cc_install(installed_lib DEPS external_pkg)
```
This is valid because the imported target is listed in the registered dependency's
`TARGETS` metadata and the installed target declares that logical dependency with
`cc_install(... DEPS external_pkg)`. Without that metadata, finalization rejects the
installed static library.
Raw link items, linker flags, and generator expressions that are not CMake targets are
left untouched by this validation.
## Consumer verification
The narrow install-consumer fixture validates the intended installed-package contract:
1. configure, build, and install a package fixture;
2. configure a separate consumer using only `find_package(ConsumerTestLib CONFIG REQUIRED)`;
3. link against imported targets `ConsumerTestLib::consumer_test_math` and
`ConsumerTestLib::consumer_test_str`;
4. build and run the consumer executable.
Run it with:
```sh
python3 scripts/dev_check.py install-consumer
```
The fixture is intentionally narrow. It verifies package config generation, target
export, installed headers, and separate consumer usage; it does not imply arbitrary
transitive dependencies are automatically installed or bundled.
## Troubleshooting
### `target "..." is marked for install but has no ALIAS`
The installed target has no export identity. Add a namespaced alias:
```cmake
cc_library(my_lib TYPE STATIC SRCS my_lib.cc ALIAS MyPackage::my_lib INSTALL)
```
For manually created targets, set `_CC_ALIAS` before `cc_install()`:
```cmake
add_library(tooling INTERFACE)
set_target_properties(tooling PROPERTIES _CC_ALIAS "MyPackage::tooling")
cc_install(tooling)
```
### `ALIAS "..." is not namespaced` or `must contain exactly one "::" separator`
Use exactly `namespace::name`. Do not use plain names (`my_lib`) or nested aliases
(`a::b::c`). Keep one namespace for all installed targets in the package.
### `target(s) reference unknown install-time dependency "..."`
A target declares `cc_install(target DEPS logical_name)` but no matching
`cc_register_dependency(logical_name PACKAGE ...)` exists. Register the dependency before
finalization.
### `cc_register_dependency(...): PACKAGE is required`
Every dependency registration must name the package that consumers will find:
```cmake
cc_register_dependency(fmt PACKAGE fmt TARGETS fmt::fmt)
```
### `PRIVATE imported dependency "..." is not covered by a registered install dependency`
An installed static library links a CMake imported target privately, but that imported
target is not covered by dependency registry metadata. Register the external package with
`TARGETS <imported-target>` and declare the logical dependency on the installed target:
```cmake
cc_register_dependency(fmt PACKAGE fmt TARGETS fmt::fmt)
cc_install(my_static_lib DEPS fmt)
```
### `PRIVATE project-target dependency "..." that is not installed in the same export set`
An installed static library privately links another project target that is not itself
installed/exported. Install/export that dependency with a valid alias, or change the link
visibility if consumers should depend on it directly.
### Installed headers are missing
Confirm the headers live under `include/<project>/` and use `.h` or `.hpp` extensions.
Headers elsewhere are outside the helper's automatic header install policy. Also verify
that exported targets expose install-tree include directories such as
`$<INSTALL_INTERFACE:include>` when consumers include package headers.
+204
View File
@@ -0,0 +1,204 @@
# C++ Template Design Source of Truth
This document is the implementation-facing source-of-truth index for the C++ project template redesign. It does not replace the detailed drafts; it identifies which parts are normative and records the constraints implementation agents must preserve.
## Normative Inputs
- `.sisyphus/plans/cpp-template-redesign.md` is the read-only executable implementation plan owned by Atlas/Orchestrator.
- `.sisyphus/drafts/cpp-template-redesign.md` is normative for consolidated dependency, tooling, style, preset, hook, install/export, FuzzTest, and QA decisions.
- `.sisyphus/drafts/cmake-api-design.md` is normative for the CMake helper API, dependency loading policy, install/export behavior, and FuzzTest patch/dependency policy.
Older research notes, review findings, and open-question discussions in the drafts are historical unless their decisions appear in the latest consolidated or final-resolution sections.
## Fixed Project Constraints
- Primary standard: C++14.
- C++11: best-effort/smoke compatibility only, not the default blocking path.
- C++17: isolated opt-in path, primarily for the `fuzz` preset and FuzzTest.
- Toolchains: Clang/GCC only; no MSVC support.
- Dependency loading: local CPM archives under `3rd/archives/` with `URL_HASH SHA256=...`; no hidden remote fallback.
- Offline strategy: local archive/source paths and hash verification, not a global offline mode switch.
- FuzzTest: required by the redesign only in the isolated `fuzz` preset/tree, with pristine upstream archive, patch files, script-driven apply/verify workflow, and no protobuf-backed FuzzTest path.
- HTTP baseline: cpp-httplib. `libhv` is excluded from v1 unless explicitly re-approved.
- Removed/not allowed by default: `magic_enum`, protobuf FuzzTest integration, global offline mode, CMake `format`/`check_format`/`run_clang_tidy` targets.
## Normative Template C++ Style
These template-specific style rules intentionally supersede generic agent C++ defaults wherever they conflict:
- clang-format 17+ is required.
- Indentation: 4 spaces, no tabs.
- Column target: 120.
- Functions, classes, structs, enums, and namespaces use Allman braces.
- Control statements and lambdas use attached braces.
- Include order: corresponding/main header first, then project/generated quoted headers, then C headers, C++ standard headers, known third-party headers, OS/system headers, and other angle headers.
- Private/protected members use `m_` + `snake_case`.
- Static non-const members and function-local statics use `s_` + `snake_case`.
- Constants, including local constants, use `kCamelCase`.
- Public struct fields and local variables use `snake_case`.
- Types use `PascalCase`; functions and methods use `camelCase`; namespaces use `lower_case`.
- clang-tidy is strict from the start with warnings-as-errors for enabled checks.
- `.clang-format` is the formatter source-of-truth for enforceable whitespace, brace, column, pointer/reference, return-type, and include-order rules. `scripts/format.py --check` verifies selected C/C++ files and `scripts/format.py --fix` rewrites them; naming rules such as `m_`, `s_`, and `kCamelCase` remain documented conventions because clang-format cannot enforce identifier names.
## Implementation Handoff Rule
Implementation agents must treat the latest consolidated/final-resolution draft sections as authoritative. If an older section conflicts with the constraints above, the older section is historical/superseded and must not drive implementation.
## v1 Dependency Inventory (Frozen)
Source of truth: `cmake/deps/versions.cmake`.
| Name | Version | C++ Gate | CMake Targets | Presets | Archive | SHA256 |
|------|---------|----------|---------------|---------|---------|--------|
| spdlog | 1.15.3 | C++11 | `spdlog::spdlog` | debug,release,fuzz | spdlog-1.15.3.tar.gz | PENDING_T12 |
| fmt | 11.0.2 | C++11 | `fmt::fmt` | debug,release,fuzz | fmt-11.0.2.tar.gz | PENDING_T12 |
| nlohmann/json | 3.11.3 | C++11 | `nlohmann_json::nlohmann_json` | debug,release,fuzz | nlohmann-json-3.11.3.tar.gz | PENDING_T12 |
| toml11 | 4.4.0 | C++11 | `toml11::toml11` | debug,release,fuzz | toml11-4.4.0.tar.gz | PENDING_T12 |
| CLI11 | 2.4.2 | C++11 | `CLI11::CLI11` | debug,release,fuzz | CLI11-2.4.2.tar.gz | PENDING_T12 |
| asio (standalone) | 1.30.2 | C++11 | `asio::asio` | debug,release,fuzz | asio-1.30.2.tar.gz | PENDING_T12 |
| concurrentqueue | 1.0.4 | C++11 | `concurrentqueue::concurrentqueue` | debug,release,fuzz | concurrentqueue-1.0.4.tar.gz | PENDING_T12 |
| readerwriterqueue | 1.0.6 | C++11 | `readerwriterqueue::readerwriterqueue` | debug,release,fuzz | readerwriterqueue-1.0.6.tar.gz | PENDING_T12 |
| ghc::filesystem | 1.5.14 | C++11 | `ghcFilesystem::ghc_filesystem` | debug,release,fuzz | ghc-filesystem-1.5.14.tar.gz | PENDING_T12 |
| expected-lite | 0.8.0 | C++11 | `nonstd::expected-lite` | debug,release,fuzz | expected-lite-0.8.0.tar.gz | PENDING_T12 |
| cpp-httplib | 0.18.3 | C++11 | `httplib::httplib` | debug,release,fuzz | cpp-httplib-0.18.3.tar.gz | PENDING_T12 |
| GTest/GMock (normal) | 1.16.0 | C++14 | `GTest::gtest`, `GTest::gtest_main`, `GTest::gmock`, `GTest::gmock_main` | debug,release | googletest-1.16.0.tar.gz | PENDING_T12 |
| Benchmark | 1.7.1 | C++11 | `benchmark::benchmark`, `benchmark::benchmark_main` | debug,release | benchmark-1.7.1.tar.gz | PENDING_T12 |
| GTest/GMock (fuzz) | 1.17.0 | C++17 | `GTest::gtest`, `GTest::gmock` | fuzz | googletest-1.17.0.tar.gz | PENDING_T12 |
| Abseil | 20260107.1 | C++17 | `absl::*` | fuzz | abseil-cpp-20260107.1.tar.gz | PENDING_T12 |
| RE2 | 2025-11-05 | C++17 | `re2::re2` | fuzz | re2-2025-11-05.tar.gz | PENDING_T12 |
| ANTLR4 runtime | 4.13.2 | C++17 | `antlr4_static` | fuzz | antlr4-4.13.2.tar.gz | PENDING_T12 |
| FuzzTest | 2026-02-19 | C++17 | `fuzztest::fuzztest` | fuzz | fuzztest-2026-02-19.tar.gz | 1c6e04...04ef (pinned T5) |
### Excluded from v1
- `libhv` — excluded; cpp-httplib is the v1 HTTP baseline.
- `magic_enum` — removed per user request.
- `protobuf` — FuzzTest protobuf-backed paths forbidden.
- `oatpp`, `tracy`, `microprofile`, `breakpad` — removed from default template.
- `crow` — C++17-only; cpp-httplib covers C++14 HTTP.
- `BLAKE3` — CMake propagates `cxx_std_20` PUBLIC.
- `bitsery` — repo/path verification unresolved.
- `outcome` — replaced by expected-lite.
- `OpenSSL` — rejected as default dependency; too heavy.
### GTest Version Isolation
Normal C++14 builds use GTest/GMock 1.16.0. Fuzz/C++17 builds use GTest/GMock 1.17.x.
These must never coexist in the same build tree. The dependency modules enforce this separation.
### Archive & Hash Notes
- SHA256 values marked `PENDING_T12` require concrete archive acquisition (T12/T14).
- FuzzTest SHA256 is pinned from T5: `1c6e04065eb988e2c99613369db8294aa58429d392bf479740b237f1255204ef`.
- CPM `URL_HASH SHA256=...` enforcement in per-dependency modules (T13) consumes `versions.cmake` variables.
- Abseil/RE2/ANTLR4 versions must match the pinned FuzzTest release's `BuildDependencies.cmake`; re-verify when rebasing.
## CMake Preset Contract (T6)
This section defines the visible CMake preset contract that later implementation tasks must preserve. T6 is a contract-only step: the repository currently has no root `CMakePresets.json`, and T11 owns the full preset implementation and configure/build/test validation. If T11 adds hidden/internal presets, each hidden preset must be minimal, justified in this document, and must not expand the visible user-facing set.
Normative compatibility constraints:
- CMake preset file compatibility is separate from the project CMake code minimum. Project CMake code remains minimum 3.19, but a single root `CMakePresets.json` containing configure, build, and test presets should use CMakePresets schema version 2, which requires CMake 3.20+ for preset users.
- If strict CMake 3.19 preset-file compatibility is required, schema version 1 supports configure presets only; build/test behavior must then be invoked manually or documented outside presets. Do not add a `$schema` field while the project supports older preset file versions.
- Every visible configure preset must use the Ninja generator.
- Every visible configure preset must set `CMAKE_EXPORT_COMPILE_COMMANDS=ON`.
- Every visible configure preset must use `binaryDir` equivalent to `${sourceDir}/build/${presetName}`; the documented shorthand is `build/${presetName}`.
- The visible configure preset names are exactly `debug`, `release`, `asan`, `tsan`, `fuzz`, and `cxx11-smoke`. No MSVC presets are part of v1.
- Build presets should mirror each visible configure preset name and build its matching configure preset.
- Test presets should exist only where tests are part of the lane contract: `debug`, `release`, `asan`, `tsan`, and `fuzz` for deterministic fuzz smoke. `cxx11-smoke` is configure/build only unless a compatible C++11 test stack is explicitly proven later.
| Preset | Purpose | C++ standard | Build type | Tests, benchmark, fuzz behavior | Sanitizer flags intent | Build directory | Compile commands | Dependency lane |
|--------|---------|--------------|------------|-------------------------------|------------------------|-----------------|------------------|-----------------|
| `debug` | Default developer lane and fast local checks. | C++14 | `Debug` | Tests enabled; fast developer checks enabled; benchmarks may be built when the normal benchmark option is enabled; fuzz disabled. | None; sanitizer behavior belongs only to `asan`/`tsan`. | `build/debug` | `ON` | Core deps plus normal GTest/GMock 1.16.0 and Benchmark 1.7.1. |
| `release` | Optimized normal build lane. | C++14 | `Release` | Tests enabled for release validation; benchmarks may be enabled for release performance checks; fuzz disabled. | None. | `build/release` | `ON` | Core deps plus normal GTest/GMock 1.16.0 and Benchmark 1.7.1. |
| `asan` | AddressSanitizer validation lane. | C++14 | `Debug` | Tests enabled; benchmark/fuzz disabled by default so sanitizer runs stay focused on correctness tests. | Enable AddressSanitizer compile and link flags in the implementation; do not hide ASan in `debug`. | `build/asan` | `ON` | Same normal dependency lane as `debug`; no FuzzTest stack. |
| `tsan` | ThreadSanitizer validation lane. | C++14 | `Debug` | Tests enabled; benchmark/fuzz disabled by default so sanitizer runs stay focused on correctness tests. | Enable ThreadSanitizer compile and link flags in the implementation; do not hide TSan in `debug`. | `build/tsan` | `ON` | Same normal dependency lane as `debug`; no FuzzTest stack. |
| `fuzz` | Isolated deterministic fuzz smoke and FuzzTest development lane. | C++17 | `Debug` | Normal tests are not the focus; deterministic fuzz smoke is registered in CTest with fixed seed/run count; long fuzzing stays direct executable invocation outside default CTest. Benchmarks disabled. | No default sanitizer requirement in the contract; sanitizer fuzzing, if added later, must be explicit and not affect normal lanes. | `build/fuzz` | `ON` | Core deps plus isolated FuzzTest lane: GTest/GMock 1.17.0, Abseil 20260107.1, RE2 2025-11-05, ANTLR4 runtime 4.13.2, FuzzTest 2026-02-19. Requires Clang in implementation. |
| `cxx11-smoke` | Best-effort compatibility smoke for the C++11-compatible subset. | C++11 | `Debug` | Configure/build compatible subset only; tests, benchmarks, and fuzz are disabled until a compatible stack is explicitly proven. | None. | `build/cxx11-smoke` | `ON` | Core dependencies whose inventory gate is C++11 only; excludes normal GTest/GMock 1.16.0, Benchmark, and all FuzzTest-lane dependencies. |
Implementation notes for T11 and later tasks:
- Preset options must make lane behavior explicit through cache variables rather than implicit hidden magic. Suggested option intent: enable tests for normal and sanitizer lanes, enable deterministic fuzz smoke only in `fuzz`, and disable tests/benchmark/fuzz in `cxx11-smoke`.
- `debug` remains the fast default path for developer checks; it must not silently enable sanitizer flags or FuzzTest.
- `fuzz` is the only v1 lane allowed to require C++17 and Clang for FuzzTest. It must not load normal GoogleTest/GMock 1.16.0 into the same build tree as FuzzTest's GoogleTest/GMock 1.17.0 lane.
- Hidden presets are not required by this contract. If T11 uses a hidden base to remove duplication, keep it internal, do not expose extra user presets, and document the reason here and in evidence.
### T11 Implementation Status
- A hidden `_base` preset provides `generator: "Ninja"` and `CMAKE_EXPORT_COMPILE_COMMANDS=ON` to avoid repetition across six visible presets. It does not appear in `cmake --list-presets`. Rationale: the only shared state is the generator and compile-commands export; build type, C++ standard, and sanitizer flags are per-lane.
- Each visible preset sets its own explicit `binaryDir` rather than relying on `${presetName}` expansion from the hidden base, because `${presetName}` behavior with inherited presets can be ambiguous in older CMake 3.20.x patch levels.
- Sanitizer flags are set directly in preset `cacheVariables` (`CMAKE_CXX_FLAGS`, `CMAKE_EXE_LINKER_FLAGS`). Later tasks (T16+) may add CMake-level option gating (e.g., `CC_ENABLE_SANITIZERS`) that consumes or overrides these; the preset contract remains the user-facing surface.
- Three lane option cache variables are set in all presets: `CPP_TEMPLATE_ENABLE_TESTS` (ON in debug/release/asan/tsan, OFF in fuzz/cxx11-smoke), `CPP_TEMPLATE_ENABLE_BENCHMARKS` (ON in debug/release, OFF in asan/tsan/fuzz/cxx11-smoke), `CPP_TEMPLATE_FUZZ_LANE` (ON only in fuzz, OFF everywhere else).
- `fuzz` preset enforces Clang at configure time: `CMakeLists.txt` contains `if(CPP_TEMPLATE_FUZZ_LANE)` guard that calls `message(FATAL_ERROR ...)` on non-Clang compilers. Verified: GCC 13.3.0 fails with clear message; Clang 18.1.3 configures successfully.
- A deterministic fuzz smoke CTest placeholder `fuzz_lane.config_check` is registered via `cmake -E echo` inside the `CPP_TEMPLATE_FUZZ_LANE` guard. It has label `fuzz_smoke` and is gated to the fuzz preset only. Normal `calc_test` is not registered in the fuzz lane.
- `cxx11-smoke` disables tests, benchmarks, and fuzz via cache variables. Verified: configure produces 0 CTest registrations.
- `debug`, `release`, `asan`, and `tsan` configure successfully with tests enabled. Verified: `calc_test` registered in each; sanitizer presets keep benchmarks disabled.
- `cxx11-smoke` has no test preset; configure/build only per contract.
### Fuzz Smoke Testing
The `fuzz` preset runs FuzzTest targets in unit-test mode (`FUZZTEST_FUZZING_MODE=OFF`), which executes fuzz tests as deterministic GTest-compatible tests rather than continuous fuzzing campaigns.
#### Deterministic CTest Smoke
All fuzz targets registered via `cc_fuzz()` appear in CTest with the `fuzz_smoke` label:
```bash
# Run all fuzz smoke tests
ctest --test-dir build/fuzz --output-on-failure -L fuzz_smoke
```
Determinism is ensured through two mechanisms:
1. **`.WithSeeds()`** in the fuzz test source: provides concrete fixed inputs that always run. In unit-test mode, FuzzTest runs each seed once as a GTest test case.
2. **`FUZZTEST_PRNG_SEED`** environment variable: set via `SMOKE_ENV` in `cc_fuzz()` registration. Makes any additional PRNG-driven input generation reproducible across runs.
The `cc_fuzz()` helper supports three smoke-test customization keywords:
| Keyword | Purpose | Example |
|---------|---------|----------|
| `SMOKE_ARGS` | Arguments for the CTest smoke command | (empty by default) |
| `SMOKE_ENV` | Environment variables for CTest smoke | `FUZZTEST_PRNG_SEED=42` |
| `SMOKE_TIMEOUT` | CTest timeout in seconds | 30 (default) |
#### Long-Running Fuzzing (Outside CTest)
Continuous fuzzing campaigns use the fuzz executable directly with `--fuzz` flags. These are NOT registered in CTest because they run indefinitely.
**Important:** `--fuzz`, `--fuzz_for`, and `--time_limit_per_input` require a fuzzing-mode build (`FUZZTEST_FUZZING_MODE=ON`). The default `fuzz` preset CTest smoke runs in unit-test mode (`FUZZTEST_FUZZING_MODE=OFF`) using `.WithSeeds()` and `FUZZTEST_PRNG_SEED`. To use continuous fuzzing flags, rebuild with `FUZZTEST_FUZZING_MODE=ON` or invoke a fuzzing-mode binary directly.
```bash
# List available fuzz tests
./build/fuzz/calc_fuzz --gtest_list_tests
# Run a specific fuzz test in fuzzing mode with safety limits
# (requires FUZZTEST_FUZZING_MODE=ON build)
./build/fuzz/calc_fuzz \
--fuzz=CalcFuzz.AddNeverCrashes \
--time_limit_per_input=30s \
--fuzz_for=60s
```
Safety flags for long fuzzing (fuzzing mode only):
- `--fuzz_for=<duration>`: total fuzzing time (e.g., `60s`, `5m`).
- `--time_limit_per_input=<duration>`: max time per input before timeout.
- These flags have no effect in unit-test mode (`FUZZTEST_FUZZING_MODE=OFF`).
## T9 clang-tidy Policy
- `scripts/clang_tidy.py` is the script-first strict clang-tidy entry point; CMake must not define `tidy`, `run_clang_tidy`, `clang-tidy`, or `check_tidy` targets.
- The runner requires clang-tidy 17+ and a `compile_commands.json` in `--build-dir` before real execution. The default build directory is `build/debug`.
- Debug fast checks configure with `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` so `scripts/dev_check.py --fast` can feed clang-tidy without relying on CMake-integrated tidy targets.
- The runner uses the shared project path filter and checks only project C/C++ files, skipping build outputs, vendored trees, generated/task-state directories, `.sisyphus`, `.opencode`, `.git`, `3rd/`, and `third_party/`.
- Warnings are errors through both `.clang-tidy` (`WarningsAsErrors: '*'`) and the runner CLI (`--warnings-as-errors=*`). Each file is invoked separately so failures report exact `failed_file:` paths.
## T10 Local Git Hook Workflow
- Git hooks are local and opt-in. The template does not automatically install hooks during configure, build, test, formatting, clang-tidy, or other developer checks.
- `.githooks/pre-commit` is the repository hook entry point. It resolves the repository root with `git rev-parse --show-toplevel`, changes to that root, and delegates to `python3 scripts/pre_commit.py`.
- `scripts/pre_commit.py` prints and runs the exact fast-check delegation: `python3 scripts/dev_check.py --fast`. Its `--dry-run` mode prints the same command plan without executing checks.
- `scripts/setup_hooks.py` is the only setup entry point for hook installation. Non-dry-run setup verifies `.githooks/pre-commit`, ensures it is executable, and runs only `git config --local core.hooksPath .githooks`.
- `scripts/setup_hooks.py --dry-run` prints the intended local-only git config command without mutating git config. `scripts/setup_hooks.py --status` reports hook file existence, executable state, and the local `core.hooksPath` value when available.
- Global or user git configuration must never be modified by the template hook workflow.