Last active
January 9, 2025 19:07
-
-
Save Caellian/4412fd7940527f6f4ba00ac13b0e638c to your computer and use it in GitHub Desktop.
CMake modules
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Distributed under the MIT/Apache 2.0/zlib License. | |
# Author: Tin Švagelj (Caellian) <[email protected]> | |
#[[.md: | |
# DependentOption | |
```cmake | |
dependent_option( | |
<option> "<help_text>" [<initial_value>] | |
[DEPENDS <conditions>] | |
[FALLBACK <value>] | |
[SHOW_IF <conditions>] | |
[TYPE (BOOL|FILE|DIR|STRING|INT)] | |
[WARN "<warning>"] | |
) | |
``` | |
`DEPENDS <conditions>` argument forces the value of the variable value to be | |
the fallback value. | |
`SHOW_IF <conditions>` argument allows hiding the option like | |
`CMakeDependentOption` does by default if provided `<conditions>` aren't met. | |
If it's not specified then then the option is always shown. | |
`WARN "<warning>"` allows specifying a custom `<warning>` message if `DEPENDS` | |
conditions aren't satisfied and current variable value does not match the | |
fallback value. Default is inferred from other parameters. | |
`FALLBACK` allows customizing the fallback value, in case `<initial_value>` is | |
not the value that should be used in case `DEPENDS` or `HIDE_IF` aren't | |
satisfied. | |
`TYPE` allows specifying option value type. This affects how the option is | |
presented in `ccmake` and `cmake-gui`, as well as default value if neither. | |
Types `INT` and `FLOAT` are mapped to `STRING`, but `INT` value is permitted to | |
simplify composition with other macros that might need to have special handling | |
for numeric values. | |
If neither `FALLBACK` nor `<initial_value>` are specified the both initial and | |
fallback value will be: | |
- `OFF` if variable type is `BOOL`, | |
- an empty string if type is `FILE`, `DIR`, or `STRING`, | |
- `0` if variable type is `INT` or `FLOAT`, | |
- `OFF` if no `TYPE` has been specified. | |
`<conditions>` are a ;-separated list of boolean expressions. Any in scope | |
value can be used, not just other options, so long as the expression itself | |
doesn't require `;` characters. In those cases it should be stored in a | |
separate variable and that variable should be used instead. | |
All checks are deferred until `RUN_DEPENDENCY_CHECKS` macro is called in order to | |
allow out-of-order declaration of dependencies and handle dependecy graph cycles. | |
As the checks can affect each other they're run in a loop until the graph settles. | |
See: https://github.com/Kitware/CMake/blob/master/Modules/CMakeDependentOption.cmake | |
]] | |
set(__DEPENDENT_OPTIONS_CHANGE_HAPPENED true) | |
set(__DEPENDENT_OPTIONS_LATER_INVOKED_CODE "") | |
macro(__DEPENDENT_OPTIONS_TYPE_MAP TYPE_NAME) | |
if("${${TYPE_NAME}}" STREQUAL "FILE") | |
set("${TYPE_NAME}" "FILEPATH") | |
elseif("${${TYPE_NAME}}" STREQUAL "DIR") | |
set("${TYPE_NAME}" "PATH") | |
elseif("${${TYPE_NAME}}" STREQUAL "INT") | |
set("${TYPE_NAME}" "STRING") | |
elseif("${${TYPE_NAME}}" STREQUAL "FLOAT") | |
set("${TYPE_NAME}" "STRING") | |
endif() | |
# else: do nothing; assume a valid value is provided | |
endmacro() | |
macro(__DEPENDENT_OPTIONS_TYPE_DEFAULT TYPE_NAME VARIABLE_OUT) | |
if("${${TYPE_NAME}}" STREQUAL "BOOL") | |
set("${VARIABLE_OUT}" "OFF") | |
elseif("${${TYPE_NAME}}" STREQUAL "FILE") | |
set("${VARIABLE_OUT}" "\"\"") | |
elseif("${${TYPE_NAME}}" STREQUAL "DIR") | |
set("${VARIABLE_OUT}" "\"\"") | |
elseif("${${TYPE_NAME}}" STREQUAL "STRING") | |
set("${VARIABLE_OUT}" "\"\"") | |
elseif("${${TYPE_NAME}}" STREQUAL "INTERNAL") | |
set("${VARIABLE_OUT}" "\"\"") | |
elseif("${${TYPE_NAME}}" STREQUAL "INT") | |
set("${VARIABLE_OUT}" "0") | |
elseif("${${TYPE_NAME}}" STREQUAL "FLOAT") | |
set("${VARIABLE_OUT}" "0") | |
else() | |
# default for OPTION function | |
set("${VARIABLE_OUT}" "OFF") | |
endif() | |
endmacro() | |
function(DEPENDENT_OPTION option doc) | |
cmake_parse_arguments(PARSE_ARGV 2 arg | |
"" "FALLBACK WARN TYPE" "DEPENDS SHOW_IF" | |
) | |
list(LENGTH arg_UNPARSED_ARGUMENTS POSITIONAL_COUNT) | |
if(POSITIONAL_COUNT GREATER 0) | |
list(POP_FRONT arg_UNPARSED_ARGUMENTS INITIAL_VALUE) | |
math(EXPR POSITIONAL_COUNT "${POSITIONAL_COUNT}-1") | |
endif() | |
set(WARNING_MESSAGE "${option} can't be enabled.") | |
set(DYN_CHECK) | |
if(DEFINED arg_DEPENDS) | |
set(WARNING_MESSAGE "${option} requires ${arg_DEPENDS}.") | |
set(DYN_CHECK " | |
string(REGEX MATCHALL \"[^;]+\" __${option}_TOKENS \"${arg_DEPENDS}\") | |
foreach(it \${__${option}_TOKENS}) | |
cmake_language(EVAL CODE \" | |
if (\${it}) | |
else() | |
set(${option}_POSSIBLE 0) | |
endif()\") | |
endforeach() | |
unset(__${option}_TOKENS)") | |
endif() | |
set(OPTION_TYPE "BOOL") | |
if(DEFINED arg_TYPE) | |
__dependent_options_type_map(arg_TYPE) | |
set(OPTION_TYPE "${arg_TYPE}") | |
endif() | |
if(DEFINED arg_FALLBACK) | |
if(NOT DEFINED INITIAL_VALUE) | |
set(INITIAL_VALUE "${arg_FALLBACK}") | |
endif() | |
else() | |
if(DEFINED INITIAL_VALUE) | |
set(arg_FALLBACK INITIAL_VALUE) | |
else() | |
__dependent_options_type_default("${arg_TYPE}" INITIAL_VALUE) | |
__dependent_options_type_default("${arg_TYPE}" arg_FALLBACK) | |
endif() | |
endif() | |
if(DEFINED arg_SHOW_IF) | |
set("__${option}_VISIBLE" 0) | |
string(REGEX MATCHALL "[^;]+" "__${option}_HIDE_TOKENS" "${arg_SHOW_IF}") | |
foreach(it "${__${option}_HIDE_TOKENS}") | |
cmake_language(EVAL CODE " | |
if (${it}) | |
else() | |
set(__${option}_VISIBLE 1) | |
endif()") | |
endforeach() | |
endif() | |
if(DEFINED arg_WARN) | |
set(WARNING_MESSAGE "${arg_WARN}") | |
endif() | |
if(NOT DEFINED OR "${__${option}_VISIBLE}") | |
set(${option} "${INITIAL_VALUE}" CACHE "${OPTION_TYPE}" "${doc}") | |
else() | |
set(${option} "${arg_FALLBACK}" CACHE INTERNAL "${doc}") | |
return() | |
endif() | |
if(DEFINED arg_DEPENDS) | |
string(APPEND __DEPENDENT_OPTIONS_LATER_INVOKED_CODE " | |
set(${option}_POSSIBLE 1) | |
${DYN_CHECK} | |
if(NOT ${option}_POSSIBLE) | |
if(NOT \"\${${option}}\" STREQUAL \"${arg_FALLBACK}\") | |
if(NOT \"\${${option}}\" STREQUAL \"${INITIAL_VALUE}\") | |
message(WARNING \"${WARNING_MESSAGE}; setting to '${__DEPENDENT_OPTIONS_FALLBACK}'.\") | |
endif() | |
set(${option} ${__DEPENDENT_OPTIONS_FALLBACK}) | |
set(__DEPENDENT_OPTIONS_CHANGE_HAPPENED true) | |
endif() | |
endif() | |
unset(${option}_POSSIBLE)") | |
set(__DEPENDENT_OPTIONS_LATER_INVOKED_CODE "${__DEPENDENT_OPTIONS_LATER_INVOKED_CODE}" PARENT_SCOPE) | |
endif() | |
endfunction() | |
macro(RUN_DEPENDENCY_CHECKS) | |
# controls max allowed dependency chain to avoid infinite loops during | |
# configure phase | |
set(__DEPENDENT_OPTIONS_LOOP_COUNTER 10) | |
while(__DEPENDENT_OPTIONS_CHANGE_HAPPENED) | |
MATH(EXPR __DEPENDENT_OPTIONS_LOOP_COUNTER "${__DEPENDENT_OPTIONS_LOOP_COUNTER}-1") | |
set(__DEPENDENT_OPTIONS_CHANGE_HAPPENED false) | |
cmake_language(EVAL CODE "${__DEPENDENT_OPTIONS_LATER_INVOKED_CODE}") | |
if(__DEPENDENT_OPTIONS_LOOP_COUNTER LESS_EQUAL "0") | |
break() | |
endif() | |
endwhile() | |
unset(__DEPENDENT_OPTIONS_LOOP_COUNTER) | |
set(__DEPENDENT_OPTIONS_CHANGE_HAPPENED true) | |
set(__DEPENDENT_OPTIONS_LATER_INVOKED_CODE "") | |
endmacro() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Distributed under the MIT/Apache 2.0/zlib License. | |
# Author: Tin Švagelj (Caellian) <[email protected]> | |
#[[.md: | |
# TargetPlatformTests | |
This module is a replacement for standard platform checking macros provided by | |
CMake: | |
- `CHECK_INCLUDE_FILES` | |
- `CHECK_FUNCTION_EXISTS` | |
- `CHECK_SYMBOL_EXISTS` | |
- `CHECK_LIBRARY_EXISTS` | |
The provided alternatives call a `TPT_DEFINE_CB` callback if it's provided (set | |
to a function/macro name that accepts one argument). | |
Example: | |
```cmake | |
macro(source_define DEFINE_NAME) | |
set(VALUE "") | |
if(ARGC GREATER 1) | |
set(VALUE "=${ARGV1}") | |
endif() | |
target_compile_definitions(my_program INTERFACE "${DEFINE_NAME}${VALUE}") | |
endmacro() | |
set(TPT_DEFINE_CB source_define) | |
``` | |
Unlike original functions, provided alteratives should be assumed to ignore | |
globals and are exclusively bound to the one that's specified for target(s) | |
provided via `ADD_TESTING_TARGET(target)` macro. This must be cleared with | |
`CLEAR_TESTING_TARGETS()` macro after checking is finished to reset the | |
checking environment and revert globals to their original state. | |
It's near impossible to re-construct globals to their initial (compiler | |
dependant) values once they've been mutated. So if global include/link paths | |
have been modified or custom compiler options have been added to arguments, | |
these macros will be forced use those. | |
]] | |
# include(CheckFunctionExists) # It's not good enough | |
include(CheckIncludeFiles) | |
include(CheckSymbolExists) | |
include(CheckCXXSymbolExists) | |
include(CheckLibraryExists) | |
include(CheckSourceCompiles) | |
# A list of ambient variables which should be temporarily cleared when doing | |
# platform checks. | |
# See: https://cmake.org/cmake/help/latest/manual/cmake-properties.7.html | |
set(_TPT_INTERNAL_AMBIENT_VARIABLES | |
CMAKE_REQUIRED_FLAGS | |
CMAKE_REQUIRED_DEFINITIONS | |
CMAKE_REQUIRED_INCLUDES | |
CMAKE_REQUIRED_LINK_OPTIONS | |
CMAKE_REQUIRED_LIBRARIES | |
CMAKE_REQUIRED_LINK_DIRECTORIES | |
CMAKE_REQUIRED_QUIET | |
# Statefull globals like INCLUDE_DIRECTORIES and similiar are ignored. | |
# They will stay polluted until the end of the build. | |
# Generally, to avoid weird side-effects and stateful builds, one should | |
# only modify targets. | |
) | |
set(_TPT_INTERNAL_DEFAULT_LANGUAGE CXX) | |
# INTERNAL macro for ADD_TESTING_TARGET, do not use directly because it's | |
# missing checks. | |
macro(_TPT_INTERNAL_DO_ADD_TESTING_TARGET FROM) | |
# Retrieve and append target properties of FROM | |
#[[ Full list (as of 3.31.3) includes: | |
INTERFACE_AUTOMOC_MACRO_NAMES # IRELLEVANT | |
INTERFACE_AUTOUIC_OPTIONS # IRELLEVANT uic stuff | |
INTERFACE_COMPILE_DEFINITIONS # USED | |
INTERFACE_COMPILE_FEATURES # MAPPED | |
INTERFACE_COMPILE_OPTIONS # USED | |
INTERFACE_CXX_MODULE_SETS # NOT USED modules | |
INTERFACE_HEADER_SETS # NOT USED modules | |
INTERFACE_HEADER_SETS_TO_VERIFY # NOT USED modules | |
INTERFACE_INCLUDE_DIRECTORIES # USED | |
INTERFACE_LINK_DEPENDS # NOT USED linker scripts | |
INTERFACE_LINK_DIRECTORIES # USED | |
INTERFACE_LINK_LIBRARIES # USED | |
INTERFACE_LINK_LIBRARIES_DIRECT # IRELLEVANT not a dependency | |
INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE # IRELLEVANT not a dependency | |
INTERFACE_LINK_OPTIONS # USED | |
INTERFACE_POSITION_INDEPENDENT_CODE # IRELLEVANT not a dependency | |
INTERFACE_PRECOMPILE_HEADERS # IRELLEVANT not a dependency | |
INTERFACE_SOURCES # IRELLEVANT not a dependency | |
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES # IRELLEVANT no thirdparty dependencies | |
]] | |
get_target_property(_TPT_INTERNAL_COMPILE_DEFINITIONS ${FROM} INTERFACE_COMPILE_DEFINITIONS) | |
get_target_property(_TPT_INTERNAL_COMPILE_FEATURES ${FROM} INTERFACE_COMPILE_FEATURES) | |
get_target_property(_TPT_INTERNAL_COMPILE_OPTIONS ${FROM} INTERFACE_COMPILE_OPTIONS) | |
get_target_property(_TPT_INTERNAL_INCLUDE_DIRECTORIES ${FROM} INTERFACE_INCLUDE_DIRECTORIES) | |
get_target_property(_TPT_INTERNAL_LINK_OPTIONS ${FROM} INTERFACE_LINK_OPTIONS) | |
get_target_property(_TPT_INTERNAL_LINK_DIRECTORIES ${FROM} INTERFACE_LINK_DIRECTORIES) | |
get_target_property(_TPT_INTERNAL_TEST_LIBRARIES ${FROM} INTERFACE_LINK_LIBRARIES) | |
set(_TPT_INTERNAL_LIBRARIES) | |
set(_TPT_INTERNAL_TARGETS) | |
foreach(LIBRARY IN LISTS _TPT_INTERNAL_TEST_LIBRARIES) | |
if(TARGET ${LIBRARY}) | |
list(APPEND _TPT_INTERNAL_TARGETS "${LIBRARY}") | |
else() | |
list(APPEND _TPT_INTERNAL_LIBRARIES "${LIBRARY}") | |
endif() | |
endforeach() | |
if(_TPT_INTERNAL_COMPILE_FEATURES) | |
foreach(feature IN LISTS _TPT_INTERNAL_COMPILE_FEATURES) | |
if(feature STREQUAL cxx_std_11) | |
set(_TPT_INTERNAL_TEST_CUSTOM_LANGUAGE CXX) | |
set(_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX "${_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX} -std=c++11") | |
elseif(feature STREQUAL cxx_std_14) | |
set(_TPT_INTERNAL_TEST_CUSTOM_LANGUAGE CXX) | |
set(_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX "${_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX} -std=c++14") | |
elseif(feature STREQUAL cxx_std_17) | |
set(_TPT_INTERNAL_TEST_CUSTOM_LANGUAGE CXX) | |
set(_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX "${_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX} -std=c++17") | |
elseif(feature STREQUAL cxx_std_20) | |
set(_TPT_INTERNAL_TEST_CUSTOM_LANGUAGE CXX) | |
set(_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX "${_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX} -std=c++20") | |
elseif(feature STREQUAL cxx_std_23) | |
set(_TPT_INTERNAL_TEST_CUSTOM_LANGUAGE CXX) | |
set(_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX "${_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX} -std=c++23") | |
elseif(feature STREQUAL c_std_90) | |
set(_TPT_INTERNAL_TEST_CUSTOM_LANGUAGE C) | |
set(_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_C "${_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_C} -std=c90") | |
elseif(feature STREQUAL c_std_99) | |
set(_TPT_INTERNAL_TEST_CUSTOM_LANGUAGE C) | |
set(_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_C "${_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_C} -std=c99") | |
elseif(feature STREQUAL c_std_11) | |
set(_TPT_INTERNAL_TEST_CUSTOM_LANGUAGE C) | |
set(_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_C "${_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_C} -std=c11") | |
elseif(feature STREQUAL cxx_variadic_templates) | |
set(_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX "${_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX} -fvariadic-templates") | |
elseif(feature STREQUAL cxx_range_for) | |
set(_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX "${_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX} -frange-for") | |
elseif(feature STREQUAL cxx_rvalue_references) | |
set(_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX "${_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX} -frvalue-references") | |
elseif(feature STREQUAL cxx_static_assert) | |
set(_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX "${_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX} -fstatic-assert") | |
elseif(feature STREQUAL cxx_constexpr) | |
set(_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX "${_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX} -fconstexpr") | |
else() | |
# Many more. | |
message(WARNING "Feature to flag mapping not handled for ${feature}") | |
endif() | |
endforeach() | |
endif() | |
if(_TPT_INTERNAL_COMPILE_OPTIONS) | |
set(_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_COMMON ${_TPT_INTERNAL_COMPILE_OPTIONS}) | |
endif() | |
if(_TPT_INTERNAL_COMPILE_DEFINITIONS) | |
foreach(DEFINITION IN LISTS _TPT_INTERNAL_COMPILE_DEFINITIONS) | |
list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D${DEFINITION}") | |
endforeach() | |
endif() | |
if(_TPT_INTERNAL_INCLUDE_DIRECTORIES) | |
list(APPEND CMAKE_REQUIRED_INCLUDES ${_TPT_INTERNAL_INCLUDE_DIRECTORIES}) | |
endif() | |
if(_TPT_INTERNAL_LINK_OPTIONS) | |
list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${_TPT_INTERNAL_LINK_OPTIONS}) | |
endif() | |
if(_TPT_INTERNAL_LIBRARIES) | |
list(APPEND CMAKE_REQUIRED_LIBRARIES ${_TPT_INTERNAL_LIBRARIES}) | |
endif() | |
if(_TPT_INTERNAL_LINK_DIRECTORIES) | |
list(APPEND CMAKE_REQUIRED_LINK_DIRECTORIES ${_TPT_INTERNAL_LINK_DIRECTORIES}) | |
endif() | |
# Recursively add any additional target flags | |
foreach(TARGET IN LISTS _TPT_INTERNAL_TARGETS) | |
if(${TARGET} IN_LIST _TPT_INTERNAL_VISITED_DEPENDENCY_TARGETS) | |
continue() | |
endif() | |
_tpt_internal_do_add_testing_target(${TARGET}) | |
list(APPEND _TPT_INTERNAL_VISITED_DEPENDENCY_TARGETS "${TARGET}") | |
endforeach() | |
endmacro() | |
# Internal utility macros for affecting all ambient variables in bulk | |
macro(_TPT_INTERNAL_CLEAR_AMBIENT VARIABLE) | |
if(DEFINED ${VARIABLE}) | |
set("_TPT_INTERNAL_INITIAL_${VARIABLE}_VALUE" ${VARIABLE}) | |
unset(${VARIABLE}) | |
endif() | |
endmacro() | |
macro(_TPT_INTERNAL_RESET_AMBIENT VARIABLE) | |
if(DEFINED "_TPT_INTERNAL_INITIAL_${VARIABLE}_VALUE") | |
set(${VARIABLE} "_TPT_INTERNAL_INITIAL_${VARIABLE}_VALUE") | |
unset("_TPT_INTERNAL_INITIAL_${VARIABLE}_VALUE") | |
elseif(DEFINED ${VARIABLE}) | |
unset(${VARIABLE}) | |
endif() | |
endmacro() | |
macro(_TPT_INTERNAL_FOR_ALL_AMBIENT ACTION) | |
foreach(VARIABLE IN LISTS _TPT_INTERNAL_AMBIENT_VARIABLES) | |
cmake_language(EVAL CODE "${ACTION}(${VARIABLE})") | |
endforeach() | |
endmacro() | |
macro(_TPT_INTERNAL_SETUP_RUN_ENV LANGUAGE) | |
set(CMAKE_REQUIRED_FLAGS "${_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_COMMON} ${_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_${LANGUAGE}}") | |
endmacro() | |
macro(_TPT_INTERNAL_DO_DEFINE_FOR_SOURCE DEFINE_NAME) | |
if(DEFINED TPT_DEFINE_CB) | |
cmake_language(EVAL CODE "${TPT_DEFINE_CB}(${DEFINE_NAME})") | |
endif() | |
endmacro() | |
# A function signature must be formatted like: | |
# OUT_TYPE FN_NAME(ARGUMENTS) | |
# Without any attributes (constexpr, inline, noexcept, etc.) | |
macro(_TPT_INTERNAL_FN_SIG_SPLIT SIGNATURE TYPE_OUT NAME_OUT ARGS_OUT) | |
string(STRIP "${SIGNATURE}" SIGNATURE) | |
string(REGEX MATCH "[a-zA-Z_][a-zA-Z_0-9]*(::[a-zA-Z_][a-zA-Z_0-9]*)*\\(" ${NAME_OUT} "${SIGNATURE}") | |
string(LENGTH "${${NAME_OUT}}" _TPT_FN_NAME) | |
if(NOT ${_TPT_FN_NAME}) | |
message(FATAL_ERROR "Invalid function name specified in signature '${SIGNATURE}'!") | |
endif() | |
math(EXPR _TPT_FN_NAME "${_TPT_FN_NAME}-1") | |
string(SUBSTRING "${${NAME_OUT}}" 0 ${_TPT_FN_NAME} ${NAME_OUT}) | |
unset(_TPT_FN_NAME) | |
string(STRIP "${${NAME_OUT}}" ${NAME_OUT}) | |
string(FIND "${SIGNATURE}" "${${NAME_OUT}}" _TPT_FN_NAME_START) | |
math(EXPR _TPT_FN_NAME_START "${_TPT_FN_NAME_START}") | |
string(SUBSTRING "${SIGNATURE}" 0 ${_TPT_FN_NAME_START} ${TYPE_OUT}) | |
unset(_TPT_FN_NAME_START) | |
string(STRIP "${${TYPE_OUT}}" ${TYPE_OUT}) | |
string(REGEX MATCH "\\([^)]*\\)$" ${ARGS_OUT} "${SIGNATURE}") | |
string(LENGTH "${${ARGS_OUT}}" _TPT_FN_ARGS_LEN) | |
if(NOT ${_TPT_FN_ARGS_LEN}) | |
message(FATAL_ERROR "Function signature '${SIGNATURE}' is missing arguments!") | |
endif() | |
math(EXPR _TPT_FN_ARGS_LEN "${_TPT_FN_ARGS_LEN}-2") | |
string(SUBSTRING "${${ARGS_OUT}}" 1 ${_TPT_FN_ARGS_LEN} ${ARGS_OUT}) | |
unset(_TPT_FN_ARGS_LEN) | |
string(STRIP "${${ARGS_OUT}}" ${ARGS_OUT}) | |
endmacro() | |
# Copy any definitions from provided target. If called multiple times, using | |
# CLEAR_TESTING_TARGETS will reset globals to their value during first | |
# invocation. | |
macro(ADD_TESTING_TARGET FROM) | |
if(NOT _TPT_INTERNAL_CHECK_ENVIRONEMENT_SET) | |
_tpt_internal_for_all_ambient(_tpt_internal_clear_ambient) | |
endif() | |
set(_TPT_INTERNAL_VISITED_DEPENDENCY_TARGETS) | |
_tpt_internal_do_add_testing_target(${FROM}) | |
if(NOT _TPT_INTERNAL_TEST_CUSTOM_LANGUAGE) | |
set(_TPT_INTERNAL_TEST_CUSTOM_LANGUAGE "${_TPT_INTERNAL_DEFAULT_LANGUAGE}") | |
endif() | |
set(CMAKE_REQUIRED_QUIET TRUE) | |
set(_TPT_INTERNAL_CHECK_ENVIRONEMENT_SET TRUE) | |
endmacro() | |
#[[.md: | |
TEST_INCLUDES checks whether one or more headers can be included from | |
currently active testing targets. | |
```cmake | |
test_includes("<header>[;<header>...]" <variable> [LANGUAGE <language>] [REQUIRED]) | |
``` | |
Much like CHECK_INCLUDE_FILES, this check ensures one of the include | |
directories for tested targets actually provides a header that will be needed | |
by the program. | |
This is a variant of CHECK_INCLUDE_FILES that works with targets provided to | |
ADD_TESTING_TARGET, instead of requiring all the flags to be set manually. | |
See [CheckIncludeFiles](https://cmake.org/cmake/help/latest/module/CheckIncludeFiles.html) for details. | |
]] | |
function(test_includes INCLUDE VARIABLE) | |
if(NOT _TPT_INTERNAL_CHECK_ENVIRONEMENT_SET) | |
message(FATAL_ERROR "Environment not configured; Use ADD_TESTING_TARGET(target) macro!") | |
endif() | |
cmake_parse_arguments(PARSE_ARGV 2 arg | |
"REQUIRED" "LANGUAGE" "" | |
) | |
if(NOT DEFINED arg_LANGUAGE) | |
set(arg_LANGUAGE ${_TPT_INTERNAL_TEST_CUSTOM_LANGUAGE}) | |
endif() | |
_tpt_internal_setup_run_env(${arg_LANGUAGE}) | |
unset("${VARIABLE}" CACHE) | |
check_include_files("${INCLUDE}" "${VARIABLE}" LANGUAGE ${arg_LANGUAGE}) | |
if("${${VARIABLE}}") | |
if(NOT arg_REQUIRED) | |
_tpt_internal_do_define_for_source(${VARIABLE}) | |
endif() | |
message(STATUS "'${INCLUDE}' include - found") | |
set(${VARIABLE} 1 CACHE INTERNAL "Have include ${INCLUDE}") | |
else() | |
if(arg_REQUIRED) | |
message(FATAL_ERROR "'${INCLUDE}' include - not found") | |
else() | |
message(STATUS "'${INCLUDE}' include - not found") | |
endif() | |
set(${VARIABLE} 0 CACHE INTERNAL "Have include ${INCLUDE}") | |
endif() | |
endfunction() | |
#[[.md: | |
TEST_FUNCTION checks whether a function with provided signature exists (when | |
some headers are included). | |
```cmake | |
test_function("<header>[;<header>...]" <function-signature> <variable> [LANGUAGE <language>] [REQUIRED]) | |
``` | |
`function-signature` should not contain any attributes as those will be ignored. | |
This function is different from standard CHECK_FUNCTION_EXISTS macro because | |
the standard macro only checks whether a function can be linked from available | |
libraries. This version also checks the function signature. | |
This variant also works with targets provided to ADD_TESTING_TARGET, instead | |
of requiring all the flags to be set manually. | |
]] | |
function(test_function HEADERS FUNCTION_SIGNATURE VARIABLE) | |
if(NOT _TPT_INTERNAL_CHECK_ENVIRONEMENT_SET) | |
message(FATAL_ERROR "Environment not configured; Use ADD_TESTING_TARGET(target) macro!") | |
endif() | |
cmake_parse_arguments(PARSE_ARGV 3 arg | |
"REQUIRED" "LANGUAGE" "" | |
) | |
if(NOT DEFINED arg_LANGUAGE) | |
set(arg_LANGUAGE ${_TPT_INTERNAL_TEST_CUSTOM_LANGUAGE}) | |
endif() | |
_tpt_internal_fn_sig_split("${FUNCTION_SIGNATURE}" FUNCTION_RETURN FUNCTION_NAME FUNCTION_ARGUMENTS) | |
_tpt_internal_setup_run_env(${arg_LANGUAGE}) | |
set(_TPT_INTERNAL_HEADER_LIST "") | |
if(NOT HEADERS STREQUAL "") | |
foreach(HEADER IN LISTS HEADERS) | |
set(_TPT_INTERNAL_HEADER_LIST "${_TPT_INTERNAL_HEADER_LIST}\n#include <${HEADER}>") | |
endforeach() | |
endif() | |
unset("${VARIABLE}" CACHE) | |
if(arg_LANGUAGE STREQUAL "C") | |
check_source_compiles(C | |
"#include <stddef.h> | |
#include <stdlib.h>${_TPT_INTERNAL_HEADER_LIST} | |
int main(void) { | |
${FUNCTION_RETURN} (*_function_name_test)(${FUNCTION_ARGUMENTS}); | |
_function_name_test = &${FUNCTION_NAME}; | |
return _function_name_test != NULL; // use the pointer | |
}" "${VARIABLE}") | |
elseif(arg_LANGUAGE STREQUAL "CXX") | |
check_source_compiles(CXX | |
"#include <cstddef> | |
#include <cstdlib> | |
#include <type_traits>${_TPT_INTERNAL_HEADER_LIST} | |
#if __cplusplus >= 201703L | |
template <typename Fn, typename... Args> // C++17 and later | |
using result_type_t = std::result_of_t<Fn(Args...)>; | |
#else | |
template <typename Fn, typename... Args> // C++11 and C++14 | |
using result_type_t = typename std::result_of<Fn(Args...)>::type; | |
#endif | |
#pragma GCC diagnostic ignored \"-Wignored-attributes\" | |
#pragma clang diagnostic ignored \"-Wignored-attributes\" | |
int main() { | |
static_assert(std::is_same< | |
result_type_t<std::decay<decltype(&${FUNCTION_NAME})>::type, ${FUNCTION_ARGUMENTS}>, | |
${FUNCTION_RETURN} | |
>::value, \"strndup is not a function with expected signature\"); | |
return 0; | |
}" "${VARIABLE}") | |
else() | |
message(FATAL_ERROR "${arg_LANGUAGE} not supported by TEST_FUNCTION") | |
endif() | |
if("${${VARIABLE}}") | |
message(STATUS "'${FUNCTION_NAME}' function - found") | |
if(NOT arg_REQUIRED) | |
_tpt_internal_do_define_for_source("${VARIABLE}") | |
endif() | |
set(${VARIABLE} 1 CACHE INTERNAL "Have function ${FUNCTION_NAME}") | |
else() | |
if(arg_REQUIRED) | |
message("Checked function signature: ${FUNCTION_SIGNATURE}") | |
message(FATAL_ERROR "'${FUNCTION_NAME}' function - not found") | |
else() | |
message(STATUS "'${FUNCTION_NAME}' function - not found") | |
endif() | |
set(${VARIABLE} 0 CACHE INTERNAL "Have function ${FUNCTION_NAME}") | |
endif() | |
endfunction() | |
#[[.md: | |
TEST_SYMBOL checks whether `<header>`s provide some `<symbol>` needed by the | |
program. | |
```cmake | |
test_symbol("<header>[;<header>...]" <symbol> <variable> [LANGUAGE <language>] [REQUIRED]) | |
``` | |
This is a variant of CHECK_SYMBOL_EXISTS that works with targets provided to | |
ADD_TESTING_TARGET, instead of requiring all the flags to be set manually. | |
See [CheckSymbolExists](https://cmake.org/cmake/help/latest/module/CheckSymbolExists.html) for details. | |
]] | |
function(test_symbol HEADER SYMBOL VARIABLE) | |
if(NOT _TPT_INTERNAL_CHECK_ENVIRONEMENT_SET) | |
message(FATAL_ERROR "Environment not configured; Use ADD_TESTING_TARGET(target) macro!") | |
endif() | |
cmake_parse_arguments(PARSE_ARGV 3 arg | |
"REQUIRED" "LANGUAGE" "" | |
) | |
if(NOT DEFINED arg_LANGUAGE) | |
set(arg_LANGUAGE ${_TPT_INTERNAL_TEST_CUSTOM_LANGUAGE}) | |
endif() | |
unset("${VARIABLE}" CACHE) | |
if(arg_LANGUAGE STREQUAL "C") | |
check_symbol_exists("${SYMBOL}" "${HEADER}" "${VARIABLE}") | |
elseif(arg_LANGUAGE STREQUAL "CXX") | |
check_cxx_symbol_exists("${SYMBOL}" "${HEADER}" "${VARIABLE}") | |
else() | |
message(FATAL_ERROR "${LANGUAGE} not supported by TEST_SYMBOL") | |
endif() | |
_tpt_internal_setup_run_env(${arg_LANGUAGE}) | |
if("${${VARIABLE}}") | |
message(STATUS "'${SYMBOL}' symbol - found") | |
if(NOT arg_REQUIRED) | |
_tpt_internal_do_define_for_source("${VARIABLE}") | |
endif() | |
set(${VARIABLE} 1 CACHE INTERNAL "Have symbol ${SYMBOL}") | |
else() | |
if(arg_REQUIRED) | |
message(FATAL_ERROR "'${SYMBOL}' symbol - not found") | |
else() | |
message(STATUS "'${SYMBOL}' symbol - not found") | |
endif() | |
set(${VARIABLE} 0 CACHE INTERNAL "Have symbol ${SYMBOL}") | |
endif() | |
endfunction() | |
#[[.md: | |
TEST_LIBRARY checks whether a `<library>` from specific `<location>` provides | |
some `<function-name>` needed by the program. | |
```cmake | |
test_library(<library> "<function-name>" "[<location>]" <variable> [REQUIRED]) | |
``` | |
This is a variant of CHECK_LIBRARY_EXISTS that works with targets provided to | |
ADD_TESTING_TARGET, instead of requiring all the flags to be set manually. | |
See [CheckLibraryExists](https://cmake.org/cmake/help/latest/module/CheckLibraryExists.html) for details. | |
]] | |
function(test_library LIBRARY FUNCTION LOCATION VARIABLE) | |
if(NOT _TPT_INTERNAL_CHECK_ENVIRONEMENT_SET) | |
message(FATAL_ERROR "Environment not configured; Use ADD_TESTING_TARGET(target) macro!") | |
endif() | |
cmake_parse_arguments(PARSE_ARGV 4 arg | |
"REQUIRED" "" "" | |
) | |
_tpt_internal_setup_run_env(${_TPT_INTERNAL_TEST_CUSTOM_LANGUAGE}) | |
unset("${VARIABLE}" CACHE) | |
check_library_exists("${LIBRARY}" "${FUNCTION}" "${LOCATION}" "${VARIABLE}") | |
if("${${VARIABLE}}") | |
message(STATUS "'${LIBRARY}' library function '${FUNCTION}' - found") | |
if(NOT arg_REQUIRED) | |
_tpt_internal_do_define_for_source("${VARIABLE}") | |
endif() | |
set(${VARIABLE} 1 CACHE INTERNAL "Have library ${LIBRARY}") | |
else() | |
if(arg_REQUIRED) | |
message(FATAL_ERROR "'${LIBRARY}' library function '${FUNCTION}' - not found") | |
else() | |
message(STATUS "'${LIBRARY}' library function '${FUNCTION}' - not found") | |
endif() | |
set(${VARIABLE} 0 CACHE INTERNAL "Have library ${LIBRARY}") | |
endif() | |
endfunction() | |
macro(CLEAR_TESTING_TARGETS) | |
_TPT_INTERNAL_for_all_ambient(_TPT_INTERNAL_reset_ambient) | |
unset(_TPT_INTERNAL_TEST_CUSTOM_LANGUAGE) | |
unset(_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_COMMON) | |
unset(_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_C) | |
unset(_TPT_INTERNAL_CMAKE_REQUIRED_FLAGS_CXX) | |
unset(_TPT_INTERNAL_CHECK_ENVIRONEMENT_SET) | |
endmacro() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment