diff --git a/.clang-tidy b/.clang-tidy index 4365c88d..cdfaf8b2 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -18,6 +18,11 @@ WarningsAsErrors: '' HeaderFilterRegex: '' FormatStyle: none +CheckOptions: + - key: readability-identifier-length.IgnoredVariableNames + value: 'x|y|z' + - key: readability-identifier-length.IgnoredParameterNames + value: 'x|y|z' diff --git a/.github/actions/setup_cache/action.yml b/.github/actions/setup_cache/action.yml index 3291fce9..7bf260e2 100644 --- a/.github/actions/setup_cache/action.yml +++ b/.github/actions/setup_cache/action.yml @@ -11,7 +11,7 @@ inputs: generator: required: true type: string - developer_mode: + packaging_maintainer_mode: required: true type: string @@ -20,19 +20,13 @@ runs: using: "composite" steps: - name: Cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: # You might want to add .ccache to your cache configuration? path: | - ~/vcpkg - ./build/vcpkg_installed - ${{ env.CONAN_USER_HOME }} ~/.cache/pip - ${{ env.HOME }}/.cache/vcpkg/archives - ${{ env.XDG_CACHE_HOME }}/vcpkg/archives - ${{ env.LOCALAPPDATA }}\vcpkg\archives - ${{ env.APPDATA }}\vcpkg\archives - key: ${{ runner.os }}-${{ inputs.compiler }}-${{ inputs.build_type }}-${{ hashFiles('./conanfile.txt')}}-${{ inputs.generator }}-${{ inputs.developer_mode }}-${{ hashFiles('**/CMakeLists.txt') }}-${{ hashFiles('./vcpkg.json')}} + ~/.ccache + key: ${{ runner.os }}-${{ inputs.compiler }}-${{ inputs.build_type }}-${{ inputs.generator }}-${{ inputs.packaging_maintainer_mode }}-${{ hashFiles('**/CMakeLists.txt') }} restore-keys: | - ${{ runner.os }}-${{ inputs.compiler }}-${{ inputs.build_type }}-${{ hashFiles('./conanfile.txt') }} + ${{ runner.os }}-${{ inputs.compiler }}-${{ inputs.build_type }} diff --git a/.github/constants.env b/.github/constants.env new file mode 100644 index 00000000..5c8528a0 --- /dev/null +++ b/.github/constants.env @@ -0,0 +1 @@ +PROJECT_NAME=myproject diff --git a/.github/template/template_name b/.github/template/template_name index d82c7112..eb23e258 100644 --- a/.github/template/template_name +++ b/.github/template/template_name @@ -1 +1 @@ -ftxui_template +cmake_template diff --git a/.github/template/template_repository b/.github/template/template_repository index 310718d0..5494e8ad 100644 --- a/.github/template/template_repository +++ b/.github/template/template_repository @@ -1 +1 @@ -cpp-best-practices/ftxui_template +cpp-best-practices/cmake_template diff --git a/.github/workflows/auto-clang-format.yml b/.github/workflows/auto-clang-format.yml index c39fb5e7..b4e0a1b9 100644 --- a/.github/workflows/auto-clang-format.yml +++ b/.github/workflows/auto-clang-format.yml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: DoozyX/clang-format-lint-action@v0.13 with: source: '.' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff341bd5..db6c6110 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,11 +10,9 @@ on: - develop env: - # Conan cache environment variables - CONAN_SYSREQUIRES_MODE: enabled - CONAN_USER_HOME: "${{ github.workspace }}/conan-cache" - CONAN_USER_HOME_SHORT: "${{ github.workspace }}/conan-cache/short" - CLANG_TIDY_VERSION: "13.0.0" + CLANG_TIDY_VERSION: "15.0.2" + VERBOSE: 1 + jobs: Test: @@ -35,17 +33,19 @@ jobs: - macos-10.15 - windows-2019 compiler: - # you can specify the version after `-` like "llvm-13.0.0". - - llvm-13.0.0 + # you can specify the version after `-` like "llvm-15.0.2". + - llvm-15.0.2 - gcc-11 generator: - "Ninja Multi-Config" build_type: - Release - Debug - developer_mode: + packaging_maintainer_mode: - ON - OFF + build_shared: + - OFF exclude: # mingw is determined by this author to be too buggy to support @@ -57,12 +57,18 @@ jobs: # if you try to use a compiler that does not have gcov set - compiler: gcc-11 gcov_executable: gcov - - compiler: llvm-13.0.0 + enable_ipo: On + + - compiler: llvm-15.0.2 + enable_ipo: Off gcov_executable: "llvm-cov gcov" + - os: macos-10.15 + enable_ipo: Off + # Set up preferred package generators, for given build configurations - build_type: Release - developer_mode: OFF + packaging_maintainer_mode: OFF package_generator: TBZ2 # This exists solely to make sure a non-multiconfig build works @@ -71,34 +77,45 @@ jobs: generator: "Unix Makefiles" build_type: Debug gcov_executable: gcov - developer_mode: On + packaging_maintainer_mode: On + enable_ipo: Off # Windows msvc builds - os: windows-2022 compiler: msvc generator: "Visual Studio 17 2022" build_type: Debug - developer_mode: On + packaging_maintainer_mode: On + enable_ipo: On - os: windows-2022 compiler: msvc generator: "Visual Studio 17 2022" build_type: Release - developer_mode: On + packaging_maintainer_mode: On + enable_ipo: On - os: windows-2022 compiler: msvc generator: "Visual Studio 17 2022" build_type: Debug - developer_mode: Off + packaging_maintainer_mode: Off - os: windows-2022 compiler: msvc generator: "Visual Studio 17 2022" build_type: Release - developer_mode: Off + packaging_maintainer_mode: Off package_generator: ZIP + - os: windows-2022 + compiler: msvc + generator: "Visual Studio 17 2022" + build_type: Release + packaging_maintainer_mode: On + enable_ipo: On + build_shared: On + steps: - name: Check for llvm version mismatches @@ -108,16 +125,22 @@ jobs: script: | core.setFailed('There is a mismatch between configured llvm compiler and clang-tidy version chosen') - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup Cache uses: ./.github/actions/setup_cache with: compiler: ${{ matrix.compiler }} build_type: ${{ matrix.build_type }} - developer_mode: ${{ matrix.developer_mode }} + packaging_maintainer_mode: ${{ matrix.packaging_maintainer_mode }} generator: ${{ matrix.generator }} + - name: Project Name + uses: cardinalby/export-env-action@v2 + with: + envFile: '.github/constants.env' + + - name: Setup Cpp uses: aminya/setup-cpp@v1 with: @@ -126,7 +149,6 @@ jobs: cmake: true ninja: true - conan: true vcpkg: false ccache: true clangtidy: ${{ env.CLANG_TIDY_VERSION }} @@ -137,15 +159,9 @@ jobs: gcovr: true opencppcoverage: true - - name: Cleanup Conan system packages (they are not properly cached) - run: | - conan remove -f '*/system' - - # make sure coverage is only enabled for Debug builds, since it sets -O0 to make sure coverage - # has meaningful results - name: Configure CMake run: | - cmake -S . -B ./build -G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE:STRING=${{matrix.build_type}} -DENABLE_DEVELOPER_MODE:BOOL=${{matrix.developer_mode}} -DOPT_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} -DGIT_SHA:STRING=${{ github.sha }} + 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 }} - name: Build # Execute the build. You can specify a specific target with "--target " diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 05955f2b..e21dc29e 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -20,12 +20,6 @@ on: schedule: - cron: '38 0 * * 5' -env: - # Conan cache environment variables - CONAN_SYSREQUIRES_MODE: enabled - CONAN_USER_HOME: "${{ github.workspace }}/conan-cache" - CONAN_USER_HOME_SHORT: "${{ github.workspace }}/conan-cache/short" - jobs: analyze: @@ -49,21 +43,26 @@ jobs: - "Ninja Multi-Config" build_type: - Debug - developer_mode: - - OFF + packaging_maintainer_mode: + - ON steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup Cache uses: ./.github/actions/setup_cache with: compiler: ${{ matrix.compiler }} build_type: ${{ matrix.build_type }} - developer_mode: ${{ matrix.developer_mode }} + packaging_maintainer_mode: ${{ matrix.packaging_maintainer_mode }} generator: ${{ matrix.generator }} + - name: Project Name + uses: cardinalby/export-env-action@v2 + with: + envFile: '.github/constants.env' + - name: Setup Cpp uses: aminya/setup-cpp@v1 @@ -73,7 +72,6 @@ jobs: cmake: true ninja: true - conan: true vcpkg: false ccache: true clangtidy: false @@ -83,19 +81,15 @@ jobs: gcovr: false opencppcoverage: false - - name: Cleanup Conan system packages (they are not properly cached) - run: | - conan remove -f '*/system' - # make sure coverage is only enabled for Debug builds, since it sets -O0 to make sure coverage # has meaningful results - name: Configure CMake run: | - cmake -S . -B ./build -G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE:STRING=${{matrix.build_type}} -DENABLE_DEVELOPER_MODE:BOOL=${{matrix.developer_mode}} -DOPT_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} + 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' }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -110,4 +104,4 @@ jobs: cmake --build ./build --config ${{matrix.build_type}} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/template-janitor.yml b/.github/workflows/template-janitor.yml index 61c9102d..01c40363 100644 --- a/.github/workflows/template-janitor.yml +++ b/.github/workflows/template-janitor.yml @@ -14,11 +14,8 @@ on: env: TEMPLATES_PATH: ".github/template" - CONAN_SYSREQUIRES_MODE: enabled - CONAN_USER_HOME: "${{ github.workspace }}/conan-cache" - CONAN_USER_HOME_SHORT: "${{ github.workspace }}/conan-cache/short" - + jobs: template-cleanup: @@ -36,7 +33,7 @@ jobs: - OFF steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup Cache uses: ./.github/actions/setup_cache @@ -77,7 +74,7 @@ jobs: - name: Insert new org and project run: | # rename the CMake project to match the github project - sed -i "s/myproject/${{ env.NEW_SAFE_PROJECT }}/gi" CMakeLists.txt configured_files/config.hpp.in src/main.cpp test/CMakeLists.txt fuzz_test/CMakeLists.txt + 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 {} + # Update URL placeholders for project sed -i "s|%%myurl%%|${{ fromJson(steps.get_repo_meta.outputs.data).html_url }}|gi" CMakeLists.txt @@ -86,6 +83,7 @@ jobs: sed -i "s/%%myorg%%/${{ env.NEW_ORG }}/g" ${{ env.TEMPLATES_PATH }}/README.md sed -i "s/%%myproject%%/${{ env.NEW_PROJECT }}/g" ${{ env.TEMPLATES_PATH }}/README.md sed -i "s|%%description%%|${{ fromJson(steps.get_repo_meta.outputs.data).description }}|g" ${{ env.TEMPLATES_PATH }}/README.md + mv include/myproject include/${{ env.NEW_SAFE_PROJECT }} cp ${{ env.TEMPLATES_PATH }}/README.md README.md - name: Print diff after replacement @@ -115,7 +113,6 @@ jobs: cmake: true ninja: false - conan: true vcpkg: false ccache: false clangtidy: false @@ -125,13 +122,16 @@ jobs: gcovr: false opencppcoverage: false - - name: Cleanup Conan system packages (they are not properly cached) - run: | - conan remove -f '*/system' + - name: Project Name + uses: cardinalby/export-env-action@v2 + with: + envFile: '.github/constants.env' + + - name: Test simple configuration to make sure nothing broke run: | - cmake -S . -B ./build -G "${{ matrix.generator }}" -DCMAKE_BUILD_TYPE:STRING=${{ matrix.build_type }} -DENABLE_DEVELOPER_MODE:BOOL=${{ matrix.developer_mode }} -DOPT_ENABLE_COVERAGE:BOOL=OFF + cmake -S . -B ./build -G "${{ matrix.generator }}" -DCMAKE_BUILD_TYPE:STRING=${{ matrix.build_type }} -D${{ env.NEW_SAFE_PROJECT }}_PACKAGING_MAINTAINER_MODE:BOOL=ON # Build it because we may have broken something in the cpp/hpp files cmake --build build @@ -162,7 +162,7 @@ jobs: - OFF steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup Cache uses: ./.github/actions/setup_cache @@ -217,7 +217,6 @@ jobs: cmake: true ninja: false - conan: true vcpkg: false ccache: false clangtidy: false @@ -227,15 +226,12 @@ jobs: gcovr: false opencppcoverage: false - - name: Cleanup Conan system packages (they are not properly cached) - run: | - conan remove -f '*/system' - name: Test simple configuration to make sure nothing broke (default compiler,cmake,developer_mode OFF) run: | - cmake -S . -B ./build -G "${{ matrix.generator }}" -DCMAKE_BUILD_TYPE:STRING=${{ matrix.build_type }} -DENABLE_DEVELOPER_MODE:BOOL=${{ matrix.developer_mode }} -DOPT_ENABLE_COVERAGE:BOOL=OFF + cmake -S . -B ./build -G "${{ matrix.generator }}" -DCMAKE_BUILD_TYPE:STRING=${{ matrix.build_type }} -D${{ env.PROJECT_NAME }}_PACKAGING_MAINTAINER_MODE:BOOL=ON - - uses: EndBug/add-and-commit@v4 + - uses: EndBug/add-and-commit@v9 # only commit and push if we are a template and project name has changed if: fromJson(steps.get_repo_meta.outputs.data).is_template == true && env.TEST_RUN == 'false' with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d9d691f..45e19f03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,38 +1,19 @@ -cmake_minimum_required(VERSION 3.16...3.23) +cmake_minimum_required(VERSION 3.21) -# Not ideal to use this global variable, but necessary to make sure -# that tooling and projects use the same version -set(CMAKE_CXX_STANDARD 20) +# This template attempts to be "fetch_content"-able +# so that it works well with tools like CPM or other +# manual dependency management + +# Only set the cxx_standard if it is not set by someone else +if (NOT DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 20) +endif() # strongly encouraged to enable this globally to avoid conflicts between # -Wpedantic being enabled and -std=c++20 and -std=gnu++20 for example # when compiling with PCH enabled set(CMAKE_CXX_EXTENSIONS OFF) -include(FetchContent) - -# Note: by default ENABLE_DEVELOPER_MODE is True -# This means that all analysis (sanitizers, static analysis) -# is enabled and all warnings are treated as errors -# if you want to switch this behavior, change TRUE to FALSE -set(ENABLE_DEVELOPER_MODE - TRUE - CACHE BOOL "Enable 'developer mode'") - -# Change this to false if you want to disable warnings_as_errors in developer mode -set(OPT_WARNINGS_AS_ERRORS_DEVELOPER_DEFAULT TRUE) - -# Add project_options v0.20.0 -# https://github.com/cpp-best-practices/project_options -include(FetchContent) -FetchContent_Declare(_project_options URL https://github.com/aminya/project_options/archive/refs/tags/v0.20.0.zip) -FetchContent_MakeAvailable(_project_options) -include(${_project_options_SOURCE_DIR}/Index.cmake) - -# uncomment to enable vcpkg: -# # Setup vcpkg - should be called before defining project() -# run_vcpkg() - # Set the project name and language project( myproject @@ -41,17 +22,20 @@ project( HOMEPAGE_URL "%%myurl%%" LANGUAGES CXX C) -# This variable is set by project() in CMake 3.21+ -string( - COMPARE EQUAL - "${CMAKE_SOURCE_DIR}" - "${PROJECT_SOURCE_DIR}" - PROJECT_IS_TOP_LEVEL) -if(PROJECT_IS_TOP_LEVEL) - # Consider the CTest module, which creates targets and options! - # Only needed if you want to enable submissions to a CDash server. - include(CTest) -endif() +include(cmake/PreventInSourceBuilds.cmake) +include(ProjectOptions.cmake) + + +myproject_setup_options() + +myproject_global_options() +include(Dependencies.cmake) +myproject_setup_dependencies() + +myproject_local_options() + +# don't know if this should be set globally from here or not... +set(CMAKE_CXX_VISIBILITY_PRESET hidden) set(GIT_SHA "Unknown" @@ -62,71 +46,13 @@ string( 8 GIT_SHORT_SHA) -get_property(BUILDING_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if(BUILDING_MULTI_CONFIG) - if(NOT CMAKE_BUILD_TYPE) - # Make sure that all supported configuration types have their - # associated conan packages available. You can reduce this - # list to only the configuration types you use, but only if one - # is not forced-set on the command line for VS - message(TRACE "Setting up multi-config build types") - set(CMAKE_CONFIGURATION_TYPES - Debug - Release - RelWithDebInfo - MinSizeRel - CACHE STRING "Enabled build types" FORCE) - else() - message(TRACE "User chose a specific build type, so we are using that") - set(CMAKE_CONFIGURATION_TYPES - ${CMAKE_BUILD_TYPE} - CACHE STRING "Enabled build types" FORCE) - endif() -endif() +target_compile_features(myproject_options INTERFACE cxx_std_${CMAKE_CXX_STANDARD}) -include(${_project_options_SOURCE_DIR}/src/DynamicProjectOptions.cmake) - -# defaulted_project_options sets recommended defaults and provides user and developer -# modes and full GUI support for choosing options at configure time - -# for more flexibility, look into project_options() macro - -# Any default can be overridden -# set(_DEFAULT ) - set default for both user and developer modes -# set(_DEVELOPER_DEFAULT ) - set default for developer mode -# set(_USER_DEFAULT ) - set default for user mode - -# Initialize project_options variable related to this project -# This overwrites `project_options` and sets `project_warnings` -# uncomment the options to enable them: -dynamic_project_options( - # Note: PCH is disabled by default in developer mode because these headers become - # globally included and they can mask other errors - PCH_HEADERS - - # This is a list of headers to pre-compile, here are some common ones - ENABLE_CONAN - # CONAN_OPTIONS # Extra options to pass to conan - # MSVC_WARNINGS # Override the defaults for the MSVC warnings - # CLANG_WARNINGS # Override the defaults for the CLANG warnings - # GCC_WARNINGS # Override the defaults for the GCC warnings - CPPCHECK_OPTIONS - --enable=style,performance,warning,portability - --inline-suppr - # We cannot act on a bug/missing feature of cppcheck - --suppress=cppcheckError - --suppress=internalAstError - # if a file does not have an internalAstError, we get an unmatchedSuppression error - --suppress=unmatchedSuppression - --suppress=passedByValue - --suppress=syntaxError - --inconclusive -) +add_library(myproject::myproject_options ALIAS myproject_options) +add_library(myproject::myproject_warnings ALIAS myproject_warnings) -target_compile_features(project_options INTERFACE cxx_std_${CMAKE_CXX_STANDARD}) -# TODO: The INTERFACE library NAMESPACE ALIAS are missing! CK -add_library(myproject::project_options INTERFACE IMPORTED) -add_library(myproject::project_warnings INTERFACE IMPORTED) +#add_library(myproject::myproject_options INTERFACE IMPORTED) +#add_library(myproject::myproject_warnings INTERFACE IMPORTED) # configure files based on CMake configuration options add_subdirectory(configured_files) @@ -134,18 +60,27 @@ add_subdirectory(configured_files) # Adding the src: add_subdirectory(src) +# Don't even look at tests if we're not top level +if(NOT PROJECT_IS_TOP_LEVEL) + return() +endif() + # Adding the tests: -option(ENABLE_TESTING "Enable the tests" ${PROJECT_IS_TOP_LEVEL}) -if(ENABLE_TESTING) - enable_testing() +include(CTest) + +if(BUILD_TESTING) message(AUTHOR_WARNING "Building Tests. Be sure to check out test/constexpr_tests.cpp for constexpr testing") add_subdirectory(test) endif() -option(ENABLE_FUZZING "Enable the fuzz tests" OFF) -if(ENABLE_FUZZING) + +if(myproject_BUILD_FUZZ_TESTS) message(AUTHOR_WARNING "Building Fuzz Tests, using fuzzing sanitizer https://www.llvm.org/docs/LibFuzzer.html") + if (NOT myproject_ENABLE_ADDRESS_SANITIZER AND NOT myproject_ENABLE_THREAD_SANITIZER) + message(WARNING "You need asan or tsan enabled for meaningful fuzz testing") + endif() add_subdirectory(fuzz_test) + endif() # If MSVC is being used, and ASAN is enabled, we need to set the debugger environment @@ -161,13 +96,17 @@ set_property(DIRECTORY PROPERTY VS_STARTUP_PROJECT intro) if(CMAKE_SKIP_INSTALL_RULES) return() -elseif(NOT PROJECT_IS_TOP_LEVEL) - return() endif() -# Add other targets that you want installed here, be default we just package the one executable +include(cmake/PackageProject.cmake) + +# Add other targets that you want installed here, by default we just package the one executable # we know we want to ship -package_project(TARGETS intro project_options project_warnings +myproject_package_project( + TARGETS + intro + myproject_options + myproject_warnings # FIXME: this does not work! CK # PRIVATE_DEPENDENCIES_CONFIGURED project_options project_warnings ) diff --git a/Dependencies.cmake b/Dependencies.cmake new file mode 100644 index 00000000..a84378d2 --- /dev/null +++ b/Dependencies.cmake @@ -0,0 +1,43 @@ +include(cmake/CPM.cmake) + +# Done as a function so that updates to variables like +# CMAKE_CXX_FLAGS don't propagate out to other +# targets +function(myproject_setup_dependencies) + + # For each dependency, see if it's + # already been provided to us by a parent project + + if(NOT TARGET fmtlib::fmtlib) + cpmaddpackage("gh:fmtlib/fmt#9.1.0") + endif() + + if(NOT TARGET spdlog::spdlog) + cpmaddpackage( + NAME + spdlog + VERSION + 1.11.0 + GITHUB_REPOSITORY + "gabime/spdlog" + OPTIONS + "SPDLOG_FMT_EXTERNAL ON") + endif() + + if(NOT TARGET Catch2::Catch2WithMain) + cpmaddpackage("gh:catchorg/Catch2@3.3.2") + endif() + + if(NOT TARGET CLI11::CLI11) + cpmaddpackage("gh:CLIUtils/CLI11@2.3.2") + endif() + + if(NOT TARGET ftxui::screen) + cpmaddpackage("gh:ArthurSonzogni/FTXUI#e23dbc7473654024852ede60e2121276c5aab660") + endif() + + if(NOT TARGET tools::tools) + cpmaddpackage("gh:lefticus/tools#update_build_system") + endif() + +endfunction() diff --git a/ProjectOptions.cmake b/ProjectOptions.cmake new file mode 100644 index 00000000..2709aa1a --- /dev/null +++ b/ProjectOptions.cmake @@ -0,0 +1,200 @@ +include(cmake/SystemLink.cmake) +include(cmake/LibFuzzer.cmake) +include(CMakeDependentOption) +include(CheckCXXCompilerFlag) + + +macro(myproject_supports_sanitizers) + if((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" OR CMAKE_CXX_COMPILER_ID MATCHES ".*GNU.*") AND NOT WIN32) + set(SUPPORTS_UBSAN ON) + else() + set(SUPPORTS_UBSAN OFF) + endif() + + if((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" OR CMAKE_CXX_COMPILER_ID MATCHES ".*GNU.*") AND WIN32) + set(SUPPORTS_ASAN OFF) + else() + set(SUPPORTS_ASAN ON) + endif() +endmacro() + +macro(myproject_setup_options) + option(myproject_ENABLE_HARDENING "Enable hardening" ON) + option(myproject_ENABLE_COVERAGE "Enable coverage reporting" OFF) + cmake_dependent_option( + myproject_ENABLE_GLOBAL_HARDENING + "Attempt to push hardening options to built dependencies" + ON + myproject_ENABLE_HARDENING + OFF) + + myproject_supports_sanitizers() + + if(NOT PROJECT_IS_TOP_LEVEL OR myproject_PACKAGING_MAINTAINER_MODE) + option(myproject_ENABLE_IPO "Enable IPO/LTO" OFF) + option(myproject_WARNINGS_AS_ERRORS "Treat Warnings As Errors" OFF) + option(myproject_ENABLE_USER_LINKER "Enable user-selected linker" OFF) + option(myproject_ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" OFF) + option(myproject_ENABLE_SANITIZER_LEAK "Enable leak sanitizer" OFF) + option(myproject_ENABLE_SANITIZER_UNDEFINED "Enable undefined sanitizer" OFF) + option(myproject_ENABLE_SANITIZER_THREAD "Enable thread sanitizer" OFF) + option(myproject_ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" OFF) + option(myproject_ENABLE_UNITY_BUILD "Enable unity builds" OFF) + option(myproject_ENABLE_CLANG_TIDY "Enable clang-tidy" OFF) + option(myproject_ENABLE_CPPCHECK "Enable cpp-check analysis" OFF) + option(myproject_ENABLE_PCH "Enable precompiled headers" OFF) + option(myproject_ENABLE_CACHE "Enable ccache" OFF) + else() + option(myproject_ENABLE_IPO "Enable IPO/LTO" ON) + option(myproject_WARNINGS_AS_ERRORS "Treat Warnings As Errors" ON) + option(myproject_ENABLE_USER_LINKER "Enable user-selected linker" OFF) + option(myproject_ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" ${SUPPORTS_ASAN}) + option(myproject_ENABLE_SANITIZER_LEAK "Enable leak sanitizer" OFF) + option(myproject_ENABLE_SANITIZER_UNDEFINED "Enable undefined sanitizer" ${SUPPORTS_UBSAN}) + option(myproject_ENABLE_SANITIZER_THREAD "Enable thread sanitizer" OFF) + option(myproject_ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" OFF) + option(myproject_ENABLE_UNITY_BUILD "Enable unity builds" OFF) + option(myproject_ENABLE_CLANG_TIDY "Enable clang-tidy" ON) + option(myproject_ENABLE_CPPCHECK "Enable cpp-check analysis" ON) + option(myproject_ENABLE_PCH "Enable precompiled headers" OFF) + option(myproject_ENABLE_CACHE "Enable ccache" ON) + endif() + + if(NOT PROJECT_IS_TOP_LEVEL) + mark_as_advanced( + myproject_ENABLE_IPO + myproject_WARNINGS_AS_ERRORS + myproject_ENABLE_USER_LINKER + myproject_ENABLE_SANITIZER_ADDRESS + myproject_ENABLE_SANITIZER_LEAK + myproject_ENABLE_SANITIZER_UNDEFINED + myproject_ENABLE_SANITIZER_THREAD + myproject_ENABLE_SANITIZER_MEMORY + myproject_ENABLE_UNITY_BUILD + myproject_ENABLE_CLANG_TIDY + myproject_ENABLE_CPPCHECK + myproject_ENABLE_COVERAGE + myproject_ENABLE_PCH + myproject_ENABLE_CACHE) + endif() + + myproject_check_libfuzzer_support(LIBFUZZER_SUPPORTED) + if(LIBFUZZER_SUPPORTED AND (myproject_ENABLE_SANITIZER_ADDRESS OR myproject_ENABLE_SANITIZER_THREAD OR myproject_ENABLE_SANITIZER_UNDEFINED)) + set(DEFAULT_FUZZER ON) + else() + set(DEFAULT_FUZZER OFF) + endif() + + option(myproject_BUILD_FUZZ_TESTS "Enable fuzz testing executable" ${DEFAULT_FUZZER}) + +endmacro() + +macro(myproject_global_options) + if(myproject_ENABLE_IPO) + include(cmake/InterproceduralOptimization.cmake) + myproject_enable_ipo() + endif() + + myproject_supports_sanitizers() + + if(myproject_ENABLE_HARDENING AND myproject_ENABLE_GLOBAL_HARDENING) + include(cmake/Hardening.cmake) + if(NOT SUPPORTS_UBSAN + OR myproject_ENABLE_SANITIZER_UNDEFINED + OR myproject_ENABLE_SANITIZER_ADDRESS + OR myproject_ENABLE_SANITIZER_THREAD + OR myproject_ENABLE_SANITIZER_LEAK) + set(ENABLE_UBSAN_MINIMAL_RUNTIME FALSE) + else() + set(ENABLE_UBSAN_MINIMAL_RUNTIME TRUE) + endif() + message("${myproject_ENABLE_HARDENING} ${ENABLE_UBSAN_MINIMAL_RUNTIME} ${myproject_ENABLE_SANITIZER_UNDEFINED}") + myproject_enable_hardening(myproject_options ON ${ENABLE_UBSAN_MINIMAL_RUNTIME}) + endif() +endmacro() + +macro(myproject_local_options) + if(PROJECT_IS_TOP_LEVEL) + include(cmake/StandardProjectSettings.cmake) + endif() + + add_library(myproject_warnings INTERFACE) + add_library(myproject_options INTERFACE) + + include(cmake/CompilerWarnings.cmake) + myproject_set_project_warnings( + myproject_warnings + ${myproject_WARNINGS_AS_ERRORS} + "" + "" + "" + "") + + if(myproject_ENABLE_USER_LINKER) + include(cmake/Linker.cmake) + configure_linker(myproject_options) + endif() + + include(cmake/Sanitizers.cmake) + myproject_enable_sanitizers( + myproject_options + ${myproject_ENABLE_SANITIZER_ADDRESS} + ${myproject_ENABLE_SANITIZER_LEAK} + ${myproject_ENABLE_SANITIZER_UNDEFINED} + ${myproject_ENABLE_SANITIZER_THREAD} + ${myproject_ENABLE_SANITIZER_MEMORY}) + + set_target_properties(myproject_options PROPERTIES UNITY_BUILD ${myproject_ENABLE_UNITY_BUILD}) + + if(myproject_ENABLE_PCH) + target_precompile_headers( + myproject_options + INTERFACE + + + ) + endif() + + if(myproject_ENABLE_CACHE) + include(cmake/Cache.cmake) + myproject_enable_cache() + endif() + + include(cmake/StaticAnalyzers.cmake) + if(myproject_ENABLE_CLANG_TIDY) + myproject_enable_clang_tidy(myproject_options ${myproject_WARNINGS_AS_ERRORS}) + endif() + + if(myproject_ENABLE_CPPCHECK) + myproject_enable_cppcheck(${myproject_WARNINGS_AS_ERRORS} "" # override cppcheck options + ) + endif() + + if(myproject_ENABLE_COVERAGE) + include(cmake/Tests.cmake) + myproject_enable_coverage(myproject_options) + endif() + + if(myproject_WARNINGS_AS_ERRORS) + check_cxx_compiler_flag("-Wl,--fatal-warnings" LINKER_FATAL_WARNINGS) + if(LINKER_FATAL_WARNINGS) + # This is not working consistently, so disabling for now + # target_link_options(myproject_options INTERFACE -Wl,--fatal-warnings) + endif() + endif() + + if(myproject_ENABLE_HARDENING AND NOT myproject_ENABLE_GLOBAL_HARDENING) + include(cmake/Hardening.cmake) + if(NOT SUPPORTS_UBSAN + OR myproject_ENABLE_SANITIZER_UNDEFINED + OR myproject_ENABLE_SANITIZER_ADDRESS + OR myproject_ENABLE_SANITIZER_THREAD + OR myproject_ENABLE_SANITIZER_LEAK) + set(ENABLE_UBSAN_MINIMAL_RUNTIME FALSE) + else() + set(ENABLE_UBSAN_MINIMAL_RUNTIME TRUE) + endif() + myproject_enable_hardening(myproject_options OFF ${ENABLE_UBSAN_MINIMAL_RUNTIME}) + endif() + +endmacro() diff --git a/README.md b/README.md index 906afc37..bc766ae4 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,14 @@ -# ftxui_template +# cmake_template -[![ci](https://github.com/cpp-best-practices/ftxui_template/actions/workflows/ci.yml/badge.svg)](https://github.com/cpp-best-practices/ftxui_template/actions/workflows/ci.yml) -[![codecov](https://codecov.io/gh/cpp-best-practices/ftxui_template/branch/main/graph/badge.svg)](https://codecov.io/gh/cpp-best-practices/ftxui_template) -[![Language grade: C++](https://img.shields.io/lgtm/grade/cpp/github/cpp-best-practices/ftxui_template)](https://lgtm.com/projects/g/cpp-best-practices/ftxui_template/context:cpp) -[![CodeQL](https://github.com/cpp-best-practices/ftxui_template/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/cpp-best-practices/ftxui_template/actions/workflows/codeql-analysis.yml) +[![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) +[![codecov](https://codecov.io/gh/cpp-best-practices/cmake_template/branch/main/graph/badge.svg)](https://codecov.io/gh/cpp-best-practices/cmake_template) +[![Language grade: C++](https://img.shields.io/lgtm/grade/cpp/github/cpp-best-practices/cmake_template)](https://lgtm.com/projects/g/cpp-best-practices/cmake_template/context:cpp) +[![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) + +## About cmake_template + +**NOTE** This is undergoing a major overhaul on a new branch currently. -## About ftxui_template This is a C++ Best Practices GitHub template for getting up and running with C++ quickly. @@ -14,7 +17,7 @@ By default (collectively known as `ENABLE_DEVELOPER_MODE`) * Address Sanitizer and Undefined Behavior Sanitizer enabled where possible * Warnings as errors * clang-tidy and cppcheck static analysis - * conan for dependencies + * CPM for dependencies It includes @@ -25,7 +28,6 @@ It includes It requires * cmake - * conan * a compiler @@ -36,7 +38,7 @@ This project gets you started with a simple example of using FTXUI, which happen ### Use the Github template First, click the green `Use this template` button near the top of this page. -This will take you to Github's ['Generate Repository'](https://github.com/cpp-best-practices/ftxui_template/generate) page. +This will take you to Github's ['Generate Repository'](https://github.com/cpp-best-practices/cmake_template/generate) page. Fill in a repository name and short description, and click 'Create repository from template'. This will allow you to create a new repository in your Github account, prepopulated with the contents of this project. diff --git a/README_building.md b/README_building.md index b4a4b9bd..99ceacfc 100644 --- a/README_building.md +++ b/README_building.md @@ -11,9 +11,8 @@ For the subsequent builds, in case you change the source code, you only need to By default (if you don't set environment variables `CC` and `CXX`), the system default compiler will be used. -Conan and CMake use 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. +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. -CMake will detect which compiler was used to build each of the Conan targets. If you build all of your Conan targets with one compiler, and then build your CMake targets with a different compiler, the project may fail to build.
Commands for setting the compilers diff --git a/README_dependencies.md b/README_dependencies.md index 51bc71a6..42e4dab0 100644 --- a/README_dependencies.md +++ b/README_dependencies.md @@ -19,11 +19,11 @@ We have [setup-cpp](https://github.com/aminya/setup-cpp) that is a cross-platfor Please check [the setup-cpp documentation](https://github.com/aminya/setup-cpp) for more information. -For example, on Windows, you can run the following to install llvm, cmake, ninja, ccache, conan, and cppcheck. +For example, on Windows, you can run the following to install llvm, cmake, ninja, ccache, and cppcheck. ```ps1 # windows example (open shell as admin) curl -LJO "https://github.com/aminya/setup-cpp/releases/download/v0.5.7/setup_cpp_windows.exe" -./setup_cpp_windows --compiler llvm --cmake true --ninja true --ccache true --conan true --cppcheck true +./setup_cpp_windows --compiler llvm --cmake true --ninja true --ccache true --cppcheck true RefreshEnv.cmd # reload the environment ``` @@ -103,25 +103,7 @@ The following compilers should work:
-2. [Conan](https://conan.io/) -
- Install Command - - - Via pip - https://docs.conan.io/en/latest/installation.html#install-with-pip-recommended - - pip install --user conan - - - Windows: - - choco install conan -y - - - MacOS: - - brew install conan - -
- -3. [CMake 3.15+](https://cmake.org/) +2. [CMake 3.15+](https://cmake.org/)
Install Command @@ -208,29 +190,3 @@ The following compilers should work: Follow instructions here: https://github.com/include-what-you-use/include-what-you-use#how-to-install
- -#### GUI libraries -This project can be made to work with several optional GUI frameworks. - -If desired, you should install the following optional dependencies as -directed by their documentation, linked here: - -- [FLTK](https://www.fltk.org/doc-1.4/index.html) -- [GTKMM](https://www.gtkmm.org/en/documentation.html) -- [QT](https://doc.qt.io/) - -The following dependencies can be downloaded automatically by CMake and Conan. -All you need to do to install them is to turn on a CMake flag during -configuration. -If you run into difficulty using them, please refer to their documentation, -linked here: - -- [NANA](http://nanapro.org/en-us/documentation/) -- [SDL](http://wiki.libsdl.org/FrontPage) -- [IMGUI](https://github.com/ocornut/imgui/tree/master/docs): - This framework depends on SFML, and if you are using Linux, you may need - to install several of SFML's dependencies using your package manager. See - [the SFML build tutorial](https://www.sfml-dev.org/tutorials/2.5/compile-with-cmake.php) - for specifics. - - diff --git a/README_troubleshooting.md b/README_troubleshooting.md deleted file mode 100644 index 182f7f01..00000000 --- a/README_troubleshooting.md +++ /dev/null @@ -1,57 +0,0 @@ -## Troubleshooting - -### Update Conan -Many problems that users have can be resolved by updating Conan, so if you are -having any trouble with this project, you should start by doing that. - -To update conan: - - pip install --user --upgrade conan - -You may need to use `pip3` instead of `pip` in this command, depending on your -platform. - -### Clear Conan cache -If you continue to have trouble with your Conan dependencies, you can try -clearing your Conan cache: - - conan remove -f '*' - -The next time you run `cmake` or `cmake --build`, your Conan dependencies will -be rebuilt. If you aren't using your system's default compiler, don't forget to -set the CC, CXX, CMAKE_C_COMPILER, and CMAKE_CXX_COMPILER variables, as -described in the 'Build using an alternate compiler' section above. - -### Identifying misconfiguration of Conan dependencies - -If you have a dependency 'A' that requires a specific version of another -dependency 'B', and your project is trying to use the wrong version of -dependency 'B', Conan will produce warnings about this configuration error -when you run CMake. These warnings can easily get lost between a couple -hundred or thousand lines of output, depending on the size of your project. - -If your project has a Conan configuration error, you can use `conan info` to -find it. `conan info` displays information about the dependency graph of your -project, with colorized output in some terminals. - - cd build - conan info . - -In my terminal, the first couple lines of `conan info`'s output show all of the -project's configuration warnings in a bright yellow font. - -For example, the package `spdlog/1.5.0` depends on the package `fmt/6.1.2`. -If you were to modify the file `conanfile.py` so that it requires an -earlier version of `fmt`, such as `fmt/6.0.0`, and then run: - -```bash -conan remove -f '*' # clear Conan cache -rm -rf build # clear previous CMake build -cmake -S . -B ./build # rebuild Conan dependencies -conan info ./build -``` - -...the first line of output would be a warning that `spdlog` needs a more recent -version of `fmt`. - - diff --git a/build_examples.sh b/build_examples.sh deleted file mode 100644 index 3f91ca38..00000000 --- a/build_examples.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash - -# This script is intended to be run from an Ubuntu Docker image (or other Debian-derived distros). -# It uses `apt-get` to install dependencies required by the examples, - -set -euo pipefail - -function install_fltk() { - apt-get install -y --no-install-recommends libfltk1.3-dev libgl1-mesa-dev fluid -} - -function install_gtkmm() { - apt-get install -y --no-install-recommends pkg-config libgtkmm-3.0-dev -} - -function install_imgui() { - apt-get install -y --no-install-recommends pkg-config libgl1-mesa-dev -} - -function install_nana() { - # CMakeLists.txt is 'supposed' to install all of these automatically, but for - # some reason it doesn't always work. Installing them manually does work. - apt-get install -y --no-install-recommends libjpeg8-dev libpng-dev \ - libasound2-dev alsa-utils alsa-oss libx11-dev libxft-dev libxcursor-dev - # Note: Nana's headers trigger the -Wshadow warning, and cannot be compiled with -Werror. - # Supposedly, this is fixed in the develop-1.8 branch, but I cannot confirm. -} - -function install_qt() { - apt-get install -y --no-install-recommends qt5-default qtbase5-dev -} - -function install_sdl() { - pip install mako -} - -# call the above functions to install the required dependencies. As an example for qt: -# install_qt - -# build with: -cmake -S . -B "./build" -cmake --build "./build" \ No newline at end of file diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake new file mode 100644 index 00000000..a3086b79 --- /dev/null +++ b/cmake/CPM.cmake @@ -0,0 +1,33 @@ +set(CPM_DOWNLOAD_VERSION 0.38.1) + +if(CPM_SOURCE_CACHE) + set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +elseif(DEFINED ENV{CPM_SOURCE_CACHE}) + set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +else() + set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +endif() + +# Expand relative path. This is important if the provided path contains a tilde (~) +get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) + +function(download_cpm) + message(STATUS "Downloading CPM.cmake to ${CPM_DOWNLOAD_LOCATION}") + file(DOWNLOAD + https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake + ${CPM_DOWNLOAD_LOCATION} + ) +endfunction() + +if(NOT (EXISTS ${CPM_DOWNLOAD_LOCATION})) + download_cpm() +else() + # resume download if it previously failed + file(READ ${CPM_DOWNLOAD_LOCATION} check) + if("${check}" STREQUAL "") + download_cpm() + endif() + unset(check) +endif() + +include(${CPM_DOWNLOAD_LOCATION}) diff --git a/cmake/Cache.cmake b/cmake/Cache.cmake new file mode 100644 index 00000000..2164c10d --- /dev/null +++ b/cmake/Cache.cmake @@ -0,0 +1,33 @@ +# Enable cache if available +function(myproject_enable_cache) + set(CACHE_OPTION + "ccache" + CACHE STRING "Compiler cache to be used") + set(CACHE_OPTION_VALUES "ccache" "sccache") + set_property(CACHE CACHE_OPTION PROPERTY STRINGS ${CACHE_OPTION_VALUES}) + list( + FIND + CACHE_OPTION_VALUES + ${CACHE_OPTION} + CACHE_OPTION_INDEX) + + if(${CACHE_OPTION_INDEX} EQUAL -1) + message( + STATUS + "Using custom compiler cache system: '${CACHE_OPTION}', explicitly supported entries are ${CACHE_OPTION_VALUES}" + ) + endif() + + find_program(CACHE_BINARY NAMES ${CACHE_OPTION_VALUES}) + if(CACHE_BINARY) + message(STATUS "${CACHE_BINARY} found and enabled") + set(CMAKE_CXX_COMPILER_LAUNCHER + ${CACHE_BINARY} + CACHE FILEPATH "CXX compiler cache used") + set(CMAKE_C_COMPILER_LAUNCHER + ${CACHE_BINARY} + CACHE FILEPATH "C compiler cache used") + else() + message(WARNING "${CACHE_OPTION} is enabled but was not found. Not using it") + endif() +endfunction() diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake new file mode 100644 index 00000000..109297b3 --- /dev/null +++ b/cmake/CompilerWarnings.cmake @@ -0,0 +1,115 @@ +# from here: +# +# https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md + +function( + myproject_set_project_warnings + project_name + WARNINGS_AS_ERRORS + MSVC_WARNINGS + CLANG_WARNINGS + GCC_WARNINGS + CUDA_WARNINGS) + if("${MSVC_WARNINGS}" STREQUAL "") + set(MSVC_WARNINGS + /W4 # Baseline reasonable warnings + /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data + /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data + /w14263 # 'function': member function does not override any base class virtual member function + /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not + # be destructed correctly + /w14287 # 'operator': unsigned/negative constant mismatch + /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside + # the for-loop scope + /w14296 # 'operator': expression is always 'boolean_value' + /w14311 # 'variable': pointer truncation from 'type1' to 'type2' + /w14545 # expression before comma evaluates to a function which is missing an argument list + /w14546 # function call before comma missing argument list + /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect + /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? + /w14555 # expression has no effect; expected expression with side- effect + /w14619 # pragma warning: there is no warning number 'number' + /w14640 # Enable warning on thread un-safe static member initialization + /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected runtime behavior. + /w14905 # wide string literal cast to 'LPSTR' + /w14906 # string literal cast to 'LPWSTR' + /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied + /permissive- # standards conformance mode for MSVC compiler. + ) + endif() + + if("${CLANG_WARNINGS}" STREQUAL "") + set(CLANG_WARNINGS + -Wall + -Wextra # reasonable and standard + -Wshadow # warn the user if a variable declaration shadows one from a parent context + -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps + # catch hard to track down memory errors + -Wold-style-cast # warn for c-style casts + -Wcast-align # warn for potential performance problem casts + -Wunused # warn on anything being unused + -Woverloaded-virtual # warn if you overload (not override) a virtual function + -Wpedantic # warn if non-standard C++ is used + -Wconversion # warn on type conversions that may lose data + -Wsign-conversion # warn on sign conversions + -Wnull-dereference # warn if a null dereference is detected + -Wdouble-promotion # warn if float is implicit promoted to double + -Wformat=2 # warn on security issues around functions that format output (ie printf) + -Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation + ) + endif() + + if("${GCC_WARNINGS}" STREQUAL "") + set(GCC_WARNINGS + ${CLANG_WARNINGS} + -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist + -Wduplicated-cond # warn if if / else chain has duplicated conditions + -Wduplicated-branches # warn if if / else branches have duplicated code + -Wlogical-op # warn about logical operations being used where bitwise were probably wanted + -Wuseless-cast # warn if you perform a cast to the same type + ) + endif() + + if("${CUDA_WARNINGS}" STREQUAL "") + set(CUDA_WARNINGS + -Wall + -Wextra + -Wunused + -Wconversion + -Wshadow + # TODO add more Cuda warnings + ) + endif() + + if(WARNINGS_AS_ERRORS) + message(TRACE "Warnings are treated as errors") + list(APPEND CLANG_WARNINGS -Werror) + list(APPEND GCC_WARNINGS -Werror) + list(APPEND MSVC_WARNINGS /WX) + endif() + + if(MSVC) + set(PROJECT_WARNINGS_CXX ${MSVC_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + set(PROJECT_WARNINGS_CXX ${CLANG_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(PROJECT_WARNINGS_CXX ${GCC_WARNINGS}) + else() + message(AUTHOR_WARNING "No compiler warnings set for CXX compiler: '${CMAKE_CXX_COMPILER_ID}'") + # TODO support Intel compiler + endif() + + # use the same warning flags for C + set(PROJECT_WARNINGS_C "${PROJECT_WARNINGS_CXX}") + + set(PROJECT_WARNINGS_CUDA "${CUDA_WARNINGS}") + + target_compile_options( + ${project_name} + INTERFACE # C++ warnings + $<$:${PROJECT_WARNINGS_CXX}> + # C warnings + $<$:${PROJECT_WARNINGS_C}> + # Cuda warnings + $<$:${PROJECT_WARNINGS_CUDA}>) +endfunction() diff --git a/cmake/Cuda.cmake b/cmake/Cuda.cmake new file mode 100644 index 00000000..d784a882 --- /dev/null +++ b/cmake/Cuda.cmake @@ -0,0 +1,48 @@ +# ! target_link_cuda +# A function that links Cuda to the given target +# +# # Example +# add_executable(main_cuda main.cu) +# target_compile_features(main_cuda PRIVATE cxx_std_17) +# target_link_libraries(main_cuda PRIVATE project_options project_warnings) +# target_link_cuda(main_cuda) +# +macro(myproject_target_link_cuda target) + # optional named CUDA_WARNINGS + set(oneValueArgs CUDA_WARNINGS) + cmake_parse_arguments( + _cuda_args + "" + "${oneValueArgs}" + "" + ${ARGN}) + + # add CUDA to cmake language + enable_language(CUDA) + + # use the same C++ standard if not specified + if("${CMAKE_CUDA_STANDARD}" STREQUAL "") + set(CMAKE_CUDA_STANDARD "${CMAKE_CXX_STANDARD}") + endif() + + # -fPIC + set_target_properties(${target} PROPERTIES POSITION_INDEPENDENT_CODE ON) + + # We need to explicitly state that we need all CUDA files in the + # ${target} library to be built with -dc as the member functions + # could be called by other libraries and executables + set_target_properties(${target} PROPERTIES CUDA_SEPARABLE_COMPILATION ON) + + if(APPLE) + # We need to add the path to the driver (libcuda.dylib) as an rpath, + # so that the static cuda runtime can find it at runtime. + set_property(TARGET ${target} PROPERTY BUILD_RPATH ${CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES}) + endif() + + if(WIN32 AND "$ENV{VSCMD_VER}" STREQUAL "") + message( + WARNING + "Compiling Cuda on Windows outside the Visual Studio Commant prompt or without running `vcvarsall.bat x64` probably fails" + ) + endif() +endmacro() diff --git a/cmake/Doxygen.cmake b/cmake/Doxygen.cmake new file mode 100644 index 00000000..ed90c566 --- /dev/null +++ b/cmake/Doxygen.cmake @@ -0,0 +1,54 @@ +# Enable doxygen doc builds of source +function(myproject_enable_doxygen DOXYGEN_THEME) + # If not specified, use the top readme file as the first page + if((NOT DOXYGEN_USE_MDFILE_AS_MAINPAGE) AND EXISTS "${PROJECT_SOURCE_DIR}/README.md") + set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${PROJECT_SOURCE_DIR}/README.md") + endif() + + # set better defaults for doxygen + is_verbose(_is_verbose) + if(NOT ${_is_verbose}) + set(DOXYGEN_QUIET YES) + endif() + set(DOXYGEN_CALLER_GRAPH YES) + set(DOXYGEN_CALL_GRAPH YES) + set(DOXYGEN_EXTRACT_ALL YES) + set(DOXYGEN_GENERATE_TREEVIEW YES) + # svg files are much smaller than jpeg and png, and yet they have higher quality + set(DOXYGEN_DOT_IMAGE_FORMAT svg) + set(DOXYGEN_DOT_TRANSPARENT YES) + + # If not specified, exclude the vcpkg files and the files CMake downloads under _deps (like project_options) + if(NOT DOXYGEN_EXCLUDE_PATTERNS) + set(DOXYGEN_EXCLUDE_PATTERNS "${CMAKE_CURRENT_BINARY_DIR}/vcpkg_installed/*" "${CMAKE_CURRENT_BINARY_DIR}/_deps/*") + endif() + + if("${DOXYGEN_THEME}" STREQUAL "") + set(DOXYGEN_THEME "awesome-sidebar") + endif() + + if("${DOXYGEN_THEME}" STREQUAL "awesome" OR "${DOXYGEN_THEME}" STREQUAL "awesome-sidebar") + # use a modern doxygen theme + # https://github.com/jothepro/doxygen-awesome-css v1.6.1 + FetchContent_Declare(_doxygen_theme + URL https://github.com/jothepro/doxygen-awesome-css/archive/refs/tags/v1.6.1.zip) + FetchContent_MakeAvailable(_doxygen_theme) + if("${DOXYGEN_THEME}" STREQUAL "awesome" OR "${DOXYGEN_THEME}" STREQUAL "awesome-sidebar") + set(DOXYGEN_HTML_EXTRA_STYLESHEET "${_doxygen_theme_SOURCE_DIR}/doxygen-awesome.css") + endif() + if("${DOXYGEN_THEME}" STREQUAL "awesome-sidebar") + set(DOXYGEN_HTML_EXTRA_STYLESHEET ${DOXYGEN_HTML_EXTRA_STYLESHEET} + "${_doxygen_theme_SOURCE_DIR}/doxygen-awesome-sidebar-only.css") + endif() + else() + # use the original doxygen theme + endif() + + # find doxygen and dot if available + find_package(Doxygen REQUIRED OPTIONAL_COMPONENTS dot) + + # add doxygen-docs target + message(STATUS "Adding `doxygen-docs` target that builds the documentation.") + doxygen_add_docs(doxygen-docs ALL ${PROJECT_SOURCE_DIR} + COMMENT "Generating documentation - entry file: ${CMAKE_CURRENT_BINARY_DIR}/html/index.html") +endfunction() diff --git a/cmake/Hardening.cmake b/cmake/Hardening.cmake new file mode 100644 index 00000000..5f61d229 --- /dev/null +++ b/cmake/Hardening.cmake @@ -0,0 +1,98 @@ +include(CheckCXXCompilerFlag) + +macro( + myproject_enable_hardening + target + global + ubsan_minimal_runtime) + + message(STATUS "** Enabling Hardening (Target ${target}) **") + + if(MSVC) + set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} /sdl /DYNAMICBASE /guard:cf") + message(STATUS "*** MSVC flags: /sdl /DYNAMICBASE /guard:cf /NXCOMPAT /CETCOMPAT") + set(NEW_LINK_OPTIONS "${NEW_LINK_OPTIONS} /NXCOMPAT /CETCOMPAT") + + elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang|GNU") + set(NEW_CXX_DEFINITIONS "${NEW_CXX_DEFINITIONS} -D_GLIBCXX_ASSERTIONS") + message(STATUS "*** GLIBC++ Assertions (vector[], string[], ...) enabled") + + set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3") + message(STATUS "*** g++/clang _FORTIFY_SOURCE=3 enabled") + + # check_cxx_compiler_flag(-fpie PIE) + #if(PIE) + # set(NEW_COMPILE_OPTIONS ${NEW_COMPILE_OPTIONS} -fpie) + # set(NEW_LINK_OPTIONS ${NEW_LINK_OPTIONS} -pie) + # + # message(STATUS "*** g++/clang PIE mode enabled") + #else() + # message(STATUS "*** g++/clang PIE mode NOT enabled (not supported)") + #endif() + + check_cxx_compiler_flag(-fstack-protector-strong STACK_PROTECTOR) + if(STACK_PROTECTOR) + set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -fstack-protector-strong") + message(STATUS "*** g++/clang -fstack-protector-strong enabled") + else() + message(STATUS "*** g++/clang -fstack-protector-strong NOT enabled (not supported)") + endif() + + check_cxx_compiler_flag(-fcf-protection CF_PROTECTION) + if(CF_PROTECTION) + set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -fcf-protection") + message(STATUS "*** g++/clang -fcf-protection enabled") + else() + message(STATUS "*** g++/clang -fcf-protection NOT enabled (not supported)") + endif() + + check_cxx_compiler_flag(-fstack-clash-protection CLASH_PROTECTION) + if(CLASH_PROTECTION) + if(LINUX OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") + set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -fstack-clash-protection") + message(STATUS "*** g++/clang -fstack-clash-protection enabled") + else() + message(STATUS "*** g++/clang -fstack-clash-protection NOT enabled (clang on non-Linux)") + endif() + else() + message(STATUS "*** g++/clang -fstack-clash-protection NOT enabled (not supported)") + endif() + endif() + + if(${ubsan_minimal_runtime}) + check_cxx_compiler_flag("-fsanitize=undefined -fno-sanitize-recover=undefined -fsanitize-minimal-runtime" + MINIMAL_RUNTIME) + if(MINIMAL_RUNTIME) + set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -fsanitize=undefined -fsanitize-minimal-runtime") + set(NEW_LINK_OPTIONS "${NEW_LINK_OPTIONS} -fsanitize=undefined -fsanitize-minimal-runtime") + + if(NOT ${global}) + set(NEW_COMPILE_OPTIONS "${NEW_COMPILE_OPTIONS} -fno-sanitize-recover=undefined") + set(NEW_LINK_OPTIONS "${NEW_LINK_OPTIONS} -fno-sanitize-recover=undefined") + else() + message(STATUS "** not enabling -fno-sanitize-recover=undefined for global consumption") + endif() + + message(STATUS "*** ubsan minimal runtime enabled") + else() + message(STATUS "*** ubsan minimal runtime NOT enabled (not supported)") + endif() + else() + message(STATUS "*** ubsan minimal runtime NOT enabled (not requested)") + endif() + + message(STATUS "** Hardening Compiler Flags: ${NEW_COMPILE_OPTIONS}") + message(STATUS "** Hardening Linker Flags: ${NEW_LINK_OPTIONS}") + message(STATUS "** Hardening Compiler Defines: ${NEW_CXX_DEFINITIONS}") + + if(${global}) + message(STATUS "** Setting hardening options globally for all dependencies") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${NEW_COMPILE_OPTIONS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${NEW_LINK_OPTIONS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${NEW_CXX_DEFINITIONS}") + else() + target_compile_options(${target} INTERFACE ${NEW_COMPILE_OPTIONS}) + target_link_options(${target} INTERFACE ${NEW_LINK_OPTIONS}) + target_compile_definitions(${target} INTERFACE ${NEW_CXX_DEFINITIONS}) + endif() +endmacro() diff --git a/cmake/InterproceduralOptimization.cmake b/cmake/InterproceduralOptimization.cmake new file mode 100644 index 00000000..7ea4daf7 --- /dev/null +++ b/cmake/InterproceduralOptimization.cmake @@ -0,0 +1,9 @@ +macro(myproject_enable_ipo) + include(CheckIPOSupported) + check_ipo_supported(RESULT result OUTPUT output) + if(result) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) + else() + message(SEND_ERROR "IPO is not supported: ${output}") + endif() +endmacro() diff --git a/cmake/LibFuzzer.cmake b/cmake/LibFuzzer.cmake new file mode 100644 index 00000000..7ab5eea0 --- /dev/null +++ b/cmake/LibFuzzer.cmake @@ -0,0 +1,17 @@ +function(myproject_check_libfuzzer_support var_name) + set(LibFuzzerTestSource + " +#include + +extern \"C\" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) { + return 0; +} + ") + + include(CheckCXXSourceCompiles) + + set(CMAKE_REQUIRED_FLAGS "-fsanitize=fuzzer") + set(CMAKE_REQUIRED_LINK_OPTIONS "-fsanitize=fuzzer") + check_cxx_source_compiles("${LibFuzzerTestSource}" ${var_name}) + +endfunction() diff --git a/cmake/Linker.cmake b/cmake/Linker.cmake new file mode 100644 index 00000000..067aac68 --- /dev/null +++ b/cmake/Linker.cmake @@ -0,0 +1,31 @@ +macro(myproject_configure_linker project_name) + include(CheckCXXCompilerFlag) + + set(USER_LINKER_OPTION + "lld" + CACHE STRING "Linker to be used") + set(USER_LINKER_OPTION_VALUES "lld" "gold" "bfd" "mold") + set_property(CACHE USER_LINKER_OPTION PROPERTY STRINGS ${USER_LINKER_OPTION_VALUES}) + list( + FIND + USER_LINKER_OPTION_VALUES + ${USER_LINKER_OPTION} + USER_LINKER_OPTION_INDEX) + + if(${USER_LINKER_OPTION_INDEX} EQUAL -1) + message( + STATUS + "Using custom linker: '${USER_LINKER_OPTION}', explicitly supported entries are ${USER_LINKER_OPTION_VALUES}") + endif() + + if(NOT ENABLE_USER_LINKER) + return() + endif() + + set(LINKER_FLAG "-fuse-ld=${USER_LINKER_OPTION}") + + check_cxx_compiler_flag(${LINKER_FLAG} CXX_SUPPORTS_USER_LINKER) + if(CXX_SUPPORTS_USER_LINKER) + target_compile_options(${project_name} INTERFACE ${LINKER_FLAG}) + endif() +endmacro() diff --git a/cmake/PackageProject.cmake b/cmake/PackageProject.cmake new file mode 100644 index 00000000..f8314268 --- /dev/null +++ b/cmake/PackageProject.cmake @@ -0,0 +1,190 @@ +# Uses ycm (permissive BSD-3-Clause license) and ForwardArguments (permissive MIT license) + +function(myproject_package_project) + cmake_policy(SET CMP0103 NEW) # disallow multiple calls with the same NAME + + set(_options ARCH_INDEPENDENT # default to false + ) + set(_oneValueArgs + # default to the project_name: + NAME + COMPONENT + # default to project version: + VERSION + # default to semver + COMPATIBILITY + # default to ${CMAKE_BINARY_DIR} + CONFIG_EXPORT_DESTINATION + # default to ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/${NAME} suitable for vcpkg, etc. + CONFIG_INSTALL_DESTINATION) + set(_multiValueArgs + # recursively found for the current folder if not specified + TARGETS + # a list of public/interface include directories or files + PUBLIC_INCLUDES + # the names of the INTERFACE/PUBLIC dependencies that are found using `CONFIG` + PUBLIC_DEPENDENCIES_CONFIGURED + # the INTERFACE/PUBLIC dependencies that are found by any means using `find_dependency`. + # the arguments must be specified within double quotes (e.g. " 1.0.0 EXACT" or " CONFIG"). + PUBLIC_DEPENDENCIES + # the names of the PRIVATE dependencies that are found using `CONFIG`. Only included when BUILD_SHARED_LIBS is OFF. + PRIVATE_DEPENDENCIES_CONFIGURED + # PRIVATE dependencies that are only included when BUILD_SHARED_LIBS is OFF + PRIVATE_DEPENDENCIES) + + cmake_parse_arguments( + _PackageProject + "${_options}" + "${_oneValueArgs}" + "${_multiValueArgs}" + "${ARGN}") + + # Set default options + include(GNUInstallDirs) # Define GNU standard installation directories such as CMAKE_INSTALL_DATADIR + + # set default packaged targets + if(NOT _PackageProject_TARGETS) + get_all_installable_targets(_PackageProject_TARGETS) + message(STATUS "package_project: considering ${_PackageProject_TARGETS} as the exported targets") + endif() + + # default to the name of the project or the given name + if("${_PackageProject_NAME}" STREQUAL "") + set(_PackageProject_NAME ${PROJECT_NAME}) + endif() + # ycm args + set(_PackageProject_NAMESPACE "${_PackageProject_NAME}::") + set(_PackageProject_VARS_PREFIX ${_PackageProject_NAME}) + set(_PackageProject_EXPORT ${_PackageProject_NAME}) + + # default version to the project version + if("${_PackageProject_VERSION}" STREQUAL "") + set(_PackageProject_VERSION ${PROJECT_VERSION}) + endif() + + # default compatibility to SameMajorVersion + if("${_PackageProject_COMPATIBILITY}" STREQUAL "") + set(_PackageProject_COMPATIBILITY "SameMajorVersion") + endif() + + # default to the build directory + if("${_PackageProject_CONFIG_EXPORT_DESTINATION}" STREQUAL "") + set(_PackageProject_CONFIG_EXPORT_DESTINATION "${CMAKE_BINARY_DIR}") + endif() + set(_PackageProject_EXPORT_DESTINATION "${_PackageProject_CONFIG_EXPORT_DESTINATION}") + + # use datadir (works better with vcpkg, etc) + if("${_PackageProject_CONFIG_INSTALL_DESTINATION}" STREQUAL "") + set(_PackageProject_CONFIG_INSTALL_DESTINATION "${CMAKE_INSTALL_DATADIR}/${_PackageProject_NAME}") + endif() + # ycm args + set(_PackageProject_INSTALL_DESTINATION "${_PackageProject_CONFIG_INSTALL_DESTINATION}") + + # Installation of the public/interface includes + if(NOT + "${_PackageProject_PUBLIC_INCLUDES}" + STREQUAL + "") + foreach(_INC ${_PackageProject_PUBLIC_INCLUDES}) + # make include absolute + if(NOT IS_ABSOLUTE ${_INC}) + set(_INC "${CMAKE_CURRENT_SOURCE_DIR}/${_INC}") + endif() + # install include + if(IS_DIRECTORY ${_INC}) + # 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). + install(DIRECTORY ${_INC} DESTINATION "./") + else() + install(FILES ${_INC} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") + endif() + endforeach() + endif() + + # Append the configured public dependencies + if(NOT + "${_PackageProject_PUBLIC_DEPENDENCIES_CONFIGURED}" + STREQUAL + "") + set(_PUBLIC_DEPENDENCIES_CONFIG) + foreach(DEP ${_PackageProject_PUBLIC_DEPENDENCIES_CONFIGURED}) + list(APPEND _PUBLIC_DEPENDENCIES_CONFIG "${DEP} CONFIG") + endforeach() + endif() + list(APPEND _PackageProject_PUBLIC_DEPENDENCIES ${_PUBLIC_DEPENDENCIES_CONFIG}) + # ycm arg + set(_PackageProject_DEPENDENCIES ${_PackageProject_PUBLIC_DEPENDENCIES}) + + # Append the configured private dependencies + if(NOT + "${_PackageProject_PRIVATE_DEPENDENCIES_CONFIGURED}" + STREQUAL + "") + set(_PRIVATE_DEPENDENCIES_CONFIG) + foreach(DEP ${_PackageProject_PRIVATE_DEPENDENCIES_CONFIGURED}) + list(APPEND _PRIVATE_DEPENDENCIES_CONFIG "${DEP} CONFIG") + endforeach() + endif() + # ycm arg + list(APPEND _PackageProject_PRIVATE_DEPENDENCIES ${_PRIVATE_DEPENDENCIES_CONFIG}) + + # Installation of package (compatible with vcpkg, etc) + install( + TARGETS ${_PackageProject_TARGETS} + EXPORT ${_PackageProject_EXPORT} + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT shlib + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT lib + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT bin + PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${_PackageProject_NAME}" COMPONENT dev) + + # install the usage file + set(_targets_str "") + foreach(_target ${_PackageProject_TARGETS}) + set(_targets_str "${_targets_str} ${_PackageProject_NAMESPACE}${_target}") + endforeach() + set(USAGE_FILE_CONTENT + "The package ${_PackageProject_NAME} provides CMake targets: + + find_package(${_PackageProject_NAME} CONFIG REQUIRED) + target_link_libraries(main PRIVATE ${_targets_str}) + ") + install(CODE "MESSAGE(STATUS \"${USAGE_FILE_CONTENT}\")") + file(WRITE "${_PackageProject_EXPORT_DESTINATION}/usage" "${USAGE_FILE_CONTENT}") + install(FILES "${_PackageProject_EXPORT_DESTINATION}/usage" + DESTINATION "${_PackageProject_CONFIG_INSTALL_DESTINATION}") + + unset(_PackageProject_TARGETS) + + # download ForwardArguments + FetchContent_Declare( + _fargs + URL https://github.com/polysquare/cmake-forward-arguments/archive/8c50d1f956172edb34e95efa52a2d5cb1f686ed2.zip) + FetchContent_GetProperties(_fargs) + if(NOT _fargs_POPULATED) + FetchContent_Populate(_fargs) + endif() + include("${_fargs_SOURCE_DIR}/ForwardArguments.cmake") + + # prepare the forward arguments for ycm + set(_FARGS_LIST) + cmake_forward_arguments( + _PackageProject + _FARGS_LIST + OPTION_ARGS + "${_options};" + SINGLEVAR_ARGS + "${_oneValueArgs};EXPORT_DESTINATION;INSTALL_DESTINATION;NAMESPACE;VARS_PREFIX;EXPORT" + MULTIVAR_ARGS + "${_multiValueArgs};DEPENDENCIES;PRIVATE_DEPENDENCIES") + + # download ycm + FetchContent_Declare(_ycm URL https://github.com/robotology/ycm/archive/refs/tags/v0.13.0.zip) + FetchContent_GetProperties(_ycm) + if(NOT _ycm_POPULATED) + FetchContent_Populate(_ycm) + endif() + include("${_ycm_SOURCE_DIR}/modules/InstallBasicPackageFiles.cmake") + + install_basic_package_files(${_PackageProject_NAME} "${_FARGS_LIST}") + + include("${_ycm_SOURCE_DIR}/modules/AddUninstallTarget.cmake") +endfunction() diff --git a/cmake/PreventInSourceBuilds.cmake b/cmake/PreventInSourceBuilds.cmake new file mode 100644 index 00000000..302a0bae --- /dev/null +++ b/cmake/PreventInSourceBuilds.cmake @@ -0,0 +1,19 @@ +# +# This function will prevent in-source builds +# +function(myproject_assure_out_of_source_builds) + # make sure the user doesn't play dirty with symlinks + get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) + get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH) + + # disallow in-source builds + if("${srcdir}" STREQUAL "${bindir}") + message("######################################################") + message("Warning: in-source builds are disabled") + message("Please create a separate build directory and run cmake from there") + message("######################################################") + message(FATAL_ERROR "Quitting configuration") + endif() +endfunction() + +myproject_assure_out_of_source_builds() diff --git a/cmake/Sanitizers.cmake b/cmake/Sanitizers.cmake new file mode 100644 index 00000000..e238fa21 --- /dev/null +++ b/cmake/Sanitizers.cmake @@ -0,0 +1,90 @@ +function( + myproject_enable_sanitizers + project_name + ENABLE_SANITIZER_ADDRESS + ENABLE_SANITIZER_LEAK + ENABLE_SANITIZER_UNDEFINED_BEHAVIOR + ENABLE_SANITIZER_THREAD + ENABLE_SANITIZER_MEMORY) + + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + set(SANITIZERS "") + + if(${ENABLE_SANITIZER_ADDRESS}) + list(APPEND SANITIZERS "address") + endif() + + if(${ENABLE_SANITIZER_LEAK}) + list(APPEND SANITIZERS "leak") + endif() + + if(${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR}) + list(APPEND SANITIZERS "undefined") + endif() + + if(${ENABLE_SANITIZER_THREAD}) + if("address" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS) + message(WARNING "Thread sanitizer does not work with Address and Leak sanitizer enabled") + else() + list(APPEND SANITIZERS "thread") + endif() + endif() + + if(${ENABLE_SANITIZER_MEMORY} AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + message( + WARNING + "Memory sanitizer requires all the code (including libc++) to be MSan-instrumented otherwise it reports false positives" + ) + if("address" IN_LIST SANITIZERS + OR "thread" IN_LIST SANITIZERS + OR "leak" IN_LIST SANITIZERS) + message(WARNING "Memory sanitizer does not work with Address, Thread or Leak sanitizer enabled") + else() + list(APPEND SANITIZERS "memory") + endif() + endif() + elseif(MSVC) + if(${ENABLE_SANITIZER_ADDRESS}) + list(APPEND SANITIZERS "address") + endif() + if(${ENABLE_SANITIZER_LEAK} + OR ${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR} + OR ${ENABLE_SANITIZER_THREAD} + OR ${ENABLE_SANITIZER_MEMORY}) + message(WARNING "MSVC only supports address sanitizer") + endif() + endif() + + list( + JOIN + SANITIZERS + "," + LIST_OF_SANITIZERS) + + if(LIST_OF_SANITIZERS) + if(NOT + "${LIST_OF_SANITIZERS}" + STREQUAL + "") + if(NOT MSVC) + target_compile_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) + target_link_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) + else() + string(FIND "$ENV{PATH}" "$ENV{VSINSTALLDIR}" index_of_vs_install_dir) + if("${index_of_vs_install_dir}" STREQUAL "-1") + message( + SEND_ERROR + "Using MSVC sanitizers requires setting the MSVC environment before building the project. Please manually open the MSVC command prompt and rebuild the project." + ) + endif() + target_compile_options(${project_name} INTERFACE /fsanitize=${LIST_OF_SANITIZERS} /Zi /INCREMENTAL:NO) + target_compile_definitions(${project_name} INTERFACE _DISABLE_VECTOR_ANNOTATION _DISABLE_STRING_ANNOTATION) + target_link_options(${project_name} INTERFACE /INCREMENTAL:NO) + endif() + endif() + endif() + +endfunction() + + + diff --git a/cmake/StandardProjectSettings.cmake b/cmake/StandardProjectSettings.cmake new file mode 100644 index 00000000..b9f4123e --- /dev/null +++ b/cmake/StandardProjectSettings.cmake @@ -0,0 +1,45 @@ +# Set a default build type if none was specified +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to 'RelWithDebInfo' as none was specified.") + set(CMAKE_BUILD_TYPE + RelWithDebInfo + CACHE STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui, ccmake + set_property( + CACHE CMAKE_BUILD_TYPE + PROPERTY STRINGS + "Debug" + "Release" + "MinSizeRel" + "RelWithDebInfo") +endif() + +# Generate compile_commands.json to make it easier to work with clang based tools +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Enhance error reporting and compiler messages +if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + if(WIN32) + # On Windows cuda nvcc uses cl and not clang + add_compile_options($<$:-fcolor-diagnostics> $<$:-fcolor-diagnostics>) + else() + add_compile_options(-fcolor-diagnostics) + endif() +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if(WIN32) + # On Windows cuda nvcc uses cl and not gcc + add_compile_options($<$:-fdiagnostics-color=always> + $<$:-fdiagnostics-color=always>) + else() + add_compile_options(-fdiagnostics-color=always) + endif() +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND MSVC_VERSION GREATER 1900) + add_compile_options(/diagnostics:column) +else() + message(STATUS "No colored compiler diagnostic set for '${CMAKE_CXX_COMPILER_ID}' compiler.") +endif() + + +# run vcvarsall when msvc is used +include("${CMAKE_CURRENT_LIST_DIR}/VCEnvironment.cmake") +run_vcvarsall() diff --git a/cmake/StaticAnalyzers.cmake b/cmake/StaticAnalyzers.cmake new file mode 100644 index 00000000..eb3674e0 --- /dev/null +++ b/cmake/StaticAnalyzers.cmake @@ -0,0 +1,109 @@ +macro(myproject_enable_cppcheck WARNINGS_AS_ERRORS CPPCHECK_OPTIONS) + find_program(CPPCHECK cppcheck) + if(CPPCHECK) + + if(CMAKE_GENERATOR MATCHES ".*Visual Studio.*") + set(CPPCHECK_TEMPLATE "vs") + else() + set(CPPCHECK_TEMPLATE "gcc") + endif() + + if("${CPPCHECK_OPTIONS}" STREQUAL "") + # Enable all warnings that are actionable by the user of this toolset + # style should enable the other 3, but we'll be explicit just in case + set(CMAKE_CXX_CPPCHECK + ${CPPCHECK} + --template=${CPPCHECK_TEMPLATE} + --enable=style,performance,warning,portability + --inline-suppr + # We cannot act on a bug/missing feature of cppcheck + --suppress=cppcheckError + --suppress=internalAstError + # if a file does not have an internalAstError, we get an unmatchedSuppression error + --suppress=unmatchedSuppression + # noisy and incorrect sometimes + --suppress=passedByValue + # ignores code that cppcheck thinks is invalid C++ + --suppress=syntaxError + --suppress=preprocessorErrorDirective + --inconclusive) + else() + # if the user provides a CPPCHECK_OPTIONS with a template specified, it will override this template + set(CMAKE_CXX_CPPCHECK ${CPPCHECK} --template=${CPPCHECK_TEMPLATE} ${CPPCHECK_OPTIONS}) + endif() + + if(NOT + "${CMAKE_CXX_STANDARD}" + STREQUAL + "") + set(CMAKE_CXX_CPPCHECK ${CMAKE_CXX_CPPCHECK} --std=c++${CMAKE_CXX_STANDARD}) + endif() + if(${WARNINGS_AS_ERRORS}) + list(APPEND CMAKE_CXX_CPPCHECK --error-exitcode=2) + endif() + else() + message(${WARNING_MESSAGE} "cppcheck requested but executable not found") + endif() +endmacro() + +macro(myproject_enable_clang_tidy target WARNINGS_AS_ERRORS) + + find_program(CLANGTIDY clang-tidy) + if(CLANGTIDY) + if(NOT + CMAKE_CXX_COMPILER_ID + MATCHES + ".*Clang") + + get_target_property(TARGET_PCH ${target} INTERFACE_PRECOMPILE_HEADERS) + + if("${TARGET_PCH}" STREQUAL "TARGET_PCH-NOTFOUND") + get_target_property(TARGET_PCH ${target} PRECOMPILE_HEADERS) + endif() + + if(NOT ("${TARGET_PCH}" STREQUAL "TARGET_PCH-NOTFOUND")) + message( + SEND_ERROR + "clang-tidy cannot be enabled with non-clang compiler and PCH, clang-tidy fails to handle gcc's PCH file") + endif() + endif() + + # construct the clang-tidy command line + set(CLANG_TIDY_OPTIONS + ${CLANGTIDY} + -extra-arg=-Wno-unknown-warning-option + -extra-arg=-Wno-ignored-optimization-argument + -extra-arg=-Wno-unused-command-line-argument + -p) + # set standard + if(NOT + "${CMAKE_CXX_STANDARD}" + STREQUAL + "") + if("${CLANG_TIDY_OPTIONS_DRIVER_MODE}" STREQUAL "cl") + set(CLANG_TIDY_OPTIONS ${CLANG_TIDY_OPTIONS} -extra-arg=/std:c++${CMAKE_CXX_STANDARD}) + else() + set(CLANG_TIDY_OPTIONS ${CLANG_TIDY_OPTIONS} -extra-arg=-std=c++${CMAKE_CXX_STANDARD}) + endif() + endif() + + # set warnings as errors + if(${WARNINGS_AS_ERRORS}) + list(APPEND CLANG_TIDY_OPTIONS -warnings-as-errors=*) + endif() + + message("Also setting clang-tidy globally") + set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_OPTIONS}) + else() + message(${WARNING_MESSAGE} "clang-tidy requested but executable not found") + endif() +endmacro() + +macro(myproject_enable_include_what_you_use) + find_program(INCLUDE_WHAT_YOU_USE include-what-you-use) + if(INCLUDE_WHAT_YOU_USE) + set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${INCLUDE_WHAT_YOU_USE}) + else() + message(${WARNING_MESSAGE} "include-what-you-use requested but executable not found") + endif() +endmacro() diff --git a/cmake/SystemLink.cmake b/cmake/SystemLink.cmake new file mode 100644 index 00000000..000a9ad1 --- /dev/null +++ b/cmake/SystemLink.cmake @@ -0,0 +1,83 @@ +# Include a system directory (which suppresses its warnings). +function(target_include_system_directories target) + set(multiValueArgs INTERFACE PUBLIC PRIVATE) + cmake_parse_arguments( + ARG + "" + "" + "${multiValueArgs}" + ${ARGN}) + + foreach(scope IN ITEMS INTERFACE PUBLIC PRIVATE) + foreach(lib_include_dirs IN LISTS ARG_${scope}) + if(NOT MSVC) + # system includes do not work in MSVC + # awaiting https://gitlab.kitware.com/cmake/cmake/-/issues/18272# + # awaiting https://gitlab.kitware.com/cmake/cmake/-/issues/17904 + set(_SYSTEM SYSTEM) + endif() + if(${scope} STREQUAL "INTERFACE" OR ${scope} STREQUAL "PUBLIC") + target_include_directories( + ${target} + ${_SYSTEM} + ${scope} + "$" + "$") + else() + target_include_directories( + ${target} + ${_SYSTEM} + ${scope} + ${lib_include_dirs}) + endif() + endforeach() + endforeach() + +endfunction() + +# Include the directories of a library target as system directories (which suppresses their warnings). +function( + target_include_system_library + target + scope + lib) + # check if this is a target + if(TARGET ${lib}) + get_target_property(lib_include_dirs ${lib} INTERFACE_INCLUDE_DIRECTORIES) + if(lib_include_dirs) + target_include_system_directories(${target} ${scope} ${lib_include_dirs}) + else() + message(TRACE "${lib} library does not have the INTERFACE_INCLUDE_DIRECTORIES property.") + endif() + endif() +endfunction() + +# Link a library target as a system library (which suppresses its warnings). +function( + target_link_system_library + target + scope + lib) + # Include the directories in the library + target_include_system_library(${target} ${scope} ${lib}) + + # Link the library + target_link_libraries(${target} ${scope} ${lib}) +endfunction() + +# Link multiple library targets as system libraries (which suppresses their warnings). +function(target_link_system_libraries target) + set(multiValueArgs INTERFACE PUBLIC PRIVATE) + cmake_parse_arguments( + ARG + "" + "" + "${multiValueArgs}" + ${ARGN}) + + foreach(scope IN ITEMS INTERFACE PUBLIC PRIVATE) + foreach(lib IN LISTS ARG_${scope}) + target_link_system_library(${target} ${scope} ${lib}) + endforeach() + endforeach() +endfunction() diff --git a/cmake/Tests.cmake b/cmake/Tests.cmake new file mode 100644 index 00000000..e20c7d49 --- /dev/null +++ b/cmake/Tests.cmake @@ -0,0 +1,6 @@ +function(myproject_enable_coverage project_name) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + target_compile_options(${project_name} INTERFACE --coverage -O0 -g) + target_link_libraries(${project_name} INTERFACE --coverage) + endif() +endfunction() diff --git a/cmake/Utilities.cmake b/cmake/Utilities.cmake new file mode 100644 index 00000000..6fa78b28 --- /dev/null +++ b/cmake/Utilities.cmake @@ -0,0 +1,139 @@ +# find a subtring from a string by a given prefix such as VCVARSALL_ENV_START +function( + find_substring_by_prefix + output + prefix + input) + # find the prefix + string(FIND "${input}" "${prefix}" prefix_index) + if("${prefix_index}" STREQUAL "-1") + message(SEND_ERROR "Could not find ${prefix} in ${input}") + endif() + # find the start index + string(LENGTH "${prefix}" prefix_length) + math(EXPR start_index "${prefix_index} + ${prefix_length}") + + string( + SUBSTRING "${input}" + "${start_index}" + "-1" + _output) + set("${output}" + "${_output}" + PARENT_SCOPE) +endfunction() + +# A function to set environment variables of CMake from the output of `cmd /c set` +function(set_env_from_string env_string) + # replace ; in paths with __sep__ so we can split on ; + string( + REGEX + REPLACE ";" + "__sep__" + env_string_sep_added + "${env_string}") + + # the variables are separated by \r?\n + string( + REGEX + REPLACE "\r?\n" + ";" + env_list + "${env_string_sep_added}") + + foreach(env_var ${env_list}) + # split by = + string( + REGEX + REPLACE "=" + ";" + env_parts + "${env_var}") + + list(LENGTH env_parts env_parts_length) + if("${env_parts_length}" EQUAL "2") + # get the variable name and value + list( + GET + env_parts + 0 + env_name) + list( + GET + env_parts + 1 + env_value) + + # recover ; in paths + string( + REGEX + REPLACE "__sep__" + ";" + env_value + "${env_value}") + + # set env_name to env_value + set(ENV{${env_name}} "${env_value}") + + # update cmake program path + if("${env_name}" EQUAL "PATH") + list(APPEND CMAKE_PROGRAM_PATH ${env_value}) + endif() + endif() + endforeach() +endfunction() + +function(get_all_targets var) + set(targets) + get_all_targets_recursive(targets ${CMAKE_CURRENT_SOURCE_DIR}) + set(${var} + ${targets} + PARENT_SCOPE) +endfunction() + +function(get_all_installable_targets var) + set(targets) + get_all_targets(targets) + foreach(_target ${targets}) + get_target_property(_target_type ${_target} TYPE) + if(NOT + ${_target_type} + MATCHES + ".*LIBRARY|EXECUTABLE") + list(REMOVE_ITEM targets ${_target}) + endif() + endforeach() + set(${var} + ${targets} + PARENT_SCOPE) +endfunction() + +macro(get_all_targets_recursive targets dir) + get_property( + subdirectories + DIRECTORY ${dir} + PROPERTY SUBDIRECTORIES) + foreach(subdir ${subdirectories}) + get_all_targets_recursive(${targets} ${subdir}) + endforeach() + + get_property( + current_targets + DIRECTORY ${dir} + PROPERTY BUILDSYSTEM_TARGETS) + list(APPEND ${targets} ${current_targets}) +endmacro() + +function(is_verbose var) + if("CMAKE_MESSAGE_LOG_LEVEL" STREQUAL "VERBOSE" + OR "CMAKE_MESSAGE_LOG_LEVEL" STREQUAL "DEBUG" + OR "CMAKE_MESSAGE_LOG_LEVEL" STREQUAL "TRACE") + set(${var} + ON + PARENT_SCOPE) + else() + set(${var} + OFF + PARENT_SCOPE) + endif() +endfunction() diff --git a/cmake/VCEnvironment.cmake b/cmake/VCEnvironment.cmake new file mode 100644 index 00000000..a95cb467 --- /dev/null +++ b/cmake/VCEnvironment.cmake @@ -0,0 +1,71 @@ +include("${CMAKE_CURRENT_LIST_DIR}/Utilities.cmake") + +macro(detect_architecture) + # detect the architecture + string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" CMAKE_SYSTEM_PROCESSOR_LOWER) + if(CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL x86 OR CMAKE_SYSTEM_PROCESSOR_LOWER MATCHES "^i[3456]86$") + set(VCVARSALL_ARCH x86) + elseif( + CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL x64 + OR CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL x86_64 + OR CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL amd64) + set(VCVARSALL_ARCH x64) + elseif(CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL arm) + set(VCVARSALL_ARCH arm) + elseif(CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL arm64 OR CMAKE_SYSTEM_PROCESSOR_LOWER STREQUAL aarch64) + set(VCVARSALL_ARCH arm64) + else() + if(CMAKE_HOST_SYSTEM_PROCESSOR) + set(VCVARSALL_ARCH ${CMAKE_HOST_SYSTEM_PROCESSOR}) + else() + set(VCVARSALL_ARCH x64) + message(STATUS "Unkown architecture CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR_LOWER} - using x64") + endif() + endif() +endmacro() + +# Run vcvarsall.bat and set CMake environment variables +function(run_vcvarsall) + # if MSVC but VSCMD_VER is not set, which means vcvarsall has not run + if(MSVC AND "$ENV{VSCMD_VER}" STREQUAL "") + + # find vcvarsall.bat + get_filename_component(MSVC_DIR ${CMAKE_CXX_COMPILER} DIRECTORY) + find_file( + VCVARSALL_FILE + NAMES vcvarsall.bat + PATHS "${MSVC_DIR}" + "${MSVC_DIR}/.." + "${MSVC_DIR}/../.." + "${MSVC_DIR}/../../../../../../../.." + "${MSVC_DIR}/../../../../../../.." + PATH_SUFFIXES "VC/Auxiliary/Build" "Common7/Tools" "Tools") + + if(EXISTS ${VCVARSALL_FILE}) + # detect the architecture + detect_architecture() + + # run vcvarsall and print the environment variables + message(STATUS "Running `${VCVARSALL_FILE} ${VCVARSALL_ARCH}` to set up the MSVC environment") + execute_process( + COMMAND + "cmd" "/c" ${VCVARSALL_FILE} ${VCVARSALL_ARCH} # + "&&" "call" "echo" "VCVARSALL_ENV_START" # + "&" "set" # + OUTPUT_VARIABLE VCVARSALL_OUTPUT + OUTPUT_STRIP_TRAILING_WHITESPACE) + + # parse the output and get the environment variables string + find_substring_by_prefix(VCVARSALL_ENV "VCVARSALL_ENV_START" "${VCVARSALL_OUTPUT}") + + # set the environment variables + set_env_from_string("${VCVARSALL_ENV}") + + else() + message( + WARNING + "Could not find `vcvarsall.bat` for automatic MSVC environment preparation. Please manually open the MSVC command prompt and rebuild the project. + ") + endif() + endif() +endfunction() diff --git a/cmake/_FORTIFY_SOURCE.hpp b/cmake/_FORTIFY_SOURCE.hpp new file mode 100644 index 00000000..f5132780 --- /dev/null +++ b/cmake/_FORTIFY_SOURCE.hpp @@ -0,0 +1,8 @@ +#ifdef _FORTIFY_SOURCE +#if _FORTIFY_SOURCE < 3 +#undef _FORTIFY_SOURCE +#define _FORTIFY_SOURCE 3 +#endif +#else +#define _FORTIFY_SOURCE 3 +#endif diff --git a/conanfile.txt b/conanfile.txt deleted file mode 100644 index 8757fda7..00000000 --- a/conanfile.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Docs at https://docs.conan.io/en/latest/reference/conanfile_txt.html - -[requires] -catch2/2.13.9 -cli11/2.2.0 -spdlog/1.10.0 -ftxui/2.0.0 - -[generators] -cmake_find_package_multi diff --git a/configured_files/config.hpp.in b/configured_files/config.hpp.in index 4e188065..86d5cb38 100644 --- a/configured_files/config.hpp.in +++ b/configured_files/config.hpp.in @@ -4,13 +4,13 @@ // this is a basic example of how a CMake configured file might look // in this particular case, we are using it to set the version number of our executable namespace myproject::cmake { -static constexpr std::string_view project_name = "@PROJECT_NAME@"; -static constexpr std::string_view project_version = "@PROJECT_VERSION@"; -static constexpr int project_version_major { @PROJECT_VERSION_MAJOR@ }; -static constexpr int project_version_minor { @PROJECT_VERSION_MINOR@ }; -static constexpr int project_version_patch { @PROJECT_VERSION_PATCH@ }; -static constexpr int project_version_tweak { @PROJECT_VERSION_TWEAK@ }; -static constexpr std::string_view git_sha = "@GIT_SHA@"; +inline constexpr std::string_view project_name = "@PROJECT_NAME@"; +inline constexpr std::string_view project_version = "@PROJECT_VERSION@"; +inline constexpr int project_version_major { @PROJECT_VERSION_MAJOR@ }; +inline constexpr int project_version_minor { @PROJECT_VERSION_MINOR@ }; +inline constexpr int project_version_patch { @PROJECT_VERSION_PATCH@ }; +inline constexpr int project_version_tweak { @PROJECT_VERSION_TWEAK@ }; +inline constexpr std::string_view git_sha = "@GIT_SHA@"; }// namespace myproject::cmake #endif diff --git a/fuzz_test/CMakeLists.txt b/fuzz_test/CMakeLists.txt index 750d7579..7a392f57 100644 --- a/fuzz_test/CMakeLists.txt +++ b/fuzz_test/CMakeLists.txt @@ -6,12 +6,12 @@ find_package(fmt) add_executable(fuzz_tester fuzz_tester.cpp) target_link_libraries( fuzz_tester - PRIVATE project_options - project_warnings + PRIVATE myproject_options + myproject_warnings fmt::fmt -coverage - -fsanitize=fuzzer,undefined,address) -target_compile_options(fuzz_tester PRIVATE -fsanitize=fuzzer,undefined,address) + -fsanitize=fuzzer) +target_compile_options(fuzz_tester PRIVATE -fsanitize=fuzzer) # Allow short runs during automated testing to see if something new breaks set(FUZZ_RUNTIME diff --git a/include/myproject/sample_library.hpp b/include/myproject/sample_library.hpp new file mode 100644 index 00000000..1b2b1772 --- /dev/null +++ b/include/myproject/sample_library.hpp @@ -0,0 +1,15 @@ +#ifndef SAMPLE_LIBRARY_HPP +#define SAMPLE_LIBRARY_HPP + +#include + +[[nodiscard]] SAMPLE_LIBRARY_EXPORT int factorial(int) noexcept; + +[[nodiscard]] constexpr int factorial_constexpr(int input) noexcept +{ + if (input == 0) { return 1; } + + return input * factorial_constexpr(input - 1); +} + +#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 84e09040..0f92a9d6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,20 +1,2 @@ -find_package(fmt CONFIG REQUIRED) -find_package(spdlog CONFIG REQUIRED) -find_package(CLI11 CONFIG REQUIRED) -find_package(ftxui CONFIG REQUIRED) - -# Generic test that uses conan libs -add_executable(intro main.cpp) - -target_link_libraries( - intro - PRIVATE project_options - project_warnings - CLI11::CLI11 - fmt::fmt - spdlog::spdlog - ftxui::screen - ftxui::dom - ftxui::component) - -target_include_directories(intro PRIVATE "${CMAKE_BINARY_DIR}/configured_files/include") +add_subdirectory(sample_library) +add_subdirectory(ftxui_sample) diff --git a/src/ftxui_sample/CMakeLists.txt b/src/ftxui_sample/CMakeLists.txt new file mode 100644 index 00000000..fc5c65e2 --- /dev/null +++ b/src/ftxui_sample/CMakeLists.txt @@ -0,0 +1,19 @@ +add_executable(intro main.cpp) + +target_link_libraries( + intro + PRIVATE myproject::myproject_options + myproject::myproject_warnings) + +target_link_system_libraries( + intro + PRIVATE + CLI11::CLI11 + fmt::fmt + spdlog::spdlog + lefticus::tools + ftxui::screen + ftxui::dom + ftxui::component) + +target_include_directories(intro PRIVATE "${CMAKE_BINARY_DIR}/configured_files/include") diff --git a/src/main.cpp b/src/ftxui_sample/main.cpp similarity index 66% rename from src/main.cpp rename to src/ftxui_sample/main.cpp index 27ef7f39..aa53beaf 100644 --- a/src/main.cpp +++ b/src/ftxui_sample/main.cpp @@ -11,7 +11,9 @@ #include // for ScreenInteractive #include -// This file will be generated automatically when you run the CMake +#include + +// This file will be generated automatically when cur_you run the CMake // configuration step. It creates a namespace called `myproject`. You can modify // the source template at `configured_files/config.hpp.in`. #include @@ -26,60 +28,60 @@ template struct GameBoard std::size_t move_count{ 0 }; - std::string &get_string(std::size_t x, std::size_t y) { return strings.at(x).at(y); } + std::string &get_string(std::size_t cur_x, std::size_t cur_y) { return strings.at(cur_x).at(cur_y); } - void set(std::size_t x, std::size_t y, bool new_value) + void set(std::size_t cur_x, std::size_t cur_y, bool new_value) { - get(x, y) = new_value; + get(cur_x, cur_y) = new_value; if (new_value) { - get_string(x, y) = " ON"; + get_string(cur_x, cur_y) = " ON"; } else { - get_string(x, y) = "OFF"; + get_string(cur_x, cur_y) = "OFF"; } } void visit(auto visitor) { - for (std::size_t x = 0; x < width; ++x) { - for (std::size_t y = 0; y < height; ++y) { visitor(x, y, *this); } + for (std::size_t cur_x = 0; cur_x < width; ++cur_x) { + for (std::size_t cur_y = 0; cur_y < height; ++cur_y) { visitor(cur_x, cur_y, *this); } } } - [[nodiscard]] bool get(std::size_t x, std::size_t y) const { return values.at(x).at(y); } + [[nodiscard]] bool get(std::size_t cur_x, std::size_t cur_y) const { return values.at(cur_x).at(cur_y); } - [[nodiscard]] bool &get(std::size_t x, std::size_t y) { return values.at(x).at(y); } + [[nodiscard]] bool &get(std::size_t cur_x, std::size_t cur_y) { return values.at(cur_x).at(cur_y); } GameBoard() { - visit([](const auto x, const auto y, auto &gameboard) { gameboard.set(x, y, true); }); + visit([](const auto cur_x, const auto cur_y, auto &gameboard) { gameboard.set(cur_x, cur_y, true); }); } void update_strings() { - for (std::size_t x = 0; x < width; ++x) { - for (std::size_t y = 0; y < height; ++y) { set(x, y, get(x, y)); } + for (std::size_t cur_x = 0; cur_x < width; ++cur_x) { + for (std::size_t cur_y = 0; cur_y < height; ++cur_y) { set(cur_x, cur_y, get(cur_x, cur_y)); } } } - void toggle(std::size_t x, std::size_t y) { set(x, y, !get(x, y)); } + void toggle(std::size_t cur_x, std::size_t cur_y) { set(cur_x, cur_y, !get(cur_x, cur_y)); } - void press(std::size_t x, std::size_t y) + void press(std::size_t cur_x, std::size_t cur_y) { ++move_count; - toggle(x, y); - if (x > 0) { toggle(x - 1, y); } - if (y > 0) { toggle(x, y - 1); } - if (x < width - 1) { toggle(x + 1, y); } - if (y < height - 1) { toggle(x, y + 1); } + toggle(cur_x, cur_y); + if (cur_x > 0) { toggle(cur_x - 1, cur_y); } + if (cur_y > 0) { toggle(cur_x, cur_y - 1); } + if (cur_x < width - 1) { toggle(cur_x + 1, cur_y); } + if (cur_y < height - 1) { toggle(cur_x, cur_y + 1); } } [[nodiscard]] bool solved() const { - for (std::size_t x = 0; x < width; ++x) { - for (std::size_t y = 0; y < height; ++y) { - if (!get(x, y)) { return false; } + for (std::size_t cur_x = 0; cur_x < width; ++cur_x) { + for (std::size_t cur_y = 0; cur_y < height; ++cur_y) { + if (!get(cur_x, cur_y)) { return false; } } } @@ -92,22 +94,22 @@ void consequence_game() { auto screen = ftxui::ScreenInteractive::TerminalOutput(); - GameBoard<3, 3> gb; + GameBoard<3, 3> game_board; std::string quit_text; - const auto update_quit_text = [&quit_text](const auto &game_board) { - quit_text = fmt::format("Quit ({} moves)", game_board.move_count); - if (game_board.solved()) { quit_text += " Solved!"; } + const auto update_quit_text = [&quit_text](const auto &game_board_param) { + quit_text = fmt::format("Quit ({} moves)", game_board_param.move_count); + if (game_board_param.solved()) { quit_text += " Solved!"; } }; const auto make_buttons = [&] { std::vector buttons; - for (std::size_t x = 0; x < gb.width; ++x) { - for (std::size_t y = 0; y < gb.height; ++y) { - buttons.push_back(ftxui::Button(&gb.get_string(x, y), [=, &gb] { - if (!gb.solved()) { gb.press(x, y); } - update_quit_text(gb); + for (std::size_t cur_x = 0; cur_x < game_board.width; ++cur_x) { + for (std::size_t cur_y = 0; cur_y < game_board.height; ++cur_y) { + buttons.push_back(ftxui::Button(&game_board.get_string(cur_x, cur_y), [=, &game_board] { + if (!game_board.solved()) { game_board.press(cur_x, cur_y); } + update_quit_text(game_board); })); } } @@ -123,9 +125,9 @@ void consequence_game() std::size_t idx = 0; - for (std::size_t x = 0; x < gb.width; ++x) { + for (std::size_t cur_x = 0; cur_x < game_board.width; ++cur_x) { std::vector row; - for (std::size_t y = 0; y < gb.height; ++y) { + for (std::size_t cur_y = 0; cur_y < game_board.height; ++cur_y) { row.push_back(buttons[idx]->Render()); ++idx; } @@ -142,12 +144,15 @@ void consequence_game() static constexpr int random_seed = 42; std::mt19937 gen32{ random_seed };// NOLINT fixed seed - std::uniform_int_distribution x(static_cast(0), gb.width - 1); - std::uniform_int_distribution y(static_cast(0), gb.height - 1); - for (int i = 0; i < randomization_iterations; ++i) { gb.press(x(gen32), y(gen32)); } - gb.move_count = 0; - update_quit_text(gb); + // NOLINTNEXTLINE This cannot be const + std::uniform_int_distribution cur_x(static_cast(0), game_board.width - 1); + // NOLINTNEXTLINE This cannot be const + std::uniform_int_distribution cur_y(static_cast(0), game_board.height - 1); + + for (int i = 0; i < randomization_iterations; ++i) { game_board.press(cur_x(gen32), cur_y(gen32)); } + game_board.move_count = 0; + update_quit_text(game_board); auto all_buttons = buttons; all_buttons.push_back(quit_button); @@ -160,9 +165,9 @@ void consequence_game() struct Color { - std::uint8_t R{}; - std::uint8_t G{}; - std::uint8_t B{}; + lefticus::tools::uint_np8_t R{ static_cast(0) }; + lefticus::tools::uint_np8_t G{ static_cast(0) }; + lefticus::tools::uint_np8_t B{ static_cast(0) }; }; // A simple way of representing a bitmap on screen using only characters @@ -172,7 +177,7 @@ struct Bitmap : ftxui::Node : width_(width), height_(height) {} - Color &at(std::size_t x, std::size_t y) { return pixels.at(width_ * y + x); } + Color &at(std::size_t cur_x, std::size_t cur_y) { return pixels.at(width_ * cur_y + cur_x); } void ComputeRequirement() override { @@ -183,14 +188,14 @@ struct Bitmap : ftxui::Node void Render(ftxui::Screen &screen) override { - for (std::size_t x = 0; x < width_; ++x) { - for (std::size_t y = 0; y < height_ / 2; ++y) { - auto &p = screen.PixelAt(box_.x_min + static_cast(x), box_.y_min + static_cast(y)); - p.character = "▄"; - const auto &top_color = at(x, y * 2); - const auto &bottom_color = at(x, y * 2 + 1); - p.background_color = ftxui::Color{ top_color.R, top_color.G, top_color.B }; - p.foreground_color = ftxui::Color{ bottom_color.R, bottom_color.G, bottom_color.B }; + for (std::size_t cur_x = 0; cur_x < width_; ++cur_x) { + for (std::size_t cur_y = 0; cur_y < height_ / 2; ++cur_y) { + auto &pixel = screen.PixelAt(box_.x_min + static_cast(cur_x), box_.y_min + static_cast(cur_y)); + pixel.character = "▄"; + const auto &top_color = at(cur_x, cur_y * 2); + const auto &bottom_color = at(cur_x, cur_y * 2 + 1); + pixel.background_color = ftxui::Color{ top_color.R.get(), top_color.G.get(), top_color.B.get() }; + pixel.foreground_color = ftxui::Color{ bottom_color.R.get(), bottom_color.G.get(), bottom_color.B.get() }; } } } @@ -210,7 +215,7 @@ struct Bitmap : ftxui::Node void game_iteration_canvas() { - // this should probably have a `bitmap` helper function that does what you expect + // this should probably have a `bitmap` helper function that does what cur_you expect // similar to the other parts of FTXUI auto bm = std::make_shared(50, 50);// NOLINT magic numbers auto small_bm = std::make_shared(6, 6);// NOLINT magic numbers diff --git a/src/sample_library/CMakeLists.txt b/src/sample_library/CMakeLists.txt new file mode 100644 index 00000000..b75fe637 --- /dev/null +++ b/src/sample_library/CMakeLists.txt @@ -0,0 +1,27 @@ +include(GenerateExportHeader) + + +add_library(sample_library sample_library.cpp) + + + +add_library(myproject::sample_library ALIAS sample_library) + +target_link_libraries(sample_library PRIVATE myproject_options myproject_warnings) + +target_include_directories(sample_library ${WARNING_GUARD} PUBLIC $ + $) + +target_compile_features(sample_library PUBLIC cxx_std_20) + +set_target_properties( + sample_library + PROPERTIES VERSION ${PROJECT_VERSION} + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN YES) + +generate_export_header(sample_library EXPORT_FILE_NAME ${PROJECT_BINARY_DIR}/include/myproject/sample_library_export.hpp) + +if(NOT BUILD_SHARED_LIBS) + target_compile_definitions(sample_library PUBLIC SAMPLE_LIBRARY_STATIC_DEFINE) +endif() diff --git a/src/sample_library/sample_library.cpp b/src/sample_library/sample_library.cpp new file mode 100644 index 00000000..878deae2 --- /dev/null +++ b/src/sample_library/sample_library.cpp @@ -0,0 +1,13 @@ +#include + +int factorial(int input) noexcept +{ + int result = 1; + + while (input > 0) { + result *= input; + --input; + } + + return result; +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a5e24bc6..0739f424 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -9,7 +9,7 @@ if(PROJECT_IS_TOP_LEVEL OR TEST_INSTALLED_VERSION) find_package(myproject CONFIG REQUIRED) # for intro, project_options, ... - if(NOT TARGET myproject::project_options) + if(NOT TARGET myproject_options) message(FATAL_ERROR "Requiered config package not found!") return() # be strictly paranoid for Template Janitor github action! CK endif() @@ -17,13 +17,7 @@ endif() # ---- Dependencies ---- -find_package(Catch2 CONFIG REQUIRED) - -include(Catch) - -add_library(catch_main OBJECT catch_main.cpp) -target_link_libraries(catch_main PUBLIC Catch2::Catch2) -target_link_libraries(catch_main PRIVATE myproject::project_options) +include(${Catch2_SOURCE_DIR}/extras/Catch.cmake) # Provide a simple smoke test to make sure that the CLI works and can display a --help message add_test(NAME cli.has_help COMMAND intro --help) @@ -36,7 +30,20 @@ add_test(NAME cli.version_matches COMMAND intro --version) set_tests_properties(cli.version_matches PROPERTIES PASS_REGULAR_EXPRESSION "${PROJECT_VERSION}") add_executable(tests tests.cpp) -target_link_libraries(tests PRIVATE myproject::project_warnings myproject::project_options catch_main) +target_link_libraries( + tests + PRIVATE myproject::myproject_warnings + myproject::myproject_options + myproject::sample_library + Catch2::Catch2WithMain) + +if(WIN32 AND BUILD_SHARED_LIBS) + add_custom_command( + TARGET tests + PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ $ + COMMAND_EXPAND_LISTS) +endif() # automatically discover tests that are defined in catch based test files you can modify the unittests. Set TEST_PREFIX # to whatever you want, or use different for different binaries @@ -45,7 +52,7 @@ catch_discover_tests( TEST_PREFIX "unittests." REPORTER - xml + XML OUTPUT_DIR . OUTPUT_PREFIX @@ -55,14 +62,19 @@ catch_discover_tests( # Add a file containing a set of constexpr tests add_executable(constexpr_tests constexpr_tests.cpp) -target_link_libraries(constexpr_tests PRIVATE myproject::project_options myproject::project_warnings catch_main) +target_link_libraries( + constexpr_tests + PRIVATE myproject::myproject_warnings + myproject::myproject_options + myproject::sample_library + Catch2::Catch2WithMain) catch_discover_tests( constexpr_tests TEST_PREFIX "constexpr." REPORTER - xml + XML OUTPUT_DIR . OUTPUT_PREFIX @@ -73,7 +85,12 @@ catch_discover_tests( # Disable the constexpr portion of the test, and build again this allows us to have an executable that we can debug when # things go wrong with the constexpr testing add_executable(relaxed_constexpr_tests constexpr_tests.cpp) -target_link_libraries(relaxed_constexpr_tests PRIVATE myproject::project_options myproject::project_warnings catch_main) +target_link_libraries( + relaxed_constexpr_tests + PRIVATE myproject::myproject_warnings + myproject::myproject_options + myproject::sample_library + Catch2::Catch2WithMain) target_compile_definitions(relaxed_constexpr_tests PRIVATE -DCATCH_CONFIG_RUNTIME_STATIC_REQUIRE) catch_discover_tests( @@ -81,7 +98,7 @@ catch_discover_tests( TEST_PREFIX "relaxed_constexpr." REPORTER - xml + XML OUTPUT_DIR . OUTPUT_PREFIX diff --git a/test/catch_main.cpp b/test/catch_main.cpp deleted file mode 100644 index 28ceb8c5..00000000 --- a/test/catch_main.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#define CATCH_CONFIG_MAIN// This tells the catch header to generate a main - -#include diff --git a/test/constexpr_tests.cpp b/test/constexpr_tests.cpp index 6d95f0db..fe36db5e 100644 --- a/test/constexpr_tests.cpp +++ b/test/constexpr_tests.cpp @@ -1,14 +1,12 @@ -#include +#include -constexpr unsigned int Factorial(unsigned int number)// NOLINT(misc-no-recursion) -{ - return number <= 1 ? number : Factorial(number - 1) * number; -} +#include TEST_CASE("Factorials are computed with constexpr", "[factorial]") { - STATIC_REQUIRE(Factorial(1) == 1); - STATIC_REQUIRE(Factorial(2) == 2); - STATIC_REQUIRE(Factorial(3) == 6); - STATIC_REQUIRE(Factorial(10) == 3628800); + STATIC_REQUIRE(factorial_constexpr(0) == 1); + STATIC_REQUIRE(factorial_constexpr(1) == 1); + STATIC_REQUIRE(factorial_constexpr(2) == 2); + STATIC_REQUIRE(factorial_constexpr(3) == 6); + STATIC_REQUIRE(factorial_constexpr(10) == 3628800); } diff --git a/test/tests.cpp b/test/tests.cpp index a130a5d5..5b632e2e 100644 --- a/test/tests.cpp +++ b/test/tests.cpp @@ -1,14 +1,14 @@ -#include +#include + + +#include -unsigned int Factorial(unsigned int number)// NOLINT(misc-no-recursion) -{ - return number <= 1 ? number : Factorial(number - 1) * number; -} TEST_CASE("Factorials are computed", "[factorial]") { - REQUIRE(Factorial(1) == 1); - REQUIRE(Factorial(2) == 2); - REQUIRE(Factorial(3) == 6); - REQUIRE(Factorial(10) == 3628800); + REQUIRE(factorial(0) == 1); + REQUIRE(factorial(1) == 1); + REQUIRE(factorial(2) == 2); + REQUIRE(factorial(3) == 6); + REQUIRE(factorial(10) == 3628800); }