-
Notifications
You must be signed in to change notification settings - Fork 543
CXX-2767 Add macro guards tests for bsoncxx and mongocxx #1043
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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(x) #x\n" | ||
"#define TO_STR(x) _TO_STR(x)\n" | ||
"\n" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a [[bracket string]]
to avoid the quoting and escaping.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am preferential towards being consistent with the other quoted arguments that require variable expansions (incompatible with bracket arguments) rather than using a bracket argument that requires unusual indentation to avoid it being reflected in the generated source file:
# 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) {
return (*lhs == *rhs) && (*lhs == '\0' || compare_equal(lhs + 1, rhs + 1));
}
static_assert(compare_equal("abc", "abc"), "compare_equal() sanity check failed");
static_assert(!compare_equal("abc", "def"), "compare_equal() sanity check failed");
#define _TO_STR(x) #x
#define TO_STR(x) _TO_STR(x)
]])
Would you rather we still use a bracket argument here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll leave it up to you. In such cases I don't usually mind odd indentation in generated sources, and I use string(CONFIGURE)
to do variable substitutions within large bracket strings.
cmake/MacroGuardTest.cmake
Outdated
# e.g. bsoncxx/v_noabi/bsoncxx/document/view.hpp -> bsoncxx-v_noabi-bsoncxx-document-view | ||
string(REPLACE "/" "-" test_name "${header}") | ||
string(REGEX REPLACE "^(.*)\\.(hh|hpp)$" "\\1" test_name "${test_name}") | ||
configure_file(test_macro_guards.cpp.in macro_guards/${subdir}/${test_name}.cpp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the file only expands to a single string, can this be a file(WRITE)
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered file(WRITE)
, but it doesn't seem to support "smart" target dependency handling. It's an unconditional write regardless whether the file is already generated and/or no changes have been made to its contents, so I went with configure_file()
as recommended in the docs:
If the file is a build input, use the configure_file() command to update the file only when its content changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was unaware of this feature of configure_file
. Perhaps file(GENERATE ... CONTENT ...)
instead?
file(CONFIGURE ... CONTENT ...)
does the same thing, but isn't available until CMake 3.18.
cmake/MacroGuardTest.cmake
Outdated
configure_file(test_macro_guards.cpp.in macro_guards/${subdir}/${test_name}.cpp) | ||
|
||
# Prefix library name to reduce potential for target conflicts. | ||
add_library(test-macro_guards-${subdir}-${test_name} STATIC EXCLUDE_FROM_ALL ${CMAKE_CURRENT_BINARY_DIR}/macro_guards/${subdir}/${test_name}.cpp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need an individual target for every header? Could it be a single target with multiple source files attached?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that you mention it, that makes a lot of sense! 😅 Will do.
src/bsoncxx/test/CMakeLists.txt
Outdated
"include/**/*.hpp" | ||
|
||
# Private headers. | ||
"lib/**/*.hh" | ||
|
||
# Test headers. | ||
"test/**/*.hh" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK, CMake does not recognize the **
directory pattern as anything other than two adjacent wildcards (I wish it did, though). include/*.hpp
etc will be sufficient with GLOB_RECURSE
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Will do.
src/mongocxx/test/CMakeLists.txt
Outdated
target_link_libraries(test_driver PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_logging PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_instance PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_client_side_encryption_specs PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_crud_specs PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_gridfs_specs PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_command_monitoring_specs PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_transactions_specs PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_retryable_reads_specs PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_read_write_concern_specs PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_mongohouse_specs PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_unified_format_spec PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_versioned_api PRIVATE mongocxx_test_properties_with_main) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drive-by cleanup:
target_link_libraries(test_driver PRIVATE mongocxx_test_properties_with_main) | |
target_link_libraries(test_logging PRIVATE mongocxx_test_properties_with_main) | |
target_link_libraries(test_instance PRIVATE mongocxx_test_properties_with_main) | |
target_link_libraries(test_client_side_encryption_specs PRIVATE mongocxx_test_properties_with_main) | |
target_link_libraries(test_crud_specs PRIVATE mongocxx_test_properties_with_main) | |
target_link_libraries(test_gridfs_specs PRIVATE mongocxx_test_properties_with_main) | |
target_link_libraries(test_command_monitoring_specs PRIVATE mongocxx_test_properties_with_main) | |
target_link_libraries(test_transactions_specs PRIVATE mongocxx_test_properties_with_main) | |
target_link_libraries(test_retryable_reads_specs PRIVATE mongocxx_test_properties_with_main) | |
target_link_libraries(test_read_write_concern_specs PRIVATE mongocxx_test_properties_with_main) | |
target_link_libraries(test_mongohouse_specs PRIVATE mongocxx_test_properties_with_main) | |
target_link_libraries(test_unified_format_spec PRIVATE mongocxx_test_properties_with_main) | |
target_link_libraries(test_versioned_api PRIVATE mongocxx_test_properties_with_main) | |
set_property( | |
TARGET | |
test_driver | |
test_logging | |
test_instance | |
test_client_side_encryption_specs | |
test_crud_specs | |
test_gridfs_specs | |
test_command_monitoring_specs | |
test_transactions_specs | |
test_retryable_reads_specs | |
test_read_write_concern_specs | |
test_mongohouse_specs | |
test_unified_format_spec | |
test_versioned_api | |
APPEND PROPERTY LINK_LIBRARIES | |
mongocxx_test_properties_with_main | |
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: this suggestion emphasized to me the importance of indented arguments, so I have set the allowOptionalArgumentIndentation
format option for CMake Language Support to true
going forward (which I admittedly should have already set in #1037).
However, the way it handles inline comments within argument lists is a bit quirky. Perhaps switching to https://github.com/cheshirekow/cmake_format may be preferable down the line. 🤔
cmake/MacroGuardTest.cmake
Outdated
# PROJECT_TEST_PROPERTIES: the (bsoncxx|mongocxx)_test_properties target to be linked with. | ||
function(add_macro_guard_test) | ||
set(opt_args "") | ||
set(single_args "PROJECT_NAME;PROJECT_TEST_PROPERTIES") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
set(single_args "PROJECT_NAME;PROJECT_TEST_PROPERTIES") | |
set(single_args "PROJECT_NAME;TEST_LINK_LIBRARIES") |
or
set(single_args "PROJECT_NAME;PROJECT_TEST_PROPERTIES") | |
set(single_args "PROJECT_NAME;TEST_USE_LIBRARIES") |
The name *_PROPERTIES
makes me expect that it wants a property-pair list a-la set_target_properties()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a la
set_target_properties()
That's more or less the purpose that the (bsoncxx|mongocxx)_test_properties
interface library is fulfilling, just in a convenient form. This variable was named to be symmetrical with that intended target. Renamed to PROJECT_TEST_PROPERTIES_TARGET
to more explicitly express that it's a target, not a list of properties.
cmake/MacroGuardTest.cmake
Outdated
# PROJECT_NAME: bsoncxx or mongocxx. | ||
# GUARDED_MACROS: list of macros that should be guarded by prelude/postlude headers. | ||
# INCLUDE_FILTERS: list of regex filters for headers to be added to the tests (must be relative to PROJECT_SOURCE_DIR). | ||
# EXCLUDE_FILTERS: list of regex filters for headers to be excluded (applied after INCLUDE_FILTERS). | ||
# PROJECT_TEST_PROPERTIES: the (bsoncxx|mongocxx)_test_properties target to be linked with. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a bracket-comment for easier reading+writing:
# PROJECT_NAME: bsoncxx or mongocxx. | |
# GUARDED_MACROS: list of macros that should be guarded by prelude/postlude headers. | |
# INCLUDE_FILTERS: list of regex filters for headers to be added to the tests (must be relative to PROJECT_SOURCE_DIR). | |
# EXCLUDE_FILTERS: list of regex filters for headers to be excluded (applied after INCLUDE_FILTERS). | |
# PROJECT_TEST_PROPERTIES: the (bsoncxx|mongocxx)_test_properties target to be linked with. | |
#[==[ | |
Define a build-time test that asserts that the given macros are maintained across | |
the inclusion of the header files that match the given pattern | |
add_macro_guard_test( | |
PROJECT_NAME <name> | |
INCLUDE_FILTERS [pattern...] | |
[EXCLUDE_FILTERS [pattern...]] | |
GUARDED_MACROS [macro...] | |
PROJECT_TEST_PROPERTIES <target> | |
) | |
PROJECT_NAME | |
bsoncxx or mongocxx. | |
GUARDED_MACROS | |
list of macros that should be guarded by prelude/postlude headers. | |
INCLUDE_FILTERS | |
list of regex filters for headers to be added to the tests (must be relative to PROJECT_SOURCE_DIR). | |
EXCLUDE_FILTERS | |
list of regex filters for headers to be excluded (applied after INCLUDE_FILTERS). | |
PROJECT_TEST_PROPERTIES | |
the (bsoncxx|mongocxx)_test_properties target to be linked with. | |
]==] |
cmake/MacroGuardTest.cmake
Outdated
if("${PARSED_${required_arg}}" STREQUAL "") | ||
message(FATAL_ERROR "missing required argument: ${required_arg}") | ||
endif() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if("${PARSED_${required_arg}}" STREQUAL "") | |
message(FATAL_ERROR "missing required argument: ${required_arg}") | |
endif() | |
if(NOT DEFINED "PARSED_${required_arg}") | |
message(FATAL_ERROR "missing required argument: ${required_arg}") | |
endif() |
This permits empty strings as values, but that may be part of the check. Alternatively, tweak the error message:
if("${PARSED_${required_arg}}" STREQUAL "") | |
message(FATAL_ERROR "missing required argument: ${required_arg}") | |
endif() | |
if("${PARSED_${required_arg}}" STREQUAL "") | |
message(FATAL_ERROR "Required argument ${required_arg} was not provided or was provided with an empty value") | |
endif() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, an empty argument is considered invalid input for these parameters. Your suggestion pointed me to PARSED_KEYWORDS_MISSING_VALUES
, which seems to better fulfill the intended purpose here. Tweaked the error message accordingly.
cmake/MacroGuardTest.cmake
Outdated
foreach(filter ${PARSED_INCLUDE_FILTERS}) | ||
file(GLOB_RECURSE headers | ||
LIST_DIRECTORIES false | ||
RELATIVE ${PROJECT_SOURCE_DIR} | ||
"${PROJECT_SOURCE_DIR}/${filter}" | ||
) | ||
list(APPEND GUARDED_HEADERS ${headers}) | ||
endforeach() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can pass multiple glob patterns to file(GLOB)
in a single call:
foreach(filter ${PARSED_INCLUDE_FILTERS}) | |
file(GLOB_RECURSE headers | |
LIST_DIRECTORIES false | |
RELATIVE ${PROJECT_SOURCE_DIR} | |
"${PROJECT_SOURCE_DIR}/${filter}" | |
) | |
list(APPEND GUARDED_HEADERS ${headers}) | |
endforeach() | |
# Make the include patterns absolute from the project's source dir: | |
list(TRANSFORM PARSED_INCLUDE_FILTERS PREPEND "${PROJECT_SOURCE_DIR}/") | |
# Collect included headers: | |
file(GLOB_RECURSE headers CONFIGURE_DEPENDS RELATIVE "${PROJECT_SOURCE_DIR}" ${PARSED_INCLUDE_FILTERS}) |
cmake/MacroGuardTest.cmake
Outdated
function(add_macro_guard_test) | ||
set(opt_args "") | ||
set(single_args "PROJECT_NAME;PROJECT_TEST_PROPERTIES") | ||
set(multi_args "GUARDED_MACROS;INCLUDE_FILTERS;EXCLUDE_FILTERS") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
set(multi_args "GUARDED_MACROS;INCLUDE_FILTERS;EXCLUDE_FILTERS") | |
set(multi_args "GUARDED_MACROS;INCLUDE_PATTERNS;EXCLUDE_REGEX") |
Rename suggested since they accept different syntaxes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tests and header hygiene improvements are much appreciated.
I consider the potentially source-breaking API low-risk enough not to require a major API version bump.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Latest changes verified by this patch.
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(x) #x\n" | ||
"#define TO_STR(x) _TO_STR(x)\n" | ||
"\n" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am preferential towards being consistent with the other quoted arguments that require variable expansions (incompatible with bracket arguments) rather than using a bracket argument that requires unusual indentation to avoid it being reflected in the generated source file:
# 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) {
return (*lhs == *rhs) && (*lhs == '\0' || compare_equal(lhs + 1, rhs + 1));
}
static_assert(compare_equal("abc", "abc"), "compare_equal() sanity check failed");
static_assert(!compare_equal("abc", "def"), "compare_equal() sanity check failed");
#define _TO_STR(x) #x
#define TO_STR(x) _TO_STR(x)
]])
Would you rather we still use a bracket argument here?
cmake/MacroGuardTest.cmake
Outdated
# e.g. bsoncxx/v_noabi/bsoncxx/document/view.hpp -> bsoncxx-v_noabi-bsoncxx-document-view | ||
string(REPLACE "/" "-" test_name "${header}") | ||
string(REGEX REPLACE "^(.*)\\.(hh|hpp)$" "\\1" test_name "${test_name}") | ||
configure_file(test_macro_guards.cpp.in macro_guards/${subdir}/${test_name}.cpp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered file(WRITE)
, but it doesn't seem to support "smart" target dependency handling. It's an unconditional write regardless whether the file is already generated and/or no changes have been made to its contents, so I went with configure_file()
as recommended in the docs:
If the file is a build input, use the configure_file() command to update the file only when its content changes.
cmake/MacroGuardTest.cmake
Outdated
configure_file(test_macro_guards.cpp.in macro_guards/${subdir}/${test_name}.cpp) | ||
|
||
# Prefix library name to reduce potential for target conflicts. | ||
add_library(test-macro_guards-${subdir}-${test_name} STATIC EXCLUDE_FROM_ALL ${CMAKE_CURRENT_BINARY_DIR}/macro_guards/${subdir}/${test_name}.cpp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that you mention it, that makes a lot of sense! 😅 Will do.
cmake/MacroGuardTest.cmake
Outdated
# PROJECT_TEST_PROPERTIES: the (bsoncxx|mongocxx)_test_properties target to be linked with. | ||
function(add_macro_guard_test) | ||
set(opt_args "") | ||
set(single_args "PROJECT_NAME;PROJECT_TEST_PROPERTIES") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a la
set_target_properties()
That's more or less the purpose that the (bsoncxx|mongocxx)_test_properties
interface library is fulfilling, just in a convenient form. This variable was named to be symmetrical with that intended target. Renamed to PROJECT_TEST_PROPERTIES_TARGET
to more explicitly express that it's a target, not a list of properties.
cmake/MacroGuardTest.cmake
Outdated
if("${PARSED_${required_arg}}" STREQUAL "") | ||
message(FATAL_ERROR "missing required argument: ${required_arg}") | ||
endif() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, an empty argument is considered invalid input for these parameters. Your suggestion pointed me to PARSED_KEYWORDS_MISSING_VALUES
, which seems to better fulfill the intended purpose here. Tweaked the error message accordingly.
src/mongocxx/test/CMakeLists.txt
Outdated
target_link_libraries(test_driver PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_logging PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_instance PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_client_side_encryption_specs PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_crud_specs PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_gridfs_specs PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_command_monitoring_specs PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_transactions_specs PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_retryable_reads_specs PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_read_write_concern_specs PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_mongohouse_specs PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_unified_format_spec PRIVATE mongocxx_test_properties_with_main) | ||
target_link_libraries(test_versioned_api PRIVATE mongocxx_test_properties_with_main) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: this suggestion emphasized to me the importance of indented arguments, so I have set the allowOptionalArgumentIndentation
format option for CMake Language Support to true
going forward (which I admittedly should have already set in #1037).
However, the way it handles inline comments within argument lists is a bit quirky. Perhaps switching to https://github.com/cheshirekow/cmake_format may be preferable down the line. 🤔
src/bsoncxx/test/CMakeLists.txt
Outdated
"include/**/*.hpp" | ||
|
||
# Private headers. | ||
"lib/**/*.hh" | ||
|
||
# Test headers. | ||
"test/**/*.hh" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Will do.
src/bsoncxx/test/CMakeLists.txt
Outdated
# Avoid redundant recompilation of catch/main.cpp. | ||
add_library(bsoncxx_catch_main OBJECT ${THIRD_PARTY_SOURCE_DIR}/catch/main.cpp) | ||
target_link_libraries(bsoncxx_catch_main PRIVATE bsoncxx_test_properties) | ||
add_library(bsoncxx_test_properties_with_main INTERFACE) | ||
target_sources(bsoncxx_test_properties_with_main INTERFACE $<TARGET_OBJECTS:bsoncxx_catch_main>) | ||
target_link_libraries(bsoncxx_test_properties_with_main INTERFACE bsoncxx_test_properties) | ||
|
||
target_link_libraries(test_bson PRIVATE bsoncxx_test_properties_with_main) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CMake mandates that a name with
::
be a target without falling back
That is very good to remember! Will do.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
List of regex filters for headers to be added to the tests. Must be | |
List of glob expressions for headers to be added to the tests. Must be |
After further consideration and discussion, reverted the potential API breaking changes in favor of backward compatible workarounds and documenting the issues in CXX-2769 and CXX-2770. The include directories concern will be considered a potentially breaking change to the build system, not the public API. Notably, a test macro is used to patch the missing postlude header includes for macro guard tests, as otherwise the config macro leaks pollute any/all headers that include the problematic headers, rendering the macro guard tests mostly useless. This test macro is not documented and is not intended to be used by downstream users. Verified by this patch. |
Description
This PR introduces "macro guard tests" as a preemptive and precautionary measure against introducing preprocessor macro leaks from config headers in upcoming refactors, as well as to validate correctness of the current headers. Tracked by CXX-2767. Verified by this patch (note the new compile_macro_guard_tests tasks).
Macro Guard Tests
The
prelude.hpp
andpostlude.hpp
headers in both bsoncxx and mongocxx (as well as their private counterparts) are intended to ensure preprocessor macros defined in the config headers (i.e.version.hpp
), henceforth referred to as "config macros", neither conflict with nor leak into user code. As demonstrated by changes in this PR, the application of these "macro guard" headers has not been perfect throughout the evolution of the CXX Driver codebase.Therefore, this PR adds automated "macro guard tests" in the form of two CMake
custom targetsstatic libraries:test_bsoncxx_macro_guards
andtest_mongocxx_macro_guards
.Each target in turn has dependencies onEach library is composed of generated C++ source files, each corresponding to an individual header file in the CXX Driver repository (obtained viatest-macro_guards-<...>
static library targetsfile(GLOB_RECURSE)
;CONFIGURE_DEPENDS
is currently omitted but can be added on request):Due to the volume of CMake targets that are introduced by these tests (one for every tested header), the generation of these targets are guarded by an
ENABLE_MACRO_GUARD_TESTS
CMake option, which is disabled by default to avoid penalizing regular builds of the CXX Driver.Because the guard tests depend on the individual header file as well as the set of macros expected to be guarded, these tests are all generated by CMake using
configure_file()
using a dummytest_macro_guards.cpp.in
file. These source files are generated under amacro_guards
subdirectory in the current binary directory for a bit nicer-to-navigate binary directory layout.Some headers should not be tested, such as the guarded config headers or X macro headers. These headers are explicitly excluded from the list of headers to be tested using
EXCLUDE_REGEXES
(see the newadd_macro_guard_test()
function defined inMacroGuardTest.cmake
).Missing Macro Guard Inclusion
(Reverted; see below) The following public headers currently include a prelude header without the corresponding include of the postlude header, thus leaking config macros into user code:
bsoncxx/builder/list.hpp
mongocxx/gridfs/downloader.hpp
mongocxx/gridfs/uploader.hpp
mongocxx/options/auto_encryption.hpp
mongocxx/options/data_key.hpp
mongocxx/options/range.hpp
Adding the missing postlude header inclusion may break downstream code that depends on the leaked macros. This is arguably not intended behavior and should be considered a bugfix (users should be including the prelude header instead), but I do not think this is clearly communicated in documentation, tutorials, or examples. Therefore, if we want to acknowledge this as a potentially source-breaking API change, this will require an API major version bump.
Addressing these missing postlude header inclusions revealed some source files of our own that were failing to include the prelude header as they should have been. This PR addresses those as well.
Other Potential Source-Breaking Changes
In addition to the above, there is a small risk that some of these changes may break source compatibility for downstream users. Same as above: if we want to acknowledge any of these risks as a potentially source-breaking API change, this will require an API major version bump.
These risks are:
-Wmacro-redefined
error for a user that expects#include <bsoncxx/config/prelude.hpp>
to undef an already-definedBSONCXX_ENUM
and depends on this behavior for correctness, e.g.:This change is motivated by the fact that
BSONCXX_ENUM
is not a preprocessor macro related to the config headers. It is an X-macro intended to be used with thebsoncxx/enums/*.hpp
headers. The "ensureBSONCXX_ENUM
was undefined" check is inherited bybsoncxx/types.hpp
instead.${CMAKE_INSTALL_INCLUDEDIR}
breaks header search behavior for a user due to the directory being populated by existing (unrelated) header files that may conflict with the user's expected include directories search order, e.g.:This change is motivated by the upcoming addition of stable ABI header files under the
${CMAKE_INSTALL_INCLUDEDIR}/bsoncxx/v1
alongside existing unstable ABI headers under${CMAKE_INSTALL_INCLUDEDIR}/bsoncxx/v_noabi
.-I<includedir>/bsoncxx/v_noabi
must remain for backward source compatibility (e.g.#include <bsoncxx/document/view.hpp>
) and cannot be removed at this time.Therefore, the intent is to allow inclusion of stable ABI headers via
#include <bsoncxx/v1/bsoncxx/document/view.hpp>
. This would allow symmetry with inclusion of unstable ABI headers via#include <bsoncxx/v_noabi/bsoncxx/document/view.hpp>
while still allowing existing code to use#include <bsoncxx/document/view.hpp>
. (See rationale in #1026 for more info.)bsoncxx::stdx
namespace themselves before including<mongocxx/stdx.hpp>
in a manner that conflicts with the new namespace declaration added for standalone header inclusion compatibility. However, users should not be forward-declaring entities that they do not own (see CXX-2288), so this change should fall under our rights as library maintainers.This change is motivated by the macro guard tests including each tested header on its own. This naturally also tests the header's compatibility with standalone inclusion. This is a property we should be supporting in all our public header files (excluding the guarded config headers, the prelude/postlude headers themselves, and the X macro headers). A using-declaration cannot refer to a namespace that has not yet been declared; therefore, the
bsoncxx::stdx
namespace must be forward-declared bymongocxx/stdx.hpp
to support standalone inclusion.Miscellaneous
To avoid the mass recompilation of the
catch/main.cpp
component for every test executable and macro guard test target, the object was split into a<project>_catch_main
object library that is included in a new<project>_test_properties_with_main
target. The macro guard tests are only compiled as static libraries and do not require amain()
function, so they are linked with<project>_test_properties
instead.