7011ec66 |
# https://git.rcrnstn.net/rcrnstn/cmake-common
## CMake modules
include(FetchContent)
include(CMakePackageConfigHelpers)
include(CheckIPOSupported)
include(GNUInstallDirs)
include(CTest)
if(NOT CPack_CMake_INCLUDED)
include(CPack)
endif()
function(common)
## Arguments
set(OPTIONS
DISABLE_WSHADOW
DISABLE_SANITIZERS
DISABLE_CPPCHECK
DISABLE_CLANG_TIDY
DISABLE_INCLUDE_WHAT_YOU_USE
)
set(ONE_VALUE_ARGS
CXX_STANDARD
)
set(MULTI_VALUE_ARGS
DEFINITIONS
PACKAGES
EXTERNAL
FETCHCONTENT
DEPENDENCIES_PUBLIC
DEPENDENCIES_PRIVATE
DEPENDENCIES_TESTS
)
cmake_parse_arguments(PARSE_ARGV 0 ARG
"${OPTIONS}"
"${ONE_VALUE_ARGS}"
"${MULTI_VALUE_ARGS}"
)
## Variables
set(TEST_TARGETS)
set(PROJECT_IS_TOP_LEVEL)
set(PROJECT_IS_INTERFACE_LIBRARY)
set(PROJECT_IS_DEBUG)
get_target_property(PROJECT_TYPE ${PROJECT_NAME} TYPE)
string(TOLOWER "${CMAKE_BUILD_TYPE}" PROJECT_BUILD_TYPE)
if("${CMAKE_PROJECT_NAME}" STREQUAL "${PROJECT_NAME}")
set(PROJECT_IS_TOP_LEVEL TRUE)
endif()
if("${PROJECT_TYPE}" STREQUAL "INTERFACE_LIBRARY")
set(PROJECT_IS_INTERFACE_LIBRARY TRUE)
endif()
if("${PROJECT_BUILD_TYPE}" STREQUAL "debug")
set(PROJECT_IS_DEBUG TRUE)
endif()
## Definitions
add_compile_definitions(${ARG_DEFINITIONS})
## Packages
foreach(PACKAGE ${ARG_PACKAGES})
find_package(${PACKAGE} REQUIRED)
endforeach()
## External
foreach(EXTERNAL ${ARG_EXTERNAL})
add_subdirectory(external/${EXTERNAL} EXCLUDE_FROM_ALL)
endforeach()
## FetchContent
foreach(FETCHCONTENT ${ARG_FETCHCONTENT})
get_filename_component(FETCHCONTENT_NAME ${FETCHCONTENT} NAME)
FetchContent_Declare(${FETCHCONTENT_NAME}
GIT_REPOSITORY ${FETCHCONTENT}
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
FetchContent_MakeAvailable(${FETCHCONTENT_NAME})
endforeach()
## Main target
file(GLOB_RECURSE SRC CONFIGURE_DEPENDS
RELATIVE "${PROJECT_SOURCE_DIR}"
src/*
)
file(GLOB_RECURSE INCLUDE CONFIGURE_DEPENDS
RELATIVE "${PROJECT_SOURCE_DIR}"
include/*
)
if(NOT ("${SRC}" STREQUAL "" AND "${INCLUDE}" STREQUAL ""))
if(PROJECT_IS_INTERFACE_LIBRARY)
target_include_directories(${PROJECT_NAME}
INTERFACE
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
else()
target_sources(${PROJECT_NAME}
PRIVATE
${SRC}
${INCLUDE}
)
target_include_directories(${PROJECT_NAME}
PUBLIC
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
PRIVATE
src
)
target_link_libraries(${PROJECT_NAME}
PUBLIC
${ARG_DEPENDENCIES_PUBLIC}
PRIVATE
"$<BUILD_INTERFACE:${ARG_DEPENDENCIES_PRIVATE}>"
)
endif()
if(PROJECT_IS_TOP_LEVEL)
install(
DIRECTORY
include/
DESTINATION
${CMAKE_INSTALL_INCLUDEDIR}
)
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()
## Test targets
if(PROJECT_IS_TOP_LEVEL AND BUILD_TESTING)
file(GLOB_RECURSE TESTS_COMMON CONFIGURE_DEPENDS
tests/common/*
)
file(GLOB TESTS CONFIGURE_DEPENDS
LIST_DIRECTORIES FALSE
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 TEST_TARGETS ${TEST_TARGET})
endforeach()
endif()
## Assets
file(REMOVE_RECURSE
${CMAKE_BINARY_DIR}/assets
)
file(GLOB_RECURSE ASSETS CONFIGURE_DEPENDS
RELATIVE "${PROJECT_SOURCE_DIR}"
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()
## Documentation
if(PROJECT_IS_TOP_LEVEL)
file(GLOB_RECURSE DOCS CONFIGURE_DEPENDS
RELATIVE "${PROJECT_SOURCE_DIR}"
doc/*
)
file(GLOB DOCS_ROOT CONFIGURE_DEPENDS
RELATIVE "${PROJECT_SOURCE_DIR}"
README*
LICENSE*
COPYING*
CHANGELOG*
CHANGES*
HISTORY*
NEWS*
RELEASES*
AUTHORS*
ACKNOWLEDGMENTS*
CONTRIBUTORS*
CONTRIBUTING*
CODE_OF_CONDUCT*
SECURITY*
SUPPORT*
)
list(APPEND DOCS ${DOCS_ROOT})
foreach(DOC ${DOCS})
get_filename_component(DOC_DIR ${DOC} DIRECTORY)
install(
FILES
${PROJECT_SOURCE_DIR}/${DOC}
DESTINATION
${CMAKE_INSTALL_DOCDIR}/${DOC_DIR}
)
endforeach()
endif()
## Man pages
if(PROJECT_IS_TOP_LEVEL)
file(GLOB_RECURSE MANS CONFIGURE_DEPENDS
RELATIVE "${PROJECT_SOURCE_DIR}"
man/*
)
foreach(MAN ${MANS})
get_filename_component(MAN_DIR ${MAN} DIRECTORY)
install(
FILES
${PROJECT_SOURCE_DIR}/${MAN}
DESTINATION
${CMAKE_INSTALL_MANDIR}/${MAN_DIR}
)
endforeach()
endif()
## Language version
if(PROJECT_IS_TOP_LEVEL AND NOT PROJECT_IS_INTERFACE_LIBRARY)
set_target_properties(${PROJECT_NAME} ${TEST_TARGETS} PROPERTIES
CXX_STANDARD "${ARG_CXX_STANDARD}"
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
)
endif()
## Build options
if(PROJECT_IS_TOP_LEVEL AND NOT PROJECT_IS_INTERFACE_LIBRARY)
if(MSVC)
set(COMPILE_OPTIONS
/permissive-
/WX
/W4
)
elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU|.*Clang")
set(SANITIZE_OPTIONS)
if(PROJECT_IS_DEBUG AND 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(WSHADOW_OPTION)
if(NOT ARG_DISABLE_WSHADOW)
set(WSHADOW_OPTION -Wshadow=local)
endif()
set(COMPILE_OPTIONS
-pedantic
-Werror -Wfatal-errors
-Wall -Wextra
-Wno-missing-braces -Wmissing-field-initializers
-Wconversion -Wsign-conversion
-Wdouble-promotion
-Wimplicit-fallthrough
-Wvla
-Wzero-as-null-pointer-constant
-Weffc++
${WSHADOW_OPTION}
${SANITIZE_OPTIONS}
)
set(LINK_OPTIONS
${SANITIZE_OPTIONS}
)
check_ipo_supported(RESULT INTERPROCEDURAL_OPTIMIZATION)
endif()
set_property(TARGET ${PROJECT_NAME} ${TEST_TARGETS} APPEND PROPERTY
COMPILE_OPTIONS "${COMPILE_OPTIONS}"
)
set_property(TARGET ${PROJECT_NAME} ${TEST_TARGETS} APPEND PROPERTY
LINK_OPTIONS "${LINK_OPTIONS}"
)
set_property(TARGET ${PROJECT_NAME} ${TEST_TARGETS} APPEND PROPERTY
INTERPROCEDURAL_OPTIMIZATION "${INTERPROCEDURAL_OPTIMIZATION}"
)
endif()
## 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 ${TEST_TARGETS} APPEND PROPERTY
ENVIRONMENT
ASAN_OPTIONS=${ASAN_OPTIONS}
LSAN_OPTIONS=${LSAN_OPTIONS}
UBSAN_OPTIONS=${UBSAN_OPTIONS}
)
## Tools
if(
PROJECT_IS_TOP_LEVEL AND
PROJECT_IS_DEBUG AND
NOT PROJECT_IS_INTERFACE_LIBRARY
)
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
--inline-suppr
--template "{file}:{line}: ({severity} {id}) {message}"
--error-exitcode=1
)
endif()
if(CLANG_TIDY AND NOT ARG_DISABLE_CLANG_TIDY)
set(CXX_CLANG_TIDY ${CLANG_TIDY}
--extra-arg=-Wno-error=unknown-warning-option
--extra-arg=-Wno-error=ignored-optimization-argument
)
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(${PROJECT_NAME} ${TEST_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()
|