From 5d2dc12becd2339fafe3c3188f7c0e598f8370f5 Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:28:27 +0100 Subject: [PATCH 01/17] Modernize CMake configuration and clean up build scripts --- {{cookiecutter.project_slug}}/CMakeLists.txt | 79 +++++++++++-------- {{cookiecutter.project_slug}}/README.md | 16 ++-- .../app/CMakeLists.txt | 16 +++- .../doc/CMakeLists.txt | 6 +- {{cookiecutter.project_slug}}/pyproject.toml | 2 +- .../src/CMakeLists.txt | 20 ++++- .../tests/CMakeLists.txt | 27 ++----- 7 files changed, 95 insertions(+), 71 deletions(-) diff --git a/{{cookiecutter.project_slug}}/CMakeLists.txt b/{{cookiecutter.project_slug}}/CMakeLists.txt index a53d6c9..3ca2780 100644 --- a/{{cookiecutter.project_slug}}/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/CMakeLists.txt @@ -1,12 +1,13 @@ -cmake_minimum_required(VERSION 3.9) +cmake_minimum_required(VERSION 3.23) # Set a name and a version number for your project: -project({{ cookiecutter.project_slug }} VERSION 0.0.1 LANGUAGES CXX) +project({{ cookiecutter.project_slug }} + VERSION 0.0.1 + LANGUAGES CXX +) {%- if cookiecutter.external_dependency != "None" %} - # Set CMake policies for this project - # We allow _ROOT (env) variables for locating dependencies cmake_policy(SET CMP0074 NEW) {%- endif %} @@ -14,15 +15,14 @@ cmake_policy(SET CMP0074 NEW) # Initialize some default paths include(GNUInstallDirs) -# Define the minimum C++ standard that is required -set(CMAKE_CXX_STANDARD {{ cookiecutter.cxx_minimum_standard }}) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - {% if cookiecutter.python_bindings == "Yes" -%} +# Enable PIC for Python bindings set(CMAKE_POSITION_INDEPENDENT_CODE ON) {%- endif %} +{%- if cookiecutter.python_bindings == "Yes" or cookiecutter.doxygen == "Yes" or cookiecutter.readthedocs == "Yes" %} # Compilation options +{%- endif %} {%- if cookiecutter.python_bindings == "Yes" %} option(BUILD_PYTHON "Enable building of Python bindings" OFF) {%- endif %} @@ -31,29 +31,35 @@ option(BUILD_DOCS "Enable building of documentation" ON) {%- endif %} {%- if cookiecutter.external_dependency != "None" %} - # Find external dependencies find_package({{ cookiecutter.external_dependency }}) {%- endif %} {%- if cookiecutter.header_only == "No" %} - -# compile the library +# Compile the library add_subdirectory(src) {% else %} -# Add an interface target for our header-only library +# Add an interface target for the header-only library add_library({{ cookiecutter.project_slug }} INTERFACE) -target_include_directories({{ cookiecutter.project_slug }} INTERFACE - $ - $ + +# Add the public headers that belong to this library +target_sources({{ cookiecutter.project_slug }} + INTERFACE + FILE_SET HEADERS + BASE_DIRS include + FILES + include/{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}.hpp ) + +# Request a minimum C++ standard for this target +target_compile_features({{ cookiecutter.project_slug }} INTERFACE cxx_std_{{ cookiecutter.cxx_minimum_standard }}) {%- endif %} -# compile the application +# Compile the application executable add_subdirectory(app) -# compile the tests +# Compile the tests include(CTest) if(BUILD_TESTING) {%- if cookiecutter.use_submodules == "Yes" %} @@ -64,14 +70,15 @@ if(BUILD_TESTING) endif() {% if cookiecutter.doxygen == "Yes" -%} +# Add the documentation if(BUILD_DOCS) - # Add the documentation add_subdirectory(doc) endif() {%- endif %} + {%- if cookiecutter.python_bindings == "Yes" %} +# Add Python bindings if(BUILD_PYTHON) - # Add Python bindings find_package(pybind11 REQUIRED) # Compile the Pybind11 module pybind11_add_module(_{{ cookiecutter|modname }} python/{{ cookiecutter|modname }}/_{{ cookiecutter.project_slug }}.cpp) @@ -85,55 +92,57 @@ endif() # Add an alias target for use if this project is included as a subproject in another project add_library({{ cookiecutter.project_slug }}::{{ cookiecutter.project_slug }} ALIAS {{ cookiecutter.project_slug }}) -# Install targets and configuration {%- if cookiecutter.external_dependency == "None" %} +# Install the library target and its public headers install( TARGETS {{ cookiecutter.project_slug }} EXPORT {{ cookiecutter.project_slug }}-config - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + FILE_SET HEADERS ) +# Export the installed target into a simple -config file, so that find_package({{ cookiecutter.project_slug }}) works for consumers install( EXPORT {{ cookiecutter.project_slug }}-config NAMESPACE {{ cookiecutter.project_slug }}:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/{{ cookiecutter.project_slug }} ) {%- else %} +# Install the build target and record it in an export set. install( TARGETS {{ cookiecutter.project_slug }} EXPORT {{ cookiecutter.project_slug }}-targets - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + FILE_SET HEADERS +) +# Export the target definition so other CMake projects can reconstruct the imported target via find_package() install( EXPORT {{ cookiecutter.project_slug }}-targets FILE {{ cookiecutter.project_slug }}Targets.cmake NAMESPACE {{ cookiecutter.project_slug }}:: - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/{{ cookiecutter.project_slug }}) + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/{{ cookiecutter.project_slug }} +) +# Generate a Config.cmake file from a template include(CMakePackageConfigHelpers) configure_package_config_file( ${CMAKE_CURRENT_LIST_DIR}/{{ cookiecutter.project_slug }}Config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/{{ cookiecutter.project_slug }}Config.cmake - INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/{{ cookiecutter.project_slug }}) + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/{{ cookiecutter.project_slug }} +) +# Install the generated project configuration file alongside the targets install( FILES ${CMAKE_CURRENT_BINARY_DIR}/{{ cookiecutter.project_slug }}Config.cmake - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/{{ cookiecutter.project_slug }}) + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/{{ cookiecutter.project_slug }} +) +# Export targets for the build tree export( EXPORT {{ cookiecutter.project_slug }}-targets FILE ${CMAKE_CURRENT_BINARY_DIR}/{{ cookiecutter.project_slug }}Targets.cmake - NAMESPACE {{ cookiecutter.project_slug }}::) -{%- endif %} - -install( - DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/include/ - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + NAMESPACE {{ cookiecutter.project_slug }}:: ) +{%- endif %} # This prints a summary of found dependencies include(FeatureSummary) diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md index 17912f6..e68b105 100644 --- a/{{cookiecutter.project_slug}}/README.md +++ b/{{cookiecutter.project_slug}}/README.md @@ -40,7 +40,7 @@ Building {{ cookiecutter.project_name }} requires the following software installed: * A C++{{ cookiecutter.cxx_minimum_standard }}-compliant compiler -* CMake `>= 3.9` +* CMake `>= 3.23` {%- if cookiecutter.external_dependency != "None" %} * {{ cookiecutter.external_dependency }} {%- endif %} @@ -61,10 +61,8 @@ It assumes that your current working directory is the top-level directory of the freshly cloned repository: ``` -mkdir build -cd build -cmake -DCMAKE_BUILD_TYPE=Release .. -cmake --build . +cmake -B build +cmake --build build ``` The build process can be customized with the following CMake variables, @@ -118,13 +116,13 @@ To build it locally, first ensure the requirements are installed by running this pip install -r doc/requirements.txt ``` -Then build the sphinx documentation from the top-level build directory: +Then build the sphinx documentation from the top-level directory: ``` -cmake --build . --target sphinx-doc +cmake --build build --target sphinx-doc ``` -The web documentation can then be browsed by opening `doc/sphinx/index.html` in your browser. +The web documentation can then be browsed by opening `build/doc/sphinx/index.html` in your browser. {% elif cookiecutter.doxygen == "Yes" %} {{ cookiecutter.project_name }} provides a Doxygen documentation. You can build the documentation locally by making sure that `Doxygen` is installed on your system @@ -134,7 +132,7 @@ and running this command from the top-level build directory: cmake --build . --target doxygen ``` -The web documentation can then be browsed by opening `doc/html/index.html` in your browser. +The web documentation can then be browsed by opening `build/doc/html/index.html` in your browser. {% else %} {{ cookiecutter.project_name }} *should* provide a documentation. {% endif -%} diff --git a/{{cookiecutter.project_slug}}/app/CMakeLists.txt b/{{cookiecutter.project_slug}}/app/CMakeLists.txt index 6ad2c76..7cf57e7 100644 --- a/{{cookiecutter.project_slug}}/app/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/app/CMakeLists.txt @@ -1,2 +1,14 @@ -add_executable({{ cookiecutter.project_slug }}_app {{ cookiecutter.project_slug }}_app.cpp) -target_link_libraries({{ cookiecutter.project_slug }}_app PRIVATE {{ cookiecutter.project_slug }}) +# Declare an executable target +add_executable({{ cookiecutter.project_slug }}_app) + +# Add source files to the executable target +target_sources({{ cookiecutter.project_slug }}_app + PRIVATE + {{ cookiecutter.project_slug }}_app.cpp +) + +# Link the executable against the library +target_link_libraries({{ cookiecutter.project_slug }}_app + PRIVATE + {{ cookiecutter.project_slug }} +) diff --git a/{{cookiecutter.project_slug}}/doc/CMakeLists.txt b/{{cookiecutter.project_slug}}/doc/CMakeLists.txt index 7793e77..0b213c1 100644 --- a/{{cookiecutter.project_slug}}/doc/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/doc/CMakeLists.txt @@ -5,8 +5,12 @@ set(DOXYGEN_SHORT_NAMES YES) {% if cookiecutter.readthedocs == "Yes" -%} set(DOXYGEN_GENERATE_XML YES) {%- endif %} + doxygen_add_docs(doxygen - ${CMAKE_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/include +{%- if cookiecutter.header_only == "No" %} + ${CMAKE_SOURCE_DIR}/src +{%- endif %} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT Building doxygen documentation... ) diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index 4dda9b1..67fc415 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -42,7 +42,7 @@ dependencies = [] # The configuration for the scikit-build-core build process [tool.scikit-build] -cmake.version = ">=3.9" +cmake.version = ">=3.23" build.verbose = true [tool.scikit-build.cmake.define] diff --git a/{{cookiecutter.project_slug}}/src/CMakeLists.txt b/{{cookiecutter.project_slug}}/src/CMakeLists.txt index afe63b1..7b2bccc 100644 --- a/{{cookiecutter.project_slug}}/src/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/src/CMakeLists.txt @@ -1,5 +1,17 @@ -add_library({{ cookiecutter.project_slug }} {{ cookiecutter.project_slug }}.cpp) -target_include_directories({{ cookiecutter.project_slug }} PUBLIC - $ - $ +# Declare a new library target +add_library({{ cookiecutter.project_slug }}) + +# Add the implementation file and mark the public headers that belong to this library +target_sources({{ cookiecutter.project_slug }} + PRIVATE + {{ cookiecutter.project_slug }}.cpp + + PUBLIC + FILE_SET HEADERS + BASE_DIRS ../include + FILES + ../include/{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}.hpp ) + +# Request a minimum C++ standard for this target +target_compile_features({{ cookiecutter.project_slug }} PUBLIC cxx_std_{{ cookiecutter.cxx_minimum_standard }}) diff --git a/{{cookiecutter.project_slug}}/tests/CMakeLists.txt b/{{cookiecutter.project_slug}}/tests/CMakeLists.txt index 3b7ca76..bf87b76 100644 --- a/{{cookiecutter.project_slug}}/tests/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/tests/CMakeLists.txt @@ -1,36 +1,25 @@ {%- if cookiecutter.use_submodules == "No" %} -# ------------------------------------------------------------------------------ -# Enable CMake's FetchContent module, which allows downloading dependencies like -# Catch2 automatically at configure time. -# ------------------------------------------------------------------------------ +# Enable CMake's FetchContent module, which allows downloading dependencies like Catch2 automatically at configure time. include(FetchContent) -# ------------------------------------------------------------------------------ -# Declare a dependency on Catch2 via FetchContent. This will clone the Catch2 -# GitHub repository during configuration. -# ------------------------------------------------------------------------------ +# Declare a dependency on Catch2 via FetchContent. This will clone the Catch2 GitHub repository during configuration. FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git GIT_TAG v{{ cookiecutter._catch_version }}) -# ------------------------------------------------------------------------------ -# Make Catch2 available in this project. This defines the CMake targets like -# Catch2::Catch2 and Catch2::Catch2WithMain. -# ------------------------------------------------------------------------------ +# Make Catch2 available in this project. This defines the CMake targets like Catch2::Catch2 and Catch2::Catch2WithMain. FetchContent_MakeAvailable(Catch2) -# ------------------------------------------------------------------------------ -# Make Catch2 available in this project. This defines the CMake targets like -# Catch2::Catch2 and Catch2::Catch2WithMain. -# ------------------------------------------------------------------------------ +# Make Catch2 available in this project. This defines the CMake targets like Catch2::Catch2 and Catch2::Catch2WithMain. include(Catch) {%- endif %} -# ------------------------------------------------------------------------------ # Create a single test binary that compiles all test files. -# ------------------------------------------------------------------------------ -add_executable(tests {{ cookiecutter.project_slug }}_t.cpp) +add_executable(tests) + +# Add the implementation file +target_sources(tests PRIVATE {{ cookiecutter.project_slug }}_t.cpp) # Link the test binary target_link_libraries(tests PUBLIC {{ cookiecutter.project_slug }} Catch2::Catch2WithMain) From a52bf5a4e0b1e36dd0dc72338f54e589d8e37b4c Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:37:14 +0100 Subject: [PATCH 02/17] Remove setting CMP0074 to NEW explicitly, as it the default for CMake > 3.12 --- {{cookiecutter.project_slug}}/CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/{{cookiecutter.project_slug}}/CMakeLists.txt b/{{cookiecutter.project_slug}}/CMakeLists.txt index 3ca2780..0944d84 100644 --- a/{{cookiecutter.project_slug}}/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/CMakeLists.txt @@ -6,12 +6,6 @@ project({{ cookiecutter.project_slug }} LANGUAGES CXX ) -{%- if cookiecutter.external_dependency != "None" %} -# Set CMake policies for this project -# We allow _ROOT (env) variables for locating dependencies -cmake_policy(SET CMP0074 NEW) -{%- endif %} - # Initialize some default paths include(GNUInstallDirs) From ec150ef40c0be5b6bd6a42b3e02192759d42472d Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:19:46 +0100 Subject: [PATCH 03/17] Since Catch2 is installed via FetchContent or included as a git submodule, we do not have to install it manually in CI --- .github/workflows/ci.yml | 23 ----------- .../.github/workflows/ci.yml | 38 ------------------- {{cookiecutter.project_slug}}/.gitlab-ci.yml | 11 ------ .../tests/CMakeLists.txt | 4 +- 4 files changed, 2 insertions(+), 74 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f11500..251fef3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,9 +15,6 @@ on: schedule: - cron: "0 5 * * 1" -env: - CATCH2_VERSION: 3.11.0 - jobs: test: name: Testing on ${{ matrix.os }} with Python ${{ matrix.python-version }} @@ -46,26 +43,6 @@ jobs: - uses: ssciwr/doxygen-install@v1 - - name: Install Catch2 (Linux + MacOS) - if: runner.os != 'Windows' - run: | - git clone -b v$CATCH2_VERSION https://github.com/catchorg/Catch2.git - cd Catch2 - mkdir build - cd build - cmake -DBUILD_TESTING=OFF .. - sudo cmake --build . --target install - - - name: Install Catch2 (Windows) - if: runner.os == 'Windows' - run: | - git clone -b v$Env:CATCH2_VERSION https://github.com/catchorg/Catch2.git - cd Catch2 - mkdir build - cd build - cmake -DBUILD_TESTING=OFF .. - cmake --build . --target install - - name: Set up git identity run: | git config --global user.email "ssc@citestuser.com" diff --git a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml index ad6d673..ba98713 100644 --- a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +++ b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml @@ -12,11 +12,6 @@ on: # as well as upon manual triggers through the 'Actions' tab of the Github UI workflow_dispatch: -{%- if cookiecutter.use_submodules == "No" %} -env: - CATCH2_VERSION: {{ cookiecutter._catch_version }} -{%- endif %} - jobs: build-and-test: name: Testing on ${{ "{{matrix.os}}" }} @@ -32,28 +27,6 @@ jobs: submodules: 'recursive' {%- endif %} -{% if cookiecutter.use_submodules == "No" %} - - name: Install Catch2 (Linux + MacOS) - if: runner.os != 'Windows' - run: | - git clone -b v$CATCH2_VERSION https://github.com/catchorg/Catch2.git - cd Catch2 - mkdir build - cd build - cmake -DBUILD_TESTING=OFF .. - sudo cmake --build . --target install - - - name: Install Catch2 (Windows) - if: runner.os == 'Windows' - run: | - git clone -b v$Env:CATCH2_VERSION https://github.com/catchorg/Catch2.git - cd Catch2 - mkdir build - cd build - cmake -DBUILD_TESTING=OFF .. - cmake --build . --target install -{%- endif %} - - name: make build directory run: cmake -E make_directory ${{ "{{ github.workspace }}" }}/build @@ -115,17 +88,6 @@ jobs: run: | sudo apt-get install -y lcov -{% if cookiecutter.use_submodules == "No" %} - - name: Install Catch2 - run: | - git clone -b v$CATCH2_VERSION https://github.com/catchorg/Catch2.git - cd Catch2 - mkdir build - cd build - cmake -DBUILD_TESTING=OFF .. - sudo cmake --build . --target install -{%- endif %} - {% if cookiecutter.python_bindings == "Yes" %} - name: Install Python package editable run: | diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml index 1172b1f..ce89ce3 100644 --- a/{{cookiecutter.project_slug}}/.gitlab-ci.yml +++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml @@ -1,8 +1,6 @@ variables: {%- if cookiecutter.use_submodules == "Yes" %} GIT_SUBMODULE_STRATEGY: recursive -{%- else %} - CATCH2_VERSION: {{ cookiecutter._catch_version }} {%- endif %} .template: &template @@ -13,15 +11,6 @@ variables: - echo "Installing potential dependencies..." {% if cookiecutter.python_bindings == "Yes" %} - apt install -y python3-dev -{%- endif %} -{% if cookiecutter.use_submodules == "No" %} - - git clone -b v$CATCH2_VERSION https://github.com/catchorg/Catch2.git - - cd Catch2 - - mkdir build - - cd build - - cmake -DBUILD_TESTING=OFF .. - - make install - - cd ../.. {%- endif %} script: - cmake -E make_directory build diff --git a/{{cookiecutter.project_slug}}/tests/CMakeLists.txt b/{{cookiecutter.project_slug}}/tests/CMakeLists.txt index bf87b76..426bbd8 100644 --- a/{{cookiecutter.project_slug}}/tests/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/tests/CMakeLists.txt @@ -8,10 +8,10 @@ FetchContent_Declare( GIT_REPOSITORY https://github.com/catchorg/Catch2.git GIT_TAG v{{ cookiecutter._catch_version }}) -# Make Catch2 available in this project. This defines the CMake targets like Catch2::Catch2 and Catch2::Catch2WithMain. +# Make Catch2 available in this project. This defines the imported targets Catch2::Catch2 and Catch2::Catch2WithMain. FetchContent_MakeAvailable(Catch2) -# Make Catch2 available in this project. This defines the CMake targets like Catch2::Catch2 and Catch2::Catch2WithMain. +# Include Catch2's CMake helper functions (e.g. catch_discover_tests). include(Catch) {%- endif %} From 7bf6b40e46510568dac6d1d020fc6d2269e5784b Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Thu, 18 Dec 2025 10:19:24 +0100 Subject: [PATCH 04/17] Reflect changed available C++ standards, with C++14 being the default and minimum --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6379085..382046e 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ In order to use this C++ Project Cookiecutter you need the following software in In addition, the project that is generated from this cookiecutter will require the following software: * A C++ compiler, e.g. `g++` or `clang++` -* CMake `>= 3.9` +* CMake `>= 3.23` * Doxygen (optional, but recommended) # Using C++ Project Cookiecutter @@ -73,8 +73,8 @@ This cookiecutter accepts the following configuration options: * `gitlab_ci`: Whether to add a CI workflow for GitLab CI * `readthedocs`: Whether to create a Sphinx-documentation that can automatically be deployed to readthedocs.org * `doxygen`: Whether a Doxygen documentation should be extracted from the project -* `cxx_minimum_standard`: The minimum C++ standard required for this project. It can be chosen from `11` (default), `14`, `17` and `20`. - `C++03` and earlier are not supported, because the generated project will depend on libraries that require `C++11` ([Catch2](https://github.com/catchorg/Catch2) +* `cxx_minimum_standard`: The minimum C++ standard required for this project. It can be chosen from `14` (default), `17`, `20` and `23`. + `C++11` and earlier are not supported, because the generated project will depend on libraries that require `C++14` ([Catch2](https://github.com/catchorg/Catch2) for testing and [pybind11](https://github.com/pybind/pybind11) for potential Python bindings). * `python_bindings`: Whether to automatically add a PyBind11-based Python binding package. * `pypi_release`: Whether to add an automatic PyPI deploy workflow to the CI system. From ab0cf74c267bf8bcf1b15262cfae35a4f57c626c Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:36:18 +0100 Subject: [PATCH 05/17] cmake: namespace targets, scope options, and guard app/docs builds --- .../.github/workflows/ci.yml | 6 ++--- {{cookiecutter.project_slug}}/.gitlab-ci.yml | 2 +- {{cookiecutter.project_slug}}/CMakeLists.txt | 26 ++++++++++++++----- {{cookiecutter.project_slug}}/README.md | 10 +++---- .../doc/CMakeLists.txt | 6 ++--- {{cookiecutter.project_slug}}/doc/conf.py | 4 +-- {{cookiecutter.project_slug}}/pyproject.toml | 10 +++---- .../tests/CMakeLists.txt | 10 ++++--- 8 files changed, 44 insertions(+), 30 deletions(-) diff --git a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml index ba98713..9d0cc2f 100644 --- a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +++ b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: - name: configure cmake shell: bash working-directory: ${{ "{{ github.workspace }}" }}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -DBUILD_DOCS=OFF + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -D{{ cookiecutter.project_slug }}_BUILD_DOCS=OFF - name: build shell: bash @@ -99,14 +99,14 @@ jobs: run: | python -m pytest --cov --cov-report=xml {% else %} - - name: Creat cmake build directory + - name: Create cmake build directory run: cmake -E make_directory ${{ "{{ github.workspace }}" }}/build - name: configure cmake shell: bash working-directory: ${{ "{{ github.workspace }}" }}/build run: | - cmake $GITHUB_WORKSPACE -DCMAKE_CXX_FLAGS="--coverage" -DBUILD_DOCS=OFF + cmake $GITHUB_WORKSPACE -DCMAKE_CXX_FLAGS="--coverage" -D{{ cookiecutter.project_slug }}_BUILD_DOCS=OFF - name: build shell: bash diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml index ce89ce3..a24add5 100644 --- a/{{cookiecutter.project_slug}}/.gitlab-ci.yml +++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml @@ -15,7 +15,7 @@ variables: script: - cmake -E make_directory build - cd build - - cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_DOCS=OFF .. + - cmake -DCMAKE_BUILD_TYPE=Debug -D{{ cookiecutter.project_slug }}_BUILD_DOCS=OFF .. - cmake --build . - ctest diff --git a/{{cookiecutter.project_slug}}/CMakeLists.txt b/{{cookiecutter.project_slug}}/CMakeLists.txt index 0944d84..2a27906 100644 --- a/{{cookiecutter.project_slug}}/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/CMakeLists.txt @@ -18,10 +18,20 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # Compilation options {%- endif %} {%- if cookiecutter.python_bindings == "Yes" %} -option(BUILD_PYTHON "Enable building of Python bindings" OFF) +if(PROJECT_IS_TOP_LEVEL) + option({{ cookiecutter.project_slug }}_BUILD_PYTHON "Enable building of Python bindings" ON) +else() + option({{ cookiecutter.project_slug }}_BUILD_PYTHON "Enable building of Python bindings" OFF) +endif() {%- endif %} + + {%- if cookiecutter.doxygen == "Yes" or cookiecutter.readthedocs == "Yes" %} -option(BUILD_DOCS "Enable building of documentation" ON) +if(PROJECT_IS_TOP_LEVEL) + option({{ cookiecutter.project_slug }}_BUILD_DOCS "Enable building of documentation" ON) +else() + option({{ cookiecutter.project_slug }}_BUILD_DOCS "Enable building of documentation" OFF) +endif() {%- endif %} {%- if cookiecutter.external_dependency != "None" %} @@ -50,12 +60,14 @@ target_sources({{ cookiecutter.project_slug }} target_compile_features({{ cookiecutter.project_slug }} INTERFACE cxx_std_{{ cookiecutter.cxx_minimum_standard }}) {%- endif %} -# Compile the application executable -add_subdirectory(app) +if(PROJECT_IS_TOP_LEVEL) + # Compile the application executable + add_subdirectory(app) +endif() # Compile the tests include(CTest) -if(BUILD_TESTING) +if({{ cookiecutter.project_slug }}_BUILD_TESTING) {%- if cookiecutter.use_submodules == "Yes" %} add_subdirectory(ext/Catch2) include(./ext/Catch2/extras/Catch.cmake) @@ -65,14 +77,14 @@ endif() {% if cookiecutter.doxygen == "Yes" -%} # Add the documentation -if(BUILD_DOCS) +if({{ cookiecutter.project_slug }}_BUILD_DOCS) add_subdirectory(doc) endif() {%- endif %} {%- if cookiecutter.python_bindings == "Yes" %} # Add Python bindings -if(BUILD_PYTHON) +if({{ cookiecutter.project_slug }}_BUILD_PYTHON) find_package(pybind11 REQUIRED) # Compile the Pybind11 module pybind11_add_module(_{{ cookiecutter|modname }} python/{{ cookiecutter|modname }}/_{{ cookiecutter.project_slug }}.cpp) diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md index e68b105..9736c0e 100644 --- a/{{cookiecutter.project_slug}}/README.md +++ b/{{cookiecutter.project_slug}}/README.md @@ -51,7 +51,7 @@ Building {{ cookiecutter.project_name }} requires the following software install * The testing framework [Catch2](https://github.com/catchorg/Catch2) for building the test suite {%- endif %} {%- if cookiecutter.python_bindings == "Yes" -%} -* Python `>= 3.8` for building Python bindings +* Python `>= 3.10` for building Python bindings {%- endif %} # Building {{ cookiecutter.project_name }} @@ -68,12 +68,12 @@ cmake --build build The build process can be customized with the following CMake variables, which can be set by adding `-D={ON, OFF}` to the `cmake` call: -* `BUILD_TESTING`: Enable building of the test suite (default: `ON`) +* `{{ cookiecutter.project_slug }}_BUILD_TESTING`: Enable building of the test suite (default: `ON`) {%- if cookiecutter.doxygen == "Yes" or cookiecutter.readthedocs == "Yes" %} -* `BUILD_DOCS`: Enable building the documentation (default: `ON`) +* `{{ cookiecutter.project_slug }}_BUILD_DOCS`: Enable building the documentation (default: `ON`) {%- endif %} {%- if cookiecutter.python_bindings == "Yes" %} -* `BUILD_PYTHON`: Enable building the Python bindings (default: `ON`) +* `{{ cookiecutter.project_slug }}_BUILD_PYTHON`: Enable building the Python bindings (default: `ON`) {%- endif %} {% if cookiecutter.python_bindings == "Yes" %} @@ -88,7 +88,7 @@ python -m pip install . # Testing {{ cookiecutter.project_name }} -When built according to the above explanation (with `-DBUILD_TESTING=ON`), +When built according to the above explanation (with `-D{{ cookiecutter.project_slug }}_BUILD_TESTING=ON`), the C++ test suite of `{{ cookiecutter.project_name }}` can be run using `ctest` from the build directory: diff --git a/{{cookiecutter.project_slug}}/doc/CMakeLists.txt b/{{cookiecutter.project_slug}}/doc/CMakeLists.txt index 0b213c1..0e1966e 100644 --- a/{{cookiecutter.project_slug}}/doc/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/doc/CMakeLists.txt @@ -6,7 +6,7 @@ set(DOXYGEN_SHORT_NAMES YES) set(DOXYGEN_GENERATE_XML YES) {%- endif %} -doxygen_add_docs(doxygen +doxygen_add_docs({{ cookiecutter.project_slug }}-doxygen ${CMAKE_SOURCE_DIR}/include {%- if cookiecutter.header_only == "No" %} ${CMAKE_SOURCE_DIR}/src @@ -15,7 +15,7 @@ doxygen_add_docs(doxygen COMMENT Building doxygen documentation... ) {% if cookiecutter.readthedocs == "Yes" -%} -add_custom_target(sphinx-doc +add_custom_target({{ cookiecutter.project_slug }}-sphinx-doc COMMAND sphinx-build -b html -Dbreathe_projects.{{ cookiecutter.project_slug }}="${CMAKE_CURRENT_BINARY_DIR}/xml" @@ -25,6 +25,6 @@ add_custom_target(sphinx-doc WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Generating documentation with Sphinx..." ) -add_dependencies(sphinx-doc doxygen) +add_dependencies({{ cookiecutter.project_slug }}-sphinx-doc {{ cookiecutter.project_slug }}-doxygen) {%- endif %} {%- endif -%} \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/doc/conf.py b/{{cookiecutter.project_slug}}/doc/conf.py index da38342..3a71811 100644 --- a/{{cookiecutter.project_slug}}/doc/conf.py +++ b/{{cookiecutter.project_slug}}/doc/conf.py @@ -67,6 +67,6 @@ cwd = os.getcwd() os.makedirs("build-cmake", exist_ok=True) builddir = os.path.join(cwd, "build-cmake") - subprocess.check_call("cmake -DBUILD_DOCS=ON -DBUILD_TESTING=OFF {% if cookiecutter.python_bindings == 'Yes' %}-DBUILD_PYTHON=OFF{% endif %} ../..".split(), cwd=builddir) - subprocess.check_call("cmake --build . --target doxygen".split(), cwd=builddir) + subprocess.check_call("cmake -D{{ cookiecutter.project_slug }}_BUILD_DOCS=ON -D{{ cookiecutter.project_slug }}_BUILD_TESTING=OFF {% if cookiecutter.python_bindings == 'Yes' %}-D{{ cookiecutter.project_slug }}_BUILD_PYTHON=OFF{% endif %} ../..".split(), cwd=builddir) + subprocess.check_call("cmake --build . --target {{ cookiecutter.project_slug }}-doxygen".split(), cwd=builddir) breathe_projects["{{ cookiecutter.project_slug }}"] = os.path.join(builddir, "doc", "xml") diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index 67fc415..b0017d5 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -19,7 +19,7 @@ readme = "README.md" maintainers = [ { name = "{{ cookiecutter.full_name}}", email = "your@email.com" }, ] -requires-python = ">=3.9" +requires-python = ">=3.10" {%- if cookiecutter.license != "None" %} license-files = ["LICEN[CS]E*"] {%- if cookiecutter.license == "MIT" %} @@ -46,9 +46,9 @@ cmake.version = ">=3.23" build.verbose = true [tool.scikit-build.cmake.define] -BUILD_PYTHON = "ON" -BUILD_TESTING = "OFF" -BUILD_DOCS = "OFF" +{{ cookiecutter.project_slug }}_BUILD_PYTHON = "ON" +{{ cookiecutter.project_slug }}_BUILD_TESTING = "OFF" +{{ cookiecutter.project_slug }}_BUILD_DOCS = "OFF" # The following is the configuration for the pytest test suite [tool.pytest.ini_options] @@ -67,7 +67,7 @@ testpaths = ["tests/python"] build-verbosity = 3 # We restrict ourselves to recent Python versions -skip = "pp* *p27-* cp35-* cp36-* cp37-* cp38-* *musllinux*" +skip = "pp* *p27-* cp35-* cp36-* cp37-* cp38-* cp39-* *musllinux*" # Testing commands for our wheels test-command = "pytest {package}/tests/python" diff --git a/{{cookiecutter.project_slug}}/tests/CMakeLists.txt b/{{cookiecutter.project_slug}}/tests/CMakeLists.txt index 426bbd8..dc12b70 100644 --- a/{{cookiecutter.project_slug}}/tests/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/tests/CMakeLists.txt @@ -16,13 +16,15 @@ include(Catch) {%- endif %} # Create a single test binary that compiles all test files. -add_executable(tests) +add_executable({{ cookiecutter.project_slug }}_tests) # Add the implementation file -target_sources(tests PRIVATE {{ cookiecutter.project_slug }}_t.cpp) +target_sources({{ cookiecutter.project_slug }}_tests PRIVATE {{ cookiecutter.project_slug }}_t.cpp) # Link the test binary -target_link_libraries(tests PUBLIC {{ cookiecutter.project_slug }} Catch2::Catch2WithMain) +target_link_libraries({{ cookiecutter.project_slug }}_tests PUBLIC {{ cookiecutter.project_slug }} Catch2::Catch2WithMain) # allow user to run tests with `make test` or `ctest` -catch_discover_tests(tests) +catch_discover_tests({{ cookiecutter.project_slug }}_tests + TEST_PREFIX "{{ cookiecutter.project_slug }}." +) From 79c8a6597586316f45e45aa0130006b7bf82d697 Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:37:11 +0100 Subject: [PATCH 06/17] enable modern pybind11 FindPython --- {{cookiecutter.project_slug}}/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/{{cookiecutter.project_slug}}/CMakeLists.txt b/{{cookiecutter.project_slug}}/CMakeLists.txt index 2a27906..e299dfe 100644 --- a/{{cookiecutter.project_slug}}/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/CMakeLists.txt @@ -85,6 +85,7 @@ endif() {%- if cookiecutter.python_bindings == "Yes" %} # Add Python bindings if({{ cookiecutter.project_slug }}_BUILD_PYTHON) + set(PYBIND11_FINDPYTHON ON) find_package(pybind11 REQUIRED) # Compile the Pybind11 module pybind11_add_module(_{{ cookiecutter|modname }} python/{{ cookiecutter|modname }}/_{{ cookiecutter.project_slug }}.cpp) From 0e86c22d0352b7272dd31810be7b4b311ac70b1e Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:31:29 +0100 Subject: [PATCH 07/17] Format CMLs such that baked projects have always exactly one blank line between cmake commands --- tests/test_bake_project.py | 6 ++- {{cookiecutter.project_slug}}/CMakeLists.txt | 53 ++++++++----------- .../doc/CMakeLists.txt | 3 +- .../tests/CMakeLists.txt | 7 ++- 4 files changed, 33 insertions(+), 36 deletions(-) diff --git a/tests/test_bake_project.py b/tests/test_bake_project.py index 31d9195..ad060b9 100644 --- a/tests/test_bake_project.py +++ b/tests/test_bake_project.py @@ -123,7 +123,8 @@ def test_readthedocs(cookies): ) check_bake(bake) with inside_bake(bake): - build_cmake(target='sphinx-doc') + project = bake.context['project_slug'] + build_cmake(target=f'{project}-sphinx-doc') assert os.path.exists(os.path.join(os.getcwd(), "doc", "sphinx", "index.html")) @@ -136,7 +137,8 @@ def test_doxygen(cookies): ) check_bake(bake) with inside_bake(bake): - build_cmake(target='doxygen') + project = bake.context['project_slug'] + build_cmake(target=f'{project}-doxygen') assert os.path.exists(os.path.join(os.getcwd(), "doc", "html", "index.html")) diff --git a/{{cookiecutter.project_slug}}/CMakeLists.txt b/{{cookiecutter.project_slug}}/CMakeLists.txt index e299dfe..ccd4474 100644 --- a/{{cookiecutter.project_slug}}/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/CMakeLists.txt @@ -8,38 +8,32 @@ project({{ cookiecutter.project_slug }} # Initialize some default paths include(GNUInstallDirs) +{%- if cookiecutter.python_bindings == "Yes" %} -{% if cookiecutter.python_bindings == "Yes" -%} # Enable PIC for Python bindings set(CMAKE_POSITION_INDEPENDENT_CODE ON) -{%- endif %} -{%- if cookiecutter.python_bindings == "Yes" or cookiecutter.doxygen == "Yes" or cookiecutter.readthedocs == "Yes" %} -# Compilation options -{%- endif %} -{%- if cookiecutter.python_bindings == "Yes" %} if(PROJECT_IS_TOP_LEVEL) option({{ cookiecutter.project_slug }}_BUILD_PYTHON "Enable building of Python bindings" ON) else() option({{ cookiecutter.project_slug }}_BUILD_PYTHON "Enable building of Python bindings" OFF) -endif() -{%- endif %} - +endif() +{%- endif -%} +{% if cookiecutter.doxygen == "Yes" or cookiecutter.readthedocs == "Yes" %} -{%- if cookiecutter.doxygen == "Yes" or cookiecutter.readthedocs == "Yes" %} if(PROJECT_IS_TOP_LEVEL) option({{ cookiecutter.project_slug }}_BUILD_DOCS "Enable building of documentation" ON) else() option({{ cookiecutter.project_slug }}_BUILD_DOCS "Enable building of documentation" OFF) -endif() +endif() {%- endif %} - {%- if cookiecutter.external_dependency != "None" %} + # Find external dependencies find_package({{ cookiecutter.external_dependency }}) {%- endif %} - {%- if cookiecutter.header_only == "No" %} + # Compile the library add_subdirectory(src) {% else %} @@ -65,26 +59,26 @@ if(PROJECT_IS_TOP_LEVEL) add_subdirectory(app) endif() -# Compile the tests -include(CTest) -if({{ cookiecutter.project_slug }}_BUILD_TESTING) +if(PROJECT_IS_TOP_LEVEL) + # Compile the tests + include(CTest) + if({{ cookiecutter.project_slug }}_BUILD_TESTING) {%- if cookiecutter.use_submodules == "Yes" %} - add_subdirectory(ext/Catch2) - include(./ext/Catch2/extras/Catch.cmake) + add_subdirectory(ext/Catch2) {%- endif %} - add_subdirectory(tests) + add_subdirectory(tests) + endif() endif() +{%- if cookiecutter.doxygen == "Yes" %} -{% if cookiecutter.doxygen == "Yes" -%} # Add the documentation -if({{ cookiecutter.project_slug }}_BUILD_DOCS) +if(PROJECT_IS_TOP_LEVEL AND {{ cookiecutter.project_slug }}_BUILD_DOCS) add_subdirectory(doc) endif() {%- endif %} - -{%- if cookiecutter.python_bindings == "Yes" %} +{% if cookiecutter.python_bindings == "Yes" %} # Add Python bindings -if({{ cookiecutter.project_slug }}_BUILD_PYTHON) +if(PROJECT_IS_TOP_LEVEL AND {{ cookiecutter.project_slug }}_BUILD_PYTHON) set(PYBIND11_FINDPYTHON ON) find_package(pybind11 REQUIRED) # Compile the Pybind11 module @@ -94,12 +88,10 @@ if({{ cookiecutter.project_slug }}_BUILD_PYTHON) # Install the Python module shared library install(TARGETS _{{ cookiecutter|modname }} DESTINATION .) endif() -{%- endif %} - +{% endif %} # Add an alias target for use if this project is included as a subproject in another project add_library({{ cookiecutter.project_slug }}::{{ cookiecutter.project_slug }} ALIAS {{ cookiecutter.project_slug }}) - -{%- if cookiecutter.external_dependency == "None" %} +{% if cookiecutter.external_dependency == "None" %} # Install the library target and its public headers install( TARGETS {{ cookiecutter.project_slug }} @@ -113,7 +105,7 @@ install( NAMESPACE {{ cookiecutter.project_slug }}:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/{{ cookiecutter.project_slug }} ) -{%- else %} +{% else %} # Install the build target and record it in an export set. install( TARGETS {{ cookiecutter.project_slug }} @@ -149,8 +141,7 @@ export( FILE ${CMAKE_CURRENT_BINARY_DIR}/{{ cookiecutter.project_slug }}Targets.cmake NAMESPACE {{ cookiecutter.project_slug }}:: ) -{%- endif %} - +{% endif %} # This prints a summary of found dependencies include(FeatureSummary) feature_summary(WHAT ALL) diff --git a/{{cookiecutter.project_slug}}/doc/CMakeLists.txt b/{{cookiecutter.project_slug}}/doc/CMakeLists.txt index 0e1966e..642b2ea 100644 --- a/{{cookiecutter.project_slug}}/doc/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/doc/CMakeLists.txt @@ -14,7 +14,8 @@ doxygen_add_docs({{ cookiecutter.project_slug }}-doxygen WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT Building doxygen documentation... ) -{% if cookiecutter.readthedocs == "Yes" -%} +{%- if cookiecutter.readthedocs == "Yes" %} + add_custom_target({{ cookiecutter.project_slug }}-sphinx-doc COMMAND sphinx-build -b html diff --git a/{{cookiecutter.project_slug}}/tests/CMakeLists.txt b/{{cookiecutter.project_slug}}/tests/CMakeLists.txt index dc12b70..2543f00 100644 --- a/{{cookiecutter.project_slug}}/tests/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/tests/CMakeLists.txt @@ -1,4 +1,4 @@ -{%- if cookiecutter.use_submodules == "No" %} +{%- if cookiecutter.use_submodules == "No" -%} # Enable CMake's FetchContent module, which allows downloading dependencies like Catch2 automatically at configure time. include(FetchContent) @@ -11,8 +11,11 @@ FetchContent_Declare( # Make Catch2 available in this project. This defines the imported targets Catch2::Catch2 and Catch2::Catch2WithMain. FetchContent_MakeAvailable(Catch2) -# Include Catch2's CMake helper functions (e.g. catch_discover_tests). +# Include Catch2's CMake helper functions (e.g., catch_discover_tests). include(Catch) + +{%- else -%} +include(${PROJECT_SOURCE_DIR}/ext/Catch2/extras/Catch.cmake) {%- endif %} # Create a single test binary that compiles all test files. From a2841d0fa5d4cb2a90c2f533d3d5551efa1b4504 Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:32:39 +0100 Subject: [PATCH 08/17] Remove cmake-format and cmake-lint pre-commit hooks --- .../.pre-commit-config.yaml | 6 ------ {{cookiecutter.project_slug}}/CMakeLists.txt | 14 ++++++++++---- {{cookiecutter.project_slug}}/README.md | 2 +- .../doc/CMakeLists.txt | 18 +++++++++--------- .../src/CMakeLists.txt | 5 ++++- .../tests/CMakeLists.txt | 11 +++++++++-- 6 files changed, 33 insertions(+), 23 deletions(-) diff --git a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml index eca991b..f2c2945 100644 --- a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml @@ -33,12 +33,6 @@ repos: # Unify file endings - id: end-of-file-fixer - # CMake Formatting/Linting Utility - - repo: https://github.com/cheshirekow/cmake-format-precommit - rev: v0.6.13 - hooks: - - id: cmake-format - - id: cmake-lint {%- if cookiecutter.github_actions_ci == "Yes" %} # GitHub Actions Workflow linter diff --git a/{{cookiecutter.project_slug}}/CMakeLists.txt b/{{cookiecutter.project_slug}}/CMakeLists.txt index ccd4474..1d7c3ce 100644 --- a/{{cookiecutter.project_slug}}/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/CMakeLists.txt @@ -51,7 +51,10 @@ target_sources({{ cookiecutter.project_slug }} ) # Request a minimum C++ standard for this target -target_compile_features({{ cookiecutter.project_slug }} INTERFACE cxx_std_{{ cookiecutter.cxx_minimum_standard }}) +target_compile_features({{ cookiecutter.project_slug }} + INTERFACE + cxx_std_{{ cookiecutter.cxx_minimum_standard }} +) {%- endif %} if(PROJECT_IS_TOP_LEVEL) @@ -83,7 +86,10 @@ if(PROJECT_IS_TOP_LEVEL AND {{ cookiecutter.project_slug }}_BUILD_PYTHON) find_package(pybind11 REQUIRED) # Compile the Pybind11 module pybind11_add_module(_{{ cookiecutter|modname }} python/{{ cookiecutter|modname }}/_{{ cookiecutter.project_slug }}.cpp) - target_link_libraries(_{{ cookiecutter|modname }} PUBLIC {{ cookiecutter.project_slug }}) + target_link_libraries(_{{ cookiecutter|modname }} + PUBLIC + {{ cookiecutter.project_slug }} + ) # Install the Python module shared library install(TARGETS _{{ cookiecutter|modname }} DESTINATION .) @@ -102,8 +108,8 @@ install( # Export the installed target into a simple -config file, so that find_package({{ cookiecutter.project_slug }}) works for consumers install( EXPORT {{ cookiecutter.project_slug }}-config - NAMESPACE {{ cookiecutter.project_slug }}:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/{{ cookiecutter.project_slug }} + NAMESPACE {{ cookiecutter.project_slug }}:: ) {% else %} # Install the build target and record it in an export set. @@ -117,8 +123,8 @@ install( install( EXPORT {{ cookiecutter.project_slug }}-targets FILE {{ cookiecutter.project_slug }}Targets.cmake - NAMESPACE {{ cookiecutter.project_slug }}:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/{{ cookiecutter.project_slug }} + NAMESPACE {{ cookiecutter.project_slug }}:: ) # Generate a Config.cmake file from a template diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md index 9736c0e..0c36fd0 100644 --- a/{{cookiecutter.project_slug}}/README.md +++ b/{{cookiecutter.project_slug}}/README.md @@ -50,7 +50,7 @@ Building {{ cookiecutter.project_name }} requires the following software install {%- if cookiecutter.use_submodules == "No" %} * The testing framework [Catch2](https://github.com/catchorg/Catch2) for building the test suite {%- endif %} -{%- if cookiecutter.python_bindings == "Yes" -%} +{%- if cookiecutter.python_bindings == "Yes" %} * Python `>= 3.10` for building Python bindings {%- endif %} diff --git a/{{cookiecutter.project_slug}}/doc/CMakeLists.txt b/{{cookiecutter.project_slug}}/doc/CMakeLists.txt index 642b2ea..a28041e 100644 --- a/{{cookiecutter.project_slug}}/doc/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/doc/CMakeLists.txt @@ -17,15 +17,15 @@ doxygen_add_docs({{ cookiecutter.project_slug }}-doxygen {%- if cookiecutter.readthedocs == "Yes" %} add_custom_target({{ cookiecutter.project_slug }}-sphinx-doc - COMMAND - sphinx-build -b html - -Dbreathe_projects.{{ cookiecutter.project_slug }}="${CMAKE_CURRENT_BINARY_DIR}/xml" - -c ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_BINARY_DIR}/sphinx - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Generating documentation with Sphinx..." - ) + COMMAND + sphinx-build -b html + -Dbreathe_projects.{{ cookiecutter.project_slug }}="${CMAKE_CURRENT_BINARY_DIR}/xml" + -c ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/sphinx + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating documentation with Sphinx..." +) add_dependencies({{ cookiecutter.project_slug }}-sphinx-doc {{ cookiecutter.project_slug }}-doxygen) {%- endif %} {%- endif -%} \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/src/CMakeLists.txt b/{{cookiecutter.project_slug}}/src/CMakeLists.txt index 7b2bccc..8c53dac 100644 --- a/{{cookiecutter.project_slug}}/src/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/src/CMakeLists.txt @@ -14,4 +14,7 @@ target_sources({{ cookiecutter.project_slug }} ) # Request a minimum C++ standard for this target -target_compile_features({{ cookiecutter.project_slug }} PUBLIC cxx_std_{{ cookiecutter.cxx_minimum_standard }}) +target_compile_features({{ cookiecutter.project_slug }} + PUBLIC + cxx_std_{{ cookiecutter.cxx_minimum_standard }} +) diff --git a/{{cookiecutter.project_slug}}/tests/CMakeLists.txt b/{{cookiecutter.project_slug}}/tests/CMakeLists.txt index 2543f00..55bddce 100644 --- a/{{cookiecutter.project_slug}}/tests/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/tests/CMakeLists.txt @@ -22,10 +22,17 @@ include(${PROJECT_SOURCE_DIR}/ext/Catch2/extras/Catch.cmake) add_executable({{ cookiecutter.project_slug }}_tests) # Add the implementation file -target_sources({{ cookiecutter.project_slug }}_tests PRIVATE {{ cookiecutter.project_slug }}_t.cpp) +target_sources({{ cookiecutter.project_slug }}_tests + PRIVATE + {{ cookiecutter.project_slug }}_t.cpp +) # Link the test binary -target_link_libraries({{ cookiecutter.project_slug }}_tests PUBLIC {{ cookiecutter.project_slug }} Catch2::Catch2WithMain) +target_link_libraries({{ cookiecutter.project_slug }}_tests + PUBLIC + {{ cookiecutter.project_slug }} + Catch2::Catch2WithMain +) # allow user to run tests with `make test` or `ctest` catch_discover_tests({{ cookiecutter.project_slug }}_tests From 1596acf761d66f943d314930db68586a18870365 Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Mon, 19 Jan 2026 11:41:08 +0100 Subject: [PATCH 09/17] Modernize target exports and installation --- cookiecutter.json | 4 +- hooks/post_gen_project.py | 1 - {{cookiecutter.project_slug}}/CMakeLists.txt | 53 +++++++++---------- {{cookiecutter.project_slug}}/TODO.md | 6 +-- ...cookiecutter.project_slug}}Config.cmake.in | 8 +++ ...cookiecutter.project_slug}}Config.cmake.in | 15 ------ 6 files changed, 37 insertions(+), 50 deletions(-) create mode 100644 {{cookiecutter.project_slug}}/cmake/{{cookiecutter.project_slug}}Config.cmake.in delete mode 100644 {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}Config.cmake.in diff --git a/cookiecutter.json b/cookiecutter.json index 5fe092a..47eddbf 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -21,6 +21,6 @@ "local_extensions.CurrentDateExtension" ], "__python_module": "{{ cookiecutter|modname }}", - "_catch_version": "3.11.0", - "_cibuildwheel_version": "3.3.0" + "_catch_version": "3.12.0", + "_cibuildwheel_version": "3.3.1" } diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 3bba2bc..01ad4f0 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -79,7 +79,6 @@ def conditional_remove(condition, path): conditional_remove("{{ cookiecutter.pypi_release }}" != "Yes", ".github/workflows/pypi.yml") conditional_remove("{{ cookiecutter.codecovio }}" == "No", "codecov.yml") conditional_remove("{{ cookiecutter.github_actions_ci }}" == "No", ".github") -conditional_remove("{{ cookiecutter.external_dependency }}" == "None", "{{ cookiecutter.project_slug }}Config.cmake.in") conditional_remove(not {{ have_precommit }}, ".pre-commit-config.yaml") conditional_remove(os.stat("TODO.md").st_size == 0, "TODO.md") diff --git a/{{cookiecutter.project_slug}}/CMakeLists.txt b/{{cookiecutter.project_slug}}/CMakeLists.txt index 1d7c3ce..75e3dc6 100644 --- a/{{cookiecutter.project_slug}}/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/CMakeLists.txt @@ -97,57 +97,52 @@ endif() {% endif %} # Add an alias target for use if this project is included as a subproject in another project add_library({{ cookiecutter.project_slug }}::{{ cookiecutter.project_slug }} ALIAS {{ cookiecutter.project_slug }}) -{% if cookiecutter.external_dependency == "None" %} -# Install the library target and its public headers -install( - TARGETS {{ cookiecutter.project_slug }} - EXPORT {{ cookiecutter.project_slug }}-config - FILE_SET HEADERS -) -# Export the installed target into a simple -config file, so that find_package({{ cookiecutter.project_slug }}) works for consumers -install( - EXPORT {{ cookiecutter.project_slug }}-config - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/{{ cookiecutter.project_slug }} - NAMESPACE {{ cookiecutter.project_slug }}:: -) -{% else %} -# Install the build target and record it in an export set. +# Install the target and headers install( TARGETS {{ cookiecutter.project_slug }} - EXPORT {{ cookiecutter.project_slug }}-targets + EXPORT {{ cookiecutter.project_slug }}Targets FILE_SET HEADERS ) -# Export the target definition so other CMake projects can reconstruct the imported target via find_package() +# Install the exported targets (this creates {{ cookiecutter.project_slug }}Targets.cmake) install( - EXPORT {{ cookiecutter.project_slug }}-targets - FILE {{ cookiecutter.project_slug }}Targets.cmake + EXPORT {{ cookiecutter.project_slug }}Targets DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/{{ cookiecutter.project_slug }} NAMESPACE {{ cookiecutter.project_slug }}:: ) -# Generate a Config.cmake file from a template +# Include helpers for calling configure_package_config_file include(CMakePackageConfigHelpers) + +# Generate the {{ cookiecutter.project_slug }}Config.cmake file configure_package_config_file( - ${CMAKE_CURRENT_LIST_DIR}/{{ cookiecutter.project_slug }}Config.cmake.in + ${CMAKE_CURRENT_LIST_DIR}/cmake/{{ cookiecutter.project_slug }}Config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/{{ cookiecutter.project_slug }}Config.cmake INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/{{ cookiecutter.project_slug }} ) -# Install the generated project configuration file alongside the targets +# Install the generated Config file install( - FILES ${CMAKE_CURRENT_BINARY_DIR}/{{ cookiecutter.project_slug }}Config.cmake + FILES + ${CMAKE_CURRENT_BINARY_DIR}/{{ cookiecutter.project_slug }}Config.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/{{ cookiecutter.project_slug }} ) -# Export targets for the build tree -export( - EXPORT {{ cookiecutter.project_slug }}-targets - FILE ${CMAKE_CURRENT_BINARY_DIR}/{{ cookiecutter.project_slug }}Targets.cmake - NAMESPACE {{ cookiecutter.project_slug }}:: +# Generate a version file +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/{{ cookiecutter.project_slug }}ConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion ) -{% endif %} + +# Install the version file +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/{{ cookiecutter.project_slug }}ConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/{{ cookiecutter.project_slug }} +) + # This prints a summary of found dependencies include(FeatureSummary) feature_summary(WHAT ALL) diff --git a/{{cookiecutter.project_slug}}/TODO.md b/{{cookiecutter.project_slug}}/TODO.md index 8192e76..45833ad 100644 --- a/{{cookiecutter.project_slug}}/TODO.md +++ b/{{cookiecutter.project_slug}}/TODO.md @@ -10,12 +10,12 @@ The following tasks need to be done to get a fully working project: {%- endif %} * Make sure that the following software is installed on your computer: * A C++-{{ cookiecutter.cxx_minimum_standard}}-compliant C++ compiler - * CMake `>= 3.9` + * CMake `>= 3.23` {%- if cookiecutter.use_submodules == "No" %} * The testing framework [Catch2](https://github.com/catchorg/Catch2) {%- endif %} {%- if cookiecutter.external_dependency != "None" %} - * Adapt your list of external dependencies in `CMakeLists.txt` and `{{ cookiecutter.project_slug }}Config.cmake.in`. + * Adapt your list of external dependencies in `CMakeLists.txt` and `cmake/{{ cookiecutter.project_slug }}Config.cmake.in`. You can e.g. * Link your library or applications to your dependency. For this to work, you need to see if your dependency exports targets and what their name is. As this is highly @@ -23,7 +23,7 @@ The following tasks need to be done to get a fully working project: * Add more dependencies in analogy to `{{ cookiecutter.external_dependency }}` * Make dependencies requirements by adding `REQUIRED` to `find_package()` * Add version constraints to dependencies by adding `VERSION` to `find_package()` - * Make a dependency a pure build time dependency by removing it from `{{ cookiecutter.project_slug }}Config.cmake.in` + * Make a dependency a pure build time dependency by removing it from `cmake/{{ cookiecutter.project_slug }}Config.cmake.in` {%- endif %} {%- if cookiecutter.gitlab_ci == "Yes" %} * Make sure that CI/CD pipelines are enabled in your Gitlab project settings and that diff --git a/{{cookiecutter.project_slug}}/cmake/{{cookiecutter.project_slug}}Config.cmake.in b/{{cookiecutter.project_slug}}/cmake/{{cookiecutter.project_slug}}Config.cmake.in new file mode 100644 index 0000000..83c1e48 --- /dev/null +++ b/{{cookiecutter.project_slug}}/cmake/{{cookiecutter.project_slug}}Config.cmake.in @@ -0,0 +1,8 @@ +@PACKAGE_INIT@ + +{% if cookiecutter.external_dependency != "None" %} +include(CMakeFindDependencyMacro) + +find_dependency({{ cookiecutter.external_dependency }}) +{% endif %} +include("${CMAKE_CURRENT_LIST_DIR}/{{ cookiecutter.project_slug }}Targets.cmake") diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}Config.cmake.in b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}Config.cmake.in deleted file mode 100644 index 0a57aac..0000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}Config.cmake.in +++ /dev/null @@ -1,15 +0,0 @@ -get_filename_component( - {{ cookiecutter.project_slug|upper }}_CMAKE_DIR - ${CMAKE_CURRENT_LIST_FILE} - PATH -) -set(CMAKE_MODULE_PATH ${{ "{" }}{{ cookiecutter.project_slug|upper }}_CMAKE_DIR} ${CMAKE_MODULE_PATH}) - -include(CMakeFindDependencyMacro) -if(@{{ cookiecutter.external_dependency|upper }}_FOUND@) -find_dependency({{ cookiecutter.external_dependency }}) -endif() - -if(NOT TARGET {{ cookiecutter.project_slug }}::{{ cookiecutter.project_slug }}) - include("${{ "{" }}{{ cookiecutter.project_slug|upper }}_CMAKE_DIR}/{{ cookiecutter.project_slug }}Targets.cmake") -endif() From 1173129002338a8363206256ec8029cdb68c8f13 Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Mon, 19 Jan 2026 11:47:16 +0100 Subject: [PATCH 10/17] Bump CI python versions to 3.10 and 3.14, bump actions/checkout to v6 --- .github/workflows/ci.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 251fef3..16b68ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,15 +22,11 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - # We test on the oldest and newest supported Python versions - # Unfortenately, we can't test on the latest Python version, as the - # pytest-virtualenv plugin does not support it yet, see - # https://github.com/man-group/pytest-plugins/issues/220 - python-version: ["3.9", "3.13"] + python-version: ["3.10", "3.14"] steps: - name: Checking out the cookie cutter repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 @@ -63,11 +59,11 @@ jobs: steps: - name: Checking out the cookie cutter repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: "3.13" + python-version: "3.14" - name: Install test requirements run: | From 51f0c6cd09e8ca9336e5c93ae5a3a9ac6f986574 Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Mon, 19 Jan 2026 12:22:04 +0100 Subject: [PATCH 11/17] Install doxygen and pybind11 in baked project's CI --- .../.github/workflows/ci.yml | 31 ++++++++++++------- {{cookiecutter.project_slug}}/.gitlab-ci.yml | 3 ++ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml index 9d0cc2f..b801017 100644 --- a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +++ b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml @@ -26,21 +26,24 @@ jobs: with: submodules: 'recursive' {%- endif %} - - - name: make build directory +{% if cookiecutter.doxygen == "Yes" %} + - name: Install Doxygen + uses: ssciwr/doxygen-install@v1 +{% endif %} + - name: Make build directory run: cmake -E make_directory ${{ "{{ github.workspace }}" }}/build - - name: configure cmake + - name: Configure cmake shell: bash working-directory: ${{ "{{ github.workspace }}" }}/build run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -D{{ cookiecutter.project_slug }}_BUILD_DOCS=OFF - - name: build + - name: Build shell: bash working-directory: ${{ "{{ github.workspace }}" }}/build run: cmake --build . - - name: run tests + - name: Run tests shell: bash working-directory: ${{ "{{ github.workspace }}" }}/build run: ctest @@ -63,7 +66,10 @@ jobs: uses: actions/setup-python@v5 with: python-version: "3.13" - +{% if cookiecutter.doxygen == "Yes" %} + - name: Install Doxygen + uses: ssciwr/doxygen-install@v1 +{% endif %} - name: Run Python tests shell: bash run: | @@ -87,7 +93,10 @@ jobs: - name: Install LCov run: | sudo apt-get install -y lcov - +{% if cookiecutter.doxygen == "Yes" %} + - name: Install Doxygen + uses: ssciwr/doxygen-install@v1 +{% endif %} {% if cookiecutter.python_bindings == "Yes" %} - name: Install Python package editable run: | @@ -102,24 +111,24 @@ jobs: - name: Create cmake build directory run: cmake -E make_directory ${{ "{{ github.workspace }}" }}/build - - name: configure cmake + - name: Configure cmake shell: bash working-directory: ${{ "{{ github.workspace }}" }}/build run: | cmake $GITHUB_WORKSPACE -DCMAKE_CXX_FLAGS="--coverage" -D{{ cookiecutter.project_slug }}_BUILD_DOCS=OFF - - name: build + - name: Build shell: bash working-directory: ${{ "{{ github.workspace }}" }}/build run: cmake --build . {% endif %} - - name: run tests + - name: Run tests shell: bash working-directory: ${{ "{{ github.workspace }}" }}/build run: ctest - - name: collect coverage report + - name: Collect coverage report shell: bash working-directory: ${{ "{{ github.workspace }}" }} run: | diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml index a24add5..de50805 100644 --- a/{{cookiecutter.project_slug}}/.gitlab-ci.yml +++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml @@ -12,6 +12,9 @@ variables: {% if cookiecutter.python_bindings == "Yes" %} - apt install -y python3-dev {%- endif %} +{% if cookiecutter.doxygen == "Yes" %} + - apt install -y doxygen +{% endif %} script: - cmake -E make_directory build - cd build From 49ba5294d659a0ce7a569ea3fafe68693d1e56ba Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:25:37 +0100 Subject: [PATCH 12/17] Set _BUILD_PYTHON CMake option to OFF when building with cmake only, set python version to 3.14 --- {{cookiecutter.project_slug}}/.github/workflows/ci.yml | 4 ++-- {{cookiecutter.project_slug}}/.gitlab-ci.yml | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml index b801017..f6d4db0 100644 --- a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +++ b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: - name: Configure cmake shell: bash working-directory: ${{ "{{ github.workspace }}" }}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -D{{ cookiecutter.project_slug }}_BUILD_DOCS=OFF + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Debug -D{{ cookiecutter.project_slug }}_BUILD_DOCS=OFF -D{{ cookiecutter.project_slug }}_BUILD_PYTHON=OFF - name: Build shell: bash @@ -65,7 +65,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.13" + python-version: "3.14" {% if cookiecutter.doxygen == "Yes" %} - name: Install Doxygen uses: ssciwr/doxygen-install@v1 diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml index de50805..f0dcc75 100644 --- a/{{cookiecutter.project_slug}}/.gitlab-ci.yml +++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml @@ -16,12 +16,9 @@ variables: - apt install -y doxygen {% endif %} script: - - cmake -E make_directory build - - cd build - - cmake -DCMAKE_BUILD_TYPE=Debug -D{{ cookiecutter.project_slug }}_BUILD_DOCS=OFF .. - - cmake --build . - - ctest - + - cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -D{{ cookiecutter.project_slug }}_BUILD_DOCS=OFF -D{{ cookiecutter.project_slug }}_BUILD_PYTHON=OFF + - cmake --build build + - ctest --test-dir build # Note: You can use your own Docker images here that e.g. include relevant # dependencies and development tools. We choose well-maintained images From 89282a1662f4eaf933416be7f69853dffa8a937b Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Thu, 29 Jan 2026 07:28:11 +0100 Subject: [PATCH 13/17] Enable PIC only when python bindings are requested --- {{cookiecutter.project_slug}}/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/{{cookiecutter.project_slug}}/CMakeLists.txt b/{{cookiecutter.project_slug}}/CMakeLists.txt index 75e3dc6..5c6ff08 100644 --- a/{{cookiecutter.project_slug}}/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/CMakeLists.txt @@ -10,9 +10,6 @@ project({{ cookiecutter.project_slug }} include(GNUInstallDirs) {%- if cookiecutter.python_bindings == "Yes" %} -# Enable PIC for Python bindings -set(CMAKE_POSITION_INDEPENDENT_CODE ON) - if(PROJECT_IS_TOP_LEVEL) option({{ cookiecutter.project_slug }}_BUILD_PYTHON "Enable building of Python bindings" ON) else() @@ -82,6 +79,9 @@ endif() {% if cookiecutter.python_bindings == "Yes" %} # Add Python bindings if(PROJECT_IS_TOP_LEVEL AND {{ cookiecutter.project_slug }}_BUILD_PYTHON) + # Enable PIC for Python bindings + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + set(PYBIND11_FINDPYTHON ON) find_package(pybind11 REQUIRED) # Compile the Pybind11 module From 7ca662f015d17e6e8e57985e896171e9ef04421d Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Thu, 29 Jan 2026 13:42:18 +0100 Subject: [PATCH 14/17] Ignore coverage error for unused exclude patterns --- {{cookiecutter.project_slug}}/.github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml index f6d4db0..8aca8b5 100644 --- a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +++ b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml @@ -132,7 +132,7 @@ jobs: shell: bash working-directory: ${{ "{{ github.workspace }}" }} run: | - lcov --directory ./build{% if cookiecutter.header_only == "No" %}/src{% endif %} --capture --output-file coverage.info --ignore-errors mismatch --exclude '*/catch2/*' + lcov --directory ./build{% if cookiecutter.header_only == "No" %}/src{% endif %} --capture --output-file coverage.info --ignore-errors mismatch,unused --exclude '*/catch2/*' - name: Upload C++ coverage to Codecov uses: codecov/codecov-action@v5 From 33878bdd331583c0c9a6ccb40c419608f0ad4b4d Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Thu, 29 Jan 2026 14:53:29 +0100 Subject: [PATCH 15/17] Configure Catch2 via add_subdirectory() and find_package() when using git submodules --- {{cookiecutter.project_slug}}/tests/CMakeLists.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/{{cookiecutter.project_slug}}/tests/CMakeLists.txt b/{{cookiecutter.project_slug}}/tests/CMakeLists.txt index 55bddce..6f02f52 100644 --- a/{{cookiecutter.project_slug}}/tests/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/tests/CMakeLists.txt @@ -11,13 +11,14 @@ FetchContent_Declare( # Make Catch2 available in this project. This defines the imported targets Catch2::Catch2 and Catch2::Catch2WithMain. FetchContent_MakeAvailable(Catch2) -# Include Catch2's CMake helper functions (e.g., catch_discover_tests). -include(Catch) - {%- else -%} -include(${PROJECT_SOURCE_DIR}/ext/Catch2/extras/Catch.cmake) +# Find the Catch2 package +find_package(Catch2 3 REQUIRED) {%- endif %} +# Include Catch2's CMake helper functions (e.g., catch_discover_tests). +include(Catch) + # Create a single test binary that compiles all test files. add_executable({{ cookiecutter.project_slug }}_tests) From dfd7c9bb2af72f2d9ddf2b7687d0135fbb30d087 Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Mon, 2 Feb 2026 12:52:17 +0100 Subject: [PATCH 16/17] set CMAKE_POSITION_INDEPENDENT_CODE on target instead of globally Co-authored-by: Liam Keegan --- {{cookiecutter.project_slug}}/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/{{cookiecutter.project_slug}}/CMakeLists.txt b/{{cookiecutter.project_slug}}/CMakeLists.txt index 5c6ff08..02c0fd7 100644 --- a/{{cookiecutter.project_slug}}/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/CMakeLists.txt @@ -80,7 +80,10 @@ endif() # Add Python bindings if(PROJECT_IS_TOP_LEVEL AND {{ cookiecutter.project_slug }}_BUILD_PYTHON) # Enable PIC for Python bindings - set(CMAKE_POSITION_INDEPENDENT_CODE ON) + set_target_properties({{ cookiecutter.project_slug }} + PROPERTIES + POSITION_INDEPENDENT_CODE ON + ) set(PYBIND11_FINDPYTHON ON) find_package(pybind11 REQUIRED) From 484bd8598d8ca65616c8b3e91fb3b553fe63d417 Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Mon, 2 Feb 2026 12:58:25 +0100 Subject: [PATCH 17/17] Add option for switching building tests on or off --- {{cookiecutter.project_slug}}/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/{{cookiecutter.project_slug}}/CMakeLists.txt b/{{cookiecutter.project_slug}}/CMakeLists.txt index 02c0fd7..b354d10 100644 --- a/{{cookiecutter.project_slug}}/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/CMakeLists.txt @@ -8,6 +8,9 @@ project({{ cookiecutter.project_slug }} # Initialize some default paths include(GNUInstallDirs) + +# Define build options +option({{ cookiecutter.project_slug }}_BUILD_TESTING "Enable building of tests" OFF) {%- if cookiecutter.python_bindings == "Yes" %} if(PROJECT_IS_TOP_LEVEL)