Modern CMake template for C++ libraries with comprehensive infrastructure.
cpp-library provides a standardized CMake infrastructure template for C++ libraries. It eliminates boilerplate and provides consistent patterns for:
- Project Declaration: Uses existing
project()declaration with automatic git tag-based versioning - Library Setup: INTERFACE targets for header-only libraries, static/shared libraries for compiled libraries
- Installation: CMake package config generation with proper header and library installation
- Testing: Integrated doctest with CTest and compile-fail test support
- Documentation: Doxygen with doxygen-awesome-css theme
- Development Tools: clangd integration, CMakePresets.json, clang-tidy support
- CI/CD: GitHub Actions workflows with multi-platform testing and installation verification
- Dependency Management: CPM.cmake integration
The easiest way to create a new library project using cpp-library is with the setup.cmake script. This interactive script will guide you through creating a new project with the correct structure, downloading dependencies, and generating all necessary files.
Interactive mode:
cmake -P <(curl -sSL https://raw.githubusercontent.com/stlab/cpp-library/main/setup.cmake)Or download and run:
curl -O https://raw.githubusercontent.com/stlab/cpp-library/main/setup.cmake
cmake -P setup.cmakeThe script will prompt you for:
- Library name (e.g.,
my-library) - Namespace (e.g.,
mycompany) - Description
- Header-only library? (yes/no)
- Include examples? (yes/no)
- Include tests? (yes/no)
Non-interactive mode:
cmake -P setup.cmake -- \
--name=my-library \
--namespace=mycompany \
--description="My awesome library" \
--header-only=yes \
--examples=yes \
--tests=yesThe script will:
- Create the project directory structure
- Download CPM.cmake
- Generate CMakeLists.txt with correct configuration
- Create template header files
- Create example and test files (if requested)
- Initialize a git repository
After setup completes:
cd my-library
# Generate template files (CMakePresets.json, CI workflows, etc.)
cmake -B build -DCPP_LIBRARY_FORCE_INIT=ON
# Now you can use the presets
cmake --preset=test
cmake --build --preset=test
ctest --preset=testTo regenerate template files later:
cmake --preset=init
cmake --build --preset=initIf you prefer to set up your project manually, or need to integrate cpp-library into an existing project, follow these steps.
Use CPMAddPackage to fetch cpp-library directly in your CMakeLists.txt:
cmake_minimum_required(VERSION 3.24)
include(cmake/CPM.cmake)
# Fetch cpp-library before project()
# Check https://github.com/stlab/cpp-library/releases for the latest version
CPMAddPackage("gh:stlab/cpp-library@X.Y.Z")
include(${cpp-library_SOURCE_DIR}/cpp-library.cmake)
# Enable dependency tracking before project()
cpp_library_enable_dependency_tracking()
# Now declare project
project(your-library)
# Enable testing infrastructure (required for TESTS and EXAMPLES)
include(CTest)
# Setup library
cpp_library_setup(
DESCRIPTION "Your library description"
NAMESPACE your_namespace
HEADERS your_header.hpp
# Add SOURCES for non-header-only libraries (omit for header-only)
SOURCES your_library.cpp
EXAMPLES your_example.cpp your_example_fail.cpp
TESTS your_tests.cpp
DOCS_EXCLUDE_SYMBOLS "your_namespace::implementation"
)Before using cpp-library, you'll need:
- CMake 3.24+ - Download here
- A C++17+ compiler - GCC 7+, Clang 5+, MSVC 2017+, or Apple Clang 9+
CPM.cmake is required for dependency management. Add it to your project:
mkdir -p cmake
wget -O cmake/CPM.cmake https://github.com/cpm-cmake/CPM.cmake/releases/latest/download/get_cpm.cmakeCreate the standard directory structure:
mkdir -p include/your_namespace examples testsCreate a CMakeLists.txt file following the example shown at the beginning of the Usage section.
cmake --preset=test
cmake --build --preset=test
ctest --preset=testThe preferred way to consume a library built with cpp-library is via CPM.cmake:
cmake_minimum_required(VERSION 3.24)
project(my-app)
include(cmake/CPM.cmake)
# Fetch the library directly from GitHub
# Note: Repository name must match the package name (including namespace prefix)
CPMAddPackage("gh:stlab/stlab-enum-ops@1.0.0")
add_executable(my-app main.cpp)
target_link_libraries(my-app PRIVATE stlab::enum-ops)The library will be automatically fetched and built as part of your project.
Repository Naming: Your GitHub repository name must match the package name for CPM compatibility. For a library with package name stlab-enum-ops, name your repository stlab/stlab-enum-ops. This ensures CPMAddPackage("gh:stlab/stlab-enum-ops@1.0.0") works correctly with both source builds and CPM_USE_LOCAL_PACKAGES.
Installation is optional and typically not required when using CPM. If you need to install your library (e.g., for system-wide deployment or use with a package manager) use:
# Build and install to default system location
cmake --preset=default
cmake --build --preset=default
cmake --install build/default
# Install to custom prefix
cmake --install build/default --prefix /opt/mylibFor information about using installed packages with find_package(), see the CPM.cmake documentation about controlling how dependencies are found.
cpp-library automatically generates find_dependency() calls in the installed CMake package configuration. Call cpp_library_enable_dependency_tracking() before project():
cmake_minimum_required(VERSION 3.24)
include(cmake/CPM.cmake)
# Fetch cpp-library before project()
# Check https://github.com/stlab/cpp-library/releases for the latest version
CPMAddPackage("gh:stlab/cpp-library@X.Y.Z")
include(${cpp-library_SOURCE_DIR}/cpp-library.cmake)
# Enable dependency tracking before project()
cpp_library_enable_dependency_tracking()
# Declare project
project(my-library)
# Setup library target
cpp_library_setup(
DESCRIPTION "My library"
NAMESPACE mylib
HEADERS mylib.hpp
)
# Add dependencies and link them
# Dependencies are automatically tracked and included in Config.cmake
CPMAddPackage("gh:stlab/stlab-enum-ops@1.0.0")
find_package(Boost 1.79 COMPONENTS filesystem)
target_link_libraries(my-library INTERFACE
stlab::enum-ops
Boost::filesystem
)Non-namespaced targets: For targets like opencv_core, add an explicit mapping:
cpp_library_map_dependency("opencv_core" "OpenCV 4.5.0")Complete example with dependencies and tests:
cmake_minimum_required(VERSION 3.24)
include(cmake/CPM.cmake)
# Fetch cpp-library before project()
# Check https://github.com/stlab/cpp-library/releases for the latest version
CPMAddPackage("gh:stlab/cpp-library@X.Y.Z")
include(${cpp-library_SOURCE_DIR}/cpp-library.cmake)
cpp_library_enable_dependency_tracking()
project(my-library)
# Enable testing (required if you have TESTS or EXAMPLES)
include(CTest)
# Setup library
cpp_library_setup(
DESCRIPTION "My library with tests"
NAMESPACE mylib
HEADERS mylib.hpp
TESTS my_tests.cpp
EXAMPLES my_example.cpp
)
# Add dependencies and link them
CPMAddPackage("gh:stlab/stlab-enum-ops@1.0.0")
find_package(Boost 1.79 COMPONENTS filesystem)
target_link_libraries(my-library INTERFACE
stlab::enum-ops
Boost::filesystem
)To update to the latest version of cpp-library in your project:
Change the version tag in your CPMAddPackage call:
CPMAddPackage("gh:stlab/cpp-library@X.Y.Z") # Update version hereUse the init preset to regenerate CMakePresets.json and CI workflows with the latest templates:
cmake --preset=init
cmake --build --preset=initThis ensures your project uses the latest presets and CI configurations from the updated cpp-library version.
Critical: Your GitHub repository name must match your package name for CPM compatibility.
When using project(enum-ops) with NAMESPACE stlab:
- Package name:
stlab-enum-ops - Repository name:
stlab/stlab-enum-ops
This naming convention:
- Prevents package name collisions across organizations
- Enables
CPMAddPackage("gh:stlab/stlab-enum-ops@1.0.0")to work seamlessly - Makes
CPM_USE_LOCAL_PACKAGESwork correctly withfind_package(stlab-enum-ops)
cpp-library automatically detects your library version from git tags. To version your library:
git tag v1.0.0
git push origin v1.0.0Tags should follow semantic versioning (e.g., v1.0.0, v2.1.3).
Alternatively, you can override the version using -DCPP_LIBRARY_VERSION=x.y.z (useful for package managers). See Version Management for details.
To enable automatic documentation deployment to GitHub Pages:
- Go to your repository Settings → Pages
- Under Source, select GitHub Actions
- Publish a release to trigger documentation build
Your documentation will be automatically built and deployed to https://your-org.github.io/your-library/ when you publish a GitHub release.
cpp_library_setup(
# Required parameters
DESCRIPTION description # e.g., "Type-safe operators for enums"
NAMESPACE namespace # e.g., "stlab"
HEADERS header_list # List of header filenames (e.g., "your_header.hpp")
# Source specification for non-header-only libraries
SOURCES source_list # List of source filenames (e.g., "your_library.cpp", omit for header-only libraries)
# Optional features
[EXAMPLES example_list] # Example source files to build (e.g., "example.cpp example_fail.cpp")
[TESTS test_list] # Test source files to build (e.g., "tests.cpp")
[DOCS_EXCLUDE_SYMBOLS symbols] # Symbols to exclude from docs
[REQUIRES_CPP_VERSION 17|20|23] # C++ version (default: 17)
)Notes:
- The project name is automatically taken from
PROJECT_NAME(set by theproject()command). You must callproject(your-library)beforecpp_library_setup(). - If you specify
TESTSorEXAMPLES, callinclude(CTest)afterproject()and beforecpp_library_setup(). - Clang-tidy (
CMAKE_CXX_CLANG_TIDY) analyzes whatever gets built—it doesn't change what gets built. - Version is automatically detected from git tags, or can be overridden with
-DCPP_LIBRARY_VERSION=x.y.z(see Version Management). - Examples using doctest should include
testin the filename to be visible in the C++ TestMate extension for VS Code test explorer.
Use the component name as your project name, and specify the organizational namespace separately:
project(enum-ops) # Component name only
cpp_library_setup(
NAMESPACE stlab # Organizational namespace
# ...
)This produces:
- Target name:
enum-ops - Package name:
stlab-enum-ops(used infind_package(stlab-enum-ops)) - Target alias:
stlab::enum-ops(used intarget_link_libraries()) - Repository name:
stlab/stlab-enum-ops(must match package name)
Special case — single-component namespace (e.g., project(stlab) with NAMESPACE stlab):
- Target name:
stlab - Package name:
stlab - Target alias:
stlab::stlab - Repository name:
stlab/stlab
cpp_library_map_dependency(target find_dependency_call)Maps non-namespaced targets to their package. Required only for targets like opencv_core where the package name cannot be inferred:
cpp_library_map_dependency("opencv_core" "OpenCV 4.5.0")
target_link_libraries(my-target INTERFACE opencv_core)Namespaced targets like Qt6::Core and Boost::filesystem are tracked automatically.
The template uses consistent path conventions for all file specifications:
- HEADERS: Filenames only, automatically placed in
include/<namespace>/directory- Examples:
your_header.hpp,enum_ops.hpp(automatically becomesinclude/your_namespace/your_header.hpp)
- Examples:
- SOURCES: Filenames only, automatically placed in
src/directory (omit for header-only libraries)- Examples:
your_library.cpp,implementation.cpp(automatically becomessrc/your_library.cpp)
- Examples:
- EXAMPLES: Source files with
.cppextension, located inexamples/directory- Examples:
example.cpp,example_fail.cpp
- Examples:
- TESTS: Source files with
.cppextension, located intests/directory- Examples:
tests.cpp,unit_tests.cpp
- Examples:
Header-only libraries: Specify only HEADERS, omit SOURCES
cpp_library_setup(
DESCRIPTION "Header-only library"
NAMESPACE my_lib
HEADERS my_header.hpp
# No SOURCES needed for header-only
)Non-header-only libraries: Specify both HEADERS and SOURCES
cpp_library_setup(
DESCRIPTION "Library with implementation"
NAMESPACE my_lib
HEADERS my_header.hpp
SOURCES my_library.cpp implementation.cpp
)Libraries with sources build as static libraries by default. Set BUILD_SHARED_LIBS=ON to build shared libraries instead.
cpp-library generates a CMakePresets.json file with the following configurations:
default: Release build for production usetest: Debug build with testing enableddocs: Documentation generation with Doxygenclang-tidy: Static analysis buildinit: Template regeneration (regenerates CMakePresets.json, CI workflows, etc.)
All presets automatically configure CPM_SOURCE_CACHE to ${sourceDir}/.cache/cpm for faster dependency resolution. You can override this by setting the CPM_SOURCE_CACHE environment variable.
Version is automatically detected from git tags:
- Supports
v1.2.3and1.2.3tag formats - Falls back to
0.0.0if no tag is found (with warning) - Version used in CMake package config files
For package managers or CI systems building from source archives without git history, you can override the version using the CPP_LIBRARY_VERSION cache variable:
cmake -DCPP_LIBRARY_VERSION=1.2.3 -B build
cmake --build buildThis is particularly useful for vcpkg, Conan, or other package managers that don't have access to git tags.
- Test framework: doctest
- Compile-fail tests: Automatically detected via
_failsuffix in filenames - Test discovery: Scans
tests/andexamples/directories - CTest integration: All tests registered with CTest for IDE integration
cpp-library automatically generates infrastructure files on first configuration and when using the init preset:
- CMakePresets.json: Build configurations (default, test, docs, clang-tidy, install, init)
- .github/workflows/ci.yml: Multi-platform CI/CD pipeline with testing and documentation deployment
- .gitignore: Standard C++ project ignores
- .vscode/extensions.json: Recommended VS Code extensions
- Package config files:
<Package>Config.cmakefor CMake integration (when building as top-level project)
These files are generated automatically. To regenerate with the latest templates, use cmake --preset=init.
See these projects using cpp-library:
- stlab/stlab-enum-ops - Type-safe operators for enums
- stlab/stlab-copy-on-write - Copy-on-write wrapper
Note: Repository names include the namespace prefix for CPM compatibility and collision prevention.
Problem: Error about non-namespaced dependency like opencv_core
Solution: Map the target to its package:
cpp_library_map_dependency("opencv_core" "OpenCV 4.5.0")Problem: Error that a dependency was not tracked
Solution: Ensure cpp_library_enable_dependency_tracking() is called before project(). Dependencies can be added anywhere after project() and will be automatically captured.
Problem: CPMAddPackage() fails with CPM_USE_LOCAL_PACKAGES
Solution: Repository name must match package name. For package stlab-enum-ops, use repository stlab/stlab-enum-ops, not stlab/enum-ops.
Problem: Clang-tidy reports "exceptions are disabled" when analyzing code on Windows with MSVC
Solution: This is a known clang-tidy issue (CMake #22979) where clang-tidy doesn't properly recognize MSVC's /EHsc exception handling flag. cpp-library automatically detects this scenario and adds --extra-arg=/EHsc to CMAKE_CXX_CLANG_TIDY when both MSVC and clang-tidy are enabled. This workaround is applied transparently and only on MSVC platforms.
cpp-library includes unit tests for its dependency mapping and installation logic:
# Run unit tests
cmake -P tests/install/CMakeLists.txtThe test suite covers:
- Automatic version detection
- Component merging (Qt, Boost)
- System packages (Threads, OpenMP, etc.)
- Custom dependency mappings
- Internal cpp-library dependencies
- Edge cases and error handling
See tests/install/README.md for more details.
Distributed under the Boost Software License, Version 1.0. See LICENSE.