diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 290bcc68..86dde1a0 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -7,6 +7,10 @@ on: pull_request: branches: [ main ] +env: + DEBIAN_FRONTEND: noninteractive + CMAKE_GENERATOR: Ninja + concurrency: group: ${{ github.head_ref || github.run_id }} cancel-in-progress: true @@ -56,7 +60,6 @@ jobs: CC: ${{ matrix.cc }} CXX: ${{ matrix.cxx }} CXX_STANDARD: ${{ matrix.cxx_standard }} - CMAKE_GENERATOR: Ninja run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.build_type}} - name: Build Unit Tests @@ -133,7 +136,6 @@ jobs: CC: ${{ matrix.cc }} CXX: ${{ matrix.cxx }} CXX_STANDARD: ${{ matrix.cxx_standard }} - CMAKE_GENERATOR: Ninja run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.build_type}} - name: Build Unit Tests @@ -163,8 +165,8 @@ jobs: CC: "/usr/lib/llvm-15/bin/clang" CXX: "/usr/lib/llvm-15/bin/clang++" CXX_STANDARD: 20 - CMAKE_GENERATOR: Ninja - run: cmake -B ${{github.workspace}}/build -DCLANG_TOOLS_PATH=/usr/lib/llvm-15/bin + CLANG_TOOLS_PATH: "/usr/lib/llvm-15/bin" + run: cmake -B ${{github.workspace}}/build - name: Run quality checks run: cmake --build ${{github.workspace}}/build -t quality @@ -184,8 +186,8 @@ jobs: CC: "/usr/lib/llvm-15/bin/clang" CXX: "/usr/lib/llvm-15/bin/clang++" CXX_STANDARD: 20 - CMAKE_GENERATOR: Ninja - run: cmake -B ${{github.workspace}}/build -DSANITIZERS=undefined,address + SANITIZERS: "undefined,address" + run: cmake -B ${{github.workspace}}/build - name: Build Unit Tests run: cmake --build ${{github.workspace}}/build -t unit_tests @@ -202,6 +204,10 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Install build tools + run: | + sudo apt install -y ninja-build + - name: Configure CMake run: cmake -B ${{github.workspace}}/build diff --git a/.gitignore b/.gitignore index 451f0fbd..47de7494 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ /venv /examples/hello_world/build /.vscode +/toolchains +CMakePresets.json diff --git a/CMakeLists.txt b/CMakeLists.txt index fa7f757e..75474c46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.20) project( cib @@ -15,8 +15,13 @@ if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) ) endif() +set(CMAKE_EXPORT_COMPILE_COMMANDS + ON + CACHE BOOL "Export compile commands to compile_commands.json." FORCE) + include(cmake/dependencies.cmake) include(cmake/libraries.cmake) +include(cmake/quality.cmake) add_versioned_package("gh:fmtlib/fmt#9.1.0") @@ -32,16 +37,10 @@ target_compile_options( $<$:-Wno-gnu-zero-variadic-macro-arguments>) if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) - if(CLANG_TOOLS_PATH) - list(APPEND CMAKE_PROGRAM_PATH ${CLANG_TOOLS_PATH}) - endif() - - set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - include(cmake/sanitizers.cmake) - include(cmake/test.cmake) - include(cmake/warnings.cmake) + clang_tidy_interface(cib) # Enable functional and performance test suites. + enable_testing() add_subdirectory(test) add_subdirectory(benchmark) add_subdirectory(examples) @@ -64,39 +63,9 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) add_custom_target(release_header DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/include/cib/cib.hpp) - - add_versioned_package( - NAME - Format.cmake - VERSION - 1.7.3 - GITHUB_REPOSITORY - TheLartians/Format.cmake - OPTIONS - "CMAKE_FORMAT_EXCLUDE cmake/CPM.cmake") - - add_custom_target(quality) - add_dependencies(quality check-clang-format check-cmake-format) - - if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - find_program(CLANG_TIDY_PROGRAM "clang-tidy") - if(CLANG_TIDY_PROGRAM) - add_custom_target(clang-tidy) - include(cmake/clang-tidy.cmake) - clang_tidy_interface(cib) - else() - message(STATUS "clang-tidy not found. Adding dummy target.") - set(CLANG_TIDY_NOT_FOUND_COMMAND_ARGS - COMMAND ${CMAKE_COMMAND} -E echo - "Cannot run clang-tidy because clang-tidy not found." COMMAND - ${CMAKE_COMMAND} -E false) - add_custom_target(clang-tidy ${CLANG_TIDY_NOT_FOUND_COMMAND_ARGS}) - endif() - add_dependencies(quality clang-tidy) - endif() endif() -if($ENV{SINGLE_HEADER}) +if(DEFINED ENV{SINGLE_HEADER}) add_dependencies(cib release_header) target_include_directories( diff --git a/cmake/clang-tidy.cmake b/cmake/clang-tidy.cmake index 8bdac097..3816f79f 100644 --- a/cmake/clang-tidy.cmake +++ b/cmake/clang-tidy.cmake @@ -1,8 +1,12 @@ +set(CREATE_CLANG_TIDIABLE_SCRIPT + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/create-clang-tidiable.sh" + CACHE STRING "") + function(clang_tidy_header HEADER TARGET) file(RELATIVE_PATH CT_NAME ${CMAKE_SOURCE_DIR} ${HEADER}) string(REPLACE "/" "_" CT_NAME ${CT_NAME}) get_filename_component(CT_NAME ${CT_NAME} NAME_WLE) - set(CT_NAME "clang_tidy_${CT_NAME}") + set(CT_NAME "clang-tidy_${CT_NAME}") set(CPP_NAME "${CMAKE_BINARY_DIR}/generated-sources/${TARGET}/${CT_NAME}.cpp") @@ -10,8 +14,7 @@ function(clang_tidy_header HEADER TARGET) OUTPUT "${CPP_NAME}" COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/generated-sources/${TARGET}" - COMMAND ${CMAKE_SOURCE_DIR}/cmake/create-clang-tidiable.sh ${CPP_NAME} - ${HEADER} + COMMAND "${CREATE_CLANG_TIDIABLE_SCRIPT}" ${CPP_NAME} ${HEADER} DEPENDS ${HEADER} ${CMAKE_SOURCE_DIR}/.clang-tidy) add_library(${CT_NAME} EXCLUDE_FROM_ALL ${CPP_NAME}) diff --git a/cmake/quality.cmake b/cmake/quality.cmake new file mode 100644 index 00000000..95383bca --- /dev/null +++ b/cmake/quality.cmake @@ -0,0 +1,45 @@ +function(clang_tidy_interface TARGET) + message( + STATUS + "clang_tidy_interface(${TARGET}) is disabled because CMAKE_CXX_COMPILER_ID is ${CMAKE_CXX_COMPILER_ID}." + ) +endfunction() + +if(PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + if(DEFINED ENV{CLANG_TOOLS_PATH}) + list(APPEND CMAKE_PROGRAM_PATH $ENV{CLANG_TOOLS_PATH}) + endif() + + include(cmake/sanitizers.cmake) + include(cmake/test.cmake) + include(cmake/warnings.cmake) + + add_versioned_package( + NAME + Format.cmake + VERSION + 1.7.3 + GITHUB_REPOSITORY + TheLartians/Format.cmake + OPTIONS + "CMAKE_FORMAT_EXCLUDE cmake/CPM.cmake") + + add_custom_target(quality) + add_dependencies(quality check-clang-format check-cmake-format) + + if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + find_program(CLANG_TIDY_PROGRAM "clang-tidy") + if(CLANG_TIDY_PROGRAM) + add_custom_target(clang-tidy) + include(cmake/clang-tidy.cmake) + else() + message(STATUS "clang-tidy not found. Adding dummy target.") + set(CLANG_TIDY_NOT_FOUND_COMMAND_ARGS + COMMAND ${CMAKE_COMMAND} -E echo + "Cannot run clang-tidy because clang-tidy not found." COMMAND + ${CMAKE_COMMAND} -E false) + add_custom_target(clang-tidy ${CLANG_TIDY_NOT_FOUND_COMMAND_ARGS}) + endif() + add_dependencies(quality clang-tidy) + endif() +endif() diff --git a/cmake/sanitizers.cmake b/cmake/sanitizers.cmake index e229b159..ae199228 100644 --- a/cmake/sanitizers.cmake +++ b/cmake/sanitizers.cmake @@ -1,5 +1,9 @@ add_library(sanitizers INTERFACE) +if(DEFINED ENV{SANITIZERS}) + set(SANITIZERS $ENV{SANITIZERS}) +endif() + if(SANITIZERS) target_compile_options( sanitizers diff --git a/cmake/test.cmake b/cmake/test.cmake index e150281e..ce48edac 100644 --- a/cmake/test.cmake +++ b/cmake/test.cmake @@ -1,4 +1,3 @@ -enable_testing() add_custom_target(unit_tests) if(DEFINED ENV{CXX_STANDARD} AND NOT $ENV{CXX_STANDARD} EQUAL "") @@ -7,15 +6,57 @@ else() set(CMAKE_CXX_STANDARD 17) endif() -add_versioned_package("gh:catchorg/Catch2@3.1.1") -add_versioned_package("gh:google/googletest#release-1.12.1") +macro(get_catch2) + if(NOT DEFINED CPM_GOT_CATCH) + add_versioned_package("gh:catchorg/Catch2@3.3.1") + list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/extras) + include(Catch) + set(CPM_GOT_CATCH 1) + endif() +endmacro() + +macro(get_gtest) + if(NOT DEFINED CPM_GOT_GTEST) + add_versioned_package("gh:google/googletest@1.13.0") + include(GoogleTest) + set(CPM_GOT_GTEST 1) + endif() +endmacro() -list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/extras) -include(Catch) -include(GoogleTest) +macro(get_gunit) + get_gtest() + if(NOT DEFINED CPM_GOT_GUNIT) + add_versioned_package( + NAME + gunit + GIT_TAG + d02ec96 + GITHUB_REPOSITORY + cpp-testing/GUnit + DOWNLOAD_ONLY + YES) + set(CPM_GOT_GUNIT 1) + endif() +endmacro() + +macro(add_boost_di) + if(NOT DEFINED CPM_GOT_BOOST_DI) + add_versioned_package("gh:boost-ext/di#9866a4a") + set(CPM_GOT_BOOST_DI 1) + endif() +endmacro() + +macro(add_gherkin) + if(NOT DEFINED CPM_GOT_GHERKIN) + add_subdirectory( + ${gunit_SOURCE_DIR}/libs/gherkin-cpp + ${gunit_BINARY_DIR}/libs/gherkin-cpp EXCLUDE_FROM_ALL SYSTEM) + set(CPM_GOT_GHERKIN 1) + endif() +endmacro() function(add_unit_test name) - set(options CATCH2 GTEST) + set(options CATCH2 GTEST GUNIT) set(multiValueArgs FILES INCLUDE_DIRECTORIES LIBRARIES SYSTEM_LIBRARIES) cmake_parse_arguments(UNIT "${options}" "" "${multiValueArgs}" ${ARGN}) @@ -26,16 +67,29 @@ function(add_unit_test name) target_link_libraries(${name} PRIVATE sanitizers) if(UNIT_CATCH2) + get_catch2() catch_discover_tests(${name}) target_link_libraries_system(${name} PRIVATE Catch2::Catch2WithMain) set(target_test_command $ "--order" "rand") elseif(UNIT_GTEST) + get_gtest() gtest_discover_tests(${name}) target_link_libraries_system(${name} PRIVATE gmock gtest gmock_main) set(target_test_command $ "--gtest_shuffle") + elseif(UNIT_GUNIT) + get_gunit() + add_boost_di() + gtest_discover_tests(${name}) + target_include_directories( + ${name} SYSTEM + PRIVATE ${gunit_SOURCE_DIR}/include + ${gunit_SOURCE_DIR}/libs/json/single_include/nlohmann) + target_link_libraries_system(${name} PRIVATE gtest_main gmock_main + Boost.DI) + set(target_test_command $ "--gtest_shuffle") else() - add_test(NAME ${name} COMMAND ${target_test_command}) set(target_test_command $) + add_test(NAME ${name} COMMAND ${target_test_command}) endif() add_custom_target(all_${name} ALL DEPENDS run_${name}) @@ -48,3 +102,40 @@ function(add_unit_test name) add_dependencies(unit_tests "run_${name}") endfunction() + +function(add_feature_test name) + set(singleValueArgs FEATURE) + set(multiValueArgs FILES INCLUDE_DIRECTORIES LIBRARIES SYSTEM_LIBRARIES) + cmake_parse_arguments(FEAT "${options}" "${singleValueArgs}" + "${multiValueArgs}" ${ARGN}) + + add_executable(${name} ${FEAT_FILES}) + target_include_directories(${name} PRIVATE ${FEAT_INCLUDE_DIRECTORIES}) + target_link_libraries(${name} PRIVATE ${FEAT_LIBRARIES}) + target_link_libraries_system(${name} PRIVATE ${FEAT_SYSTEM_LIBRARIES}) + target_link_libraries(${name} PRIVATE sanitizers) + + get_gunit() + add_gherkin() + add_boost_di() + gtest_discover_tests(${name}) + target_include_directories( + ${name} SYSTEM + PRIVATE ${gunit_SOURCE_DIR}/include + ${gunit_SOURCE_DIR}/libs/json/single_include/nlohmann) + target_link_libraries_system(${name} PRIVATE gtest_main gmock_main + gherkin-cpp Boost.DI) + set(target_test_command $) + + add_custom_target(all_${name} ALL DEPENDS run_${name}) + add_custom_target(run_${name} DEPENDS ${name}.passed ${FEAT_FEATURE}) + get_filename_component(FEATURE_FILE ${FEAT_FEATURE} ABSOLUTE) + add_custom_command( + OUTPUT ${name}.passed + COMMAND ${CMAKE_COMMAND} -E env SCENARIO="${FEATURE_FILE}" + ${target_test_command} + COMMAND ${CMAKE_COMMAND} "-E" "touch" "${name}.passed" + DEPENDS ${name}) + + add_dependencies(unit_tests "run_${name}") +endfunction()