├── out └── coverage │ └── .keep ├── .github ├── constants.env ├── template │ ├── template_name │ ├── removal-list │ ├── template_repository │ └── README.md ├── FUNDING.yml ├── dependabot.yml ├── workflows │ ├── auto-clang-format.yml │ ├── codeql-analysis.yml │ ├── ci.yml │ └── template-janitor.yml └── actions │ └── setup_cache │ └── action.yml ├── src ├── CMakeLists.txt ├── sample_library │ ├── sample_library.cpp │ └── CMakeLists.txt └── ftxui_sample │ ├── CMakeLists.txt │ └── main.cpp ├── cmake ├── _FORTIFY_SOURCE.hpp ├── Tests.cmake ├── InterproceduralOptimization.cmake ├── LibFuzzer.cmake ├── PreventInSourceBuilds.cmake ├── Linker.cmake ├── CPM.cmake ├── Cache.cmake ├── Cuda.cmake ├── StandardProjectSettings.cmake ├── Doxygen.cmake ├── SystemLink.cmake ├── VCEnvironment.cmake ├── Sanitizers.cmake ├── Utilities.cmake ├── StaticAnalyzers.cmake ├── Hardening.cmake ├── CompilerWarnings.cmake └── PackageProject.cmake ├── configured_files ├── CMakeLists.txt └── config.hpp.in ├── test ├── tests.cpp ├── constexpr_tests.cpp └── CMakeLists.txt ├── include └── myproject │ └── sample_library.hpp ├── .cmake-format.yaml ├── gcovr.cfg ├── .devcontainer ├── .dockerignore ├── devcontainer.json └── Dockerfile ├── .gitignore ├── fuzz_test ├── CMakeLists.txt └── fuzz_tester.cpp ├── .clang-tidy ├── Dependencies.cmake ├── .gitattributes ├── LICENSE ├── .gitlab-ci.yml ├── README.md ├── README_docker.md ├── .clang-format ├── CMakeLists.txt ├── README_dependencies.md ├── README_building.md ├── ProjectOptions.cmake └── CMakePresets.json /out/coverage/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/constants.env: -------------------------------------------------------------------------------- 1 | PROJECT_NAME=myproject 2 | -------------------------------------------------------------------------------- /.github/template/template_name: -------------------------------------------------------------------------------- 1 | cmake_template 2 | -------------------------------------------------------------------------------- /.github/template/removal-list: -------------------------------------------------------------------------------- 1 | LICENSE 2 | .github/FUNDING.yml -------------------------------------------------------------------------------- /.github/template/template_repository: -------------------------------------------------------------------------------- 1 | cpp-best-practices/cmake_template 2 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(sample_library) 2 | add_subdirectory(ftxui_sample) 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: lefticus 4 | patreon: lefticus 5 | -------------------------------------------------------------------------------- /cmake/_FORTIFY_SOURCE.hpp: -------------------------------------------------------------------------------- 1 | #ifdef _FORTIFY_SOURCE 2 | #if _FORTIFY_SOURCE < 3 3 | #undef _FORTIFY_SOURCE 4 | #define _FORTIFY_SOURCE 3 5 | #endif 6 | #else 7 | #define _FORTIFY_SOURCE 3 8 | #endif 9 | -------------------------------------------------------------------------------- /src/sample_library/sample_library.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int factorial(int input) noexcept 4 | { 5 | int result = 1; 6 | 7 | while (input > 0) { 8 | result *= input; 9 | --input; 10 | } 11 | 12 | return result; 13 | } 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Set update schedule for GitHub Actions 2 | 3 | version: 2 4 | updates: 5 | 6 | - package-ecosystem: "github-actions" 7 | directory: "/" 8 | schedule: 9 | # Check for updates to GitHub Actions every week 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /cmake/Tests.cmake: -------------------------------------------------------------------------------- 1 | function(myproject_enable_coverage project_name) 2 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 3 | target_compile_options(${project_name} INTERFACE --coverage -O0 -g) 4 | target_link_libraries(${project_name} INTERFACE --coverage) 5 | endif() 6 | endfunction() 7 | -------------------------------------------------------------------------------- /cmake/InterproceduralOptimization.cmake: -------------------------------------------------------------------------------- 1 | macro(myproject_enable_ipo) 2 | include(CheckIPOSupported) 3 | check_ipo_supported(RESULT result OUTPUT output) 4 | if(result) 5 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) 6 | else() 7 | message(SEND_ERROR "IPO is not supported: ${output}") 8 | endif() 9 | endmacro() 10 | -------------------------------------------------------------------------------- /configured_files/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | # A very simple example of a configured file that might need to be 3 | # converted to one that is publicly installed in the case that 4 | # you are developing a library 5 | configure_file("config.hpp.in" "${CMAKE_BINARY_DIR}/configured_files/include/internal_use_only/config.hpp" ESCAPE_QUOTES) 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | #include 5 | 6 | 7 | TEST_CASE("Factorials are computed", "[factorial]") 8 | { 9 | REQUIRE(factorial(0) == 1); 10 | REQUIRE(factorial(1) == 1); 11 | REQUIRE(factorial(2) == 2); 12 | REQUIRE(factorial(3) == 6); 13 | REQUIRE(factorial(10) == 3628800); 14 | } 15 | -------------------------------------------------------------------------------- /include/myproject/sample_library.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SAMPLE_LIBRARY_HPP 2 | #define SAMPLE_LIBRARY_HPP 3 | 4 | #include 5 | 6 | [[nodiscard]] SAMPLE_LIBRARY_EXPORT int factorial(int) noexcept; 7 | 8 | [[nodiscard]] constexpr int factorial_constexpr(int input) noexcept 9 | { 10 | if (input == 0) { return 1; } 11 | 12 | return input * factorial_constexpr(input - 1); 13 | } 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /.cmake-format.yaml: -------------------------------------------------------------------------------- 1 | additional_commands: 2 | foo: 3 | flags: 4 | - BAR 5 | - BAZ 6 | kwargs: 7 | DEPENDS: '*' 8 | HEADERS: '*' 9 | SOURCES: '*' 10 | bullet_char: '*' 11 | dangle_parens: false 12 | enum_char: . 13 | line_ending: unix 14 | line_width: 120 15 | max_pargs_hwrap: 3 16 | separate_ctrl_name_with_space: false 17 | separate_fn_name_with_space: false 18 | tab_size: 2 19 | 20 | markup: 21 | enable_markup: false 22 | -------------------------------------------------------------------------------- /gcovr.cfg: -------------------------------------------------------------------------------- 1 | root = . 2 | search-path = out 3 | 4 | filter = src/* 5 | filter = include/* 6 | 7 | exclude-directories = install 8 | exclude-directories = out/*/*/_deps 9 | exclude-directories = test 10 | exclude-directories = fuzz_test 11 | 12 | gcov-ignore-parse-errors = all 13 | print-summary = yes 14 | 15 | html-details = ./out/coverage/index.html 16 | 17 | cobertura-pretty = yes 18 | cobertura = out/cobertura.xml 19 | 20 | #TBD delete-gcov-files = yes 21 | -------------------------------------------------------------------------------- /test/constexpr_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST_CASE("Factorials are computed with constexpr", "[factorial]") 6 | { 7 | STATIC_REQUIRE(factorial_constexpr(0) == 1); 8 | STATIC_REQUIRE(factorial_constexpr(1) == 1); 9 | STATIC_REQUIRE(factorial_constexpr(2) == 2); 10 | STATIC_REQUIRE(factorial_constexpr(3) == 6); 11 | STATIC_REQUIRE(factorial_constexpr(10) == 3628800); 12 | } 13 | -------------------------------------------------------------------------------- /cmake/LibFuzzer.cmake: -------------------------------------------------------------------------------- 1 | function(myproject_check_libfuzzer_support var_name) 2 | set(LibFuzzerTestSource 3 | " 4 | #include 5 | 6 | extern \"C\" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) { 7 | return 0; 8 | } 9 | ") 10 | 11 | include(CheckCXXSourceCompiles) 12 | 13 | set(CMAKE_REQUIRED_FLAGS "-fsanitize=fuzzer") 14 | set(CMAKE_REQUIRED_LINK_OPTIONS "-fsanitize=fuzzer") 15 | check_cxx_source_compiles("${LibFuzzerTestSource}" ${var_name}) 16 | 17 | endfunction() 18 | -------------------------------------------------------------------------------- /src/ftxui_sample/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(intro main.cpp) 2 | 3 | target_link_libraries( 4 | intro 5 | PRIVATE myproject::myproject_options 6 | myproject::myproject_warnings) 7 | 8 | target_link_system_libraries( 9 | intro 10 | PRIVATE 11 | CLI11::CLI11 12 | fmt::fmt 13 | spdlog::spdlog 14 | lefticus::tools 15 | ftxui::screen 16 | ftxui::dom 17 | ftxui::component) 18 | 19 | target_include_directories(intro PRIVATE "${CMAKE_BINARY_DIR}/configured_files/include") 20 | -------------------------------------------------------------------------------- /.devcontainer/.dockerignore: -------------------------------------------------------------------------------- 1 | # Build directories and binary files 2 | build/ 3 | out/ 4 | cmake-build-*/ 5 | 6 | # User spesific settings 7 | CMakeUserPresets.json 8 | 9 | # IDE files 10 | .vs/ 11 | .idea/ 12 | .vscode/ 13 | !.vscode/settings.json 14 | !.vscode/tasks.json 15 | !.vscode/launch.json 16 | !.vscode/extensions.json 17 | *.swp 18 | *~ 19 | _ReSharper* 20 | *.log 21 | 22 | # OS Generated Files 23 | .DS_Store 24 | .AppleDouble 25 | .LSOverride 26 | ._* 27 | .Spotlight-V100 28 | .Trashes 29 | .Trash-* 30 | $RECYCLE.BIN/ 31 | .TemporaryItems 32 | ehthumbs.db 33 | Thumbs.db 34 | Dockerfile -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build directories and binary files 2 | build/ 3 | out/ 4 | out/coverage/* 5 | cmake-build-*/ 6 | conan-cache/ 7 | 8 | # User spesific settings 9 | CMakeUserPresets.json 10 | 11 | # IDE files 12 | .vs/ 13 | .idea/ 14 | .vscode/ 15 | !.vscode/settings.json 16 | !.vscode/tasks.json 17 | !.vscode/launch.json 18 | !.vscode/extensions.json 19 | *.bak 20 | *.swp 21 | *~ 22 | _ReSharper* 23 | *.log 24 | 25 | # OS Generated Files 26 | .DS_Store 27 | .AppleDouble 28 | .LSOverride 29 | ._* 30 | .Spotlight-V100 31 | .Trashes 32 | .Trash-* 33 | $RECYCLE.BIN/ 34 | .TemporaryItems 35 | ehthumbs.db 36 | Thumbs.db 37 | -------------------------------------------------------------------------------- /.github/workflows/auto-clang-format.yml: -------------------------------------------------------------------------------- 1 | name: auto-clang-format 2 | on: [pull_request] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: DoozyX/clang-format-lint-action@v0.20 11 | with: 12 | source: '.' 13 | exclude: './third_party ./external' 14 | extensions: 'h,cpp,hpp' 15 | clangFormatVersion: 19 16 | inplace: True 17 | - uses: EndBug/add-and-commit@v9 18 | with: 19 | author_name: Clang Robot 20 | author_email: robot@example.com 21 | message: ':art: Committing clang-format changes' 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /fuzz_test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # A fuzz test runs until it finds an error. This particular one is going to rely on libFuzzer. 2 | # 3 | 4 | find_package(fmt) 5 | 6 | add_executable(fuzz_tester fuzz_tester.cpp) 7 | target_link_libraries( 8 | fuzz_tester 9 | PRIVATE myproject_options 10 | myproject_warnings 11 | fmt::fmt 12 | -coverage 13 | -fsanitize=fuzzer) 14 | target_compile_options(fuzz_tester PRIVATE -fsanitize=fuzzer) 15 | 16 | # Allow short runs during automated testing to see if something new breaks 17 | set(FUZZ_RUNTIME 18 | 10 19 | CACHE STRING "Number of seconds to run fuzz tests during ctest run") # Default of 10 seconds 20 | 21 | add_test(NAME fuzz_tester_run COMMAND fuzz_tester -max_total_time=${FUZZ_RUNTIME}) 22 | -------------------------------------------------------------------------------- /fuzz_test/fuzz_tester.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | [[nodiscard]] auto sum_values(const uint8_t *Data, size_t Size) 7 | { 8 | constexpr auto scale = 1000; 9 | 10 | int value = 0; 11 | for (std::size_t offset = 0; offset < Size; ++offset) { 12 | value += static_cast(*std::next(Data, static_cast(offset))) * scale; 13 | } 14 | return value; 15 | } 16 | 17 | // Fuzzer that attempts to invoke undefined behavior for signed integer overflow 18 | // cppcheck-suppress unusedFunction symbolName=LLVMFuzzerTestOneInput 19 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) 20 | { 21 | fmt::print("Value sum: {}, len{}\n", sum_values(Data, Size), Size); 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /.github/template/README.md: -------------------------------------------------------------------------------- 1 | # %%myproject%% 2 | 3 | [![ci](https://github.com/%%myorg%%/%%myproject%%/actions/workflows/ci.yml/badge.svg)](https://github.com/%%myorg%%/%%myproject%%/actions/workflows/ci.yml) 4 | [![codecov](https://codecov.io/gh/%%myorg%%/%%myproject%%/branch/main/graph/badge.svg)](https://codecov.io/gh/%%myorg%%/%%myproject%%) 5 | [![CodeQL](https://github.com/%%myorg%%/%%myproject%%/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/%%myorg%%/%%myproject%%/actions/workflows/codeql-analysis.yml) 6 | 7 | ## About %%myproject%% 8 | %%description%% 9 | 10 | 11 | ## More Details 12 | 13 | * [Dependency Setup](README_dependencies.md) 14 | * [Building Details](README_building.md) 15 | * [Troubleshooting](README_troubleshooting.md) 16 | * [Docker](README_docker.md) 17 | -------------------------------------------------------------------------------- /cmake/PreventInSourceBuilds.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # This function will prevent in-source builds 3 | # 4 | function(myproject_assure_out_of_source_builds) 5 | # make sure the user doesn't play dirty with symlinks 6 | get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) 7 | get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH) 8 | 9 | # disallow in-source builds 10 | if("${srcdir}" STREQUAL "${bindir}") 11 | message("######################################################") 12 | message("Warning: in-source builds are disabled") 13 | message("Please create a separate build directory and run cmake from there") 14 | message("######################################################") 15 | message(FATAL_ERROR "Quitting configuration") 16 | endif() 17 | endfunction() 18 | 19 | myproject_assure_out_of_source_builds() 20 | -------------------------------------------------------------------------------- /configured_files/config.hpp.in: -------------------------------------------------------------------------------- 1 | #ifndef MYPROJECT_CONFIG_HPP 2 | #define MYPROJECT_CONFIG_HPP 3 | 4 | // this is a basic example of how a CMake configured file might look 5 | // in this particular case, we are using it to set the version number of our executable 6 | namespace myproject::cmake { 7 | inline constexpr std::string_view project_name = "@PROJECT_NAME@"; 8 | inline constexpr std::string_view project_version = "@PROJECT_VERSION@"; 9 | inline constexpr int project_version_major { @PROJECT_VERSION_MAJOR@ }; 10 | inline constexpr int project_version_minor { @PROJECT_VERSION_MINOR@ }; 11 | inline constexpr int project_version_patch { @PROJECT_VERSION_PATCH@ }; 12 | inline constexpr int project_version_tweak { @PROJECT_VERSION_TWEAK@ }; 13 | inline constexpr std::string_view git_sha = "@GIT_SHA@"; 14 | }// namespace myproject::cmake 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: "*, 3 | -abseil-*, 4 | -altera-*, 5 | -android-*, 6 | -fuchsia-*, 7 | -google-*, 8 | -llvm*, 9 | -modernize-use-trailing-return-type, 10 | -zircon-*, 11 | -readability-else-after-return, 12 | -readability-static-accessed-through-instance, 13 | -readability-avoid-const-params-in-decls, 14 | -cppcoreguidelines-non-private-member-variables-in-classes, 15 | -misc-non-private-member-variables-in-classes, 16 | -misc-no-recursion, 17 | -misc-use-anonymous-namespace, 18 | -misc-use-internal-linkage 19 | " 20 | WarningsAsErrors: '' 21 | HeaderFilterRegex: '' 22 | FormatStyle: none 23 | 24 | CheckOptions: 25 | - key: readability-identifier-length.IgnoredVariableNames 26 | value: 'x|y|z' 27 | - key: readability-identifier-length.IgnoredParameterNames 28 | value: 'x|y|z' 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /.github/actions/setup_cache/action.yml: -------------------------------------------------------------------------------- 1 | 2 | name: 'setup_cache' 3 | description: 'sets up the shared cache' 4 | inputs: 5 | compiler: 6 | required: true 7 | type: string 8 | build_type: 9 | required: true 10 | type: string 11 | generator: 12 | required: true 13 | type: string 14 | packaging_maintainer_mode: 15 | required: true 16 | type: string 17 | 18 | 19 | runs: 20 | using: "composite" 21 | steps: 22 | - name: Cache 23 | uses: actions/cache@v3 24 | with: 25 | # You might want to add .ccache to your cache configuration? 26 | path: | 27 | ~/.cache/pip 28 | ~/.ccache 29 | key: ${{ runner.os }}-${{ inputs.compiler }}-${{ inputs.build_type }}-${{ inputs.generator }}-${{ inputs.packaging_maintainer_mode }}-${{ hashFiles('**/CMakeLists.txt') }} 30 | restore-keys: | 31 | ${{ runner.os }}-${{ inputs.compiler }}-${{ inputs.build_type }} 32 | 33 | -------------------------------------------------------------------------------- /cmake/Linker.cmake: -------------------------------------------------------------------------------- 1 | macro(myproject_configure_linker project_name) 2 | include(CheckCXXCompilerFlag) 3 | 4 | set(USER_LINKER_OPTION 5 | "lld" 6 | CACHE STRING "Linker to be used") 7 | set(USER_LINKER_OPTION_VALUES "lld" "gold" "bfd" "mold") 8 | set_property(CACHE USER_LINKER_OPTION PROPERTY STRINGS ${USER_LINKER_OPTION_VALUES}) 9 | list( 10 | FIND 11 | USER_LINKER_OPTION_VALUES 12 | ${USER_LINKER_OPTION} 13 | USER_LINKER_OPTION_INDEX) 14 | 15 | if(${USER_LINKER_OPTION_INDEX} EQUAL -1) 16 | message( 17 | STATUS 18 | "Using custom linker: '${USER_LINKER_OPTION}', explicitly supported entries are ${USER_LINKER_OPTION_VALUES}") 19 | endif() 20 | 21 | if(NOT myproject_ENABLE_USER_LINKER) 22 | return() 23 | endif() 24 | 25 | set(LINKER_FLAG "-fuse-ld=${USER_LINKER_OPTION}") 26 | 27 | check_cxx_compiler_flag(${LINKER_FLAG} CXX_SUPPORTS_USER_LINKER) 28 | if(CXX_SUPPORTS_USER_LINKER) 29 | target_compile_options(${project_name} INTERFACE ${LINKER_FLAG}) 30 | endif() 31 | endmacro() 32 | -------------------------------------------------------------------------------- /src/sample_library/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(GenerateExportHeader) 2 | 3 | 4 | add_library(sample_library sample_library.cpp) 5 | 6 | 7 | 8 | add_library(myproject::sample_library ALIAS sample_library) 9 | 10 | target_link_libraries(sample_library PRIVATE myproject_options myproject_warnings) 11 | 12 | target_include_directories(sample_library ${WARNING_GUARD} PUBLIC $ 13 | $) 14 | 15 | target_compile_features(sample_library PUBLIC cxx_std_20) 16 | 17 | set_target_properties( 18 | sample_library 19 | PROPERTIES VERSION ${PROJECT_VERSION} 20 | CXX_VISIBILITY_PRESET hidden 21 | VISIBILITY_INLINES_HIDDEN YES) 22 | 23 | generate_export_header(sample_library EXPORT_FILE_NAME ${PROJECT_BINARY_DIR}/include/myproject/sample_library_export.hpp) 24 | 25 | if(NOT BUILD_SHARED_LIBS) 26 | target_compile_definitions(sample_library PUBLIC SAMPLE_LIBRARY_STATIC_DEFINE) 27 | endif() 28 | -------------------------------------------------------------------------------- /cmake/CPM.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors 4 | 5 | set(CPM_DOWNLOAD_VERSION 0.40.8) 6 | set(CPM_HASH_SUM "78ba32abdf798bc616bab7c73aac32a17bbd7b06ad9e26a6add69de8f3ae4791") 7 | 8 | if(CPM_SOURCE_CACHE) 9 | set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 10 | elseif(DEFINED ENV{CPM_SOURCE_CACHE}) 11 | set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 12 | else() 13 | set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 14 | endif() 15 | 16 | # Expand relative path. This is important if the provided path contains a tilde (~) 17 | get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) 18 | 19 | file(DOWNLOAD 20 | https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake 21 | ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} 22 | ) 23 | 24 | include(${CPM_DOWNLOAD_LOCATION}) 25 | -------------------------------------------------------------------------------- /Dependencies.cmake: -------------------------------------------------------------------------------- 1 | include(cmake/CPM.cmake) 2 | 3 | # Done as a function so that updates to variables like 4 | # CMAKE_CXX_FLAGS don't propagate out to other 5 | # targets 6 | function(myproject_setup_dependencies) 7 | 8 | # For each dependency, see if it's 9 | # already been provided to us by a parent project 10 | 11 | if(NOT TARGET fmtlib::fmtlib) 12 | cpmaddpackage("gh:fmtlib/fmt#11.1.4") 13 | endif() 14 | 15 | if(NOT TARGET spdlog::spdlog) 16 | cpmaddpackage( 17 | NAME 18 | spdlog 19 | VERSION 20 | 1.15.2 21 | GITHUB_REPOSITORY 22 | "gabime/spdlog" 23 | OPTIONS 24 | "SPDLOG_FMT_EXTERNAL ON") 25 | endif() 26 | 27 | if(NOT TARGET Catch2::Catch2WithMain) 28 | cpmaddpackage("gh:catchorg/Catch2@3.8.1") 29 | endif() 30 | 31 | if(NOT TARGET CLI11::CLI11) 32 | cpmaddpackage("gh:CLIUtils/CLI11@2.5.0") 33 | endif() 34 | 35 | if(NOT TARGET ftxui::screen) 36 | cpmaddpackage("gh:ArthurSonzogni/FTXUI@6.0.2") 37 | endif() 38 | 39 | if(NOT TARGET tools::tools) 40 | cpmaddpackage("gh:lefticus/tools#update_build_system") 41 | endif() 42 | 43 | endfunction() 44 | -------------------------------------------------------------------------------- /cmake/Cache.cmake: -------------------------------------------------------------------------------- 1 | # Enable cache if available 2 | function(myproject_enable_cache) 3 | set(CACHE_OPTION 4 | "ccache" 5 | CACHE STRING "Compiler cache to be used") 6 | set(CACHE_OPTION_VALUES "ccache" "sccache") 7 | set_property(CACHE CACHE_OPTION PROPERTY STRINGS ${CACHE_OPTION_VALUES}) 8 | list( 9 | FIND 10 | CACHE_OPTION_VALUES 11 | ${CACHE_OPTION} 12 | CACHE_OPTION_INDEX) 13 | 14 | if(${CACHE_OPTION_INDEX} EQUAL -1) 15 | message( 16 | STATUS 17 | "Using custom compiler cache system: '${CACHE_OPTION}', explicitly supported entries are ${CACHE_OPTION_VALUES}" 18 | ) 19 | endif() 20 | 21 | find_program(CACHE_BINARY NAMES ${CACHE_OPTION_VALUES}) 22 | if(CACHE_BINARY) 23 | message(STATUS "${CACHE_BINARY} found and enabled") 24 | set(CMAKE_CXX_COMPILER_LAUNCHER 25 | ${CACHE_BINARY} 26 | CACHE FILEPATH "CXX compiler cache used") 27 | set(CMAKE_C_COMPILER_LAUNCHER 28 | ${CACHE_BINARY} 29 | CACHE FILEPATH "C compiler cache used") 30 | else() 31 | message(WARNING "${CACHE_OPTION} is enabled but was not found. Not using it") 32 | endif() 33 | endfunction() 34 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Git Line Endings # 3 | ############################### 4 | 5 | * text=auto eol=lf 6 | *.{cmd,[cC][mM][dD]} text eol=crlf 7 | *.{bat,[bB][aA][tT]} text eol=crlf 8 | *.{vcxproj,vcxproj.filters} text eol=crlf 9 | 10 | ############################### 11 | # Git Large File System (LFS) # 12 | ############################### 13 | 14 | # Archives 15 | #*.7z filter=lfs diff=lfs merge=lfs -text 16 | #*.br filter=lfs diff=lfs merge=lfs -text 17 | #*.gz filter=lfs diff=lfs merge=lfs -text 18 | #*.tar filter=lfs diff=lfs merge=lfs -text 19 | #*.zip filter=lfs diff=lfs merge=lfs -text 20 | 21 | # Documents 22 | #*.pdf filter=lfs diff=lfs merge=lfs -text 23 | 24 | # Images 25 | #*.gif filter=lfs diff=lfs merge=lfs -text 26 | #*.ico filter=lfs diff=lfs merge=lfs -text 27 | #*.jpg filter=lfs diff=lfs merge=lfs -text 28 | #*.pdf filter=lfs diff=lfs merge=lfs -text 29 | #*.png filter=lfs diff=lfs merge=lfs -text 30 | #*.psd filter=lfs diff=lfs merge=lfs -text 31 | #*.webp filter=lfs diff=lfs merge=lfs -text 32 | 33 | # Fonts 34 | #*.woff2 filter=lfs diff=lfs merge=lfs -text 35 | 36 | # Other 37 | #*.exe filter=lfs diff=lfs merge=lfs -text -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: ubuntu:latest 2 | 3 | stages: 4 | - test 5 | 6 | .setup_linux: &setup_linux | 7 | DEBIAN_FRONTEND=noninteractive 8 | 9 | # set time-zone 10 | TZ=Canada/Pacific 11 | ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 12 | 13 | # for downloading 14 | apt-get update -qq 15 | apt-get install -y --no-install-recommends curl gnupg ca-certificates 16 | 17 | # keys used by apt 18 | apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32 19 | apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5 20 | apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1E9377A2BA9EF27F 21 | 22 | .setup_cpp: &setup_cpp | 23 | curl -LJO "https://github.com/aminya/setup-cpp/releases/download/v0.10.0/setup_cpp_linux" 24 | chmod +x setup_cpp_linux 25 | ./setup_cpp_linux --compiler $compiler --cmake true --ninja true --conan true --ccache true --clangtidy true --clangformat true --cppcheck true 26 | source ~/.profile 27 | 28 | .test: &test | 29 | # Build and Test 30 | cmake -S . -B ./build -DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo 31 | cmake --build ./build --config RelWithDebInfo 32 | 33 | test_linux_llvm: 34 | stage: test 35 | variables: 36 | compiler: llvm 37 | script: 38 | - *setup_linux 39 | - *setup_cpp 40 | - *test 41 | 42 | test_linux_gcc: 43 | stage: test 44 | variables: 45 | compiler: gcc 46 | script: 47 | - *setup_linux 48 | - *setup_cpp 49 | - *test 50 | -------------------------------------------------------------------------------- /cmake/Cuda.cmake: -------------------------------------------------------------------------------- 1 | # ! target_link_cuda 2 | # A function that links Cuda to the given target 3 | # 4 | # # Example 5 | # add_executable(main_cuda main.cu) 6 | # target_compile_features(main_cuda PRIVATE cxx_std_17) 7 | # target_link_libraries(main_cuda PRIVATE project_options project_warnings) 8 | # target_link_cuda(main_cuda) 9 | # 10 | macro(myproject_target_link_cuda target) 11 | # optional named CUDA_WARNINGS 12 | set(oneValueArgs CUDA_WARNINGS) 13 | cmake_parse_arguments( 14 | _cuda_args 15 | "" 16 | "${oneValueArgs}" 17 | "" 18 | ${ARGN}) 19 | 20 | # add CUDA to cmake language 21 | enable_language(CUDA) 22 | 23 | # use the same C++ standard if not specified 24 | if("${CMAKE_CUDA_STANDARD}" STREQUAL "") 25 | set(CMAKE_CUDA_STANDARD "${CMAKE_CXX_STANDARD}") 26 | endif() 27 | 28 | # -fPIC 29 | set_target_properties(${target} PROPERTIES POSITION_INDEPENDENT_CODE ON) 30 | 31 | # We need to explicitly state that we need all CUDA files in the 32 | # ${target} library to be built with -dc as the member functions 33 | # could be called by other libraries and executables 34 | set_target_properties(${target} PROPERTIES CUDA_SEPARABLE_COMPILATION ON) 35 | 36 | if(APPLE) 37 | # We need to add the path to the driver (libcuda.dylib) as an rpath, 38 | # so that the static cuda runtime can find it at runtime. 39 | set_property(TARGET ${target} PROPERTY BUILD_RPATH ${CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES}) 40 | endif() 41 | 42 | if(WIN32 AND "$ENV{VSCMD_VER}" STREQUAL "") 43 | message( 44 | WARNING 45 | "Compiling CUDA on Windows outside the Visual Studio Command prompt or without running `vcvarsall.bat x64` probably fails" 46 | ) 47 | endif() 48 | endmacro() 49 | -------------------------------------------------------------------------------- /cmake/StandardProjectSettings.cmake: -------------------------------------------------------------------------------- 1 | # Set a default build type if none was specified 2 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 3 | message(STATUS "Setting build type to 'RelWithDebInfo' as none was specified.") 4 | set(CMAKE_BUILD_TYPE 5 | RelWithDebInfo 6 | CACHE STRING "Choose the type of build." FORCE) 7 | # Set the possible values of build type for cmake-gui, ccmake 8 | set_property( 9 | CACHE CMAKE_BUILD_TYPE 10 | PROPERTY STRINGS 11 | "Debug" 12 | "Release" 13 | "MinSizeRel" 14 | "RelWithDebInfo") 15 | endif() 16 | 17 | # Generate compile_commands.json to make it easier to work with clang based tools 18 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 19 | 20 | # Enhance error reporting and compiler messages 21 | if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 22 | if(WIN32) 23 | # On Windows cuda nvcc uses cl and not clang 24 | add_compile_options($<$:-fcolor-diagnostics> $<$:-fcolor-diagnostics>) 25 | else() 26 | add_compile_options(-fcolor-diagnostics) 27 | endif() 28 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 29 | if(WIN32) 30 | # On Windows cuda nvcc uses cl and not gcc 31 | add_compile_options($<$:-fdiagnostics-color=always> 32 | $<$:-fdiagnostics-color=always>) 33 | else() 34 | add_compile_options(-fdiagnostics-color=always) 35 | endif() 36 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND MSVC_VERSION GREATER 1900) 37 | add_compile_options(/diagnostics:column) 38 | else() 39 | message(STATUS "No colored compiler diagnostic set for '${CMAKE_CXX_COMPILER_ID}' compiler.") 40 | endif() 41 | 42 | 43 | # run vcvarsall when msvc is used 44 | include("${CMAKE_CURRENT_LIST_DIR}/VCEnvironment.cmake") 45 | run_vcvarsall() 46 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.205.2/containers/cpp 3 | { 4 | "name": "C++", 5 | "build": { 6 | "dockerfile": "Dockerfile", 7 | // Update 'VARIANT' to pick an Ubuntu OS version. Options: [bionic, focal]. Default: focal 8 | // Update 'GCC_VER' to pick a gcc and g++ version. Options: [7, 8, 9, 10, 11]. Default: 11 9 | // Update 'LLVM_VER' to pick clang version. Options: [10, 11, 12, 13]. Default: 13 10 | // Update 'USE_CLANG' to set clang as the default C and C++ compiler. Options: [1, null]. Default null 11 | // "args": { 12 | // "VARIANT": "focal", 13 | // "GCC_VER": "11", 14 | // "LLVM_VER": "13" 15 | // } 16 | }, 17 | "runArgs": [ 18 | "--cap-add=SYS_PTRACE", 19 | "--security-opt", 20 | "seccomp=unconfined" 21 | ], 22 | // Set *default* container specific settings.json values on container create. 23 | "settings": { 24 | "cmake.configureOnOpen": true, 25 | "editor.formatOnSave": true 26 | }, 27 | // Add the IDs of extensions you want installed when the container is created. 28 | "extensions": [ 29 | "ms-vscode.cpptools", 30 | "ms-vscode.cmake-tools", 31 | "twxs.cmake", 32 | "ms-vscode.cpptools-themes", 33 | "cschlosser.doxdocgen", 34 | "eamodio.gitlens", 35 | "ms-python.python", 36 | "ms-python.vscode-pylance", 37 | "mutantdino.resourcemonitor" 38 | ], 39 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 40 | // "forwardPorts": [], 41 | // Use 'postCreateCommand' to run commands after the container is created. 42 | //"postCreateCommand": "uname -a", 43 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 44 | //"remoteUser": "vscode", 45 | "workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/${localWorkspaceFolderBasename},type=bind,consistency=delegated", 46 | "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", 47 | "features": { 48 | "git": "latest", 49 | "git-lfs": "latest", 50 | "powershell": "latest" 51 | } 52 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cmake_template 2 | 3 | [![ci](https://github.com/cpp-best-practices/cmake_template/actions/workflows/ci.yml/badge.svg)](https://github.com/cpp-best-practices/cmake_template/actions/workflows/ci.yml) 4 | [![codecov](https://codecov.io/gh/cpp-best-practices/cmake_template/branch/main/graph/badge.svg)](https://codecov.io/gh/cpp-best-practices/cmake_template) 5 | [![CodeQL](https://github.com/cpp-best-practices/cmake_template/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/cpp-best-practices/cmake_template/actions/workflows/codeql-analysis.yml) 6 | 7 | ## About cmake_template 8 | 9 | This is a C++ Best Practices GitHub template for getting up and running with C++ quickly. 10 | 11 | By default (collectively known as `ENABLE_DEVELOPER_MODE`) 12 | 13 | * Address Sanitizer and Undefined Behavior Sanitizer enabled where possible 14 | * Warnings as errors 15 | * clang-tidy and cppcheck static analysis 16 | * CPM for dependencies 17 | 18 | It includes 19 | 20 | * a basic CLI example 21 | * examples for fuzz, unit, and constexpr testing 22 | * large GitHub action testing matrix 23 | 24 | It requires 25 | 26 | * cmake 27 | * a compiler 28 | 29 | 30 | This project gets you started with a simple example of using FTXUI, which happens to also be a game. 31 | 32 | 33 | ## Getting Started 34 | 35 | ### Use the GitHub template 36 | First, click the green `Use this template` button near the top of this page. 37 | This will take you to GitHub's ['Generate Repository'](https://github.com/cpp-best-practices/cmake_template/generate) 38 | page. 39 | Fill in a repository name and short description, and click 'Create repository from template'. 40 | This will allow you to create a new repository in your GitHub account, 41 | prepopulated with the contents of this project. 42 | 43 | After creating the project please wait until the cleanup workflow has finished 44 | setting up your project and committed the changes. 45 | 46 | Now you can clone the project locally and get to work! 47 | 48 | git clone https://github.com//.git 49 | 50 | ## More Details 51 | 52 | * [Dependency Setup](README_dependencies.md) 53 | * [Building Details](README_building.md) 54 | * [Docker](README_docker.md) 55 | 56 | ## Testing 57 | 58 | See [Catch2 tutorial](https://github.com/catchorg/Catch2/blob/master/docs/tutorial.md) 59 | 60 | ## Fuzz testing 61 | 62 | See [libFuzzer Tutorial](https://github.com/google/fuzzing/blob/master/tutorial/libFuzzerTutorial.md) 63 | 64 | 65 | -------------------------------------------------------------------------------- /cmake/Doxygen.cmake: -------------------------------------------------------------------------------- 1 | # Enable doxygen doc builds of source 2 | function(myproject_enable_doxygen DOXYGEN_THEME) 3 | # If not specified, use the top readme file as the first page 4 | if((NOT DOXYGEN_USE_MDFILE_AS_MAINPAGE) AND EXISTS "${PROJECT_SOURCE_DIR}/README.md") 5 | set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${PROJECT_SOURCE_DIR}/README.md") 6 | endif() 7 | 8 | # set better defaults for doxygen 9 | is_verbose(_is_verbose) 10 | if(NOT ${_is_verbose}) 11 | set(DOXYGEN_QUIET YES) 12 | endif() 13 | set(DOXYGEN_CALLER_GRAPH YES) 14 | set(DOXYGEN_CALL_GRAPH YES) 15 | set(DOXYGEN_EXTRACT_ALL YES) 16 | set(DOXYGEN_GENERATE_TREEVIEW YES) 17 | # svg files are much smaller than jpeg and png, and yet they have higher quality 18 | set(DOXYGEN_DOT_IMAGE_FORMAT svg) 19 | set(DOXYGEN_DOT_TRANSPARENT YES) 20 | 21 | # If not specified, exclude the vcpkg files and the files CMake downloads under _deps (like project_options) 22 | if(NOT DOXYGEN_EXCLUDE_PATTERNS) 23 | set(DOXYGEN_EXCLUDE_PATTERNS "${CMAKE_CURRENT_BINARY_DIR}/vcpkg_installed/*" "${CMAKE_CURRENT_BINARY_DIR}/_deps/*") 24 | endif() 25 | 26 | if("${DOXYGEN_THEME}" STREQUAL "") 27 | set(DOXYGEN_THEME "awesome-sidebar") 28 | endif() 29 | 30 | if("${DOXYGEN_THEME}" STREQUAL "awesome" OR "${DOXYGEN_THEME}" STREQUAL "awesome-sidebar") 31 | # use a modern doxygen theme 32 | # https://github.com/jothepro/doxygen-awesome-css v1.6.1 33 | FetchContent_Declare(_doxygen_theme 34 | URL https://github.com/jothepro/doxygen-awesome-css/archive/refs/tags/v1.6.1.zip) 35 | FetchContent_MakeAvailable(_doxygen_theme) 36 | if("${DOXYGEN_THEME}" STREQUAL "awesome" OR "${DOXYGEN_THEME}" STREQUAL "awesome-sidebar") 37 | set(DOXYGEN_HTML_EXTRA_STYLESHEET "${_doxygen_theme_SOURCE_DIR}/doxygen-awesome.css") 38 | endif() 39 | if("${DOXYGEN_THEME}" STREQUAL "awesome-sidebar") 40 | set(DOXYGEN_HTML_EXTRA_STYLESHEET ${DOXYGEN_HTML_EXTRA_STYLESHEET} 41 | "${_doxygen_theme_SOURCE_DIR}/doxygen-awesome-sidebar-only.css") 42 | endif() 43 | else() 44 | # use the original doxygen theme 45 | endif() 46 | 47 | # find doxygen and dot if available 48 | find_package(Doxygen REQUIRED OPTIONAL_COMPONENTS dot) 49 | 50 | # add doxygen-docs target 51 | message(STATUS "Adding `doxygen-docs` target that builds the documentation.") 52 | doxygen_add_docs(doxygen-docs ALL ${PROJECT_SOURCE_DIR} 53 | COMMENT "Generating documentation - entry file: ${CMAKE_CURRENT_BINARY_DIR}/html/index.html") 54 | endfunction() 55 | -------------------------------------------------------------------------------- /cmake/SystemLink.cmake: -------------------------------------------------------------------------------- 1 | # Include a system directory (which suppresses its warnings). 2 | function(target_include_system_directories target) 3 | set(multiValueArgs INTERFACE PUBLIC PRIVATE) 4 | cmake_parse_arguments( 5 | ARG 6 | "" 7 | "" 8 | "${multiValueArgs}" 9 | ${ARGN}) 10 | 11 | foreach(scope IN ITEMS INTERFACE PUBLIC PRIVATE) 12 | foreach(lib_include_dirs IN LISTS ARG_${scope}) 13 | if(NOT MSVC) 14 | # system includes do not work in MSVC 15 | # awaiting https://gitlab.kitware.com/cmake/cmake/-/issues/18272# 16 | # awaiting https://gitlab.kitware.com/cmake/cmake/-/issues/17904 17 | set(_SYSTEM SYSTEM) 18 | endif() 19 | if(${scope} STREQUAL "INTERFACE" OR ${scope} STREQUAL "PUBLIC") 20 | target_include_directories( 21 | ${target} 22 | ${_SYSTEM} 23 | ${scope} 24 | "$" 25 | "$") 26 | else() 27 | target_include_directories( 28 | ${target} 29 | ${_SYSTEM} 30 | ${scope} 31 | ${lib_include_dirs}) 32 | endif() 33 | endforeach() 34 | endforeach() 35 | 36 | endfunction() 37 | 38 | # Include the directories of a library target as system directories (which suppresses their warnings). 39 | function( 40 | target_include_system_library 41 | target 42 | scope 43 | lib) 44 | # check if this is a target 45 | if(TARGET ${lib}) 46 | get_target_property(lib_include_dirs ${lib} INTERFACE_INCLUDE_DIRECTORIES) 47 | if(lib_include_dirs) 48 | target_include_system_directories(${target} ${scope} ${lib_include_dirs}) 49 | else() 50 | message(TRACE "${lib} library does not have the INTERFACE_INCLUDE_DIRECTORIES property.") 51 | endif() 52 | endif() 53 | endfunction() 54 | 55 | # Link a library target as a system library (which suppresses its warnings). 56 | function( 57 | target_link_system_library 58 | target 59 | scope 60 | lib) 61 | # Include the directories in the library 62 | target_include_system_library(${target} ${scope} ${lib}) 63 | 64 | # Link the library 65 | target_link_libraries(${target} ${scope} ${lib}) 66 | endfunction() 67 | 68 | # Link multiple library targets as system libraries (which suppresses their warnings). 69 | function(target_link_system_libraries target) 70 | set(multiValueArgs INTERFACE PUBLIC PRIVATE) 71 | cmake_parse_arguments( 72 | ARG 73 | "" 74 | "" 75 | "${multiValueArgs}" 76 | ${ARGN}) 77 | 78 | foreach(scope IN ITEMS INTERFACE PUBLIC PRIVATE) 79 | foreach(lib IN LISTS ARG_${scope}) 80 | target_link_system_library(${target} ${scope} ${lib}) 81 | endforeach() 82 | endforeach() 83 | endfunction() 84 | -------------------------------------------------------------------------------- /README_docker.md: -------------------------------------------------------------------------------- 1 | ## Docker Instructions 2 | 3 | If you have [Docker](https://www.docker.com/) installed, you can run this 4 | in your terminal, when the Dockerfile is inside the `.devcontainer` directory: 5 | 6 | ```bash 7 | docker build -f ./.devcontainer/Dockerfile --tag=my_project:latest . 8 | docker run -it my_project:latest 9 | ``` 10 | 11 | This command will put you in a `bash` session in a Ubuntu 20.04 Docker container, 12 | with all of the tools listed in the [Dependencies](#dependencies) section already installed. 13 | Additionally, you will have `g++-11` and `clang++-13` installed as the default 14 | versions of `g++` and `clang++`. 15 | 16 | If you want to build this container using some other versions of gcc and clang, 17 | you may do so with the `GCC_VER` and `LLVM_VER` arguments: 18 | 19 | ```bash 20 | docker build --tag=myproject:latest --build-arg GCC_VER=10 --build-arg LLVM_VER=11 . 21 | ``` 22 | 23 | The CC and CXX environment variables are set to GCC version 11 by default. 24 | If you wish to use clang as your default CC and CXX environment variables, you 25 | may do so like this: 26 | 27 | ```bash 28 | docker build --tag=my_project:latest --build-arg USE_CLANG=1 . 29 | ``` 30 | 31 | You will be logged in as root, so you will see the `#` symbol as your prompt. 32 | You will be in a directory that contains a copy of the `cpp_starter_project`; 33 | any changes you make to your local copy will not be updated in the Docker image 34 | until you rebuild it. 35 | If you need to mount your local copy directly in the Docker image, see 36 | [Docker volumes docs](https://docs.docker.com/storage/volumes/). 37 | TLDR: 38 | 39 | ```bash 40 | docker run -it \ 41 | -v absolute_path_on_host_machine:absolute_path_in_guest_container \ 42 | my_project:latest 43 | ``` 44 | 45 | You can configure and build [as directed above](#build) using these commands: 46 | 47 | ```bash 48 | /starter_project# mkdir build 49 | /starter_project# cmake -S . -B ./build 50 | /starter_project# cmake --build ./build 51 | ``` 52 | 53 | You can configure and build using `clang-13`, without rebuilding the container, 54 | with these commands: 55 | 56 | ```bash 57 | /starter_project# mkdir build 58 | /starter_project# CC=clang CXX=clang++ cmake -S . -B ./build 59 | /starter_project# cmake --build ./build 60 | ``` 61 | 62 | The `ccmake` tool is also installed; you can substitute `ccmake` for `cmake` to 63 | configure the project interactively. 64 | All of the tools this project supports are installed in the Docker image; 65 | enabling them is as simple as flipping a switch using the `ccmake` interface. 66 | Be aware that some of the sanitizers conflict with each other, so be sure to 67 | run them separately. 68 | 69 | A script called `build_examples.sh` is provided to help you to build the example 70 | GUI projects in this container. 71 | 72 | -------------------------------------------------------------------------------- /cmake/VCEnvironment.cmake: -------------------------------------------------------------------------------- 1 | include("${CMAKE_CURRENT_LIST_DIR}/Utilities.cmake") 2 | 3 | macro(detect_architecture) 4 | # detect the architecture 5 | string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" CMAKE_SYSTEM_PROCESSOR_LOWER) 6 | if(CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL x86 OR CMAKE_SYSTEM_PROCESSOR_LOWER MATCHES "^i[3456]86$") 7 | set(VCVARSALL_ARCH x86) 8 | elseif( 9 | CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL x64 10 | OR CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL x86_64 11 | OR CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL amd64) 12 | set(VCVARSALL_ARCH x64) 13 | elseif(CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL arm) 14 | set(VCVARSALL_ARCH arm) 15 | elseif(CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL arm64 OR CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL aarch64) 16 | set(VCVARSALL_ARCH arm64) 17 | else() 18 | if(CMAKE_HOST_SYSTEM_PROCESSOR) 19 | set(VCVARSALL_ARCH ${CMAKE_HOST_SYSTEM_PROCESSOR}) 20 | else() 21 | set(VCVARSALL_ARCH x64) 22 | message(STATUS "Unknown architecture CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR_LOWER} - using x64") 23 | endif() 24 | endif() 25 | endmacro() 26 | 27 | # Run vcvarsall.bat and set CMake environment variables 28 | function(run_vcvarsall) 29 | # if MSVC but VSCMD_VER is not set, which means vcvarsall has not run 30 | if(MSVC AND "$ENV{VSCMD_VER}" STREQUAL "") 31 | 32 | # find vcvarsall.bat 33 | get_filename_component(MSVC_DIR ${CMAKE_CXX_COMPILER} DIRECTORY) 34 | find_file( 35 | VCVARSALL_FILE 36 | NAMES vcvarsall.bat 37 | PATHS "${MSVC_DIR}" 38 | "${MSVC_DIR}/.." 39 | "${MSVC_DIR}/../.." 40 | "${MSVC_DIR}/../../../../../../../.." 41 | "${MSVC_DIR}/../../../../../../.." 42 | PATH_SUFFIXES "VC/Auxiliary/Build" "Common7/Tools" "Tools") 43 | 44 | if(EXISTS ${VCVARSALL_FILE}) 45 | # detect the architecture 46 | detect_architecture() 47 | 48 | # run vcvarsall and print the environment variables 49 | message(STATUS "Running `${VCVARSALL_FILE} ${VCVARSALL_ARCH}` to set up the MSVC environment") 50 | execute_process( 51 | COMMAND 52 | "cmd" "/c" ${VCVARSALL_FILE} ${VCVARSALL_ARCH} # 53 | "&&" "call" "echo" "VCVARSALL_ENV_START" # 54 | "&" "set" # 55 | OUTPUT_VARIABLE VCVARSALL_OUTPUT 56 | OUTPUT_STRIP_TRAILING_WHITESPACE) 57 | 58 | # parse the output and get the environment variables string 59 | find_substring_by_prefix(VCVARSALL_ENV "VCVARSALL_ENV_START" "${VCVARSALL_OUTPUT}") 60 | 61 | # set the environment variables 62 | set_env_from_string("${VCVARSALL_ENV}") 63 | 64 | else() 65 | message( 66 | WARNING 67 | "Could not find `vcvarsall.bat` for automatic MSVC environment preparation. Please manually open the MSVC command prompt and rebuild the project. 68 | ") 69 | endif() 70 | endif() 71 | endfunction() 72 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | AccessModifierOffset: -2 2 | AlignAfterOpenBracket: DontAlign 3 | AlignConsecutiveAssignments: false 4 | AlignConsecutiveDeclarations: false 5 | AlignEscapedNewlines: Left 6 | AlignOperands: true 7 | AlignTrailingComments: false 8 | AllowAllParametersOfDeclarationOnNextLine: false 9 | AllowShortBlocksOnASingleLine: true 10 | AllowShortCaseLabelsOnASingleLine: false 11 | AllowShortFunctionsOnASingleLine: All 12 | AllowShortIfStatementsOnASingleLine: true 13 | AllowShortLoopsOnASingleLine: true 14 | AlwaysBreakAfterDefinitionReturnType: None 15 | AlwaysBreakAfterReturnType: None 16 | AlwaysBreakBeforeMultilineStrings: true 17 | AlwaysBreakTemplateDeclarations: false 18 | BinPackArguments: false 19 | BinPackParameters: false 20 | BraceWrapping: 21 | AfterClass: true 22 | AfterControlStatement: false 23 | AfterEnum: false 24 | AfterFunction: true 25 | AfterNamespace: false 26 | AfterObjCDeclaration: false 27 | AfterStruct: true 28 | AfterUnion: false 29 | BeforeCatch: false 30 | BeforeElse: false 31 | IndentBraces: false 32 | SplitEmptyFunction: false 33 | SplitEmptyNamespace: true 34 | SplitEmptyRecord: true 35 | BreakAfterJavaFieldAnnotations: true 36 | BreakBeforeBinaryOperators: NonAssignment 37 | BreakBeforeBraces: Custom 38 | BreakBeforeInheritanceComma: true 39 | BreakBeforeTernaryOperators: true 40 | BreakConstructorInitializers: BeforeColon 41 | BreakConstructorInitializersBeforeComma: false 42 | BreakStringLiterals: true 43 | ColumnLimit: 120 44 | CommentPragmas: '^ IWYU pragma:' 45 | CompactNamespaces: false 46 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 47 | ConstructorInitializerIndentWidth: 2 48 | ContinuationIndentWidth: 2 49 | Cpp11BracedListStyle: false 50 | DerivePointerAlignment: false 51 | DisableFormat: false 52 | ExperimentalAutoDetectBinPacking: true 53 | FixNamespaceComments: true 54 | ForEachMacros: 55 | - foreach 56 | - Q_FOREACH 57 | - BOOST_FOREACH 58 | IncludeCategories: 59 | - Priority: 2 60 | Regex: ^"(llvm|llvm-c|clang|clang-c)/ 61 | - Priority: 3 62 | Regex: ^(<|"(gtest|gmock|isl|json)/) 63 | - Priority: 1 64 | Regex: .* 65 | IncludeIsMainRegex: (Test)?$ 66 | IndentCaseLabels: false 67 | IndentWidth: 2 68 | IndentWrappedFunctionNames: true 69 | JavaScriptQuotes: Leave 70 | JavaScriptWrapImports: true 71 | KeepEmptyLinesAtTheStartOfBlocks: true 72 | Language: Cpp 73 | MacroBlockBegin: '' 74 | MacroBlockEnd: '' 75 | MaxEmptyLinesToKeep: 2 76 | NamespaceIndentation: Inner 77 | ObjCBlockIndentWidth: 7 78 | ObjCSpaceAfterProperty: true 79 | ObjCSpaceBeforeProtocolList: false 80 | PointerAlignment: Right 81 | ReflowComments: true 82 | SortIncludes: true 83 | SortUsingDeclarations: false 84 | SpaceAfterCStyleCast: false 85 | SpaceAfterTemplateKeyword: false 86 | SpaceBeforeAssignmentOperators: true 87 | SpaceBeforeParens: ControlStatements 88 | SpaceInEmptyParentheses: false 89 | SpacesBeforeTrailingComments: 0 90 | SpacesInAngles: false 91 | SpacesInCStyleCastParentheses: false 92 | SpacesInContainerLiterals: true 93 | SpacesInParentheses: false 94 | SpacesInSquareBrackets: false 95 | Standard: c++20 96 | TabWidth: 8 97 | UseTab: Never 98 | 99 | -------------------------------------------------------------------------------- /cmake/Sanitizers.cmake: -------------------------------------------------------------------------------- 1 | function( 2 | myproject_enable_sanitizers 3 | project_name 4 | ENABLE_SANITIZER_ADDRESS 5 | ENABLE_SANITIZER_LEAK 6 | ENABLE_SANITIZER_UNDEFINED_BEHAVIOR 7 | ENABLE_SANITIZER_THREAD 8 | ENABLE_SANITIZER_MEMORY) 9 | 10 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 11 | set(SANITIZERS "") 12 | 13 | if(${ENABLE_SANITIZER_ADDRESS}) 14 | list(APPEND SANITIZERS "address") 15 | endif() 16 | 17 | if(${ENABLE_SANITIZER_LEAK}) 18 | list(APPEND SANITIZERS "leak") 19 | endif() 20 | 21 | if(${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR}) 22 | list(APPEND SANITIZERS "undefined") 23 | endif() 24 | 25 | if(${ENABLE_SANITIZER_THREAD}) 26 | if("address" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS) 27 | message(WARNING "Thread sanitizer does not work with Address and Leak sanitizer enabled") 28 | else() 29 | list(APPEND SANITIZERS "thread") 30 | endif() 31 | endif() 32 | 33 | if(${ENABLE_SANITIZER_MEMORY} AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 34 | message( 35 | WARNING 36 | "Memory sanitizer requires all the code (including libc++) to be MSan-instrumented otherwise it reports false positives" 37 | ) 38 | if("address" IN_LIST SANITIZERS 39 | OR "thread" IN_LIST SANITIZERS 40 | OR "leak" IN_LIST SANITIZERS) 41 | message(WARNING "Memory sanitizer does not work with Address, Thread or Leak sanitizer enabled") 42 | else() 43 | list(APPEND SANITIZERS "memory") 44 | endif() 45 | endif() 46 | elseif(MSVC) 47 | if(${ENABLE_SANITIZER_ADDRESS}) 48 | list(APPEND SANITIZERS "address") 49 | endif() 50 | if(${ENABLE_SANITIZER_LEAK} 51 | OR ${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR} 52 | OR ${ENABLE_SANITIZER_THREAD} 53 | OR ${ENABLE_SANITIZER_MEMORY}) 54 | message(WARNING "MSVC only supports address sanitizer") 55 | endif() 56 | endif() 57 | 58 | list( 59 | JOIN 60 | SANITIZERS 61 | "," 62 | LIST_OF_SANITIZERS) 63 | 64 | if(LIST_OF_SANITIZERS) 65 | if(NOT 66 | "${LIST_OF_SANITIZERS}" 67 | STREQUAL 68 | "") 69 | if(NOT MSVC) 70 | target_compile_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) 71 | target_link_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) 72 | else() 73 | string(FIND "$ENV{PATH}" "$ENV{VSINSTALLDIR}" index_of_vs_install_dir) 74 | if("${index_of_vs_install_dir}" STREQUAL "-1") 75 | message( 76 | SEND_ERROR 77 | "Using MSVC sanitizers requires setting the MSVC environment before building the project. Please manually open the MSVC command prompt and rebuild the project." 78 | ) 79 | endif() 80 | target_compile_options(${project_name} INTERFACE /fsanitize=${LIST_OF_SANITIZERS} /Zi /INCREMENTAL:NO) 81 | target_compile_definitions(${project_name} INTERFACE _DISABLE_VECTOR_ANNOTATION _DISABLE_STRING_ANNOTATION) 82 | target_link_options(${project_name} INTERFACE /INCREMENTAL:NO) 83 | endif() 84 | endif() 85 | endif() 86 | 87 | endfunction() 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15...3.23) 2 | 3 | project(CmakeConfigPackageTests LANGUAGES CXX) 4 | 5 | # ---- Test as standalone project the exported config package ---- 6 | 7 | if(PROJECT_IS_TOP_LEVEL OR TEST_INSTALLED_VERSION) 8 | enable_testing() 9 | 10 | find_package(myproject CONFIG REQUIRED) # for intro, project_options, ... 11 | 12 | if(NOT TARGET myproject_options) 13 | message(FATAL_ERROR "Required config package not found!") 14 | return() # be strictly paranoid for Template Janitor github action! CK 15 | endif() 16 | endif() 17 | 18 | # ---- Dependencies ---- 19 | 20 | include(${Catch2_SOURCE_DIR}/extras/Catch.cmake) 21 | 22 | # Provide a simple smoke test to make sure that the CLI works and can display a --help message 23 | add_test(NAME cli.has_help COMMAND intro --help) 24 | 25 | # Provide a test to verify that the version being reported from the application 26 | # matches the version given to CMake. This will be important once you package 27 | # your program. Real world shows that this is the kind of simple mistake that is easy 28 | # to make, but also easy to test for. 29 | add_test(NAME cli.version_matches COMMAND intro --version) 30 | set_tests_properties(cli.version_matches PROPERTIES PASS_REGULAR_EXPRESSION "${PROJECT_VERSION}") 31 | 32 | add_executable(tests tests.cpp) 33 | target_link_libraries( 34 | tests 35 | PRIVATE myproject::myproject_warnings 36 | myproject::myproject_options 37 | myproject::sample_library 38 | Catch2::Catch2WithMain) 39 | 40 | if(WIN32 AND BUILD_SHARED_LIBS) 41 | add_custom_command( 42 | TARGET tests 43 | PRE_BUILD 44 | COMMAND ${CMAKE_COMMAND} -E copy $ $ 45 | COMMAND_EXPAND_LISTS) 46 | endif() 47 | 48 | # automatically discover tests that are defined in catch based test files you can modify the unittests. Set TEST_PREFIX 49 | # to whatever you want, or use different for different binaries 50 | catch_discover_tests( 51 | tests 52 | TEST_PREFIX 53 | "unittests." 54 | REPORTER 55 | XML 56 | OUTPUT_DIR 57 | . 58 | OUTPUT_PREFIX 59 | "unittests." 60 | OUTPUT_SUFFIX 61 | .xml) 62 | 63 | # Add a file containing a set of constexpr tests 64 | add_executable(constexpr_tests constexpr_tests.cpp) 65 | target_link_libraries( 66 | constexpr_tests 67 | PRIVATE myproject::myproject_warnings 68 | myproject::myproject_options 69 | myproject::sample_library 70 | Catch2::Catch2WithMain) 71 | 72 | catch_discover_tests( 73 | constexpr_tests 74 | TEST_PREFIX 75 | "constexpr." 76 | REPORTER 77 | XML 78 | OUTPUT_DIR 79 | . 80 | OUTPUT_PREFIX 81 | "constexpr." 82 | OUTPUT_SUFFIX 83 | .xml) 84 | 85 | # Disable the constexpr portion of the test, and build again this allows us to have an executable that we can debug when 86 | # things go wrong with the constexpr testing 87 | add_executable(relaxed_constexpr_tests constexpr_tests.cpp) 88 | target_link_libraries( 89 | relaxed_constexpr_tests 90 | PRIVATE myproject::myproject_warnings 91 | myproject::myproject_options 92 | myproject::sample_library 93 | Catch2::Catch2WithMain) 94 | target_compile_definitions(relaxed_constexpr_tests PRIVATE -DCATCH_CONFIG_RUNTIME_STATIC_REQUIRE) 95 | 96 | catch_discover_tests( 97 | relaxed_constexpr_tests 98 | TEST_PREFIX 99 | "relaxed_constexpr." 100 | REPORTER 101 | XML 102 | OUTPUT_DIR 103 | . 104 | OUTPUT_PREFIX 105 | "relaxed_constexpr." 106 | OUTPUT_SUFFIX 107 | .xml) 108 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main, develop ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main, develop ] 20 | schedule: 21 | - cron: '38 0 * * 5' 22 | 23 | 24 | jobs: 25 | analyze: 26 | name: Analyze 27 | runs-on: ubuntu-latest 28 | permissions: 29 | actions: read 30 | contents: read 31 | security-events: write 32 | 33 | strategy: 34 | fail-fast: false 35 | matrix: 36 | language: [ 'cpp' ] 37 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 38 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 39 | compiler: 40 | # you can specify the version after `-` like "llvm-13.0.0". 41 | - gcc-14 42 | generator: 43 | - "Ninja Multi-Config" 44 | build_type: 45 | - Debug 46 | packaging_maintainer_mode: 47 | - ON 48 | 49 | 50 | steps: 51 | - uses: actions/checkout@v3 52 | 53 | - name: Setup Cache 54 | uses: ./.github/actions/setup_cache 55 | with: 56 | compiler: ${{ matrix.compiler }} 57 | build_type: ${{ matrix.build_type }} 58 | packaging_maintainer_mode: ${{ matrix.packaging_maintainer_mode }} 59 | generator: ${{ matrix.generator }} 60 | 61 | - name: Project Name 62 | uses: cardinalby/export-env-action@v2 63 | with: 64 | envFile: '.github/constants.env' 65 | 66 | 67 | - name: Setup Cpp 68 | uses: aminya/setup-cpp@v1 69 | with: 70 | compiler: ${{ matrix.compiler }} 71 | vcvarsall: ${{ contains(matrix.os, 'windows' )}} 72 | 73 | cmake: true 74 | ninja: true 75 | vcpkg: false 76 | ccache: true 77 | clangtidy: false 78 | 79 | cppcheck: false 80 | 81 | gcovr: false 82 | opencppcoverage: false 83 | 84 | # make sure coverage is only enabled for Debug builds, since it sets -O0 to make sure coverage 85 | # has meaningful results 86 | - name: Configure CMake 87 | run: | 88 | cmake -S . -B ./build -G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE:STRING=${{matrix.build_type}} -D${{ env.PROJECT_NAME }}_PACKAGING_MAINTAINER_MODE:BOOL=${{matrix.packaging_maintainer_mode}} -D${{ env.PROJECT_NAME }}_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} 89 | 90 | # Initializes the CodeQL tools for scanning. 91 | - name: Initialize CodeQL 92 | uses: github/codeql-action/init@v2 93 | with: 94 | languages: ${{ matrix.language }} 95 | # If you wish to specify custom queries, you can do so here or in a config file. 96 | # By default, queries listed here will override any specified in a config file. 97 | # Prefix the list here with "+" to use these queries and those in the config file. 98 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 99 | 100 | 101 | - name: Build 102 | # Execute the build. You can specify a specific target with "--target " 103 | run: | 104 | cmake --build ./build --config ${{matrix.build_type}} 105 | 106 | - name: Perform CodeQL Analysis 107 | uses: github/codeql-action/analyze@v2 108 | -------------------------------------------------------------------------------- /cmake/Utilities.cmake: -------------------------------------------------------------------------------- 1 | # find a substring from a string by a given prefix such as VCVARSALL_ENV_START 2 | function( 3 | find_substring_by_prefix 4 | output 5 | prefix 6 | input) 7 | # find the prefix 8 | string(FIND "${input}" "${prefix}" prefix_index) 9 | if("${prefix_index}" STREQUAL "-1") 10 | message(SEND_ERROR "Could not find ${prefix} in ${input}") 11 | endif() 12 | # find the start index 13 | string(LENGTH "${prefix}" prefix_length) 14 | math(EXPR start_index "${prefix_index} + ${prefix_length}") 15 | 16 | string( 17 | SUBSTRING "${input}" 18 | "${start_index}" 19 | "-1" 20 | _output) 21 | set("${output}" 22 | "${_output}" 23 | PARENT_SCOPE) 24 | endfunction() 25 | 26 | # A function to set environment variables of CMake from the output of `cmd /c set` 27 | function(set_env_from_string env_string) 28 | # replace ; in paths with __sep__ so we can split on ; 29 | string( 30 | REGEX 31 | REPLACE ";" 32 | "__sep__" 33 | env_string_sep_added 34 | "${env_string}") 35 | 36 | # the variables are separated by \r?\n 37 | string( 38 | REGEX 39 | REPLACE "\r?\n" 40 | ";" 41 | env_list 42 | "${env_string_sep_added}") 43 | 44 | foreach(env_var ${env_list}) 45 | # split by = 46 | string( 47 | REGEX 48 | REPLACE "=" 49 | ";" 50 | env_parts 51 | "${env_var}") 52 | 53 | list(LENGTH env_parts env_parts_length) 54 | if("${env_parts_length}" EQUAL "2") 55 | # get the variable name and value 56 | list( 57 | GET 58 | env_parts 59 | 0 60 | env_name) 61 | list( 62 | GET 63 | env_parts 64 | 1 65 | env_value) 66 | 67 | # recover ; in paths 68 | string( 69 | REGEX 70 | REPLACE "__sep__" 71 | ";" 72 | env_value 73 | "${env_value}") 74 | 75 | # set env_name to env_value 76 | set(ENV{${env_name}} "${env_value}") 77 | 78 | # update cmake program path 79 | if("${env_name}" EQUAL "PATH") 80 | list(APPEND CMAKE_PROGRAM_PATH ${env_value}) 81 | endif() 82 | endif() 83 | endforeach() 84 | endfunction() 85 | 86 | function(get_all_targets var) 87 | set(targets) 88 | get_all_targets_recursive(targets ${CMAKE_CURRENT_SOURCE_DIR}) 89 | set(${var} 90 | ${targets} 91 | PARENT_SCOPE) 92 | endfunction() 93 | 94 | function(get_all_installable_targets var) 95 | set(targets) 96 | get_all_targets(targets) 97 | foreach(_target ${targets}) 98 | get_target_property(_target_type ${_target} TYPE) 99 | if(NOT 100 | ${_target_type} 101 | MATCHES 102 | ".*LIBRARY|EXECUTABLE") 103 | list(REMOVE_ITEM targets ${_target}) 104 | endif() 105 | endforeach() 106 | set(${var} 107 | ${targets} 108 | PARENT_SCOPE) 109 | endfunction() 110 | 111 | macro(get_all_targets_recursive targets dir) 112 | get_property( 113 | subdirectories 114 | DIRECTORY ${dir} 115 | PROPERTY SUBDIRECTORIES) 116 | foreach(subdir ${subdirectories}) 117 | get_all_targets_recursive(${targets} ${subdir}) 118 | endforeach() 119 | 120 | get_property( 121 | current_targets 122 | DIRECTORY ${dir} 123 | PROPERTY BUILDSYSTEM_TARGETS) 124 | list(APPEND ${targets} ${current_targets}) 125 | endmacro() 126 | 127 | function(is_verbose var) 128 | if("CMAKE_MESSAGE_LOG_LEVEL" STREQUAL "VERBOSE" 129 | OR "CMAKE_MESSAGE_LOG_LEVEL" STREQUAL "DEBUG" 130 | OR "CMAKE_MESSAGE_LOG_LEVEL" STREQUAL "TRACE") 131 | set(${var} 132 | ON 133 | PARENT_SCOPE) 134 | else() 135 | set(${var} 136 | OFF 137 | PARENT_SCOPE) 138 | endif() 139 | endfunction() 140 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | 3 | # This template attempts to be "fetch_content"-able 4 | # so that it works well with tools like CPM or other 5 | # manual dependency management 6 | 7 | # Only set the cxx_standard if it is not set by someone else 8 | if (NOT DEFINED CMAKE_CXX_STANDARD) 9 | set(CMAKE_CXX_STANDARD 23) 10 | endif() 11 | 12 | # strongly encouraged to enable this globally to avoid conflicts between 13 | # -Wpedantic being enabled and -std=c++20 and -std=gnu++20 for example 14 | # when compiling with PCH enabled 15 | set(CMAKE_CXX_EXTENSIONS OFF) 16 | 17 | # Set the project name and language 18 | project( 19 | myproject 20 | VERSION 0.0.1 21 | DESCRIPTION "" 22 | HOMEPAGE_URL "%%myurl%%" 23 | LANGUAGES CXX C) 24 | 25 | include(cmake/PreventInSourceBuilds.cmake) 26 | include(ProjectOptions.cmake) 27 | 28 | 29 | myproject_setup_options() 30 | 31 | myproject_global_options() 32 | include(Dependencies.cmake) 33 | myproject_setup_dependencies() 34 | 35 | myproject_local_options() 36 | 37 | # don't know if this should be set globally from here or not... 38 | set(CMAKE_CXX_VISIBILITY_PRESET hidden) 39 | 40 | set(GIT_SHA 41 | "Unknown" 42 | CACHE STRING "SHA this build was generated from") 43 | string( 44 | SUBSTRING "${GIT_SHA}" 45 | 0 46 | 8 47 | GIT_SHORT_SHA) 48 | 49 | target_compile_features(myproject_options INTERFACE cxx_std_${CMAKE_CXX_STANDARD}) 50 | 51 | add_library(myproject::myproject_options ALIAS myproject_options) 52 | add_library(myproject::myproject_warnings ALIAS myproject_warnings) 53 | 54 | #add_library(myproject::myproject_options INTERFACE IMPORTED) 55 | #add_library(myproject::myproject_warnings INTERFACE IMPORTED) 56 | 57 | # configure files based on CMake configuration options 58 | add_subdirectory(configured_files) 59 | 60 | # Adding the src: 61 | add_subdirectory(src) 62 | 63 | # Don't even look at tests if we're not top level 64 | if(NOT PROJECT_IS_TOP_LEVEL) 65 | return() 66 | endif() 67 | 68 | # Adding the tests: 69 | include(CTest) 70 | 71 | if(BUILD_TESTING) 72 | message(AUTHOR_WARNING "Building Tests. Be sure to check out test/constexpr_tests.cpp for constexpr testing") 73 | add_subdirectory(test) 74 | endif() 75 | 76 | 77 | if(myproject_BUILD_FUZZ_TESTS) 78 | message(AUTHOR_WARNING "Building Fuzz Tests, using fuzzing sanitizer https://www.llvm.org/docs/LibFuzzer.html") 79 | if (NOT myproject_ENABLE_SANITIZER_ADDRESS AND NOT myproject_ENABLE_SANITIZER_THREAD) 80 | message(WARNING "You need asan or tsan enabled for meaningful fuzz testing") 81 | endif() 82 | add_subdirectory(fuzz_test) 83 | 84 | endif() 85 | 86 | # If MSVC is being used, and ASAN is enabled, we need to set the debugger environment 87 | # so that it behaves well with MSVC's debugger, and we can run the target from visual studio 88 | if(MSVC) 89 | get_all_installable_targets(all_targets) 90 | message("all_targets=${all_targets}") 91 | set_target_properties(${all_targets} PROPERTIES VS_DEBUGGER_ENVIRONMENT "PATH=$(VC_ExecutablePath_x64);%PATH%") 92 | endif() 93 | 94 | # set the startup project for the "play" button in MSVC 95 | set_property(DIRECTORY PROPERTY VS_STARTUP_PROJECT intro) 96 | 97 | if(CMAKE_SKIP_INSTALL_RULES) 98 | return() 99 | endif() 100 | 101 | include(cmake/PackageProject.cmake) 102 | 103 | # Add other targets that you want installed here, by default we just package the one executable 104 | # we know we want to ship 105 | myproject_package_project( 106 | TARGETS 107 | intro 108 | myproject_options 109 | myproject_warnings 110 | # FIXME: this does not work! CK 111 | # PRIVATE_DEPENDENCIES_CONFIGURED project_options project_warnings 112 | ) 113 | 114 | # Experience shows that explicit package naming can help make it easier to sort 115 | # out potential ABI related issues before they start, while helping you 116 | # track a build to a specific GIT SHA 117 | set(CPACK_PACKAGE_FILE_NAME 118 | "${CMAKE_PROJECT_NAME}-${CMAKE_PROJECT_VERSION}-${GIT_SHORT_SHA}-${CMAKE_SYSTEM_NAME}-${CMAKE_BUILD_TYPE}-${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION}" 119 | ) 120 | 121 | include(CPack) 122 | -------------------------------------------------------------------------------- /cmake/StaticAnalyzers.cmake: -------------------------------------------------------------------------------- 1 | macro(myproject_enable_cppcheck WARNINGS_AS_ERRORS CPPCHECK_OPTIONS) 2 | find_program(CPPCHECK cppcheck) 3 | if(CPPCHECK) 4 | 5 | if(CMAKE_GENERATOR MATCHES ".*Visual Studio.*") 6 | set(CPPCHECK_TEMPLATE "vs") 7 | else() 8 | set(CPPCHECK_TEMPLATE "gcc") 9 | endif() 10 | 11 | if("${CPPCHECK_OPTIONS}" STREQUAL "") 12 | # Enable all warnings that are actionable by the user of this toolset 13 | # style should enable the other 3, but we'll be explicit just in case 14 | set(SUPPRESS_DIR "*:${CMAKE_CURRENT_BINARY_DIR}/_deps/*.h") 15 | message(STATUS "CPPCHECK_OPTIONS suppress: ${SUPPRESS_DIR}") 16 | set(CMAKE_CXX_CPPCHECK 17 | ${CPPCHECK} 18 | --template=${CPPCHECK_TEMPLATE} 19 | --enable=style,performance,warning,portability 20 | --inline-suppr 21 | # We cannot act on a bug/missing feature of cppcheck 22 | --suppress=cppcheckError 23 | --suppress=internalAstError 24 | # if a file does not have an internalAstError, we get an unmatchedSuppression error 25 | --suppress=unmatchedSuppression 26 | # noisy and incorrect sometimes 27 | --suppress=passedByValue 28 | # ignores code that cppcheck thinks is invalid C++ 29 | --suppress=syntaxError 30 | --suppress=preprocessorErrorDirective 31 | # ignores static_assert type failures 32 | --suppress=knownConditionTrueFalse 33 | --inconclusive 34 | --suppress=${SUPPRESS_DIR}) 35 | else() 36 | # if the user provides a CPPCHECK_OPTIONS with a template specified, it will override this template 37 | set(CMAKE_CXX_CPPCHECK ${CPPCHECK} --template=${CPPCHECK_TEMPLATE} ${CPPCHECK_OPTIONS}) 38 | endif() 39 | 40 | if(NOT 41 | "${CMAKE_CXX_STANDARD}" 42 | STREQUAL 43 | "") 44 | set(CMAKE_CXX_CPPCHECK ${CMAKE_CXX_CPPCHECK} --std=c++${CMAKE_CXX_STANDARD}) 45 | endif() 46 | if(${WARNINGS_AS_ERRORS}) 47 | list(APPEND CMAKE_CXX_CPPCHECK --error-exitcode=2) 48 | endif() 49 | else() 50 | message(${WARNING_MESSAGE} "cppcheck requested but executable not found") 51 | endif() 52 | endmacro() 53 | 54 | macro(myproject_enable_clang_tidy target WARNINGS_AS_ERRORS) 55 | 56 | find_program(CLANGTIDY clang-tidy) 57 | if(CLANGTIDY) 58 | if(NOT 59 | CMAKE_CXX_COMPILER_ID 60 | MATCHES 61 | ".*Clang") 62 | 63 | get_target_property(TARGET_PCH ${target} INTERFACE_PRECOMPILE_HEADERS) 64 | 65 | if("${TARGET_PCH}" STREQUAL "TARGET_PCH-NOTFOUND") 66 | get_target_property(TARGET_PCH ${target} PRECOMPILE_HEADERS) 67 | endif() 68 | 69 | if(NOT ("${TARGET_PCH}" STREQUAL "TARGET_PCH-NOTFOUND")) 70 | message( 71 | SEND_ERROR 72 | "clang-tidy cannot be enabled with non-clang compiler and PCH, clang-tidy fails to handle gcc's PCH file") 73 | endif() 74 | endif() 75 | 76 | # construct the clang-tidy command line 77 | set(CLANG_TIDY_OPTIONS 78 | ${CLANGTIDY} 79 | -extra-arg=-Wno-unknown-warning-option 80 | -extra-arg=-Wno-ignored-optimization-argument 81 | -extra-arg=-Wno-unused-command-line-argument 82 | -p) 83 | # set standard 84 | if(NOT 85 | "${CMAKE_CXX_STANDARD}" 86 | STREQUAL 87 | "") 88 | if("${CLANG_TIDY_OPTIONS_DRIVER_MODE}" STREQUAL "cl") 89 | set(CLANG_TIDY_OPTIONS ${CLANG_TIDY_OPTIONS} -extra-arg=/std:c++${CMAKE_CXX_STANDARD}) 90 | else() 91 | set(CLANG_TIDY_OPTIONS ${CLANG_TIDY_OPTIONS} -extra-arg=-std=c++${CMAKE_CXX_STANDARD}) 92 | endif() 93 | endif() 94 | 95 | # set warnings as errors 96 | if(${WARNINGS_AS_ERRORS}) 97 | list(APPEND CLANG_TIDY_OPTIONS -warnings-as-errors=*) 98 | endif() 99 | 100 | message("Also setting clang-tidy globally") 101 | set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_OPTIONS}) 102 | else() 103 | message(${WARNING_MESSAGE} "clang-tidy requested but executable not found") 104 | endif() 105 | endmacro() 106 | 107 | macro(myproject_enable_include_what_you_use) 108 | find_program(INCLUDE_WHAT_YOU_USE include-what-you-use) 109 | if(INCLUDE_WHAT_YOU_USE) 110 | set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${INCLUDE_WHAT_YOU_USE}) 111 | else() 112 | message(${WARNING_MESSAGE} "include-what-you-use requested but executable not found") 113 | endif() 114 | endmacro() 115 | -------------------------------------------------------------------------------- /cmake/Hardening.cmake: -------------------------------------------------------------------------------- 1 | include(CheckCXXCompilerFlag) 2 | 3 | macro( 4 | myproject_enable_hardening 5 | target 6 | global 7 | ubsan_minimal_runtime) 8 | 9 | message(STATUS "** Enabling Hardening (Target ${target}) **") 10 | 11 | if(MSVC) 12 | set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} /sdl /DYNAMICBASE /guard:cf") 13 | message(STATUS "*** MSVC flags: /sdl /DYNAMICBASE /guard:cf /NXCOMPAT /CETCOMPAT") 14 | set(NEW_LINK_OPTIONS "${NEW_LINK_OPTIONS} /NXCOMPAT /CETCOMPAT") 15 | 16 | elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang|GNU") 17 | set(NEW_CXX_DEFINITIONS "${NEW_CXX_DEFINITIONS} -D_GLIBCXX_ASSERTIONS") 18 | message(STATUS "*** GLIBC++ Assertions (vector[], string[], ...) enabled") 19 | 20 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 21 | set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3") 22 | endif() 23 | message(STATUS "*** g++/clang _FORTIFY_SOURCE=3 enabled") 24 | 25 | # check_cxx_compiler_flag(-fpie PIE) 26 | #if(PIE) 27 | # set(NEW_COMPILE_OPTIONS ${NEW_COMPILE_OPTIONS} -fpie) 28 | # set(NEW_LINK_OPTIONS ${NEW_LINK_OPTIONS} -pie) 29 | # 30 | # message(STATUS "*** g++/clang PIE mode enabled") 31 | #else() 32 | # message(STATUS "*** g++/clang PIE mode NOT enabled (not supported)") 33 | #endif() 34 | 35 | check_cxx_compiler_flag(-fstack-protector-strong STACK_PROTECTOR) 36 | if(STACK_PROTECTOR) 37 | set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -fstack-protector-strong") 38 | message(STATUS "*** g++/clang -fstack-protector-strong enabled") 39 | else() 40 | message(STATUS "*** g++/clang -fstack-protector-strong NOT enabled (not supported)") 41 | endif() 42 | 43 | check_cxx_compiler_flag(-fcf-protection CF_PROTECTION) 44 | if(CF_PROTECTION) 45 | set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -fcf-protection") 46 | message(STATUS "*** g++/clang -fcf-protection enabled") 47 | else() 48 | message(STATUS "*** g++/clang -fcf-protection NOT enabled (not supported)") 49 | endif() 50 | 51 | check_cxx_compiler_flag(-fstack-clash-protection CLASH_PROTECTION) 52 | if(CLASH_PROTECTION) 53 | if(LINUX OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") 54 | set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -fstack-clash-protection") 55 | message(STATUS "*** g++/clang -fstack-clash-protection enabled") 56 | else() 57 | message(STATUS "*** g++/clang -fstack-clash-protection NOT enabled (clang on non-Linux)") 58 | endif() 59 | else() 60 | message(STATUS "*** g++/clang -fstack-clash-protection NOT enabled (not supported)") 61 | endif() 62 | endif() 63 | 64 | if(${ubsan_minimal_runtime}) 65 | check_cxx_compiler_flag("-fsanitize=undefined -fno-sanitize-recover=undefined -fsanitize-minimal-runtime" 66 | MINIMAL_RUNTIME) 67 | if(MINIMAL_RUNTIME) 68 | set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -fsanitize=undefined -fsanitize-minimal-runtime") 69 | set(NEW_LINK_OPTIONS "${NEW_LINK_OPTIONS} -fsanitize=undefined -fsanitize-minimal-runtime") 70 | 71 | if(NOT ${global}) 72 | set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -fno-sanitize-recover=undefined") 73 | set(NEW_LINK_OPTIONS "${NEW_LINK_OPTIONS} -fno-sanitize-recover=undefined") 74 | else() 75 | message(STATUS "** not enabling -fno-sanitize-recover=undefined for global consumption") 76 | endif() 77 | 78 | message(STATUS "*** ubsan minimal runtime enabled") 79 | else() 80 | message(STATUS "*** ubsan minimal runtime NOT enabled (not supported)") 81 | endif() 82 | else() 83 | message(STATUS "*** ubsan minimal runtime NOT enabled (not requested)") 84 | endif() 85 | 86 | message(STATUS "** Hardening Compiler Flags: ${NEW_COMPILE_OPTIONS}") 87 | message(STATUS "** Hardening Linker Flags: ${NEW_LINK_OPTIONS}") 88 | message(STATUS "** Hardening Compiler Defines: ${NEW_CXX_DEFINITIONS}") 89 | 90 | if(${global}) 91 | message(STATUS "** Setting hardening options globally for all dependencies") 92 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${NEW_COMPILE_OPTIONS}") 93 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${NEW_LINK_OPTIONS}") 94 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${NEW_CXX_DEFINITIONS}") 95 | else() 96 | target_compile_options(${target} INTERFACE ${NEW_COMPILE_OPTIONS}) 97 | target_link_options(${target} INTERFACE ${NEW_LINK_OPTIONS}) 98 | target_compile_definitions(${target} INTERFACE ${NEW_CXX_DEFINITIONS}) 99 | endif() 100 | endmacro() 101 | -------------------------------------------------------------------------------- /README_dependencies.md: -------------------------------------------------------------------------------- 1 | ## Dependencies 2 | 3 | Note about install commands: 4 | - for Windows, we use [choco](https://chocolatey.org/install). 5 | - for macOS, we use [brew](https://brew.sh/). 6 | - In case of an error in cmake, make sure that the dependencies are on the PATH. 7 | 8 | 9 | ### Too Long, Didn't Install 10 | 11 | This is a really long list of dependencies, and it's easy to mess up. That's why: 12 | 13 | #### Docker 14 | We have a Docker image that's already set up for you. See the [Docker instructions](./README_docker.md). 15 | 16 | #### Setup-cpp 17 | 18 | We have [setup-cpp](https://github.com/aminya/setup-cpp) that is a cross-platform tool to install all the compilers and dependencies on the system. 19 | 20 | Please check [the setup-cpp documentation](https://github.com/aminya/setup-cpp) for more information. 21 | 22 | For example, on Windows, you can run the following to install llvm, cmake, ninja, ccache, and cppcheck. 23 | ```ps1 24 | # windows example (open shell as admin) 25 | curl -LJO "https://github.com/aminya/setup-cpp/releases/download/v0.5.7/setup_cpp_windows.exe" 26 | ./setup_cpp_windows --compiler llvm --cmake true --ninja true --ccache true --cppcheck true 27 | 28 | RefreshEnv.cmd # reload the environment 29 | ``` 30 | 31 | ### Necessary Dependencies 32 | 1. A C++ compiler that supports C++17. 33 | See [cppreference.com](https://en.cppreference.com/w/cpp/compiler_support) 34 | to see which features are supported by each compiler. 35 | The following compilers should work: 36 | 37 | * [gcc 7+](https://gcc.gnu.org/) 38 |
39 | Install command 40 | 41 | - Debian/Ubuntu: 42 | 43 | sudo apt install build-essential 44 | 45 | - Windows: 46 | 47 | choco install mingw -y 48 | 49 | - MacOS: 50 | 51 | brew install gcc 52 |
53 | 54 | * [clang 6+](https://clang.llvm.org/) 55 |
56 | Install command 57 | 58 | - Debian/Ubuntu: 59 | 60 | bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" 61 | 62 | - Windows: 63 | 64 | Visual Studio 2019 ships with LLVM (see the Visual Studio section). However, to install LLVM separately: 65 | 66 | choco install llvm -y 67 | 68 | llvm-utils for using external LLVM with Visual Studio generator: 69 | 70 | git clone https://github.com/zufuliu/llvm-utils.git 71 | cd llvm-utils/VS2017 72 | .\install.bat 73 | 74 | - MacOS: 75 | 76 | brew install llvm 77 |
78 | 79 | * [Visual Studio 2019 or higher](https://visualstudio.microsoft.com/) 80 |
81 | Install command + Environment setup 82 | 83 | On Windows, you need to install Visual Studio 2019 because of the SDK and libraries that ship with it. 84 | 85 | Visual Studio IDE - 2019 Community (installs Clang too): 86 | 87 | choco install -y visualstudio2019community --package-parameters "add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended --includeOptional --passive --locale en-US" 88 | 89 | Put MSVC compiler, Clang compiler, and vcvarsall.bat on the path: 90 | 91 | choco install vswhere -y 92 | refreshenv 93 | 94 | # change to x86 for 32bit 95 | $clpath = vswhere -products * -latest -prerelease -find **/Hostx64/x64/* 96 | $clangpath = vswhere -products * -latest -prerelease -find **/Llvm/bin/* 97 | $vcvarsallpath = vswhere -products * -latest -prerelease -find **/Auxiliary/Build/* 98 | 99 | $path = [System.Environment]::GetEnvironmentVariable("PATH", "User") 100 | [Environment]::SetEnvironmentVariable("Path", $path + ";$clpath" + ";$clangpath" + ";$vcvarsallpath", "User") 101 | refreshenv 102 | 103 |
104 | 105 | 106 | 2. [CMake 3.21+](https://cmake.org/) 107 |
108 | Install Command 109 | 110 | - Debian/Ubuntu: 111 | 112 | sudo apt-get install cmake 113 | 114 | - Windows: 115 | 116 | choco install cmake -y 117 | 118 | - MacOS: 119 | 120 | brew install cmake 121 | 122 |
123 | 124 | ### Optional Dependencies 125 | #### C++ Tools 126 | * [Doxygen](http://doxygen.nl/) 127 |
128 | Install Command 129 | 130 | - Debian/Ubuntu: 131 | 132 | sudo apt-get install doxygen 133 | sudo apt-get install graphviz 134 | 135 | - Windows: 136 | 137 | choco install doxygen.install -y 138 | choco install graphviz -y 139 | 140 | - MacOS: 141 | 142 | brew install doxygen 143 | brew install graphviz 144 | 145 |
146 | 147 | 148 | * [ccache](https://ccache.dev/) 149 |
150 | Install Command 151 | 152 | - Debian/Ubuntu: 153 | 154 | sudo apt-get install ccache 155 | 156 | - Windows: 157 | 158 | choco install ccache -y 159 | 160 | - MacOS: 161 | 162 | brew install ccache 163 | 164 |
165 | 166 | 167 | * [Cppcheck](http://cppcheck.sourceforge.net/) 168 |
169 | Install Command 170 | 171 | - Debian/Ubuntu: 172 | 173 | sudo apt-get install cppcheck 174 | 175 | - Windows: 176 | 177 | choco install cppcheck -y 178 | 179 | - MacOS: 180 | 181 | brew install cppcheck 182 | 183 |
184 | 185 | 186 | * [include-what-you-use](https://include-what-you-use.org/) 187 |
188 | Install Command 189 | 190 | Follow instructions here: 191 | https://github.com/include-what-you-use/include-what-you-use#how-to-install 192 |
193 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # [Choice] bionic (18.04), focal (20.04) 2 | ARG VARIANT="focal" 3 | FROM ubuntu:${VARIANT} 4 | 5 | # Restate the variant to use it later on in the llvm and cmake installations 6 | ARG VARIANT 7 | 8 | # Install necessary packages available from standard repos 9 | RUN apt-get update -qq && export DEBIAN_FRONTEND=noninteractive && \ 10 | apt-get install -y --no-install-recommends \ 11 | software-properties-common wget apt-utils file zip \ 12 | openssh-client gpg-agent socat rsync \ 13 | make ninja-build git \ 14 | python3 python3-pip 15 | 16 | # Install conan 17 | RUN python3 -m pip install --upgrade pip setuptools && \ 18 | python3 -m pip install conan && \ 19 | conan --version 20 | 21 | # By default, anything you run in Docker is done as superuser. 22 | # Conan runs some install commands as superuser, and will prepend `sudo` to 23 | # these commands, unless `CONAN_SYSREQUIRES_SUDO=0` is in your env variables. 24 | ENV CONAN_SYSREQUIRES_SUDO 0 25 | # Some packages request that Conan use the system package manager to install 26 | # a few dependencies. This flag allows Conan to proceed with these installations; 27 | # leaving this flag undefined can cause some installation failures. 28 | ENV CONAN_SYSREQUIRES_MODE enabled 29 | 30 | # User-settable versions: 31 | # This Dockerfile should support gcc-[7, 8, 9, 10, 11] and clang-[10, 11, 12, 13] 32 | # Earlier versions of clang will require significant modifications to the IWYU section 33 | ARG GCC_VER="11" 34 | # Add gcc-${GCC_VER} 35 | RUN add-apt-repository -y ppa:ubuntu-toolchain-r/test && \ 36 | apt-get update -qq && export DEBIAN_FRONTEND=noninteractive && \ 37 | apt-get install -y --no-install-recommends \ 38 | gcc-${GCC_VER} g++-${GCC_VER} gdb 39 | 40 | # Set gcc-${GCC_VER} as default gcc 41 | RUN update-alternatives --install /usr/bin/gcc gcc $(which gcc-${GCC_VER}) 100 42 | RUN update-alternatives --install /usr/bin/g++ g++ $(which g++-${GCC_VER}) 100 43 | 44 | ARG LLVM_VER="13" 45 | # Add clang-${LLVM_VER} 46 | ARG LLVM_URL="http://apt.llvm.org/${VARIANT}/" 47 | ARG LLVM_PKG="llvm-toolchain-${VARIANT}-${LLVM_VER}" 48 | RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - 2>/dev/null && \ 49 | add-apt-repository -y "deb ${LLVM_URL} ${LLVM_PKG} main" && \ 50 | apt-get update -qq && export DEBIAN_FRONTEND=noninteractive && \ 51 | apt-get install -y --no-install-recommends \ 52 | clang-${LLVM_VER} lldb-${LLVM_VER} lld-${LLVM_VER} clangd-${LLVM_VER} \ 53 | llvm-${LLVM_VER}-dev libclang-${LLVM_VER}-dev clang-tidy-${LLVM_VER} 54 | 55 | # Set the default clang-tidy, so CMake can find it 56 | RUN update-alternatives --install /usr/bin/clang-tidy clang-tidy $(which clang-tidy-${LLVM_VER}) 1 57 | 58 | # Set clang-${LLVM_VER} as default clang 59 | RUN update-alternatives --install /usr/bin/clang clang $(which clang-${LLVM_VER}) 100 60 | RUN update-alternatives --install /usr/bin/clang++ clang++ $(which clang++-${LLVM_VER}) 100 61 | 62 | # Add current cmake/ccmake, from Kitware 63 | ARG CMAKE_URL="https://apt.kitware.com/ubuntu/" 64 | ARG CMAKE_PKG=${VARIANT} 65 | RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null \ 66 | | gpg --dearmor - | tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null && \ 67 | apt-add-repository -y "deb ${CMAKE_URL} ${CMAKE_PKG} main" && \ 68 | apt-get update -qq && export DEBIAN_FRONTEND=noninteractive && \ 69 | apt-get install -y --no-install-recommends cmake cmake-curses-gui 70 | 71 | # Install editors 72 | RUN apt-get update -qq && export DEBIAN_FRONTEND=noninteractive && \ 73 | apt-get install -y --no-install-recommends \ 74 | neovim emacs nano 75 | 76 | # Install optional dependecies 77 | RUN apt-get update -qq && export DEBIAN_FRONTEND=noninteractive && \ 78 | apt-get install -y --no-install-recommends \ 79 | doxygen graphviz ccache cppcheck 80 | 81 | # Install include-what-you-use 82 | ENV IWYU /home/iwyu 83 | ENV IWYU_BUILD ${IWYU}/build 84 | ENV IWYU_SRC ${IWYU}/include-what-you-use 85 | RUN mkdir -p ${IWYU_BUILD} && \ 86 | git clone --branch clang_${LLVM_VER} \ 87 | https://github.com/include-what-you-use/include-what-you-use.git \ 88 | ${IWYU_SRC} 89 | RUN CC=clang-${LLVM_VER} CXX=clang++-${LLVM_VER} cmake -S ${IWYU_SRC} \ 90 | -B ${IWYU_BUILD} \ 91 | -G "Unix Makefiles" -DCMAKE_PREFIX_PATH=/usr/lib/llvm-${LLVM_VER} && \ 92 | cmake --build ${IWYU_BUILD} -j && \ 93 | cmake --install ${IWYU_BUILD} 94 | 95 | # Per https://github.com/include-what-you-use/include-what-you-use#how-to-install: 96 | # `You need to copy the Clang include directory to the expected location before 97 | # running (similarly, use include-what-you-use -print-resource-dir to learn 98 | # exactly where IWYU wants the headers).` 99 | RUN mkdir -p $(include-what-you-use -print-resource-dir 2>/dev/null) 100 | RUN ln -s $(readlink -f /usr/lib/clang/${LLVM_VER}/include) \ 101 | $(include-what-you-use -print-resource-dir 2>/dev/null)/include 102 | 103 | ## Cleanup cached apt data we don't need anymore 104 | RUN apt-get autoremove -y && apt-get clean && \ 105 | rm -rf /var/lib/apt/lists/* 106 | 107 | # Allow the user to set compiler defaults 108 | ARG USE_CLANG 109 | # if --build-arg USE_CLANG=1, set CC to 'clang' or set to null otherwise. 110 | ENV CC=${USE_CLANG:+"clang"} 111 | ENV CXX=${USE_CLANG:+"clang++"} 112 | # if CC is null, set it to 'gcc' (or leave as is otherwise). 113 | ENV CC=${CC:-"gcc"} 114 | ENV CXX=${CXX:-"g++"} 115 | 116 | # Include project 117 | #ADD . /workspaces/cpp_starter_project 118 | #WORKDIR /workspaces/cpp_starter_project 119 | 120 | CMD ["/bin/bash"] 121 | -------------------------------------------------------------------------------- /cmake/CompilerWarnings.cmake: -------------------------------------------------------------------------------- 1 | # from here: 2 | # 3 | # https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md 4 | 5 | function( 6 | myproject_set_project_warnings 7 | project_name 8 | WARNINGS_AS_ERRORS 9 | MSVC_WARNINGS 10 | CLANG_WARNINGS 11 | GCC_WARNINGS 12 | CUDA_WARNINGS) 13 | if("${MSVC_WARNINGS}" STREQUAL "") 14 | set(MSVC_WARNINGS 15 | /W4 # Baseline reasonable warnings 16 | /w14242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data 17 | /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data 18 | /w14263 # 'function': member function does not override any base class virtual member function 19 | /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not 20 | # be destructed correctly 21 | /w14287 # 'operator': unsigned/negative constant mismatch 22 | /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside 23 | # the for-loop scope 24 | /w14296 # 'operator': expression is always 'boolean_value' 25 | /w14311 # 'variable': pointer truncation from 'type1' to 'type2' 26 | /w14545 # expression before comma evaluates to a function which is missing an argument list 27 | /w14546 # function call before comma missing argument list 28 | /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect 29 | /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? 30 | /w14555 # expression has no effect; expected expression with side- effect 31 | /w14619 # pragma warning: there is no warning number 'number' 32 | /w14640 # Enable warning on thread un-safe static member initialization 33 | /w14826 # Conversion from 'type1' to 'type2' is sign-extended. This may cause unexpected runtime behavior. 34 | /w14905 # wide string literal cast to 'LPSTR' 35 | /w14906 # string literal cast to 'LPWSTR' 36 | /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied 37 | /permissive- # standards conformance mode for MSVC compiler. 38 | ) 39 | endif() 40 | 41 | if("${CLANG_WARNINGS}" STREQUAL "") 42 | set(CLANG_WARNINGS 43 | -Wall 44 | -Wextra # reasonable and standard 45 | -Wshadow # warn the user if a variable declaration shadows one from a parent context 46 | -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps 47 | # catch hard to track down memory errors 48 | -Wold-style-cast # warn for c-style casts 49 | -Wcast-align # warn for potential performance problem casts 50 | -Wunused # warn on anything being unused 51 | -Woverloaded-virtual # warn if you overload (not override) a virtual function 52 | -Wpedantic # warn if non-standard C++ is used 53 | -Wconversion # warn on type conversions that may lose data 54 | -Wsign-conversion # warn on sign conversions 55 | -Wnull-dereference # warn if a null dereference is detected 56 | -Wdouble-promotion # warn if float is implicit promoted to double 57 | -Wformat=2 # warn on security issues around functions that format output (ie printf) 58 | -Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation 59 | ) 60 | endif() 61 | 62 | if("${GCC_WARNINGS}" STREQUAL "") 63 | set(GCC_WARNINGS 64 | ${CLANG_WARNINGS} 65 | -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist 66 | -Wduplicated-cond # warn if if / else chain has duplicated conditions 67 | -Wduplicated-branches # warn if if / else branches have duplicated code 68 | -Wlogical-op # warn about logical operations being used where bitwise were probably wanted 69 | -Wuseless-cast # warn if you perform a cast to the same type 70 | -Wsuggest-override # warn if an overridden member function is not marked 'override' or 'final' 71 | ) 72 | endif() 73 | 74 | if("${CUDA_WARNINGS}" STREQUAL "") 75 | set(CUDA_WARNINGS 76 | -Wall 77 | -Wextra 78 | -Wunused 79 | -Wconversion 80 | -Wshadow 81 | # TODO add more Cuda warnings 82 | ) 83 | endif() 84 | 85 | if(WARNINGS_AS_ERRORS) 86 | message(TRACE "Warnings are treated as errors") 87 | list(APPEND CLANG_WARNINGS -Werror) 88 | list(APPEND GCC_WARNINGS -Werror) 89 | list(APPEND MSVC_WARNINGS /WX) 90 | endif() 91 | 92 | if(MSVC) 93 | set(PROJECT_WARNINGS_CXX ${MSVC_WARNINGS}) 94 | elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 95 | set(PROJECT_WARNINGS_CXX ${CLANG_WARNINGS}) 96 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 97 | set(PROJECT_WARNINGS_CXX ${GCC_WARNINGS}) 98 | else() 99 | message(AUTHOR_WARNING "No compiler warnings set for CXX compiler: '${CMAKE_CXX_COMPILER_ID}'") 100 | # TODO support Intel compiler 101 | endif() 102 | 103 | # use the same warning flags for C 104 | set(PROJECT_WARNINGS_C "${PROJECT_WARNINGS_CXX}") 105 | 106 | set(PROJECT_WARNINGS_CUDA "${CUDA_WARNINGS}") 107 | 108 | target_compile_options( 109 | ${project_name} 110 | INTERFACE # C++ warnings 111 | $<$:${PROJECT_WARNINGS_CXX}> 112 | # C warnings 113 | $<$:${PROJECT_WARNINGS_C}> 114 | # Cuda warnings 115 | $<$:${PROJECT_WARNINGS_CUDA}>) 116 | endfunction() 117 | -------------------------------------------------------------------------------- /README_building.md: -------------------------------------------------------------------------------- 1 | ## Build Instructions 2 | 3 | A full build has different steps: 4 | 1) Specifying the compiler using environment variables 5 | 2) Configuring the project 6 | 3) Building the project 7 | 8 | For the subsequent builds, in case you change the source code, you only need to repeat the last step. 9 | 10 | ### (1) Specify the compiler using environment variables 11 | 12 | By default (if you don't set environment variables `CC` and `CXX`), the system default compiler will be used. 13 | 14 | CMake uses the environment variables CC and CXX to decide which compiler to use. So to avoid the conflict issues only specify the compilers using these variables. 15 | 16 | 17 |
18 | Commands for setting the compilers 19 | 20 | - Debian/Ubuntu/MacOS: 21 | 22 | Set your desired compiler (`clang`, `gcc`, etc): 23 | 24 | - Temporarily (only for the current shell) 25 | 26 | Run one of the followings in the terminal: 27 | 28 | - clang 29 | 30 | CC=clang CXX=clang++ 31 | 32 | - gcc 33 | 34 | CC=gcc CXX=g++ 35 | 36 | - Permanent: 37 | 38 | Open `~/.bashrc` using your text editor: 39 | 40 | gedit ~/.bashrc 41 | 42 | Add `CC` and `CXX` to point to the compilers: 43 | 44 | export CC=clang 45 | export CXX=clang++ 46 | 47 | Save and close the file. 48 | 49 | - Windows: 50 | 51 | - Permanent: 52 | 53 | Run one of the followings in PowerShell: 54 | 55 | - Visual Studio generator and compiler (cl) 56 | 57 | [Environment]::SetEnvironmentVariable("CC", "cl.exe", "User") 58 | [Environment]::SetEnvironmentVariable("CXX", "cl.exe", "User") 59 | refreshenv 60 | 61 | Set the architecture using [vcvarsall](https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=vs-2019#vcvarsall-syntax): 62 | 63 | vcvarsall.bat x64 64 | 65 | - clang 66 | 67 | [Environment]::SetEnvironmentVariable("CC", "clang.exe", "User") 68 | [Environment]::SetEnvironmentVariable("CXX", "clang++.exe", "User") 69 | refreshenv 70 | 71 | - gcc 72 | 73 | [Environment]::SetEnvironmentVariable("CC", "gcc.exe", "User") 74 | [Environment]::SetEnvironmentVariable("CXX", "g++.exe", "User") 75 | refreshenv 76 | 77 | 78 | - Temporarily (only for the current shell): 79 | 80 | $Env:CC="clang.exe" 81 | $Env:CXX="clang++.exe" 82 | 83 |
84 | 85 | ### (2) Configure your build 86 | 87 | To configure the project, you could use `cmake`, or `ccmake` or `cmake-gui`. Each of them are explained in the following: 88 | 89 | #### (2.a) Configuring via cmake: 90 | With Cmake directly: 91 | 92 | cmake -S . -B ./build 93 | 94 | Cmake will automatically create the `./build` folder if it does not exist, and it wil configure the project. 95 | 96 | Instead, if you have CMake version 3.21+, you can use one of the configuration presets that are listed in the CmakePresets.json file. 97 | 98 | cmake . --preset 99 | cmake --build 100 | 101 | #### (2.b) Configuring via ccmake: 102 | 103 | With the Cmake Curses Dialog Command Line tool: 104 | 105 | ccmake -S . -B ./build 106 | 107 | Once `ccmake` has finished setting up, press 'c' to configure the project, 108 | press 'g' to generate, and 'q' to quit. 109 | 110 | #### (2.c) Configuring via cmake-gui: 111 | 112 | To use the GUI of the cmake: 113 | 114 | 2.c.1) Open cmake-gui from the project directory: 115 | ``` 116 | cmake-gui . 117 | ``` 118 | 2.c.2) Set the build directory: 119 | 120 | ![build_dir](https://user-images.githubusercontent.com/16418197/82524586-fa48e380-9af4-11ea-8514-4e18a063d8eb.jpg) 121 | 122 | 2.c.3) Configure the generator: 123 | 124 | In cmake-gui, from the upper menu select `Tools/Configure`. 125 | 126 | **Warning**: if you have set `CC` and `CXX` always choose the `use default native compilers` option. This picks `CC` and `CXX`. Don't change the compiler at this stage! 127 | 128 |
129 | Windows - MinGW Makefiles 130 | 131 | Choose MinGW Makefiles as the generator: 132 | 133 | mingw 134 | 135 |
136 | 137 |
138 | Windows - Visual Studio generator and compiler 139 | 140 | You should have already set `C` and `CXX` to `cl.exe`. 141 | 142 | Choose "Visual Studio 16 2019" as the generator: 143 | 144 | default_vs 145 | 146 |
147 | 148 |
149 | 150 | Windows - Visual Studio generator and Clang Compiler 151 | 152 | You should have already set `C` and `CXX` to `clang.exe` and `clang++.exe`. 153 | 154 | Choose "Visual Studio 16 2019" as the generator. To tell Visual studio to use `clang-cl.exe`: 155 | - If you use the LLVM that is shipped with Visual Studio: write `ClangCl` under "optional toolset to use". 156 | 157 | visual_studio 158 | 159 | - If you use an external LLVM: write [`LLVM_v142`](https://github.com/zufuliu/llvm-utils#llvm-for-visual-studio-2017-and-2019) 160 | under "optional toolset to use". 161 | 162 | visual_studio 163 | 164 |
165 |
166 | 167 | 2.c.4) Choose the Cmake options and then generate: 168 | 169 | ![generate](https://user-images.githubusercontent.com/16418197/82781591-c97feb80-9e1f-11ea-86c8-f2748b96f516.png) 170 | 171 | ### (3) Build the project 172 | Once you have selected all the options you would like to use, you can build the 173 | project (all targets): 174 | 175 | cmake --build ./build 176 | 177 | For Visual Studio, give the build configuration (Release, RelWithDeb, Debug, etc) like the following: 178 | 179 | cmake --build ./build -- /p:configuration=Release 180 | 181 | 182 | ### Running the tests 183 | 184 | You can use the `ctest` command run the tests. 185 | 186 | ```shell 187 | cd ./build 188 | ctest -C Debug 189 | cd ../ 190 | ``` 191 | 192 | 193 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | pull_request: 4 | release: 5 | types: [published] 6 | push: 7 | tags: 8 | branches: 9 | - main 10 | - develop 11 | 12 | env: 13 | CLANG_TIDY_VERSION: "19.1.1" 14 | VERBOSE: 1 15 | 16 | 17 | jobs: 18 | Test: 19 | name: ${{matrix.os}} ${{matrix.compiler}} ${{matrix.build_type}} ${{matrix.packaging_maintainer_mode == 'ON' && '(maintainer mode)' || ''}} 20 | runs-on: ${{ matrix.os }} 21 | strategy: 22 | fail-fast: false 23 | 24 | # Recommendations: 25 | # * support at least 2 operating systems 26 | # * support at least 2 compilers 27 | # * make sure all supported configurations for your project are built 28 | # 29 | # Disable/enable builds in this list to meet the above recommendations 30 | # and your own projects needs 31 | matrix: 32 | os: 33 | - ubuntu-latest 34 | - macos-latest 35 | - windows-latest 36 | compiler: 37 | # you can specify the version after `-` like "llvm-15.0.2". 38 | - llvm-19.1.1 39 | - gcc-14 40 | generator: 41 | - "Ninja Multi-Config" 42 | build_type: 43 | - Release 44 | - Debug 45 | packaging_maintainer_mode: 46 | - ON 47 | - OFF 48 | build_shared: 49 | - OFF 50 | 51 | exclude: 52 | # mingw is determined by this author to be too buggy to support 53 | - os: windows-latest 54 | compiler: gcc-14 55 | 56 | include: 57 | # Add appropriate variables for gcov version required. This will intentionally break 58 | # if you try to use a compiler that does not have gcov set 59 | - compiler: gcc-14 60 | gcov_executable: gcov-14 61 | enable_ipo: On 62 | 63 | - compiler: llvm-19.1.1 64 | enable_ipo: Off 65 | gcov_executable: "llvm-cov gcov" 66 | 67 | - os: macos-latest 68 | enable_ipo: Off 69 | 70 | # Set up preferred package generators, for given build configurations 71 | - build_type: Release 72 | packaging_maintainer_mode: On 73 | package_generator: TBZ2 74 | 75 | # This exists solely to make sure a non-multiconfig build works 76 | - os: ubuntu-latest 77 | compiler: gcc-14 78 | generator: "Unix Makefiles" 79 | build_type: Debug 80 | gcov_executable: gcov-14 81 | packaging_maintainer_mode: On 82 | enable_ipo: Off 83 | 84 | # Windows msvc builds 85 | - os: windows-latest 86 | compiler: msvc 87 | generator: "Visual Studio 17 2022" 88 | build_type: Debug 89 | packaging_maintainer_mode: On 90 | enable_ipo: On 91 | 92 | - os: windows-latest 93 | compiler: msvc 94 | generator: "Visual Studio 17 2022" 95 | build_type: Release 96 | packaging_maintainer_mode: On 97 | enable_ipo: On 98 | 99 | - os: windows-latest 100 | compiler: msvc 101 | generator: "Visual Studio 17 2022" 102 | build_type: Debug 103 | packaging_maintainer_mode: Off 104 | 105 | - os: windows-latest 106 | compiler: msvc 107 | generator: "Visual Studio 17 2022" 108 | build_type: Release 109 | packaging_maintainer_mode: Off 110 | package_generator: ZIP 111 | 112 | - os: windows-latest 113 | compiler: msvc 114 | generator: "Visual Studio 17 2022" 115 | build_type: Release 116 | packaging_maintainer_mode: On 117 | enable_ipo: On 118 | build_shared: On 119 | 120 | 121 | steps: 122 | - name: Check for llvm version mismatches 123 | if: ${{ contains(matrix.compiler, 'llvm') && !contains(matrix.compiler, env.CLANG_TIDY_VERSION) }} 124 | uses: actions/github-script@v7 125 | with: 126 | script: | 127 | core.setFailed('There is a mismatch between configured llvm compiler and clang-tidy version chosen') 128 | 129 | - uses: actions/checkout@v3 130 | 131 | - name: Setup Cache 132 | uses: ./.github/actions/setup_cache 133 | with: 134 | compiler: ${{ matrix.compiler }} 135 | build_type: ${{ matrix.build_type }} 136 | packaging_maintainer_mode: ${{ matrix.packaging_maintainer_mode }} 137 | generator: ${{ matrix.generator }} 138 | 139 | - name: Project Name 140 | uses: cardinalby/export-env-action@v2 141 | with: 142 | envFile: '.github/constants.env' 143 | 144 | 145 | - name: Setup Cpp 146 | uses: aminya/setup-cpp@v1 147 | with: 148 | compiler: ${{ matrix.compiler }} 149 | vcvarsall: ${{ contains(matrix.os, 'windows' )}} 150 | 151 | cmake: true 152 | ninja: true 153 | vcpkg: false 154 | ccache: true 155 | clangtidy: ${{ env.CLANG_TIDY_VERSION }} 156 | 157 | 158 | cppcheck: true 159 | 160 | gcovr: true 161 | opencppcoverage: true 162 | 163 | - name: Configure CMake 164 | run: | 165 | cmake -S . -B ./build -G "${{matrix.generator}}" -D${{ env.PROJECT_NAME }}_ENABLE_IPO=${{matrix.enable_ipo }} -DCMAKE_BUILD_TYPE:STRING=${{matrix.build_type}} -D${{ env.PROJECT_NAME }}_PACKAGING_MAINTAINER_MODE:BOOL=${{matrix.packaging_maintainer_mode}} -D${{ env.PROJECT_NAME }}_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} -DGIT_SHA:STRING=${{ github.sha }} 166 | 167 | - name: Build 168 | # Execute the build. You can specify a specific target with "--target " 169 | run: | 170 | cmake --build ./build --config ${{matrix.build_type}} 171 | 172 | - name: Unix - Test and coverage 173 | if: runner.os != 'Windows' 174 | working-directory: ./build 175 | # Execute tests defined by the CMake configuration. 176 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 177 | run: | 178 | ctest -C ${{matrix.build_type}} 179 | gcovr -j ${{env.nproc}} --root ../ --print-summary --xml-pretty --xml coverage.xml . --gcov-executable '${{ matrix.gcov_executable }}' 180 | 181 | - name: Windows - Test and coverage 182 | if: runner.os == 'Windows' 183 | working-directory: ./build 184 | run: | 185 | OpenCppCoverage.exe --export_type cobertura:coverage.xml --cover_children -- ctest -C ${{matrix.build_type}} 186 | 187 | - name: CPack 188 | if: matrix.package_generator != '' 189 | working-directory: ./build 190 | run: | 191 | cpack -C ${{matrix.build_type}} -G ${{matrix.package_generator}} 192 | 193 | - name: Publish Tagged Release 194 | uses: softprops/action-gh-release@v2 195 | if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.package_generator != '' }} 196 | with: 197 | files: | 198 | build/*-*${{ matrix.build_type }}*-*.* 199 | 200 | 201 | - name: Publish to codecov 202 | uses: codecov/codecov-action@v5 203 | with: 204 | fail_ci_if_error: true # we weren't posting previously 205 | flags: ${{ runner.os }} 206 | name: ${{ runner.os }}-coverage 207 | token: ${{ secrets.CODECOV_TOKEN }} 208 | files: ./build/coverage.xml 209 | -------------------------------------------------------------------------------- /cmake/PackageProject.cmake: -------------------------------------------------------------------------------- 1 | # Uses ycm (permissive BSD-3-Clause license) and ForwardArguments (permissive MIT license) 2 | 3 | function(myproject_package_project) 4 | cmake_policy(SET CMP0103 NEW) # disallow multiple calls with the same NAME 5 | 6 | set(_options ARCH_INDEPENDENT # default to false 7 | ) 8 | set(_oneValueArgs 9 | # default to the project_name: 10 | NAME 11 | COMPONENT 12 | # default to project version: 13 | VERSION 14 | # default to semver 15 | COMPATIBILITY 16 | # default to ${CMAKE_BINARY_DIR} 17 | CONFIG_EXPORT_DESTINATION 18 | # default to ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/${NAME} suitable for vcpkg, etc. 19 | CONFIG_INSTALL_DESTINATION) 20 | set(_multiValueArgs 21 | # recursively found for the current folder if not specified 22 | TARGETS 23 | # a list of public/interface include directories or files 24 | PUBLIC_INCLUDES 25 | # the names of the INTERFACE/PUBLIC dependencies that are found using `CONFIG` 26 | PUBLIC_DEPENDENCIES_CONFIGURED 27 | # the INTERFACE/PUBLIC dependencies that are found by any means using `find_dependency`. 28 | # the arguments must be specified within double quotes (e.g. " 1.0.0 EXACT" or " CONFIG"). 29 | PUBLIC_DEPENDENCIES 30 | # the names of the PRIVATE dependencies that are found using `CONFIG`. Only included when BUILD_SHARED_LIBS is OFF. 31 | PRIVATE_DEPENDENCIES_CONFIGURED 32 | # PRIVATE dependencies that are only included when BUILD_SHARED_LIBS is OFF 33 | PRIVATE_DEPENDENCIES) 34 | 35 | cmake_parse_arguments( 36 | _PackageProject 37 | "${_options}" 38 | "${_oneValueArgs}" 39 | "${_multiValueArgs}" 40 | "${ARGN}") 41 | 42 | # Set default options 43 | include(GNUInstallDirs) # Define GNU standard installation directories such as CMAKE_INSTALL_DATADIR 44 | 45 | # set default packaged targets 46 | if(NOT _PackageProject_TARGETS) 47 | get_all_installable_targets(_PackageProject_TARGETS) 48 | message(STATUS "package_project: considering ${_PackageProject_TARGETS} as the exported targets") 49 | endif() 50 | 51 | # default to the name of the project or the given name 52 | if("${_PackageProject_NAME}" STREQUAL "") 53 | set(_PackageProject_NAME ${PROJECT_NAME}) 54 | endif() 55 | # ycm args 56 | set(_PackageProject_NAMESPACE "${_PackageProject_NAME}::") 57 | set(_PackageProject_VARS_PREFIX ${_PackageProject_NAME}) 58 | set(_PackageProject_EXPORT ${_PackageProject_NAME}) 59 | 60 | # default version to the project version 61 | if("${_PackageProject_VERSION}" STREQUAL "") 62 | set(_PackageProject_VERSION ${PROJECT_VERSION}) 63 | endif() 64 | 65 | # default compatibility to SameMajorVersion 66 | if("${_PackageProject_COMPATIBILITY}" STREQUAL "") 67 | set(_PackageProject_COMPATIBILITY "SameMajorVersion") 68 | endif() 69 | 70 | # default to the build directory 71 | if("${_PackageProject_CONFIG_EXPORT_DESTINATION}" STREQUAL "") 72 | set(_PackageProject_CONFIG_EXPORT_DESTINATION "${CMAKE_BINARY_DIR}") 73 | endif() 74 | set(_PackageProject_EXPORT_DESTINATION "${_PackageProject_CONFIG_EXPORT_DESTINATION}") 75 | 76 | # use datadir (works better with vcpkg, etc) 77 | if("${_PackageProject_CONFIG_INSTALL_DESTINATION}" STREQUAL "") 78 | set(_PackageProject_CONFIG_INSTALL_DESTINATION "${CMAKE_INSTALL_DATADIR}/${_PackageProject_NAME}") 79 | endif() 80 | # ycm args 81 | set(_PackageProject_INSTALL_DESTINATION "${_PackageProject_CONFIG_INSTALL_DESTINATION}") 82 | 83 | # Installation of the public/interface includes 84 | if(NOT 85 | "${_PackageProject_PUBLIC_INCLUDES}" 86 | STREQUAL 87 | "") 88 | foreach(_INC ${_PackageProject_PUBLIC_INCLUDES}) 89 | # make include absolute 90 | if(NOT IS_ABSOLUTE ${_INC}) 91 | set(_INC "${CMAKE_CURRENT_SOURCE_DIR}/${_INC}") 92 | endif() 93 | # install include 94 | if(IS_DIRECTORY ${_INC}) 95 | # the include directories are directly installed to the install destination. If you want an `include` folder in the install destination, name your include directory as `include` (or install it manually using `install()` command). 96 | install(DIRECTORY ${_INC} DESTINATION "./") 97 | else() 98 | install(FILES ${_INC} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") 99 | endif() 100 | endforeach() 101 | endif() 102 | 103 | # Append the configured public dependencies 104 | if(NOT 105 | "${_PackageProject_PUBLIC_DEPENDENCIES_CONFIGURED}" 106 | STREQUAL 107 | "") 108 | set(_PUBLIC_DEPENDENCIES_CONFIG) 109 | foreach(DEP ${_PackageProject_PUBLIC_DEPENDENCIES_CONFIGURED}) 110 | list(APPEND _PUBLIC_DEPENDENCIES_CONFIG "${DEP} CONFIG") 111 | endforeach() 112 | endif() 113 | list(APPEND _PackageProject_PUBLIC_DEPENDENCIES ${_PUBLIC_DEPENDENCIES_CONFIG}) 114 | # ycm arg 115 | set(_PackageProject_DEPENDENCIES ${_PackageProject_PUBLIC_DEPENDENCIES}) 116 | 117 | # Append the configured private dependencies 118 | if(NOT 119 | "${_PackageProject_PRIVATE_DEPENDENCIES_CONFIGURED}" 120 | STREQUAL 121 | "") 122 | set(_PRIVATE_DEPENDENCIES_CONFIG) 123 | foreach(DEP ${_PackageProject_PRIVATE_DEPENDENCIES_CONFIGURED}) 124 | list(APPEND _PRIVATE_DEPENDENCIES_CONFIG "${DEP} CONFIG") 125 | endforeach() 126 | endif() 127 | # ycm arg 128 | list(APPEND _PackageProject_PRIVATE_DEPENDENCIES ${_PRIVATE_DEPENDENCIES_CONFIG}) 129 | 130 | # Installation of package (compatible with vcpkg, etc) 131 | install( 132 | TARGETS ${_PackageProject_TARGETS} 133 | EXPORT ${_PackageProject_EXPORT} 134 | LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT shlib 135 | ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT lib 136 | RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT bin 137 | PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${_PackageProject_NAME}" COMPONENT dev) 138 | 139 | # install the usage file 140 | set(_targets_str "") 141 | foreach(_target ${_PackageProject_TARGETS}) 142 | set(_targets_str "${_targets_str} ${_PackageProject_NAMESPACE}${_target}") 143 | endforeach() 144 | set(USAGE_FILE_CONTENT 145 | "The package ${_PackageProject_NAME} provides CMake targets: 146 | 147 | find_package(${_PackageProject_NAME} CONFIG REQUIRED) 148 | target_link_libraries(main PRIVATE ${_targets_str}) 149 | ") 150 | install(CODE "MESSAGE(STATUS \"${USAGE_FILE_CONTENT}\")") 151 | file(WRITE "${_PackageProject_EXPORT_DESTINATION}/usage" "${USAGE_FILE_CONTENT}") 152 | install(FILES "${_PackageProject_EXPORT_DESTINATION}/usage" 153 | DESTINATION "${_PackageProject_CONFIG_INSTALL_DESTINATION}") 154 | 155 | unset(_PackageProject_TARGETS) 156 | 157 | # download ForwardArguments 158 | FetchContent_Populate ( 159 | _fargs 160 | URL https://github.com/polysquare/cmake-forward-arguments/archive/8c50d1f956172edb34e95efa52a2d5cb1f686ed2.zip) 161 | 162 | FetchContent_GetProperties(_fargs) 163 | 164 | include("${_fargs_SOURCE_DIR}/ForwardArguments.cmake") 165 | 166 | # prepare the forward arguments for ycm 167 | set(_FARGS_LIST) 168 | cmake_forward_arguments( 169 | _PackageProject 170 | _FARGS_LIST 171 | OPTION_ARGS 172 | "${_options};" 173 | SINGLEVAR_ARGS 174 | "${_oneValueArgs};EXPORT_DESTINATION;INSTALL_DESTINATION;NAMESPACE;VARS_PREFIX;EXPORT" 175 | MULTIVAR_ARGS 176 | "${_multiValueArgs};DEPENDENCIES;PRIVATE_DEPENDENCIES") 177 | 178 | # download ycm 179 | FetchContent_Populate(_ycm URL https://github.com/robotology/ycm/archive/refs/tags/v0.13.0.zip) 180 | FetchContent_GetProperties(_ycm) 181 | include("${_ycm_SOURCE_DIR}/modules/InstallBasicPackageFiles.cmake") 182 | 183 | install_basic_package_files(${_PackageProject_NAME} "${_FARGS_LIST}") 184 | 185 | include("${_ycm_SOURCE_DIR}/modules/AddUninstallTarget.cmake") 186 | endfunction() 187 | -------------------------------------------------------------------------------- /ProjectOptions.cmake: -------------------------------------------------------------------------------- 1 | include(cmake/SystemLink.cmake) 2 | include(cmake/LibFuzzer.cmake) 3 | include(CMakeDependentOption) 4 | include(CheckCXXCompilerFlag) 5 | 6 | 7 | include(CheckCXXSourceCompiles) 8 | 9 | 10 | macro(myproject_supports_sanitizers) 11 | if((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" OR CMAKE_CXX_COMPILER_ID MATCHES ".*GNU.*") AND NOT WIN32) 12 | 13 | message(STATUS "Sanity checking UndefinedBehaviorSanitizer, it should be supported on this platform") 14 | set(TEST_PROGRAM "int main() { return 0; }") 15 | 16 | # Check if UndefinedBehaviorSanitizer works at link time 17 | set(CMAKE_REQUIRED_FLAGS "-fsanitize=undefined") 18 | set(CMAKE_REQUIRED_LINK_OPTIONS "-fsanitize=undefined") 19 | check_cxx_source_compiles("${TEST_PROGRAM}" HAS_UBSAN_LINK_SUPPORT) 20 | 21 | if(HAS_UBSAN_LINK_SUPPORT) 22 | message(STATUS "UndefinedBehaviorSanitizer is supported at both compile and link time.") 23 | set(SUPPORTS_UBSAN ON) 24 | else() 25 | message(WARNING "UndefinedBehaviorSanitizer is NOT supported at link time.") 26 | set(SUPPORTS_UBSAN OFF) 27 | endif() 28 | else() 29 | set(SUPPORTS_UBSAN OFF) 30 | endif() 31 | 32 | if((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" OR CMAKE_CXX_COMPILER_ID MATCHES ".*GNU.*") AND WIN32) 33 | set(SUPPORTS_ASAN OFF) 34 | else() 35 | if (NOT WIN32) 36 | message(STATUS "Sanity checking AddressSanitizer, it should be supported on this platform") 37 | set(TEST_PROGRAM "int main() { return 0; }") 38 | 39 | # Check if AddressSanitizer works at link time 40 | set(CMAKE_REQUIRED_FLAGS "-fsanitize=address") 41 | set(CMAKE_REQUIRED_LINK_OPTIONS "-fsanitize=address") 42 | check_cxx_source_compiles("${TEST_PROGRAM}" HAS_ASAN_LINK_SUPPORT) 43 | 44 | if(HAS_ASAN_LINK_SUPPORT) 45 | message(STATUS "AddressSanitizer is supported at both compile and link time.") 46 | set(SUPPORTS_ASAN ON) 47 | else() 48 | message(WARNING "AddressSanitizer is NOT supported at link time.") 49 | set(SUPPORTS_ASAN OFF) 50 | endif() 51 | else() 52 | set(SUPPORTS_ASAN ON) 53 | endif() 54 | endif() 55 | endmacro() 56 | 57 | macro(myproject_setup_options) 58 | option(myproject_ENABLE_HARDENING "Enable hardening" ON) 59 | option(myproject_ENABLE_COVERAGE "Enable coverage reporting" OFF) 60 | cmake_dependent_option( 61 | myproject_ENABLE_GLOBAL_HARDENING 62 | "Attempt to push hardening options to built dependencies" 63 | ON 64 | myproject_ENABLE_HARDENING 65 | OFF) 66 | 67 | myproject_supports_sanitizers() 68 | 69 | if(NOT PROJECT_IS_TOP_LEVEL OR myproject_PACKAGING_MAINTAINER_MODE) 70 | option(myproject_ENABLE_IPO "Enable IPO/LTO" OFF) 71 | option(myproject_WARNINGS_AS_ERRORS "Treat Warnings As Errors" OFF) 72 | option(myproject_ENABLE_USER_LINKER "Enable user-selected linker" OFF) 73 | option(myproject_ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" OFF) 74 | option(myproject_ENABLE_SANITIZER_LEAK "Enable leak sanitizer" OFF) 75 | option(myproject_ENABLE_SANITIZER_UNDEFINED "Enable undefined sanitizer" OFF) 76 | option(myproject_ENABLE_SANITIZER_THREAD "Enable thread sanitizer" OFF) 77 | option(myproject_ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" OFF) 78 | option(myproject_ENABLE_UNITY_BUILD "Enable unity builds" OFF) 79 | option(myproject_ENABLE_CLANG_TIDY "Enable clang-tidy" OFF) 80 | option(myproject_ENABLE_CPPCHECK "Enable cpp-check analysis" OFF) 81 | option(myproject_ENABLE_PCH "Enable precompiled headers" OFF) 82 | option(myproject_ENABLE_CACHE "Enable ccache" OFF) 83 | else() 84 | option(myproject_ENABLE_IPO "Enable IPO/LTO" ON) 85 | option(myproject_WARNINGS_AS_ERRORS "Treat Warnings As Errors" ON) 86 | option(myproject_ENABLE_USER_LINKER "Enable user-selected linker" OFF) 87 | option(myproject_ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" ${SUPPORTS_ASAN}) 88 | option(myproject_ENABLE_SANITIZER_LEAK "Enable leak sanitizer" OFF) 89 | option(myproject_ENABLE_SANITIZER_UNDEFINED "Enable undefined sanitizer" ${SUPPORTS_UBSAN}) 90 | option(myproject_ENABLE_SANITIZER_THREAD "Enable thread sanitizer" OFF) 91 | option(myproject_ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" OFF) 92 | option(myproject_ENABLE_UNITY_BUILD "Enable unity builds" OFF) 93 | option(myproject_ENABLE_CLANG_TIDY "Enable clang-tidy" ON) 94 | option(myproject_ENABLE_CPPCHECK "Enable cpp-check analysis" ON) 95 | option(myproject_ENABLE_PCH "Enable precompiled headers" OFF) 96 | option(myproject_ENABLE_CACHE "Enable ccache" ON) 97 | endif() 98 | 99 | if(NOT PROJECT_IS_TOP_LEVEL) 100 | mark_as_advanced( 101 | myproject_ENABLE_IPO 102 | myproject_WARNINGS_AS_ERRORS 103 | myproject_ENABLE_USER_LINKER 104 | myproject_ENABLE_SANITIZER_ADDRESS 105 | myproject_ENABLE_SANITIZER_LEAK 106 | myproject_ENABLE_SANITIZER_UNDEFINED 107 | myproject_ENABLE_SANITIZER_THREAD 108 | myproject_ENABLE_SANITIZER_MEMORY 109 | myproject_ENABLE_UNITY_BUILD 110 | myproject_ENABLE_CLANG_TIDY 111 | myproject_ENABLE_CPPCHECK 112 | myproject_ENABLE_COVERAGE 113 | myproject_ENABLE_PCH 114 | myproject_ENABLE_CACHE) 115 | endif() 116 | 117 | myproject_check_libfuzzer_support(LIBFUZZER_SUPPORTED) 118 | if(LIBFUZZER_SUPPORTED AND (myproject_ENABLE_SANITIZER_ADDRESS OR myproject_ENABLE_SANITIZER_THREAD OR myproject_ENABLE_SANITIZER_UNDEFINED)) 119 | set(DEFAULT_FUZZER ON) 120 | else() 121 | set(DEFAULT_FUZZER OFF) 122 | endif() 123 | 124 | option(myproject_BUILD_FUZZ_TESTS "Enable fuzz testing executable" ${DEFAULT_FUZZER}) 125 | 126 | endmacro() 127 | 128 | macro(myproject_global_options) 129 | if(myproject_ENABLE_IPO) 130 | include(cmake/InterproceduralOptimization.cmake) 131 | myproject_enable_ipo() 132 | endif() 133 | 134 | myproject_supports_sanitizers() 135 | 136 | if(myproject_ENABLE_HARDENING AND myproject_ENABLE_GLOBAL_HARDENING) 137 | include(cmake/Hardening.cmake) 138 | if(NOT SUPPORTS_UBSAN 139 | OR myproject_ENABLE_SANITIZER_UNDEFINED 140 | OR myproject_ENABLE_SANITIZER_ADDRESS 141 | OR myproject_ENABLE_SANITIZER_THREAD 142 | OR myproject_ENABLE_SANITIZER_LEAK) 143 | set(ENABLE_UBSAN_MINIMAL_RUNTIME FALSE) 144 | else() 145 | set(ENABLE_UBSAN_MINIMAL_RUNTIME TRUE) 146 | endif() 147 | message("${myproject_ENABLE_HARDENING} ${ENABLE_UBSAN_MINIMAL_RUNTIME} ${myproject_ENABLE_SANITIZER_UNDEFINED}") 148 | myproject_enable_hardening(myproject_options ON ${ENABLE_UBSAN_MINIMAL_RUNTIME}) 149 | endif() 150 | endmacro() 151 | 152 | macro(myproject_local_options) 153 | if(PROJECT_IS_TOP_LEVEL) 154 | include(cmake/StandardProjectSettings.cmake) 155 | endif() 156 | 157 | add_library(myproject_warnings INTERFACE) 158 | add_library(myproject_options INTERFACE) 159 | 160 | include(cmake/CompilerWarnings.cmake) 161 | myproject_set_project_warnings( 162 | myproject_warnings 163 | ${myproject_WARNINGS_AS_ERRORS} 164 | "" 165 | "" 166 | "" 167 | "") 168 | 169 | if(myproject_ENABLE_USER_LINKER) 170 | include(cmake/Linker.cmake) 171 | myproject_configure_linker(myproject_options) 172 | endif() 173 | 174 | include(cmake/Sanitizers.cmake) 175 | myproject_enable_sanitizers( 176 | myproject_options 177 | ${myproject_ENABLE_SANITIZER_ADDRESS} 178 | ${myproject_ENABLE_SANITIZER_LEAK} 179 | ${myproject_ENABLE_SANITIZER_UNDEFINED} 180 | ${myproject_ENABLE_SANITIZER_THREAD} 181 | ${myproject_ENABLE_SANITIZER_MEMORY}) 182 | 183 | set_target_properties(myproject_options PROPERTIES UNITY_BUILD ${myproject_ENABLE_UNITY_BUILD}) 184 | 185 | if(myproject_ENABLE_PCH) 186 | target_precompile_headers( 187 | myproject_options 188 | INTERFACE 189 | 190 | 191 | ) 192 | endif() 193 | 194 | if(myproject_ENABLE_CACHE) 195 | include(cmake/Cache.cmake) 196 | myproject_enable_cache() 197 | endif() 198 | 199 | include(cmake/StaticAnalyzers.cmake) 200 | if(myproject_ENABLE_CLANG_TIDY) 201 | myproject_enable_clang_tidy(myproject_options ${myproject_WARNINGS_AS_ERRORS}) 202 | endif() 203 | 204 | if(myproject_ENABLE_CPPCHECK) 205 | myproject_enable_cppcheck(${myproject_WARNINGS_AS_ERRORS} "" # override cppcheck options 206 | ) 207 | endif() 208 | 209 | if(myproject_ENABLE_COVERAGE) 210 | include(cmake/Tests.cmake) 211 | myproject_enable_coverage(myproject_options) 212 | endif() 213 | 214 | if(myproject_WARNINGS_AS_ERRORS) 215 | check_cxx_compiler_flag("-Wl,--fatal-warnings" LINKER_FATAL_WARNINGS) 216 | if(LINKER_FATAL_WARNINGS) 217 | # This is not working consistently, so disabling for now 218 | # target_link_options(myproject_options INTERFACE -Wl,--fatal-warnings) 219 | endif() 220 | endif() 221 | 222 | if(myproject_ENABLE_HARDENING AND NOT myproject_ENABLE_GLOBAL_HARDENING) 223 | include(cmake/Hardening.cmake) 224 | if(NOT SUPPORTS_UBSAN 225 | OR myproject_ENABLE_SANITIZER_UNDEFINED 226 | OR myproject_ENABLE_SANITIZER_ADDRESS 227 | OR myproject_ENABLE_SANITIZER_THREAD 228 | OR myproject_ENABLE_SANITIZER_LEAK) 229 | set(ENABLE_UBSAN_MINIMAL_RUNTIME FALSE) 230 | else() 231 | set(ENABLE_UBSAN_MINIMAL_RUNTIME TRUE) 232 | endif() 233 | myproject_enable_hardening(myproject_options OFF ${ENABLE_UBSAN_MINIMAL_RUNTIME}) 234 | endif() 235 | 236 | endmacro() 237 | -------------------------------------------------------------------------------- /.github/workflows/template-janitor.yml: -------------------------------------------------------------------------------- 1 | # This workflow should cleanup everything unneeded from the template project 2 | 3 | name: Template Janitor 4 | 5 | on: 6 | pull_request: 7 | release: 8 | types: [published] 9 | push: 10 | branches: 11 | - main 12 | - develop 13 | permissions: 14 | contents: write 15 | 16 | env: 17 | TEMPLATES_PATH: ".github/template" 18 | 19 | 20 | jobs: 21 | 22 | template-cleanup: 23 | name: Cleanup after create 24 | runs-on: ubuntu-latest 25 | strategy: 26 | matrix: 27 | compiler: 28 | - gcc-14 29 | generator: 30 | - "Unix Makefiles" 31 | build_type: 32 | - Debug 33 | packaging_maintainer_mode: 34 | - OFF 35 | 36 | steps: 37 | - uses: actions/checkout@v3 38 | 39 | - name: Setup Cache 40 | uses: ./.github/actions/setup_cache 41 | with: 42 | compiler: ${{ matrix.compiler }} 43 | build_type: ${{ matrix.build_type }} 44 | package_maintainer_mode: ${{ matrix.package_maintainer_mode }} 45 | generator: ${{ matrix.generator }} 46 | 47 | - name: Get organization and project name 48 | run: | 49 | echo "NEW_ORG=${{ github.repository_owner }}" >> $GITHUB_ENV 50 | echo "NEW_PROJECT=${{ github.event.repository.name }}" >> $GITHUB_ENV 51 | echo "NEW_URL=${{ github.repositoryUrl }}" >> $GITHUB_ENV 52 | 53 | - uses: octokit/request-action@v2.x 54 | id: get_repo_meta 55 | with: 56 | route: GET /repos/${{ env.NEW_ORG }}/${{ env.NEW_PROJECT }} 57 | env: 58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | 60 | - name: Use testing variables if still a template 61 | if: fromJson(steps.get_repo_meta.outputs.data).is_template == true 62 | run: | 63 | # This name is unsafe because it is not a valid C++ identifier 64 | echo "NEW_PROJECT=my-unsafe.project" >> $GITHUB_ENV 65 | 66 | - name: Add safe replacement variable versions 67 | run: | 68 | # hyphens and dots in c++ identifiers are forbidden. Use underscores instead. 69 | NEW_SAFE_PROJECT=$(echo ${{ env.NEW_PROJECT }} | sed "s/-/_/g" | sed "s/\./_/g" ) 70 | echo "NEW_SAFE_PROJECT=$NEW_SAFE_PROJECT" >> $GITHUB_ENV 71 | 72 | # Rename all cpp_starter_project occurences to current repository and remove this workflow 73 | - name: Insert new org and project 74 | run: | 75 | # rename the CMake project to match the github project 76 | find src include test fuzz_test cmake -type f -exec sed -i "s/myproject/${{ env.NEW_SAFE_PROJECT }}/gi" .github/constants.env CMakeLists.txt Dependencies.cmake ProjectOptions.cmake .github/workflows/ci.yml .github/workflows/codeql-analysis.yml configured_files/config.hpp.in {} + 77 | 78 | # Update URL placeholders for project 79 | sed -i "s|%%myurl%%|${{ fromJson(steps.get_repo_meta.outputs.data).html_url }}|gi" CMakeLists.txt 80 | 81 | # fill in placeholders of readme and move it into place 82 | sed -i "s/%%myorg%%/${{ env.NEW_ORG }}/g" ${{ env.TEMPLATES_PATH }}/README.md 83 | sed -i "s/%%myproject%%/${{ env.NEW_PROJECT }}/g" ${{ env.TEMPLATES_PATH }}/README.md 84 | sed -i "s|%%description%%|${{ fromJson(steps.get_repo_meta.outputs.data).description }}|g" ${{ env.TEMPLATES_PATH }}/README.md 85 | mv include/myproject include/${{ env.NEW_SAFE_PROJECT }} 86 | cp ${{ env.TEMPLATES_PATH }}/README.md README.md 87 | 88 | - name: Print diff after replacement 89 | run: | 90 | # Exclude the README as that is checked separately! 91 | git diff ':!README.md' 92 | # following should not have any diffs 93 | diff ${{ env.TEMPLATES_PATH }}/README.md README.md 94 | 95 | - name: Remove unwanted files 96 | run: | 97 | # No tests needed as this will fail if any file from the list is missing/misspelled 98 | xargs rm -r < ${{ env.TEMPLATES_PATH }}/removal-list 99 | 100 | - name: Clean up before commit and push 101 | run: | 102 | rm -r ${{ env.TEMPLATES_PATH }} 103 | 104 | # Can we get that from a variable? 105 | # Remove this workflow as it has fulfilled its purpose 106 | rm .github/workflows/template-janitor.yml 107 | 108 | - name: Setup Cpp 109 | uses: aminya/setup-cpp@v1 110 | with: 111 | compiler: ${{ matrix.compiler }} 112 | 113 | cmake: true 114 | ninja: false 115 | vcpkg: false 116 | ccache: false 117 | clangtidy: false 118 | 119 | cppcheck: false 120 | 121 | gcovr: false 122 | opencppcoverage: false 123 | 124 | - name: Project Name 125 | uses: cardinalby/export-env-action@v2 126 | with: 127 | envFile: '.github/constants.env' 128 | 129 | 130 | 131 | - name: Test simple configuration to make sure nothing broke 132 | run: | 133 | cmake -S . -B ./build -G "${{ matrix.generator }}" -DCMAKE_BUILD_TYPE:STRING=${{ matrix.build_type }} -D${{ env.NEW_SAFE_PROJECT }}_PACKAGING_MAINTAINER_MODE:BOOL=ON 134 | # Build it because we may have broken something in the cpp/hpp files 135 | cmake --build build 136 | 137 | - uses: EndBug/add-and-commit@v9 138 | # only commit and push if we are not a template project anymore! 139 | if: fromJson(steps.get_repo_meta.outputs.data).is_template != true 140 | with: 141 | add: -A 142 | author_name: Template Janitor 143 | author_email: template.janitor@example.com 144 | message: 'Cleanup template and initialize repository' 145 | pathspec_error_handling: exitImmediately 146 | env: 147 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 148 | 149 | 150 | 151 | template-rename: 152 | name: Renames template when a new name is detected 153 | runs-on: ubuntu-latest 154 | strategy: 155 | matrix: 156 | compiler: 157 | - gcc-11 158 | generator: 159 | - "Unix Makefiles" 160 | build_type: 161 | - Debug 162 | packaging_maintainer_mode: 163 | - ON 164 | 165 | steps: 166 | - uses: actions/checkout@v3 167 | 168 | - name: Setup Cache 169 | uses: ./.github/actions/setup_cache 170 | with: 171 | compiler: ${{ matrix.compiler }} 172 | build_type: ${{ matrix.build_type }} 173 | packaging_maintainer_mode: ${{ matrix.packaging_maintainer_mode }} 174 | generator: ${{ matrix.generator }} 175 | 176 | - name: Get organization and project name 177 | run: | 178 | echo "TEST_RUN=false" >> $GITHUB_ENV 179 | echo "NEW_ORG=${{ github.repository_owner }}" >> $GITHUB_ENV 180 | echo "NEW_PROJECT=${{ github.event.repository.name }}" >> $GITHUB_ENV 181 | echo "NEW_REPOSITORY=${{ github.repository }}" >> $GITHUB_ENV 182 | echo "TEMPLATE_NAME=`cat ${{ env.TEMPLATES_PATH }}/template_name`" >> $GITHUB_ENV 183 | echo "TEMPLATE_REPOSITORY=`cat ${{ env.TEMPLATES_PATH }}/template_repository`" >> $GITHUB_ENV 184 | 185 | - uses: octokit/request-action@v2.x 186 | id: get_repo_meta 187 | with: 188 | route: GET /repos/${{ env.NEW_ORG }}/${{ env.NEW_PROJECT }} 189 | env: 190 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 191 | 192 | - name: Setup fake test org/project names if project didn't change 193 | if: env.TEMPLATE_NAME == env.NEW_PROJECT 194 | run: | 195 | echo "TEST_RUN=true" >> $GITHUB_ENV 196 | echo "NEW_ORG=${{ github.repository_owner }}" >> $GITHUB_ENV 197 | echo "NEW_PROJECT=TEST_PROJECT" >> $GITHUB_ENV 198 | echo "NEW_REPOSITORY=TEST_REPOSITORY" >> $GITHUB_ENV 199 | 200 | 201 | # Rename all cpp_starter_project occurrences to current repository and remove this workflow 202 | - name: Update repository to match new template information 203 | run: | 204 | # Update the README and template files to match the new org / repository names 205 | sed -i "s|${{ env.TEMPLATE_REPOSITORY }}|${{ env.NEW_REPOSITORY }}|g" README.md ${{ env.TEMPLATES_PATH }}/template_repository 206 | sed -i "s|${{ env.TEMPLATE_NAME }}|${{ env.NEW_PROJECT }}|g" README.md ${{ env.TEMPLATES_PATH }}/template_name 207 | 208 | - name: Print diff after template name replacement 209 | run: | 210 | git diff 211 | 212 | - name: Setup Cpp 213 | uses: aminya/setup-cpp@v1 214 | with: 215 | compiler: gcc 216 | 217 | cmake: true 218 | ninja: false 219 | vcpkg: false 220 | ccache: false 221 | clangtidy: false 222 | 223 | cppcheck: false 224 | 225 | gcovr: false 226 | opencppcoverage: false 227 | 228 | 229 | - name: Test simple configuration to make sure nothing broke (default compiler,cmake,packaging_maintainer_mode OFF) 230 | run: | 231 | cmake -S . -B ./build -G "${{ matrix.generator }}" -DCMAKE_BUILD_TYPE:STRING=${{ matrix.build_type }} -D${{ env.PROJECT_NAME }}_PACKAGING_MAINTAINER_MODE:BOOL=ON 232 | 233 | - uses: EndBug/add-and-commit@v9 234 | # only commit and push if we are a template and project name has changed 235 | if: fromJson(steps.get_repo_meta.outputs.data).is_template == true && env.TEST_RUN == 'false' 236 | with: 237 | add: -A 238 | author_name: Template Janitor 239 | author_email: template.janitor@example.com 240 | message: 'Change Template Name' 241 | pathspec_error_handling: exitImmediately 242 | env: 243 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 244 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 21, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "conf-common", 11 | "description": "General settings that apply to all configurations", 12 | "hidden": true, 13 | "generator": "Ninja", 14 | "binaryDir": "${sourceDir}/out/build/${presetName}", 15 | "installDir": "${sourceDir}/out/install/${presetName}" 16 | }, 17 | { 18 | "name": "conf-windows-common", 19 | "description": "Windows settings for MSBuild toolchain that apply to msvc and clang", 20 | "hidden": true, 21 | "inherits": "conf-common", 22 | "condition": { 23 | "type": "equals", 24 | "lhs": "${hostSystemName}", 25 | "rhs": "Windows" 26 | }, 27 | "architecture": { 28 | "value": "x64", 29 | "strategy": "external" 30 | }, 31 | "toolset": { 32 | "value": "host=x64", 33 | "strategy": "external" 34 | }, 35 | "cacheVariables": { 36 | "ENABLE_CPPCHECK_DEFAULT": "FALSE", 37 | "ENABLE_CLANG_TIDY_DEFAULT": "FALSE" 38 | } 39 | }, 40 | { 41 | "name": "conf-unixlike-common", 42 | "description": "Unix-like OS settings for gcc and clang toolchains", 43 | "hidden": true, 44 | "inherits": "conf-common", 45 | "condition": { 46 | "type": "inList", 47 | "string": "${hostSystemName}", 48 | "list": [ 49 | "Linux", 50 | "Darwin" 51 | ] 52 | }, 53 | "vendor": { 54 | "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { 55 | "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" 56 | } 57 | } 58 | }, 59 | { 60 | "name": "windows-msvc-debug-developer-mode", 61 | "displayName": "msvc Debug (Developer Mode)", 62 | "description": "Target Windows with the msvc compiler, debug build type", 63 | "inherits": "conf-windows-common", 64 | "cacheVariables": { 65 | "CMAKE_C_COMPILER": "cl", 66 | "CMAKE_CXX_COMPILER": "cl", 67 | "CMAKE_BUILD_TYPE": "Debug", 68 | "ENABLE_DEVELOPER_MODE": "ON" 69 | } 70 | }, 71 | { 72 | "name": "windows-msvc-release-developer-mode", 73 | "displayName": "msvc Release (Developer Mode)", 74 | "description": "Target Windows with the msvc compiler, release build type", 75 | "inherits": "conf-windows-common", 76 | "cacheVariables": { 77 | "CMAKE_C_COMPILER": "cl", 78 | "CMAKE_CXX_COMPILER": "cl", 79 | "CMAKE_BUILD_TYPE": "RelWithDebInfo", 80 | "ENABLE_DEVELOPER_MODE": "ON" 81 | } 82 | }, 83 | { 84 | "name": "windows-msvc-debug-user-mode", 85 | "displayName": "msvc Debug (User Mode)", 86 | "description": "Target Windows with the msvc compiler, debug build type", 87 | "inherits": "conf-windows-common", 88 | "cacheVariables": { 89 | "CMAKE_C_COMPILER": "cl", 90 | "CMAKE_CXX_COMPILER": "cl", 91 | "CMAKE_BUILD_TYPE": "Debug", 92 | "ENABLE_DEVELOPER_MODE": "OFF" 93 | } 94 | }, 95 | { 96 | "name": "windows-msvc-release-user-mode", 97 | "displayName": "msvc Release (User Mode)", 98 | "description": "Target Windows with the msvc compiler, release build type", 99 | "inherits": "conf-windows-common", 100 | "cacheVariables": { 101 | "CMAKE_C_COMPILER": "cl", 102 | "CMAKE_CXX_COMPILER": "cl", 103 | "CMAKE_BUILD_TYPE": "RelWithDebInfo", 104 | "ENABLE_DEVELOPER_MODE": "OFF" 105 | } 106 | }, 107 | { 108 | "name": "windows-clang-debug", 109 | "displayName": "clang Debug", 110 | "description": "Target Windows with the clang compiler, debug build type", 111 | "inherits": "conf-windows-common", 112 | "cacheVariables": { 113 | "CMAKE_C_COMPILER": "clang-cl", 114 | "CMAKE_CXX_COMPILER": "clang-cl", 115 | "CMAKE_BUILD_TYPE": "Debug" 116 | }, 117 | "vendor": { 118 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 119 | "intelliSenseMode": "windows-clang-x64" 120 | } 121 | } 122 | }, 123 | { 124 | "name": "windows-clang-release", 125 | "displayName": "clang Release", 126 | "description": "Target Windows with the clang compiler, release build type", 127 | "inherits": "conf-windows-common", 128 | "cacheVariables": { 129 | "CMAKE_C_COMPILER": "clang-cl", 130 | "CMAKE_CXX_COMPILER": "clang-cl", 131 | "CMAKE_BUILD_TYPE": "RelWithDebInfo" 132 | }, 133 | "vendor": { 134 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 135 | "intelliSenseMode": "windows-clang-x64" 136 | } 137 | } 138 | }, 139 | { 140 | "name": "unixlike-gcc-debug", 141 | "displayName": "gcc Debug", 142 | "description": "Target Unix-like OS with the gcc compiler, debug build type", 143 | "inherits": "conf-unixlike-common", 144 | "cacheVariables": { 145 | "CMAKE_C_COMPILER": "gcc", 146 | "CMAKE_CXX_COMPILER": "g++", 147 | "CMAKE_BUILD_TYPE": "Debug" 148 | } 149 | }, 150 | { 151 | "name": "unixlike-gcc-release", 152 | "displayName": "gcc Release", 153 | "description": "Target Unix-like OS with the gcc compiler, release build type", 154 | "inherits": "conf-unixlike-common", 155 | "cacheVariables": { 156 | "CMAKE_C_COMPILER": "gcc", 157 | "CMAKE_CXX_COMPILER": "g++", 158 | "CMAKE_BUILD_TYPE": "RelWithDebInfo" 159 | } 160 | }, 161 | { 162 | "name": "unixlike-clang-debug", 163 | "displayName": "clang Debug", 164 | "description": "Target Unix-like OS with the clang compiler, debug build type", 165 | "inherits": "conf-unixlike-common", 166 | "cacheVariables": { 167 | "CMAKE_C_COMPILER": "clang", 168 | "CMAKE_CXX_COMPILER": "clang++", 169 | "CMAKE_BUILD_TYPE": "Debug" 170 | } 171 | }, 172 | { 173 | "name": "unixlike-clang-release", 174 | "displayName": "clang Release", 175 | "description": "Target Unix-like OS with the clang compiler, release build type", 176 | "inherits": "conf-unixlike-common", 177 | "cacheVariables": { 178 | "CMAKE_C_COMPILER": "clang", 179 | "CMAKE_CXX_COMPILER": "clang++", 180 | "CMAKE_BUILD_TYPE": "RelWithDebInfo" 181 | } 182 | } 183 | ], 184 | "testPresets": [ 185 | { 186 | "name": "test-common", 187 | "description": "Test CMake settings that apply to all configurations", 188 | "hidden": true, 189 | "output": { 190 | "outputOnFailure": true 191 | }, 192 | "execution": { 193 | "noTestsAction": "error", 194 | "stopOnFailure": true 195 | } 196 | }, 197 | { 198 | "name": "test-windows-msvc-debug-developer-mode", 199 | "displayName": "Strict", 200 | "description": "Enable output and stop on failure", 201 | "inherits": "test-common", 202 | "configurePreset": "windows-msvc-debug-developer-mode" 203 | }, 204 | { 205 | "name": "test-windows-msvc-release-developer-mode", 206 | "displayName": "Strict", 207 | "description": "Enable output and stop on failure", 208 | "inherits": "test-common", 209 | "configurePreset": "windows-msvc-release-developer-mode" 210 | }, 211 | { 212 | "name": "test-windows-clang-debug", 213 | "displayName": "Strict", 214 | "description": "Enable output and stop on failure", 215 | "inherits": "test-common", 216 | "configurePreset": "windows-clang-debug" 217 | }, 218 | { 219 | "name": "test-windows-clang-release", 220 | "displayName": "Strict", 221 | "description": "Enable output and stop on failure", 222 | "inherits": "test-common", 223 | "configurePreset": "windows-clang-release" 224 | }, 225 | { 226 | "name": "test-unixlike-gcc-debug", 227 | "displayName": "Strict", 228 | "description": "Enable output and stop on failure", 229 | "inherits": "test-common", 230 | "configurePreset": "unixlike-gcc-debug" 231 | }, 232 | { 233 | "name": "test-unixlike-gcc-release", 234 | "displayName": "Strict", 235 | "description": "Enable output and stop on failure", 236 | "inherits": "test-common", 237 | "configurePreset": "unixlike-gcc-release" 238 | }, 239 | { 240 | "name": "test-unixlike-clang-debug", 241 | "displayName": "Strict", 242 | "description": "Enable output and stop on failure", 243 | "inherits": "test-common", 244 | "configurePreset": "unixlike-clang-debug" 245 | }, 246 | { 247 | "name": "test-unixlike-clang-release", 248 | "displayName": "Strict", 249 | "description": "Enable output and stop on failure", 250 | "inherits": "test-common", 251 | "configurePreset": "unixlike-clang-release" 252 | } 253 | ] 254 | } -------------------------------------------------------------------------------- /src/ftxui_sample/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | #include 19 | #include // for Slider 20 | #include // for ScreenInteractive 21 | #include 22 | 23 | #include 24 | 25 | // This file will be generated automatically when cur_you run the CMake 26 | // configuration step. It creates a namespace called `myproject`. You can modify 27 | // the source template at `configured_files/config.hpp.in`. 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | template struct GameBoard 35 | { 36 | static constexpr std::size_t width = Width; 37 | static constexpr std::size_t height = Height; 38 | 39 | std::array, width> strings; 40 | std::array, width> values{}; 41 | 42 | std::size_t move_count{ 0 }; 43 | 44 | std::string &get_string(std::size_t cur_x, std::size_t cur_y) { return strings.at(cur_x).at(cur_y); } 45 | 46 | 47 | void set(std::size_t cur_x, std::size_t cur_y, bool new_value) 48 | { 49 | get(cur_x, cur_y) = new_value; 50 | 51 | if (new_value) { 52 | get_string(cur_x, cur_y) = " ON"; 53 | } else { 54 | get_string(cur_x, cur_y) = "OFF"; 55 | } 56 | } 57 | 58 | void visit(auto visitor) 59 | { 60 | for (std::size_t cur_x = 0; cur_x < width; ++cur_x) { 61 | for (std::size_t cur_y = 0; cur_y < height; ++cur_y) { visitor(cur_x, cur_y, *this); } 62 | } 63 | } 64 | 65 | [[nodiscard]] bool get(std::size_t cur_x, std::size_t cur_y) const { return values.at(cur_x).at(cur_y); } 66 | 67 | [[nodiscard]] bool &get(std::size_t cur_x, std::size_t cur_y) { return values.at(cur_x).at(cur_y); } 68 | 69 | GameBoard() 70 | { 71 | visit([](const auto cur_x, const auto cur_y, auto &gameboard) { gameboard.set(cur_x, cur_y, true); }); 72 | } 73 | 74 | void update_strings() 75 | { 76 | for (std::size_t cur_x = 0; cur_x < width; ++cur_x) { 77 | for (std::size_t cur_y = 0; cur_y < height; ++cur_y) { set(cur_x, cur_y, get(cur_x, cur_y)); } 78 | } 79 | } 80 | 81 | void toggle(std::size_t cur_x, std::size_t cur_y) { set(cur_x, cur_y, !get(cur_x, cur_y)); } 82 | 83 | void press(std::size_t cur_x, std::size_t cur_y) 84 | { 85 | ++move_count; 86 | toggle(cur_x, cur_y); 87 | if (cur_x > 0) { toggle(cur_x - 1, cur_y); } 88 | if (cur_y > 0) { toggle(cur_x, cur_y - 1); } 89 | if (cur_x < width - 1) { toggle(cur_x + 1, cur_y); } 90 | if (cur_y < height - 1) { toggle(cur_x, cur_y + 1); } 91 | } 92 | 93 | [[nodiscard]] bool solved() const 94 | { 95 | for (std::size_t cur_x = 0; cur_x < width; ++cur_x) { 96 | for (std::size_t cur_y = 0; cur_y < height; ++cur_y) { 97 | if (!get(cur_x, cur_y)) { return false; } 98 | } 99 | } 100 | 101 | return true; 102 | } 103 | }; 104 | 105 | namespace { 106 | void consequence_game() 107 | { 108 | auto screen = ftxui::ScreenInteractive::TerminalOutput(); 109 | 110 | GameBoard<3, 3> game_board; 111 | 112 | std::string quit_text; 113 | 114 | const auto update_quit_text = [&quit_text](const auto &game_board_param) { 115 | quit_text = fmt::format("Quit ({} moves)", game_board_param.move_count); 116 | if (game_board_param.solved()) { quit_text += " Solved!"; } 117 | }; 118 | 119 | const auto make_buttons = [&] { 120 | std::vector buttons; 121 | for (std::size_t cur_x = 0; cur_x < game_board.width; ++cur_x) { 122 | for (std::size_t cur_y = 0; cur_y < game_board.height; ++cur_y) { 123 | buttons.push_back(ftxui::Button(&game_board.get_string(cur_x, cur_y), [=, &game_board] { 124 | if (!game_board.solved()) { game_board.press(cur_x, cur_y); } 125 | update_quit_text(game_board); 126 | })); 127 | } 128 | } 129 | return buttons; 130 | }; 131 | 132 | auto buttons = make_buttons(); 133 | 134 | auto quit_button = ftxui::Button(&quit_text, screen.ExitLoopClosure()); 135 | 136 | auto make_layout = [&] { 137 | std::vector rows; 138 | 139 | std::size_t idx = 0; 140 | 141 | for (std::size_t cur_x = 0; cur_x < game_board.width; ++cur_x) { 142 | std::vector row; 143 | for (std::size_t cur_y = 0; cur_y < game_board.height; ++cur_y) { 144 | row.push_back(buttons[idx]->Render()); 145 | ++idx; 146 | } 147 | rows.push_back(ftxui::hbox(std::move(row))); 148 | } 149 | 150 | rows.push_back(ftxui::hbox({ quit_button->Render() })); 151 | 152 | return ftxui::vbox(std::move(rows)); 153 | }; 154 | 155 | 156 | static constexpr int randomization_iterations = 100; 157 | static constexpr int random_seed = 42; 158 | 159 | std::mt19937 gen32{ random_seed };// NOLINT fixed seed 160 | 161 | // NOLINTNEXTLINE This cannot be const 162 | std::uniform_int_distribution cur_x(static_cast(0), game_board.width - 1); 163 | // NOLINTNEXTLINE This cannot be const 164 | std::uniform_int_distribution cur_y(static_cast(0), game_board.height - 1); 165 | 166 | for (int i = 0; i < randomization_iterations; ++i) { game_board.press(cur_x(gen32), cur_y(gen32)); } 167 | game_board.move_count = 0; 168 | update_quit_text(game_board); 169 | 170 | auto all_buttons = buttons; 171 | all_buttons.push_back(quit_button); 172 | auto container = ftxui::Container::Horizontal(all_buttons); 173 | 174 | auto renderer = ftxui::Renderer(container, make_layout); 175 | 176 | screen.Loop(renderer); 177 | } 178 | }// namespace 179 | 180 | struct Color 181 | { 182 | lefticus::tools::uint_np8_t R{ static_cast(0) }; 183 | lefticus::tools::uint_np8_t G{ static_cast(0) }; 184 | lefticus::tools::uint_np8_t B{ static_cast(0) }; 185 | }; 186 | 187 | // A simple way of representing a bitmap on screen using only characters 188 | struct Bitmap : ftxui::Node 189 | { 190 | Bitmap(std::size_t width, std::size_t height)// NOLINT same typed parameters adjacent to each other 191 | : width_(width), height_(height) 192 | {} 193 | 194 | Color &at(std::size_t cur_x, std::size_t cur_y) { return pixels.at((width_ * cur_y) + cur_x); } 195 | 196 | void ComputeRequirement() override 197 | { 198 | requirement_.min_x = static_cast(width_); 199 | requirement_.min_y = static_cast(height_ / 2); 200 | } 201 | 202 | void Render(ftxui::Screen &screen) override 203 | { 204 | for (std::size_t cur_x = 0; cur_x < width_; ++cur_x) { 205 | for (std::size_t cur_y = 0; cur_y < height_ / 2; ++cur_y) { 206 | auto &pixel = screen.PixelAt(box_.x_min + static_cast(cur_x), box_.y_min + static_cast(cur_y)); 207 | pixel.character = "▄"; 208 | const auto &top_color = at(cur_x, cur_y * 2); 209 | const auto &bottom_color = at(cur_x, (cur_y * 2) + 1); 210 | pixel.background_color = ftxui::Color{ top_color.R.get(), top_color.G.get(), top_color.B.get() }; 211 | pixel.foreground_color = ftxui::Color{ bottom_color.R.get(), bottom_color.G.get(), bottom_color.B.get() }; 212 | } 213 | } 214 | } 215 | 216 | [[nodiscard]] auto width() const noexcept { return width_; } 217 | 218 | [[nodiscard]] auto height() const noexcept { return height_; } 219 | 220 | [[nodiscard]] auto &data() noexcept { return pixels; } 221 | 222 | private: 223 | std::size_t width_; 224 | std::size_t height_; 225 | 226 | std::vector pixels = std::vector(width_ * height_, Color{}); 227 | }; 228 | 229 | namespace { 230 | void game_iteration_canvas() 231 | { 232 | // this should probably have a `bitmap` helper function that does what cur_you expect 233 | // similar to the other parts of FTXUI 234 | auto bm = std::make_shared(50, 50);// NOLINT magic numbers 235 | auto small_bm = std::make_shared(6, 6);// NOLINT magic numbers 236 | 237 | double fps = 0; 238 | 239 | std::size_t max_row = 0; 240 | std::size_t max_col = 0; 241 | 242 | // to do, add total game time clock also, not just current elapsed time 243 | auto game_iteration = [&](const std::chrono::steady_clock::duration elapsed_time) { 244 | // in here we simulate however much game time has elapsed. Update animations, 245 | // run character AI, whatever, update stats, etc 246 | 247 | // this isn't actually timing based for now, it's just updating the display however fast it can 248 | fps = 1.0 249 | / (static_cast(std::chrono::duration_cast(elapsed_time).count()) 250 | / 1'000'000.0);// NOLINT magic numbers 251 | 252 | for (std::size_t row = 0; row < max_row; ++row) { 253 | for (std::size_t col = 0; col < bm->width(); ++col) { ++(bm->at(col, row).R); } 254 | } 255 | 256 | for (std::size_t row = 0; row < bm->height(); ++row) { 257 | for (std::size_t col = 0; col < max_col; ++col) { ++(bm->at(col, row).G); } 258 | } 259 | 260 | // for the fun of it, let's have a second window doing interesting things 261 | auto &small_bm_pixel = 262 | small_bm->data().at(static_cast(elapsed_time.count()) % small_bm->data().size()); 263 | 264 | switch (elapsed_time.count() % 3) { 265 | case 0: 266 | small_bm_pixel.R += 11;// NOLINT Magic Number 267 | break; 268 | case 1: 269 | small_bm_pixel.G += 11;// NOLINT Magic Number 270 | break; 271 | case 2: 272 | small_bm_pixel.B += 11;// NOLINT Magic Number 273 | break; 274 | default:// literally impossible 275 | std::unreachable(); 276 | } 277 | 278 | 279 | ++max_row; 280 | if (max_row >= bm->height()) { max_row = 0; } 281 | ++max_col; 282 | if (max_col >= bm->width()) { max_col = 0; } 283 | }; 284 | 285 | auto screen = ftxui::ScreenInteractive::TerminalOutput(); 286 | 287 | int counter = 0; 288 | 289 | auto last_time = std::chrono::steady_clock::now(); 290 | 291 | auto make_layout = [&] { 292 | // This code actually processes the draw event 293 | const auto new_time = std::chrono::steady_clock::now(); 294 | 295 | ++counter; 296 | // we will dispatch to the game_iteration function, where the work happens 297 | game_iteration(new_time - last_time); 298 | last_time = new_time; 299 | 300 | // now actually draw the game elements 301 | return ftxui::hbox({ bm | ftxui::border, 302 | ftxui::vbox({ ftxui::text("Frame: " + std::to_string(counter)), 303 | ftxui::text("FPS: " + std::to_string(fps)), 304 | small_bm | ftxui::border }) }); 305 | }; 306 | 307 | auto renderer = ftxui::Renderer(make_layout); 308 | 309 | 310 | std::atomic refresh_ui_continue = true; 311 | 312 | // This thread exists to make sure that the event queue has an event to 313 | // process at approximately a rate of 30 FPS 314 | std::thread refresh_ui([&] { 315 | while (refresh_ui_continue) { 316 | using namespace std::chrono_literals; 317 | std::this_thread::sleep_for(1.0s / 30.0);// NOLINT magic numbers 318 | screen.PostEvent(ftxui::Event::Custom); 319 | } 320 | }); 321 | 322 | screen.Loop(renderer); 323 | 324 | refresh_ui_continue = false; 325 | refresh_ui.join(); 326 | } 327 | }// namespace 328 | 329 | // NOLINTNEXTLINE(bugprone-exception-escape) 330 | int main(int argc, const char **argv) 331 | { 332 | try { 333 | CLI::App app{ fmt::format("{} version {}", myproject::cmake::project_name, myproject::cmake::project_version) }; 334 | 335 | std::optional message; 336 | app.add_option("-m,--message", message, "A message to print back out"); 337 | bool show_version = false; 338 | app.add_flag("--version", show_version, "Show version information"); 339 | 340 | bool is_turn_based = false; 341 | auto *turn_based = app.add_flag("--turn_based", is_turn_based); 342 | 343 | bool is_loop_based = false; 344 | auto *loop_based = app.add_flag("--loop_based", is_loop_based); 345 | 346 | turn_based->excludes(loop_based); 347 | loop_based->excludes(turn_based); 348 | 349 | 350 | CLI11_PARSE(app, argc, argv); 351 | 352 | if (show_version) { 353 | fmt::print("{}\n", myproject::cmake::project_version); 354 | return EXIT_SUCCESS; 355 | } 356 | 357 | if (is_turn_based) { 358 | consequence_game(); 359 | } else { 360 | game_iteration_canvas(); 361 | } 362 | 363 | } catch (const std::exception &e) { 364 | spdlog::error("Unhandled exception in main: {}", e.what()); 365 | } 366 | } 367 | --------------------------------------------------------------------------------