# https://git.rcrnstn.net/rcrnstn/cmake-cxx function(cxx) ## Arguments set(OPTIONS DISABLE_SANITIZERS DISABLE_CPPCHECK DISABLE_CLANG_TIDY DISABLE_INCLUDE_WHAT_YOU_USE ) set(ONE_VALUE_ARGS CXX_STANDARD ) set(MULTI_VALUE_ARGS PACKAGES FETCHCONTENT DEPENDENCIES_PUBLIC DEPENDENCIES_PRIVATE DEPENDENCIES_TESTS DEFINES ) cmake_parse_arguments(PARSE_ARGV 0 ARG "${OPTIONS}" "${ONE_VALUE_ARGS}" "${MULTI_VALUE_ARGS}" ) ## Helper variables set(NON_INTERFACE_TARGETS) set(PROJECT_IS_INTERFACE_LIBRARY) set(PROJECT_IS_TOP_LEVEL) get_target_property(PROJECT_TYPE ${PROJECT_NAME} TYPE) if("${PROJECT_TYPE}" STREQUAL "INTERFACE_LIBRARY") set(PROJECT_IS_INTERFACE_LIBRARY TRUE) endif() if("${CMAKE_PROJECT_NAME}" STREQUAL "${PROJECT_NAME}") set(PROJECT_IS_TOP_LEVEL TRUE) endif() ## CMake modules include(FetchContent) include(CMakePackageConfigHelpers) include(CheckIPOSupported) include(GNUInstallDirs) if(PROJECT_IS_TOP_LEVEL) include(CTest) include(CPack) endif() ## Packages foreach(PACKAGE ${ARG_PACKAGES}) find_package(${PACKAGE} REQUIRED) endforeach() ## Fetch content foreach(FETCHCONTENT ${ARG_FETCHCONTENT}) get_filename_component(FETCHCONTENT_NAME ${FETCHCONTENT} NAME) FetchContent_Declare(${FETCHCONTENT_NAME} GIT_REPOSITORY ${FETCHCONTENT} ) FetchContent_MakeAvailable(${FETCHCONTENT_NAME}) endforeach() ## Configure main target file(GLOB_RECURSE SRC CONFIGURE_DEPENDS src/* ) file(GLOB_RECURSE INCLUDE CONFIGURE_DEPENDS include/* ) if(NOT ("${SRC}" STREQUAL "" AND "${INCLUDE}" STREQUAL "")) target_sources(${PROJECT_NAME} PRIVATE ${SRC} ${INCLUDE} ) set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY PUBLIC_HEADER ${INCLUDE} ) if(PROJECT_IS_INTERFACE_LIBRARY) target_include_directories(${PROJECT_NAME} INTERFACE "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>" "$<INSTALL_INTERFACE:include>" ) else() target_include_directories(${PROJECT_NAME} PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>" "$<INSTALL_INTERFACE:include>" PRIVATE src ) list(APPEND NON_INTERFACE_TARGETS ${PROJECT_NAME}) endif() target_link_libraries(${PROJECT_NAME} PUBLIC ${ARG_DEPENDENCIES_PUBLIC} PRIVATE "$<BUILD_INTERFACE:${ARG_DEPENDENCIES_PRIVATE}>" ) if(PROJECT_IS_TOP_LEVEL) install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}) install(EXPORT ${PROJECT_NAME} FILE ${PROJECT_NAME}Config.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} ) write_basic_package_version_file(${PROJECT_NAME}ConfigVersion.cmake COMPATIBILITY SameMajorVersion ) install( FILES ${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} ) endif() endif() ## Declare and configure test targets if(PROJECT_IS_TOP_LEVEL AND BUILD_TESTING) file(GLOB_RECURSE TESTS_COMMON CONFIGURE_DEPENDS tests/common/* ) file(GLOB TESTS LIST_DIRECTORIES FALSE CONFIGURE_DEPENDS tests/* ) foreach(TEST ${TESTS}) get_filename_component(TEST_NAME "${TEST}" NAME_WE) set(TEST_TARGET "${PROJECT_NAME}-test-${TEST_NAME}") add_executable(${TEST_TARGET} ${TEST} ${TESTS_COMMON} ) target_include_directories(${TEST_TARGET} PRIVATE tests ) target_link_libraries(${TEST_TARGET} PRIVATE ${PROJECT_NAME} ${ARG_DEPENDENCIES_TESTS} ) add_test( NAME ${TEST_TARGET} COMMAND ${TEST_TARGET} ) list(APPEND NON_INTERFACE_TARGETS ${TEST_TARGET}) endforeach() endif() ## Declare and configure assets file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/assets ) file(GLOB_RECURSE ASSETS RELATIVE "${PROJECT_SOURCE_DIR}" CONFIGURE_DEPENDS assets/* ) if(NOT "${ASSETS}" STREQUAL "") add_custom_target(${PROJECT_NAME}-assets) add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}-assets) endif() foreach(ASSET ${ASSETS}) get_filename_component(ASSET_DIR ${ASSET} DIRECTORY) add_custom_command( DEPENDS ${PROJECT_SOURCE_DIR}/${ASSET} OUTPUT ${CMAKE_BINARY_DIR}/${ASSET} COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/${ASSET_DIR} COMMAND ${CMAKE_COMMAND} -E create_symlink ${PROJECT_SOURCE_DIR}/${ASSET} ${CMAKE_BINARY_DIR}/${ASSET} VERBATIM ) set_property(TARGET ${PROJECT_NAME}-assets APPEND PROPERTY SOURCES ${CMAKE_BINARY_DIR}/${ASSET} ) if(NOT "${ASSET}" MATCHES "/tests/") install( FILES ${PROJECT_SOURCE_DIR}/${ASSET} DESTINATION ${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}/${ASSET_DIR} ) endif() endforeach() ## Preprocessor defines set_property(TARGET ${NON_INTERFACE_TARGETS} APPEND PROPERTY COMPILE_DEFINITIONS ${ARG_DEFINES} ) ## Language version if(NOT "${CXX_STANDARD}" STREQUAL "") set_target_properties(${NON_INTERFACE_TARGETS} PROPERTIES CXX_STANDARD ${ARG_CXX_STANDARD} CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF ) endif() ## Build options if(MSVC) set(COMPILE_OPTIONS /permissive- /WX /W4 ) elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU|.*Clang") if(NOT ARG_DISABLE_SANITIZERS) set(SANITIZE_OPTIONS $<$<CONFIG:Debug>: -g -fno-omit-frame-pointer -fsanitize=address,leak,undefined -fsanitize=float-divide-by-zero -fsanitize=float-cast-overflow # -fsanitize=pointer-compare # -fsanitize=pointer-subtract # -fsanitize=implicit-conversion -fno-sanitize-recover=all > ) endif() set(COMPILE_OPTIONS -pedantic -Werror # -Wfatal-errors -Wall -Wextra -Wconversion -Wsign-conversion -Wdouble-promotion -Wimplicit-fallthrough -Wvla # -Wzero-as-null-pointer-constant # -Wshadow -Weffc++ ${SANITIZE_OPTIONS} ) set(LINK_OPTIONS ${SANITIZE_OPTIONS} ) check_ipo_supported(RESULT INTERPROCEDURAL_OPTIMIZATION) endif() set_property(TARGET ${NON_INTERFACE_TARGETS} APPEND PROPERTY COMPILE_OPTIONS "${COMPILE_OPTIONS}" ) set_property(TARGET ${NON_INTERFACE_TARGETS} APPEND PROPERTY LINK_OPTIONS "${LINK_OPTIONS}" ) set_property(TARGET ${NON_INTERFACE_TARGETS} APPEND PROPERTY INTERPROCEDURAL_OPTIMIZATION "${INTERPROCEDURAL_OPTIMIZATION}" ) ## Sanitizer environment variables set(SAN_STRIP_PATH_PREFIX strip_path_prefix=${PROJECT_BINARY_DIR}) set(ASAN_OPTIONS "${SAN_STRIP_PATH_PREFIX}") set(LSAN_OPTIONS "${SAN_STRIP_PATH_PREFIX}") set(UBSAN_OPTIONS "${SAN_STRIP_PATH_PREFIX}") file(GLOB ASAN_SUPP CONFIGURE_DEPENDS .asan.supp ) file(GLOB LSAN_SUPP CONFIGURE_DEPENDS .lsan.supp ) file(GLOB UBSAN_SUPP CONFIGURE_DEPENDS .ubsan.supp ) if(NOT "${ASAN_SUPP}" STREQUAL "") set(ASAN_OPTIONS "${ASAN_OPTIONS},suppressions=${ASAN_SUPP}") endif() if(NOT "${LSAN_SUPP}" STREQUAL "") set(LSAN_OPTIONS "${LSAN_OPTIONS},suppressions=${LSAN_SUPP}") endif() if(NOT "${UBSAN_SUPP}" STREQUAL "") set(UBSAN_OPTIONS "${UBSAN_OPTIONS},suppressions=${UBSAN_SUPP}") endif() # set(ASAN_OPTIONS "${ASAN_OPTIONS},detect_leaks=1") # set(UBSAN_OPTIONS "${UBSAN_OPTIONS},print_stacktrace=1") set_property(TEST ${NON_INTERFACE_TARGETS} APPEND PROPERTY ENVIRONMENT ASAN_OPTIONS=${ASAN_OPTIONS} LSAN_OPTIONS=${LSAN_OPTIONS} UBSAN_OPTIONS=${UBSAN_OPTIONS} ) ## Tools if(PROJECT_IS_TOP_LEVEL) find_program(CPPCHECK cppcheck) find_program(CLANG_TIDY clang-tidy) find_program(INCLUDE_WHAT_YOU_USE include-what-you-use) if(CPPCHECK AND NOT ARG_DISABLE_CPPCHECK) set(CXX_CPPCHECK ${CPPCHECK} --enable=warning,style --error-exitcode=1 ) endif() if(CLANG_TIDY AND NOT ARG_DISABLE_CLANG_TIDY) set(CXX_CLANG_TIDY ${CLANG_TIDY}) endif() if(INCLUDE_WHAT_YOU_USE AND NOT ARG_DISABLE_INCLUDE_WHAT_YOU_USE) set(CXX_INCLUDE_WHAT_YOU_USE ${INCLUDE_WHAT_YOU_USE}) endif() set_target_properties(${NON_INTERFACE_TARGETS} PROPERTIES EXPORT_COMPILE_COMMANDS ON CXX_CPPCHECK "${CXX_CPPCHECK}" CXX_CLANG_TIDY "${CXX_CLANG_TIDY}" CXX_INCLUDE_WHAT_YOU_USE "${CXX_INCLUDE_WHAT_YOU_USE}" ) endif() endfunction()