diff --git a/CMakeLists.txt b/CMakeLists.txt index cc413fb7..89c7d202 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ function(join result_var) set(${result_var} "${result}" PARENT_SCOPE) endfunction() +# DEPRECATED! Should be merged into add_module_library. function(enable_module target) if (MSVC) set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc) @@ -35,6 +36,79 @@ function(enable_module target) endif () endfunction() +# Adds a library compiled with C++20 module support. +# `enabled` is a CMake variables that specifies if modules are enabled. +# If modules are disabled `add_module_library` falls back to creating a +# non-modular library. +# +# Usage: +# add_module_library( [sources...] FALLBACK [sources...] [IF enabled]) +function(add_module_library name) + cmake_parse_arguments(AML "" "IF" "FALLBACK" ${ARGN}) + set(sources ${AML_UNPARSED_ARGUMENTS}) + + add_library(${name}) + set_target_properties(${name} PROPERTIES LINKER_LANGUAGE CXX) + + if (NOT ${${AML_IF}}) + # Create a non-modular library. + target_sources(${name} PRIVATE ${AML_FALLBACK}) + return() + endif () + + # Check if modules are supported. + set(have_modules FALSE) + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND + CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 16.0) + set(have_modules TRUE) + endif () + if (NOT have_modules) + message(FATAL_ERROR "Modules not supported.") + endif () + + # Modules require C++20. + target_compile_features(${name} PUBLIC cxx_std_20) + + # `std` is affected by CMake options and may be higher than C++20. + get_target_property(std ${name} CXX_STANDARD) + + set(pcms) + foreach (src ${sources}) + get_filename_component(pcm ${src} NAME_WE) + set(pcm ${pcm}.pcm) + + # Propagate -fmodule-file=*.pcm to targets that link with this library. + target_compile_options(${name} PUBLIC -fmodule-file=${pcm}) + + # Use an absolute path to prevent target_link_libraries prepending -l to it. + set(pcms ${pcms} ${CMAKE_CURRENT_BINARY_DIR}/${pcm}) + add_custom_command( + OUTPUT ${pcm} + COMMAND ${CMAKE_CXX_COMPILER} + -std=c++${std} -x c++-module --precompile -c + -o ${pcm} ${CMAKE_CURRENT_SOURCE_DIR}/${src} + "-I$,;-I>" + # Required by the -I generator expression above. + COMMAND_EXPAND_LISTS + DEPENDS ${src}) + endforeach () + + # Add .pcm files as sources to make sure they are built before the library. + set(files) + foreach (pcm ${pcms}) + get_filename_component(pcm_we ${pcm} NAME_WE) + set(obj ${pcm_we}.o) + # Use an absolute path to prevent target_link_libraries prepending -l. + set(files ${files} ${pcm} ${CMAKE_CURRENT_BINARY_DIR}/${obj}) + add_custom_command( + OUTPUT ${obj} + COMMAND ${CMAKE_CXX_COMPILER} $ + -c -o ${obj} ${pcm} + DEPENDS ${pcm}) + endforeach () + target_sources(${name} PRIVATE ${files}) +endfunction() + include(CMakeParseArguments) # Sets a cache variable with a docstring joined from multiple arguments: @@ -214,16 +288,18 @@ endfunction() add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h format-inl.h os.h ostream.h printf.h ranges.h std.h xchar.h) -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) +set(FMT_SOURCES src/format.cc) +if (FMT_OS) + set(FMT_SOURCES ${FMT_SOURCES} src/os.cc) endif () -add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst) +add_module_library(fmt src/fmt.cc FALLBACK + ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst + IF FMT_MODULE) add_library(fmt::fmt ALIAS fmt) +if (FMT_MODULE) + enable_module(fmt) +endif () if (FMT_WERROR) target_compile_options(fmt PRIVATE ${WERROR_FLAG}) @@ -232,25 +308,6 @@ if (FMT_PEDANTIC) target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS}) endif () -if (FMT_MODULE) - if (CMAKE_VERSION VERSION_LESS 3.26) - message(FATAL_ERROR "Modules require CMake 3.26+.") - endif () - set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API - "2182bf5c-ef0d-489a-91da-49dbc3090d2a") - set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1) - enable_module(fmt) - target_sources(fmt PUBLIC - FILE_SET cxx_modules TYPE CXX_MODULES FILES src/fmt.cc) - # Workaround a CMake issue when using fmt as a subdirectory. - target_compile_options( - fmt PUBLIC - -fprebuilt-module-path=${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/fmt.dir) - # Workaround a bug in clang-scan-deps. - target_include_directories( - fmt PUBLIC /usr/lib/gcc/x86_64-linux-gnu/12/include) -endif () - target_compile_features(fmt PUBLIC cxx_std_11) target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC @@ -334,7 +391,6 @@ if (FMT_INSTALL) LIBRARY DESTINATION ${FMT_LIB_DIR} ARCHIVE DESTINATION ${FMT_LIB_DIR} PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt" - FILE_SET cxx_modules DESTINATION ${FMT_LIB_DIR} FRAMEWORK DESTINATION "." RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})