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
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:
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user