4ad2b510 |
# 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()
|