-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Add a test for the module #2283
Conversation
3e88071
to
c2a78d3
Compare
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.
Thank you for the PR!
CMakeLists.txt
Outdated
set(FMT_BMI "${CMAKE_BINARY_DIR}/fmt.ifc") | ||
set(BMI_BUILD_FLAGS /interface /ifcOutput ${FMT_BMI}) | ||
set(BMI_USE_FLAGS /reference "\"fmt=${FMT_BMI}\"") | ||
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.
Indent is off.
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.
Yeah. That's also about to change. My current private code is different and I thought about generating these variables by functions that contain all the compiler-specific knowledge on how to create the required compiler options and syntax from a name and a location given as function arguments. This function can then be used to both create a module when the user consumes {fmt} that way and also in the tests where the library must be compiled in both modes at the same time: as a traditional library and a module (only for testing purposes) with a name different from the traditional library. This way all existing functional tests remain as they are and can reach into implementation details as they please. The additional tests for module-capable compilers will not interfere. They don't need to (and also can't) test the implementation aspect again but rather look at the public, user-facing API, possible regressions reagarding unwittingly exposing implementation details like namespace detail
and contents of it, and (at least in the beginning) the salient properties of modules and their compiler-specific implementations.
Do you have an opinion about this?
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 think this function should take a target name and set the required properties instead of setting a bunch of variables, something like:
function(enable_modules name)
if (MSVC)
target_compile_options(${name}
PRIVATE /interface /ifcOutput ${CMAKE_CURRENT_BINARY_DIR}
INTERFACE /reference ${name}=${CMAKE_CURRENT_BINARY_DIR}/${name}.ifc)
...
endif ()
endfunction()
Note that ${CMAKE_CURRENT_BINARY_DIR}
is probably not needed because that will be the current directory anyway.
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.
Very good idea! You are definitely more fluent in CMake than I am.
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.
This seemingly unnecessary ${CMAKE_CURRENT_BINARY_DIR}
is required to generate correct compiler options. Checked with both the ninja and the vcproj generator.
CMakeLists.txt
Outdated
|
||
set(FMT_INTERFACE_UNIT src/fmt.cc) | ||
if (FMT_MODULE) | ||
set(FMT_SOURCES ${FMT_INTERFACE_UNIT}) |
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 think you can replace ${FMT_INTERFACE_UNIT}
with src/fmt.cc
and remove FMT_INTERFACE_UNIT
. The only other place that uses FMT_INTERFACE_UNIT
should probably use FMT_SOURCES
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 can change this into inline text. FMT_SOURCES
won't work in the tests where both a traditional library and a module must be compiled. Please see my other comment on this.
CMakeLists.txt
Outdated
@@ -257,6 +282,20 @@ target_include_directories(fmt-header-only INTERFACE | |||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> | |||
$<INSTALL_INTERFACE:${FMT_INC_DIR}>) | |||
|
|||
if (FMT_CAN_MODULE AND FMT_TEST) | |||
set (tstmod "modfmt") | |||
add_library(${tstmod} STATIC ${FMT_INTERFACE_UNIT} ${FMT_HEADERS}) |
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.
What is this target for?
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 additional module besides the traditional library.
I want to move this target into the other CMakeLists.txt
in the test
subdirectory. Hence the idea of generating the required compiler-specific compile options by CMake functions rather than hardcoding them. If at all possible, I want to turn the test-module target into a OBJECT
library if there is a way to introduce a dependency from the test-module-related object artifact to the invocation of the compile step of the module tests where the BMI must be available for consumption. So far I haven't found a way to express this. Either the syntax doesn't provide a way to get at the path to the object artifact, or CMake complains about a dependency on the BMI artifact. In the latter case it doesn't accept it because it either has no clue about how to create this artifact or it simply refuses to accept the file extension. In the former case there doesn't seem to exist a reliable and portable way to get at the full path, CMake generator expressions aren't supported in the required syntactical constructs. I need a "proxy" for the actual BMI and this is either a library name at a known path that I can then reliably resynthesize, or the object artifact with the unknown path. So far I go with this test-only library.
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 don't think we should be building another module from the same source. Not doing this also eliminates the need in the bmi_vars
function.
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.
This is not about compiling "another module". It is about compiling a module at all to enable testing the module-related stuff. The test must be conducted in "traditional lib" mode to make all of the existing tests work. {fmt} is compiled as it ever was. Because the module-based tests cannot excercise those parts of the current functional tests that need access to internal, non-exported parts of the library, {fmt} better be compiled so for testing. Therefore the need for a test-only additional module binary hidden away in the test subdirectory. And because it can no longer clash with the Real Stuff® in the test's root directory the name of the binary is fixed now.
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.
Makes sense now, thanks for clarification. I would recommend adding a comment explaining this.
CMakeLists.txt
Outdated
set(INSTALL_TARGETS fmt fmt-header-only) | ||
if (FMT_MODULE) | ||
set(INSTALL_TARGETS fmt) | ||
else() | ||
set(INSTALL_TARGETS fmt fmt-header-only) | ||
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.
I suggest simplifying this a bit:
set(INSTALL_TARGETS fmt)
if (NOT FMT_MODULE)
set(INSTALL_TARGETS ${INSTALL_TARGETS} fmt-header-only)
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.
Ok. TBH, I haven't even looked at the install part at all besides providing something. I'm interested in the test aspect.
May be I shouldn't even bother with that in this PR at all.
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 would suggest leaving the install targets as before unless there is some specific problem with them.
test/module-compile-fail.cc
Outdated
import fmt; | ||
|
||
// the detail namespace must be invisible | ||
using namespace fmt::detail; | ||
|
||
#if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && _MSC_FULL_VER <= 192930035 | ||
// bug in msvc 16.10-pre3: | ||
// the namespace is visible even when it is neither | ||
// implicitly nor explicitly exported | ||
# error namespace 'fmt::detail' is visible! | ||
#endif | ||
|
||
int 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.
I think it would be better to make it a normal test case in module-test
. You can detect absence of fmt::detail
just using lookup mechanisms, something like:
namespace fmt {
//namespace detail {}
}
namespace detail {
int test_detail_namespace;
}
namespace fmt {
void test() {
using namespace detail;
int success = test_detail_namespace; // fails to compile if fmt::detail is visible
}
}
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.
Well, mulling over this suggestion and playing with it a little I have to admit that I don't understand its benefit. May be I'm just dense.
In both implementations, the compiler will stop with a hard error in one of both possible outcomes:
- in my implementation it causes a hard error if everything is fine and the conditional compilation part will substitute it with an
#error
directive if namespacefmt::detail
erroneously happens to be visible. The test is supposed to fail compilation andctest
will flagmodule-compile-fail
as "failing" if the compilation succeeds despite the expectation.
The other, 'normal'module-test
is supposed to compile. The visibility offmt::detail
is benign in the sense that it doesn't preclude clients from using the module in full. So it shouldn't affect the other module-related tests. - in your suggested implementation, the compile failure is not handled by the test infrastructure if namespace
fmt::detail
is visible and no other test inmodule-test
is run. I don't think this kind of testing behaviour is helpful.
I've been looking for a SFINAE-friendly option to check for the visibility of a namespace with unknown contents, but there doesn't seem to be one. After all, given a conforming implementation (which msvc 16.10-pre3 isn't and 16.10 likely won't be, too !) this test is meant to defend contributors from unwittingly exposing supposedly inaccessible implementation details. Namespace fmt::detail
is one of them, afaik there are more. I'm not sure if a check for these additional namespaces like dragonbox
or safe_duration_cast
is necessary.
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 benefit is that the "test" is moved from CMake to normal C++ code. CMake tests are slow and hard to maintain. It's not different from any other compile error so it's fine if other tests are not run.
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.
Ok, the support for compile-fail checks is removed, too.
c04eb25
to
036f799
Compare
CMakeLists.txt
Outdated
|
||
set(FMT_CAN_MODULE OFF) | ||
if (CMAKE_CXX_STANDARD GREATER_EQUAL 20 AND | ||
(MSVC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.29.30035)) # msvc 16.10-pre3 |
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.
Parentheses around the second AND look unneeded. Also please break the long line e.g. by moving the comment to a separate line.
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.
Ok.
test/module-compile-fail.cc
Outdated
import fmt; | ||
|
||
// the detail namespace must be invisible | ||
using namespace fmt::detail; | ||
|
||
#if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && _MSC_FULL_VER <= 192930035 | ||
// bug in msvc 16.10-pre3: | ||
// the namespace is visible even when it is neither | ||
// implicitly nor explicitly exported | ||
# error namespace 'fmt::detail' is visible! | ||
#endif | ||
|
||
int 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.
The benefit is that the "test" is moved from CMake to normal C++ code. CMake tests are slow and hard to maintain. It's not different from any other compile error so it's fine if other tests are not run.
Ok, no problem on my side with your suggested change. From my CTest runs, I can not see an exceptionally slow test at all, though. Almost all examples that I could find regarding how to implement compile fail test are slow, this implementation isn't:
|
Running tests is fine but it also requires building a separate target. It's not much in absolute terms but these numbers add up quickly since we have a lot of CI configs. Having to deal with CMake code is even more problematic. |
036f799
to
8f6164e
Compare
endif () | ||
if (FMT_TEST AND FMT_MODULE) | ||
# The tests require {fmt} to be compiled as traditional library | ||
message(STATUS "Testing is incompatible with build mode 'module'.") |
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.
Who needs tests anyway =).
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.
🤡
Thank you! |
This is mostly just the groundwork. The module build rule supports only msvc 16.10-pre3 or later so far, none of which isn't yet available in CI. Others can chime in and add support for gcc or clang.
I'm a total cmake noob and this is the first time I did anything cmake-related beyond running away from it. If anybody has a better way to express the compile-dependency from the BMI generation to TUs importing that BMI this should be improved. I'm worn out and annoyed by all the restrictions and the lack of expressiveness.
Real tests to check {fmt}'s API in the module use test will follow in future PRs.