A C++23 project template with Conan 2.25+ dependency management, CMake presets, testing, sanitizers, coverage, CI, and IDE setup.
This repository uses:
- CMake configure, build, and test presets as the public build interface
- Conan 2 for dependency management
- spdlog via Conan as the sample compiled dependency
- CLI11 via Conan for command-line parsing
- GoogleTest via Conan
- Doxygen for generated API documentation
After creating your own repository from this
template,
rename the project with scripts/rename.py. The script uses only the Python
standard library:
python3 scripts/rename.py my-project --github-owner your-github-userFrom my-project, the script derives my_project for CMake targets, the C++ namespace, and the
include directory. It derives MyProjectConan for the Conan recipe class. It then updates the
tracked project files, moves include/cpp_boilerplate/ to include/my_project/, and removes both
the script and this section.
Run with --dry-run first to preview every change. Use --title "My Project" to override the
README heading; the default is the project name in title case.
The build is layered, and each layer owns one thing:
- Conan owns the dependency graph, the toolchain, the CMake generator, and the ABI-relevant
settings. It resolves them from
conanfile.pyand theprofiles/files;conan.lockpins the exact graph for reproducible builds. - CMake presets (
CMakePresets.json) are the public build interface: thedebug,release,sanitize*, andcoveragenames you configure, build, and test. They are checked in and are the source of truth for how the project is built. - Conan writes its toolchain details into a generated
ConanPresets.jsonthatCMakePresets.jsonincludes. That file is an implementation detail, not an interface;make bootstrapis the one-time per-clone step that materializes it. - The
Makefileis a thin convenience wrapper: each target runsconan installfor the right profile, thencmake --workflow --preset <name>. On Windows you run those two commands directly (see Windows).
When you change the Conan configuration (versions, options, or profile), rerun make bootstrap or
the matching make target and keep using the same public preset names. The preset interface is
stable across toolchain changes.
.
├── include/ public headers
├── src/ application sources
├── tests/ unit tests
├── conanfile.py Conan dependency definition
├── conan/settings_user.yml custom sanitizer setting
├── profiles/ default and sanitizer Conan profiles
└── CMakePresets.json project-owned public presets
- CMake 3.29+ (for workflow presets and
CMAKE_LINKER_TYPE) - Conan 2.25+ (for the
CMakeConfigDepsgenerator) - Ninja (optional, Unix only; GNU Make is used when absent)
- ccache (optional; used automatically for first-party targets when on PATH; not with the Visual Studio generator, which ignores compiler launchers)
- mold or LLD (optional, Linux only; used automatically to link first-party targets when on PATH, with mold preferred)
- Doxygen (optional; required only for
make docs) - A compiler and standard library with working C++23 support
- GCC 13+
- LLVM Clang 17+
- Apple Clang 17+ recommended
- MSVC 2022 (17.10+) on Windows
The Conan recipe selects the CMake generator:
Ninjaon Unix-like systems when it is availableUnix Makefileson Unix-like systems whenninjais not installed- The Visual Studio generator matching the detected MSVC on Windows (multi-config; locates MSVC
itself, so no extra tool or
vcvarsenvironment is needed)
This boilerplate supports Linux, macOS, and Windows.
git clone https://github.com/megabyde/cpp-boilerplate.git
cd cpp-boilerplate
make bootstrap # generate ConanPresets.json
make debugTip
Run make help to list local convenience targets.
The Makefile is a Unix convenience wrapper. On Windows, drive Conan and the CMake presets directly
from any shell; the Visual Studio generator locates MSVC on its own, so no Developer PowerShell or
vcvarsall setup is required:
conan install . -pr=profiles/default -s="build_type=Release" --build=missing --lockfile=conan.lock
cmake --workflow --preset releasecmake --workflow --preset <name> runs configure, build, and test in one step; it is what the
make targets call on Unix too. The sanitize, sanitize-asan, sanitize-ubsan, and coverage
presets are Unix-only.
make sanitize
make sanitize-asan
make sanitize-ubsanThis uses a dedicated sanitizer build tree (Conan names it build/debug-addressundefinedbehavior
after the build type and compiler.sanitizer setting) and a Conan sanitize profile so dependencies
are rebuilt with matching instrumentation, not linked from their plain (uninstrumented) Debug
binaries.
The three modes use Conan profile inheritance. Each mode inherits
profiles/sanitize-common, which includes profiles/default, selects
Debug, and defines the common instrumentation flags and [runenv]. The mode profile then appends
its own -fsanitize flags:
sanitize: combined ASan + UBSan (profiles/sanitize); driven bymake sanitizeand CI.sanitize-asan: AddressSanitizer only (profiles/sanitize-asan).sanitize-ubsan: UndefinedBehaviorSanitizer only (profiles/sanitize-ubsan).
Each make sanitize* target installs the matching instrumented dependency graph, then runs the
matching CMake workflow preset. All three modes share conan.lock.
Each mode gets its own package_id and build tree (build/debug-address,
build/debug-undefinedbehavior, build/debug-addressundefinedbehavior).
ASan/UBSan runtime options (halt_on_error, print_stacktrace, and related checks) live in
profiles/sanitize-common under [runenv] (inherited by every mode). Conan injects them into the
generated per-mode test preset, which the public sanitize* test preset inherits, so
ctest/cmake --workflow runs the instrumented tests with those options: a single source of truth,
no duplication in CMakePresets.json.
That separation relies on a custom compiler.sanitizer setting defined in
conan/settings_user.yml. The setting gives instrumented dependency
binaries a distinct Conan package_id; the sanitizer flags themselves travel through the profile's
tools.build:* conf, which does not affect package_id. Without the setting, --build=missing
would silently reuse the uninstrumented Debug binaries.
make bootstrap-sanitize installs that file into your Conan home with conan config install conan/
(the repository's conan/ directory) before resolving dependencies, but only when the home has no
settings_user.yml yet. An existing file (for example one your dotfiles manage) is left untouched;
it just has to cover the sanitizer values this project uses. A superset (extra compilers, values, or
other subsettings) is fine: scripts/check_settings_subset.py
verifies this and the build stops with a merge instruction when values are missing. Installing the
file changes the global Conan configuration: it adds the compiler.sanitizer subsetting. Its
default is null, which is omitted from package_id, so non-sanitize builds do not change.
The default make bootstrap does not install sanitizer-instrumented dependencies. Run
make bootstrap-sanitize or the matching make sanitize* target when you need them.
First-party targets are instrumented by the same Conan toolchain (the profile's tools.build:*
flags reach the consumer), so the profiles are the single source of sanitizer flags.
Tests are controlled by CMake's built-in BUILD_TESTING option from include(CTest). This project
leaves it at the default ON, so make debug, make release, make sanitize*, and
make coverage all run the GTest suite.
The main workflow presets are debug, release, sanitize, sanitize-asan, sanitize-ubsan, and
coverage. Configure, build, and test presets use the same names. docs is configure/build only
because it generates Doxygen HTML instead of compiling and testing the application.
The Conan-generated conan-* presets are internal implementation details and are not the public
interface for developers or CI.
conan.lock pins the exact dependency graph for reproducible builds. To update dependencies:
- Edit version pins in
conanfile.py. - Regenerate the lock file with
make lock. - Run the appropriate
maketarget to verify. - Commit both
conanfile.pyandconan.lock.
make format
make format-check
make lintmake format and make format-check cover C++ sources (clang-format), CMakeLists.txt
(cmake-format, from the cmakelang package), Python scripts
(ruff format), and tracked Markdown/JSON/YAML files
(prettier; conan.lock is excluded because Conan owns its formatting).
make lint runs clang-tidy against the debug compilation database, ruff check on scripts/, and
markdownlint on Markdown files; every enabled
check is an error. CI pins all lint and format tool versions in .github/ci.env.
Build, test, and generate an HTML coverage report with an enforced line floor:
make coverage-reportBoth compilers emit GCov-format data (--coverage), reported by a single tool:
gcovr (pip install gcovr). It writes coverage-report/index.html and
coverage.xml under build/coverage/.
The report fails if line coverage falls below COVERAGE_FAIL_UNDER (default 100; override with
make coverage-report COVERAGE_FAIL_UNDER=80).
Generate Doxygen HTML documentation locally:
make docsThe output is written to build/docs/html/. GitHub Pages builds the same target and publishes the
result from the main branch.
Warnings are errors by default (WARNINGS_AS_ERRORS, default ON) in every preset, locally and in
CI. Relax it for a single build tree with cmake --preset <name> -DWARNINGS_AS_ERRORS=OFF.
First-party targets also build with hardening flags by default (ENABLE_HARDENING, default ON;
disable with -DENABLE_HARDENING=OFF on any configure preset):
- GCC/Clang/AppleClang:
-fstack-protector-strongand architecture-matched control-flow protection (-fcf-protection=fullon x86-64;-mbranch-protection=standardon AArch64, Linux only, because pac-ret frames break exception unwinding under macOS's compact-unwind format) in every configuration; optimized configurations add-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2(Debug skips fortification because it requires optimization). Executables link as PIE so ASLR covers the program image, and on Linux the linker adds full RELRO (-z relro -z now) and an explicitly non-executable stack. - MSVC:
/guard:cf(Control Flow Guard) at compile and link; x64 also links with/CETCOMPAT(CET shadow stack). - Other compilers build unhardened rather than failing to configure.
First-party targets build the release preset with link-time optimization (LTO) by default
(ENABLE_LTO, default ON; disable with -DENABLE_LTO=OFF). It applies only to the Release
configuration (CMake's INTERPROCEDURAL_OPTIMIZATION_RELEASE property), so debug, sanitize*,
and coverage builds are unaffected; when the toolchain reports no LTO support, configure logs the
reason and continues without it rather than failing.
Sanitizer and coverage builds omit fortification (they build as Debug, which never defines
_FORTIFY_SOURCE): fortify conflicts with the ASan interceptors, and coverage builds run at -O0
where glibc fortification warns. The other hardening flags stay on. Like the warning options,
hardening covers first-party code only; dependency binaries from the Conan cache are not rebuilt
with these flags.
cmake --install installs the application binary to <prefix>/bin. Only the executable is
installed; the static library is an internal build artifact and is deliberately not installed or
exported.
$ cmake --workflow --preset release
$ cmake --install build/release --prefix /path/to/prefix
$ /path/to/prefix/bin/cpp_boilerplate
[2026-06-30 20:46:16.431] [info] cpp-boilerplate 0.1.0 starting
[2026-06-30 20:46:16.431] [info] field 0: alpha
[2026-06-30 20:46:16.431] [info] field 1: beta
[2026-06-30 20:46:16.431] [info] field 2: gamma
[2026-06-30 20:46:16.431] [info] done
$ /path/to/prefix/bin/cpp_boilerplate --version
0.1.0VS Code with CMake Tools will discover the checked-in public presets automatically after Conan
generates ConanPresets.json. Run make bootstrap first.
Then open the folder, accept the recommended extensions, and select the matching public preset. For
the checked-in launch configuration, choose the target you want in the CMake Tools sidebar and start
Debug: CMake Target. F5 builds the selected debug target and launches it from build/debug.
CLion can use the same public presets. Run make bootstrap first, then in CLion:
- Open the project root
- Select the
debug,release,sanitize*, orcoveragepreset as the active CMake profile - Reload CMake
Note
No IDE-specific task files are required for the build. The presets are the source of truth.
debug, sanitize, and coverage each use their own build tree, so switching between them
does not require forcing a fresh reconfigure.