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/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. 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}}/.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}}/.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 a53d6c9..203b440 100644 --- a/{{cookiecutter.project_slug}}/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/CMakeLists.txt @@ -1,77 +1,85 @@ -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) - -{%- 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 %} +project({{ cookiecutter.project_slug }} + VERSION 0.0.1 + LANGUAGES CXX +) # Initialize some default paths include(GNUInstallDirs) +{%- if cookiecutter.python_bindings == "Yes" %} -# 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 %} -# Compilation options -{%- if cookiecutter.python_bindings == "Yes" %} -option(BUILD_PYTHON "Enable building of Python bindings" OFF) -{%- endif %} -{%- if cookiecutter.doxygen == "Yes" or cookiecutter.readthedocs == "Yes" %} -option(BUILD_DOCS "Enable building of documentation" ON) -{%- endif %} +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" %} +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" %} # 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 -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(PROJECT_IS_TOP_LEVEL) + # Compile the tests + include(CTest) + if(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" -%} -if(BUILD_DOCS) - # Add the documentation +# Add the documentation +if(PROJECT_IS_TOP_LEVEL AND {{ cookiecutter.project_slug }}_BUILD_DOCS) add_subdirectory(doc) endif() {%- endif %} -{%- if cookiecutter.python_bindings == "Yes" %} -if(BUILD_PYTHON) - # Add Python bindings +{% if cookiecutter.python_bindings == "Yes" %} +# Add Python bindings +if(PROJECT_IS_TOP_LEVEL AND {{ 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) @@ -80,61 +88,60 @@ if(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 }}) - -# Install targets and configuration -{%- if cookiecutter.external_dependency == "None" %} +{% 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 %} +{% 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) feature_summary(WHAT ALL) 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..642b2ea 100644 --- a/{{cookiecutter.project_slug}}/doc/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/doc/CMakeLists.txt @@ -5,13 +5,18 @@ set(DOXYGEN_SHORT_NAMES YES) {% if cookiecutter.readthedocs == "Yes" -%} set(DOXYGEN_GENERATE_XML YES) {%- endif %} -doxygen_add_docs(doxygen - ${CMAKE_SOURCE_DIR} + +doxygen_add_docs({{ cookiecutter.project_slug }}-doxygen + ${CMAKE_SOURCE_DIR}/include +{%- if cookiecutter.header_only == "No" %} + ${CMAKE_SOURCE_DIR}/src +{%- endif %} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT Building doxygen documentation... ) -{% if cookiecutter.readthedocs == "Yes" -%} -add_custom_target(sphinx-doc +{%- 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" @@ -21,6 +26,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}}/src/CMakeLists.txt b/{{cookiecutter.project_slug}}/src/CMakeLists.txt index afe63b1..0c363fc 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 }} PRIVATE cxx_std_{{ cookiecutter.cxx_minimum_standard }}) diff --git a/{{cookiecutter.project_slug}}/tests/CMakeLists.txt b/{{cookiecutter.project_slug}}/tests/CMakeLists.txt index 3b7ca76..e0851f7 100644 --- a/{{cookiecutter.project_slug}}/tests/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/tests/CMakeLists.txt @@ -1,39 +1,34 @@ -{%- if cookiecutter.use_submodules == "No" %} -# ------------------------------------------------------------------------------ -# Enable CMake's FetchContent module, which allows downloading dependencies like -# Catch2 automatically at configure time. -# ------------------------------------------------------------------------------ +{%- if cookiecutter.use_submodules == "No" -%} +# 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 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) + +{%- else -%} +include(${PROJECT_SOURCE_DIR}/ext/Catch2/extras/Catch.cmake) {%- endif %} -# ------------------------------------------------------------------------------ # Create a single test binary that compiles all test files. -# ------------------------------------------------------------------------------ -add_executable(tests {{ cookiecutter.project_slug }}_t.cpp) +add_executable({{ cookiecutter.project_slug }}_tests) + +# Add the implementation file +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 }}." + +)