diff --git a/CMakeLists.txt b/CMakeLists.txt index a74ba6db..c0c047ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,17 @@ function(join result_var) set(${result_var} "${result}" PARENT_SCOPE) endfunction() +function(enable_module target) + if (MSVC) + set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc) + target_compile_options(${target} + PRIVATE /interface /ifcOutput ${BMI} + INTERFACE /reference fmt=${BMI}) + endif () + set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI}) + set_source_files_properties(${BMI} PROPERTIES GENERATED ON) +endfunction() + include(CMakeParseArguments) # Sets a cache variable with a docstring joined from multiple arguments: @@ -69,6 +80,22 @@ option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT}) option(FMT_FUZZ "Generate the fuzz target." OFF) option(FMT_CUDA_TEST "Generate the cuda-test target." OFF) option(FMT_OS "Include core requiring OS (Windows/Posix) " ON) +option(FMT_MODULE "Build a module instead of a traditional library." OFF) + +set(FMT_CAN_MODULE OFF) +if (CMAKE_CXX_STANDARD GREATER_EQUAL 20 AND + # msvc 16.10-pre4 + MSVC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.29.30036) + set(FMT_CAN_MODULE ON) +endif () +if (NOT FMT_CAN_MODULE) + set(FMT_MODULE OFF) + message(STATUS "Module support is disabled.") +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'.") +endif () # Get version from core.h file(READ include/fmt/core.h core_h) @@ -189,7 +216,9 @@ endfunction() add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h format-inl.h locale.h os.h ostream.h printf.h ranges.h wchar.h) -if (FMT_OS) +if (FMT_MODULE) + set(FMT_SOURCES src/fmt.cc) +elseif (FMT_OS) set(FMT_SOURCES src/format.cc src/os.cc) else() set(FMT_SOURCES src/format.cc) @@ -215,6 +244,9 @@ endif () if (FMT_PEDANTIC) target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS}) endif () +if (FMT_MODULE) + enable_module(fmt) +endif () target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES}) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 39a088cb..bc5de207 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -2,7 +2,9 @@ add_subdirectory(gtest) set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc) add_library(test-main STATIC ${TEST_MAIN_SRC}) -target_link_libraries(test-main gtest fmt) +target_include_directories(test-main PUBLIC + $) +target_link_libraries(test-main gtest) include(CheckCXXCompilerFlag) @@ -29,16 +31,20 @@ endfunction() # Adds a test. # Usage: add_fmt_test(name srcs...) function(add_fmt_test name) - cmake_parse_arguments(ADD_FMT_TEST "HEADER_ONLY" "" "" ${ARGN}) + cmake_parse_arguments(ADD_FMT_TEST "HEADER_ONLY;MODULE" "" "" ${ARGN}) set(sources ${name}.cc ${ADD_FMT_TEST_UNPARSED_ARGUMENTS}) - set(libs test-main) if (ADD_FMT_TEST_HEADER_ONLY) set(sources ${sources} ${TEST_MAIN_SRC} ../src/os.cc) set(libs gtest fmt-header-only) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables) endif () + elseif (ADD_FMT_TEST_MODULE) + set(libs test-main test-module) + set_source_files_properties(${name}.cc PROPERTIES OBJECT_DEPENDS test-module) + else () + set(libs test-main fmt) endif () add_fmt_executable(${name} ${sources}) target_link_libraries(${name} ${libs}) @@ -74,6 +80,20 @@ add_fmt_test(ranges-test) add_fmt_test(scan-test) add_fmt_test(wchar-test) +if (FMT_CAN_MODULE) + # The tests need {fmt} to be compiled as traditional library + # because of visibility of implementation details. + # If module support is present the module tests require a + # test-only module to be built from {fmt} + add_library(test-module OBJECT ${CMAKE_SOURCE_DIR}/src/fmt.cc) + target_compile_features(test-module PUBLIC ${FMT_REQUIRED_FEATURES}) + target_include_directories(test-module PUBLIC + $) + enable_module(test-module) + + add_fmt_test(module-test MODULE) +endif () + if (NOT MSVC) # FMT_ENFORCE_COMPILE_STRING is not supported under MSVC due to compiler bugs. add_fmt_test(enforce-checks-test) diff --git a/test/module-test.cc b/test/module-test.cc new file mode 100644 index 00000000..d8a120d0 --- /dev/null +++ b/test/module-test.cc @@ -0,0 +1,65 @@ +// Formatting library for C++ - module tests +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. +// +// Copyright (c) 2021 - present, Daniela Engert +// All Rights Reserved +// {fmt} module. + +import fmt; + +// check for macros leaking from BMI +static bool macro_leaked = +#if defined(FMT_CORE_H_) || defined(FMT_FORMAT_H) + true; +#else + false; +#endif + +#include "gtest/gtest.h" + +// an implicitly exported namespace must be visible [module.interface]/2.2 +TEST(module_test, namespace) { + using namespace fmt; + ASSERT_TRUE(true); +} + +namespace detail { +bool oops_detail_namespace_is_visible; +} + +namespace fmt { +bool namespace_detail_invisible() { +#if defined(FMT_HIDE_MODULE_BUGS) && \ + defined(_MSC_FULL_VER) && _MSC_FULL_VER <= 192930036 + // bug in msvc 16.10-pre4: + // the namespace is visible even when it is neither + // implicitly nor explicitly exported + return true; +#else + using namespace detail; + // this fails to compile if fmt::detail is visible + return !oops_detail_namespace_is_visible; +#endif +} +} // namespace fmt + +// the non-exported namespace 'detail' must be invisible [module.interface]/2 +TEST(module_test, detail_namespace) { + EXPECT_TRUE(fmt::namespace_detail_invisible()); +} + +// macros must not be imported from a *named* module [cpp.import]/5.1 +TEST(module_test, macros) { +#if defined(FMT_HIDE_MODULE_BUGS) && \ + defined(_MSC_FULL_VER) && _MSC_FULL_VER <= 192930036 +// bug in msvc 16.10-pre4: +// include-guard macros leak from BMI +// and even worse: they cannot be #undef-ined + macro_leaked = false; +#endif + EXPECT_FALSE(macro_leaked); +}