From fc54a93e0fa2cf8b2c5b941748db9dccfcfc01ef Mon Sep 17 00:00:00 2001 From: r4nx Date: Sun, 20 Apr 2025 05:18:33 +0300 Subject: [PATCH] Implement complete CMake support This commit introduces an ability to install the library in any way (at least among those that are known to me): install, `FetchContent`, `add_subdirectory`. Tests are moved into a separate directory, as well as the related `CMakeLists.txt` file. Note that tests are still built by default (which, though, can be turned off by setting `BUILD_TESTING` option to `OFF`) and test cases are completely untouched. --- CMakeLists.txt | 277 ++++-------------- cmake/config.cmake.in | 3 + tests/CMakeLists.txt | 229 +++++++++++++++ catch_tests.cpp => tests/catch_tests.cpp | 0 .../compile_time_tests.cpp | 0 5 files changed, 290 insertions(+), 219 deletions(-) create mode 100644 cmake/config.cmake.in create mode 100644 tests/CMakeLists.txt rename catch_tests.cpp => tests/catch_tests.cpp (100%) rename compile_time_tests.cpp => tests/compile_time_tests.cpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index c849825..0c44500 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,231 +1,70 @@ -cmake_minimum_required(VERSION 3.11) # for FetchContent -project(scope_guard) +cmake_minimum_required(VERSION 3.11) -# handle inclusions and dependencies -include(CheckCXXSymbolExists) -include(CheckCXXCompilerFlag) -include(GNUInstallDirs) - -Include(FetchContent) - -FetchContent_Declare( - Catch2 - GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG v2.13.6 +project( + scope_guard + VERSION 1.1.0 + LANGUAGES CXX ) -FetchContent_MakeAvailable(Catch2) - -# global configurations -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - -# check for compiler support of noexcept in type -set(CMAKE_REQUIRED_FLAGS "-std=c++1z") # only for this check -CHECK_CXX_SYMBOL_EXISTS(__cpp_noexcept_function_type "" HAS_NOEXCEPT_IN_TYPE) -unset(CMAKE_REQUIRED_FLAGS) - -# request C++11 explicitly by default - works around newer compilers otherwise -# omitting `-std=c++11` despite 11 being requested with target_compile_features -set(CMAKE_CXX_STANDARD 11) - -# compiler warnings -if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set(clang_warnings "-Werror -Wall -Wextra -pedantic -Wno-c++98-compat \ - -Wno-unreachable-code -Wno-padded -Wno-exit-time-destructors \ - -Wno-global-constructors -Wno-unused-variable -Wno-unused-member-function \ - -Wno-unused-function -Wno-class-varargs") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${clang_warnings}") -elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wall -Wpedantic \ - -Wno-unused") - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10) - # earlier versions complain of unused "nodiscard" results in unevaluated contexts - # (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89070) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=unused-result") - endif() - - check_cxx_compiler_flag("-Wnoexcept-type" HAS_NOEXCEPT_TYPE_WARNING) - if(HAS_NOEXCEPT_TYPE_WARNING) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-noexcept-type") - endif() +include(GNUInstallDirs) - option(ENABLE_COVERAGE "Enable coverage reporting" FALSE) -elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") - string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") - endif() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /WX") +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + set(SCOPE_GUARD_MASTER_PROJECT ON) endif() -# utility to derive a string with success or failure from boolean param -function(expect_str ret should_succeed) - if(${should_succeed}) - set(${ret} "success" PARENT_SCOPE) - else() - set(${ret} "failure" PARENT_SCOPE) - endif() -endfunction() - -# utility to derive noexcept requirement status string from boolean param -function(noexc_str ret require_noexcept) - if(${require_noexcept}) - set(${ret} "reqnoexc" PARENT_SCOPE) - else() - set(${ret} "allowexc" PARENT_SCOPE) - endif() -endfunction() - -# utility to derive standard number from boolean param -function(std_num ret use_cxx17) - if(${use_cxx17}) - set(${ret} 17 PARENT_SCOPE) - else() - set(${ret} 11 PARENT_SCOPE) - endif() -endfunction() - -# utility to derive a string describing the c++ standard -function(std_str ret stdn) - set(${ret} cpp${stdn} PARENT_SCOPE) -endfunction() - -# utility to derive the c++ standard compile feature -function(std_ftr ret stdn) - set(${ret} cxx_std_${stdn} PARENT_SCOPE) -endfunction() - -# utility to derive strings for test configuration -function(derive_common_test_strings - tst_ret exe_ret ftr_ret # out params - base_name should_succeed cxx17 require_noexcept # in params - ) - expect_str(expect ${should_succeed}) - noexc_str(noexc ${require_noexcept}) - std_num(stdn ${cxx17}) - std_str(std ${stdn}) - std_ftr(${ftr_ret} ${stdn}) - - string(CONCAT ${exe_ret} ${base_name} "_" ${expect} "_" ${std} "_" - ${noexc}) - string(CONCAT ${tst_ret} "test_" ${${exe_ret}}) - - # return these - set(${tst_ret} ${${tst_ret}} PARENT_SCOPE) - set(${exe_ret} ${${exe_ret}} PARENT_SCOPE) - set(${ftr_ret} ${${ftr_ret}} PARENT_SCOPE) -endfunction() - -# utility to add executable and configure common properties -function(add_test_exe exe src ftr require_noexcept) - add_executable(${exe} ${src}) - target_compile_features(${exe} PRIVATE ${ftr}) - if(${require_noexcept}) - target_compile_definitions(${exe} PRIVATE SG_REQUIRE_NOEXCEPT_IN_CPP17) - endif() -endfunction() +add_library(scope_guard INTERFACE) +add_library(scope_guard::scope_guard ALIAS scope_guard) -# utility to add a batch of catch tests with the specified c++ standard and -# noexcept requirement -function(add_catch_tests_batch exe_ret src cxx17 require_noexcept) - derive_common_test_strings(tst exe ftr # out params - "catch_batch" TRUE ${cxx17} ${require_noexcept}) # in params - add_test_exe(${exe} ${src} ${ftr} ${require_noexcept}) - target_link_libraries(${exe} PRIVATE Catch2::Catch2) - - add_test(NAME ${tst} COMMAND ${exe} "--order" "lex") - - set(${exe_ret} ${exe} PARENT_SCOPE) # return -endfunction() - -# utility to add a compilation test, with the specified C++ standard and -# noexcept requirement, along with a success/failure expectation and a counter -# that identifies what parts of the code to activate -function(add_compilation_test src should_succeed cxx17 require_noexcept countid) - derive_common_test_strings(tst exe ftr # out params - "compilation" ${should_succeed} ${cxx17} ${require_noexcept}) # in params - - string(APPEND tst "_" ${countid}) - string(APPEND exe "_" ${countid}) - string(CONCAT def "test_" ${countid}) - add_test_exe(${exe} ${src} ${ftr} ${require_noexcept}) - - # only build this when running tests (building _is_ the test) - set_target_properties(${exe} - PROPERTIES EXCLUDE_FROM_ALL ON - EXCLUDE_FROM_DEFAULT_BUILD ON) - - # add macro to select active code section - target_compile_definitions(${exe} PRIVATE ${def}) - - # determine the test command - set(tst_cmd ${CMAKE_COMMAND} --build . --target ${exe} --config $) - - # if using MSBuild, prevent logo and warning summary - if(${CMAKE_GENERATOR} MATCHES "[vV]isual [sS]tudio") - list(APPEND tst_cmd -- /nologo /verbosity:minimal) - endif() - - # create the test - add_test(NAME ${tst} - COMMAND ${tst_cmd} - WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) - - # configure the test to expect compilation success or failure - if(${should_succeed}) - set_tests_properties(${tst} # confirm no warnings in successful test - PROPERTIES FAIL_REGULAR_EXPRESSION "warn;Warn;WARN") - else() - set_tests_properties(${tst} PROPERTIES WILL_FAIL TRUE) # expect a failure - endif() -endfunction() - -# utility to determine whether a compilation test should expect building to fail -function(expect_result ret count cxx17 reqne) - if(${count} LESS 35) - set(${ret} TRUE PARENT_SCOPE) - - elseif(${count} LESS 52) - if(${cxx17} AND ${reqne}) - set(${ret} FALSE PARENT_SCOPE) - else() - set(${ret} TRUE PARENT_SCOPE) - endif() - else() - set(${ret} FALSE PARENT_SCOPE) - endif() -endfunction() +target_include_directories(scope_guard + INTERFACE + $ + $ +) -set(cxx17_possibilities FALSE) -if(HAS_NOEXCEPT_IN_TYPE) - list(APPEND cxx17_possibilities TRUE) +option(SCOPE_GUARD_INSTALL "Generate the install targets" ${SCOPE_GUARD_MASTER_PROJECT}) +if(SCOPE_GUARD_INSTALL) + include(CMakePackageConfigHelpers) + + install( + TARGETS scope_guard + EXPORT ${PROJECT_NAME}-targets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ) + install(FILES "scope_guard.hpp" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + + set(config_file "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake") + set(version_file "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake") + set(config_install_destination "lib/cmake/${PROJECT_NAME}") + + configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/config.cmake.in" + ${config_file} + INSTALL_DESTINATION ${config_install_destination} + ) + + write_basic_package_version_file( + ${version_file} + COMPATIBILITY SameMajorVersion + ) + + install( + FILES ${config_file} ${version_file} + DESTINATION ${config_install_destination} + ) + install( + EXPORT ${PROJECT_NAME}-targets + NAMESPACE ${PROJECT_NAME}:: + DESTINATION ${config_install_destination} + ) endif() -# actually add the tests -foreach(cxx17 ${cxx17_possibilities}) - foreach(reqne FALSE TRUE) - # add catch tests for this standard/noexcept-requirement combination - add_catch_tests_batch(catch_batch_exe catch_tests.cpp ${cxx17} ${reqne}) +if(SCOPE_GUARD_MASTER_PROJECT) + include(CTest) - if(ENABLE_COVERAGE) # configure catch tests for coverage if needed - target_compile_options(${catch_batch_exe} PRIVATE --coverage -O0) - target_link_libraries(${catch_batch_exe} PRIVATE --coverage) - else() # only run compile time tests in non-coverage builds - # add noexcept compilation tests for this standard/noexcept-requirement - # combination - foreach(count RANGE 71) # range inclusive in cmake (so 0 or 72 tests) - # 0-34: always succeed (0 = all test macros off) - # 35-51: fail when requiring noexcept - # 52-71: always fail - expect_result(success ${count} ${cxx17} ${reqne}) - add_compilation_test(compile_time_tests.cpp - ${success} ${cxx17} ${reqne} ${count}) - endforeach() + if(BUILD_TESTING) + add_subdirectory(tests) endif() - endforeach() -endforeach() - -add_custom_target(test_verbose COMMAND ${CMAKE_CTEST_COMMAND} --verbose) -enable_testing() +endif() diff --git a/cmake/config.cmake.in b/cmake/config.cmake.in new file mode 100644 index 0000000..6bd5004 --- /dev/null +++ b/cmake/config.cmake.in @@ -0,0 +1,3 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake") diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..93d4516 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,229 @@ +# handle inclusions and dependencies +include(CheckCXXSymbolExists) +include(CheckCXXCompilerFlag) +include(GNUInstallDirs) + +Include(FetchContent) + +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v2.13.6 +) + +FetchContent_MakeAvailable(Catch2) + +# global configurations +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# check for compiler support of noexcept in type +set(CMAKE_REQUIRED_FLAGS "-std=c++1z") # only for this check +CHECK_CXX_SYMBOL_EXISTS(__cpp_noexcept_function_type "" HAS_NOEXCEPT_IN_TYPE) +unset(CMAKE_REQUIRED_FLAGS) + +# request C++11 explicitly by default - works around newer compilers otherwise +# omitting `-std=c++11` despite 11 being requested with target_compile_features +set(CMAKE_CXX_STANDARD 11) + +# compiler warnings +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(clang_warnings "-Werror -Wall -Wextra -pedantic -Wno-c++98-compat \ + -Wno-unreachable-code -Wno-padded -Wno-exit-time-destructors \ + -Wno-global-constructors -Wno-unused-variable -Wno-unused-member-function \ + -Wno-unused-function -Wno-class-varargs") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${clang_warnings}") +elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wall -Wpedantic \ + -Wno-unused") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10) + # earlier versions complain of unused "nodiscard" results in unevaluated contexts + # (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89070) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=unused-result") + endif() + + check_cxx_compiler_flag("-Wnoexcept-type" HAS_NOEXCEPT_TYPE_WARNING) + if(HAS_NOEXCEPT_TYPE_WARNING) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-noexcept-type") + endif() + + option(ENABLE_COVERAGE "Enable coverage reporting" FALSE) +elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") + string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") + endif() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /WX") +endif() + +# utility to derive a string with success or failure from boolean param +function(expect_str ret should_succeed) + if(${should_succeed}) + set(${ret} "success" PARENT_SCOPE) + else() + set(${ret} "failure" PARENT_SCOPE) + endif() +endfunction() + +# utility to derive noexcept requirement status string from boolean param +function(noexc_str ret require_noexcept) + if(${require_noexcept}) + set(${ret} "reqnoexc" PARENT_SCOPE) + else() + set(${ret} "allowexc" PARENT_SCOPE) + endif() +endfunction() + +# utility to derive standard number from boolean param +function(std_num ret use_cxx17) + if(${use_cxx17}) + set(${ret} 17 PARENT_SCOPE) + else() + set(${ret} 11 PARENT_SCOPE) + endif() +endfunction() + +# utility to derive a string describing the c++ standard +function(std_str ret stdn) + set(${ret} cpp${stdn} PARENT_SCOPE) +endfunction() + +# utility to derive the c++ standard compile feature +function(std_ftr ret stdn) + set(${ret} cxx_std_${stdn} PARENT_SCOPE) +endfunction() + +# utility to derive strings for test configuration +function(derive_common_test_strings + tst_ret exe_ret ftr_ret # out params + base_name should_succeed cxx17 require_noexcept # in params + ) + expect_str(expect ${should_succeed}) + noexc_str(noexc ${require_noexcept}) + std_num(stdn ${cxx17}) + std_str(std ${stdn}) + std_ftr(${ftr_ret} ${stdn}) + + string(CONCAT ${exe_ret} ${base_name} "_" ${expect} "_" ${std} "_" + ${noexc}) + string(CONCAT ${tst_ret} "test_" ${${exe_ret}}) + + # return these + set(${tst_ret} ${${tst_ret}} PARENT_SCOPE) + set(${exe_ret} ${${exe_ret}} PARENT_SCOPE) + set(${ftr_ret} ${${ftr_ret}} PARENT_SCOPE) +endfunction() + +# utility to add executable and configure common properties +function(add_test_exe exe src ftr require_noexcept) + add_executable(${exe} ${src}) + target_link_libraries(${exe} PRIVATE scope_guard::scope_guard) + target_compile_features(${exe} PRIVATE ${ftr}) + if(${require_noexcept}) + target_compile_definitions(${exe} PRIVATE SG_REQUIRE_NOEXCEPT_IN_CPP17) + endif() +endfunction() + +# utility to add a batch of catch tests with the specified c++ standard and +# noexcept requirement +function(add_catch_tests_batch exe_ret src cxx17 require_noexcept) + derive_common_test_strings(tst exe ftr # out params + "catch_batch" TRUE ${cxx17} ${require_noexcept}) # in params + add_test_exe(${exe} ${src} ${ftr} ${require_noexcept}) + target_link_libraries(${exe} PRIVATE Catch2::Catch2) + + add_test(NAME ${tst} COMMAND ${exe} "--order" "lex") + + set(${exe_ret} ${exe} PARENT_SCOPE) # return +endfunction() + +# utility to add a compilation test, with the specified C++ standard and +# noexcept requirement, along with a success/failure expectation and a counter +# that identifies what parts of the code to activate +function(add_compilation_test src should_succeed cxx17 require_noexcept countid) + derive_common_test_strings(tst exe ftr # out params + "compilation" ${should_succeed} ${cxx17} ${require_noexcept}) # in params + + string(APPEND tst "_" ${countid}) + string(APPEND exe "_" ${countid}) + string(CONCAT def "test_" ${countid}) + add_test_exe(${exe} ${src} ${ftr} ${require_noexcept}) + + # only build this when running tests (building _is_ the test) + set_target_properties(${exe} + PROPERTIES EXCLUDE_FROM_ALL ON + EXCLUDE_FROM_DEFAULT_BUILD ON) + + # add macro to select active code section + target_compile_definitions(${exe} PRIVATE ${def}) + + # determine the test command + set(tst_cmd ${CMAKE_COMMAND} --build . --target ${exe} --config $) + + # if using MSBuild, prevent logo and warning summary + if(${CMAKE_GENERATOR} MATCHES "[vV]isual [sS]tudio") + list(APPEND tst_cmd -- /nologo /verbosity:minimal) + endif() + + # create the test + add_test(NAME ${tst} + COMMAND ${tst_cmd} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + + # configure the test to expect compilation success or failure + if(${should_succeed}) + set_tests_properties(${tst} # confirm no warnings in successful test + PROPERTIES FAIL_REGULAR_EXPRESSION "warn;Warn;WARN") + else() + set_tests_properties(${tst} PROPERTIES WILL_FAIL TRUE) # expect a failure + endif() +endfunction() + +# utility to determine whether a compilation test should expect building to fail +function(expect_result ret count cxx17 reqne) + if(${count} LESS 35) + set(${ret} TRUE PARENT_SCOPE) + + elseif(${count} LESS 52) + if(${cxx17} AND ${reqne}) + set(${ret} FALSE PARENT_SCOPE) + else() + set(${ret} TRUE PARENT_SCOPE) + endif() + else() + set(${ret} FALSE PARENT_SCOPE) + endif() +endfunction() + +set(cxx17_possibilities FALSE) +if(HAS_NOEXCEPT_IN_TYPE) + list(APPEND cxx17_possibilities TRUE) +endif() + +# actually add the tests +foreach(cxx17 ${cxx17_possibilities}) + foreach(reqne FALSE TRUE) + # add catch tests for this standard/noexcept-requirement combination + add_catch_tests_batch(catch_batch_exe catch_tests.cpp ${cxx17} ${reqne}) + + if(ENABLE_COVERAGE) # configure catch tests for coverage if needed + target_compile_options(${catch_batch_exe} PRIVATE --coverage -O0) + target_link_libraries(${catch_batch_exe} PRIVATE --coverage) + else() # only run compile time tests in non-coverage builds + # add noexcept compilation tests for this standard/noexcept-requirement + # combination + foreach(count RANGE 71) # range inclusive in cmake (so 0 or 72 tests) + # 0-34: always succeed (0 = all test macros off) + # 35-51: fail when requiring noexcept + # 52-71: always fail + expect_result(success ${count} ${cxx17} ${reqne}) + add_compilation_test(compile_time_tests.cpp + ${success} ${cxx17} ${reqne} ${count}) + endforeach() + endif() + endforeach() +endforeach() + +add_custom_target(test_verbose COMMAND ${CMAKE_CTEST_COMMAND} --verbose) +enable_testing() diff --git a/catch_tests.cpp b/tests/catch_tests.cpp similarity index 100% rename from catch_tests.cpp rename to tests/catch_tests.cpp diff --git a/compile_time_tests.cpp b/tests/compile_time_tests.cpp similarity index 100% rename from compile_time_tests.cpp rename to tests/compile_time_tests.cpp