1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
#[==[
Define a build-time static library target whose compilation asserts that
header files are properly guarding config macros using inclusion of
prelude/postlude headers.
Usage:
add_macro_guard_test(
PROJECT_NAME <name>
PROJECT_TEST_PROPERTIES_TARGET <target>
INCLUDE_PATTERNS [pattern...]
[EXCLUDE_REGEXES [pattern...]]
GUARDED_MACROS [macro...]
)
PROJECT_NAME
Either "bsoncxx" or "mongocxx".
PROJECT_TEST_PROPERTIES_TARGET
Either the bsoncxx_test_properties or mongocxx_test_properties target.
GUARDED_MACROS
List of macros that should be guarded by prelude/postlude headers.
INCLUDE_PATTERNS
List of regex filters for headers to be added to the tests. Must be
relative to PROJECT_SOURCE_DIR.
EXCLUDE_REGEXES
List of regex filters for headers to be excluded. Applied after
INCLUDE_PATTERNS.
]==]
function(add_macro_guard_test)
set(opt_args "")
set(single_args "PROJECT_NAME;PROJECT_TEST_PROPERTIES_TARGET")
set(multi_args "GUARDED_MACROS;INCLUDE_PATTERNS;EXCLUDE_REGEXES")
cmake_parse_arguments(PARSED "${opt_args}" "${single_args}" "${multi_args}" ${ARGN})
if(NOT "${PARSED_UNPARSED_ARGUMENTS}" STREQUAL "")
message(FATAL_ERROR "unrecognized argument: ${PARSED_UNPARSED_ARGUMENTS}")
endif()
foreach(required_arg PROJECT_NAME PROJECT_TEST_PROPERTIES_TARGET GUARDED_MACROS INCLUDE_PATTERNS)
if("${required_arg}" IN_LIST PARSED_KEYWORDS_MISSING_VALUES)
message(FATAL_ERROR "missing value for required argument ${required_arg}")
endif()
endforeach()
list(TRANSFORM PARSED_INCLUDE_PATTERNS PREPEND "${PROJECT_SOURCE_DIR}/")
file(GLOB_RECURSE GUARDED_HEADERS
LIST_DIRECTORIES false
RELATIVE ${PROJECT_SOURCE_DIR}
${PARSED_INCLUDE_PATTERNS}
)
foreach(filter ${PARSED_EXCLUDE_REGEXES})
list(FILTER GUARDED_HEADERS EXCLUDE REGEX "${filter}")
endforeach()
set(MACRO_GUARD_TEST_PRELUDE "")
# Check and set initial state.
foreach(macro ${PARSED_GUARDED_MACROS})
string(APPEND MACRO_GUARD_TEST_PRELUDE
"#if defined(${macro})\n"
"#error \"${macro} is already defined\"\n"
"#endif\n"
"#define ${macro} macro guard test\n"
"\n"
)
endforeach()
# Implement as recursive algorithm for C++11 compatibility.
string(APPEND MACRO_GUARD_TEST_PRELUDE
"static constexpr bool compare_equal(const char* lhs, const char* rhs) {\n"
" return (*lhs == *rhs) && (*lhs == '\\0' || compare_equal(lhs + 1, rhs + 1));\n"
"}\n"
"\n"
"static_assert(compare_equal(\"abc\", \"abc\"), \"compare_equal() sanity check failed\");\n"
"static_assert(!compare_equal(\"abc\", \"def\"), \"compare_equal() sanity check failed\");\n"
"\n"
"#define TO_STR_1(x) #x\n"
"#define TO_STR(x) TO_STR_1(x)\n"
"\n"
)
add_library(test_${PARSED_PROJECT_NAME}_macro_guards STATIC EXCLUDE_FROM_ALL)
target_link_libraries(test_${PARSED_PROJECT_NAME}_macro_guards PRIVATE ${PARSED_PROJECT_TEST_PROPERTIES_TARGET})
# Avoid noisy warnings.
target_compile_options(test_${PARSED_PROJECT_NAME}_macro_guards PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wno-unused-macros>
)
# Test each header individually.
foreach(header ${GUARDED_HEADERS})
set(MACRO_GUARD_TEST "${MACRO_GUARD_TEST_PRELUDE}")
# Strip the subdir.
string(REGEX REPLACE "^(include|lib|test)/(.*)$" "\\1" subdir "${header}")
string(REGEX REPLACE "^(include|lib|test)/(.*)$" "\\2" header ${header})
# Apply include prefix to test headers.
if("${subdir}" STREQUAL "test")
set(relheader "${PARSED_PROJECT_NAME}/test/${header}")
else()
set(relheader "${header}")
endif()
# The include directive.
string(APPEND MACRO_GUARD_TEST "#include <${relheader}>\n\n")
# Test all guarded macros have been properly restored.
foreach(macro ${PARSED_GUARDED_MACROS})
string(APPEND MACRO_GUARD_TEST
"static_assert(\n"
" compare_equal(TO_STR(${macro}),\"macro guard test\"),\n"
" \"${macro} was not correctly restored by <${relheader}>\"\n"
");\n"
"\n"
)
endforeach()
# e.g. bsoncxx/v_noabi/bsoncxx/document/view.hpp -> bsoncxx-v_noabi-bsoncxx-document-view.cpp
string(REPLACE "/" "-" test_name "${header}")
string(REGEX REPLACE "^(.*)\\.(hh|hpp)$" "\\1" test_name "${test_name}")
# e.g. macro_guards/(include|lib|test)/bsoncxx-v_noabi-bsoncxx-document-view.cpp
configure_file(test_macro_guards.cpp.in macro_guards/${subdir}/${test_name}.cpp)
target_sources(test_${PARSED_PROJECT_NAME}_macro_guards PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/macro_guards/${subdir}/${test_name}.cpp
)
endforeach()
endfunction()
|