From 6ffc411323e139030d6292087a9fa9722ce1ab82 Mon Sep 17 00:00:00 2001 From: arpittkhandelwal Date: Thu, 30 Apr 2026 08:41:34 +0530 Subject: [PATCH] Fix C++20 modules BMI configuration and stabilize external build tests - Resolved Linux configuration mismatches (GNU extensions) by propagating CMAKE_CXX_EXTENSIONS and HPX_CXX_STANDARD to external tests. - Fixed Windows installation crashes by removing CXX_MODULES_DIRECTORY from install(EXPORT) and adding MSVC guards for Clang linker flags. - Improved module metadata propagation by explicitly linking HPXInternal module targets in external examples. - Added CXX_STANDARD support to hpx_setup_target and applied it to hello_world_component. - Skipped pkg-config tests when modules are enabled due to lack of BMI discovery support. - Fixed CMake quoting and logic bugs in standard header collection. - Applied project-wide cmake-format and resolved CI linting failures. --- .cmake-format.py | 2 +- cmake/HPX_CXXModules.cmake | 120 +++++------------- cmake/HPX_CollectStdHeaders.cmake | 8 +- cmake/HPX_GeneratePackage.cmake | 10 +- cmake/HPX_SetupTarget.cmake | 26 +++- examples/gtest_emulation/CMakeLists.txt | 22 +++- examples/hello_world_component/CMakeLists.txt | 39 +++++- libs/CMakeLists.txt | 5 +- tests/unit/build/CMakeLists.txt | 33 +++-- 9 files changed, 140 insertions(+), 125 deletions(-) diff --git a/.cmake-format.py b/.cmake-format.py index 9b9336bb9d8d..832560aa2dc9 100644 --- a/.cmake-format.py +++ b/.cmake-format.py @@ -586,7 +586,7 @@ 'INCLUDE_DIRS': '+', 'FOUND_HEADERS': 1}, 'pargs': { 'flags': [], 'nargs': '1+'}}, - 'hpx_configure_module_producer': { 'kwargs': { 'MODULE_OUT_DIR': 1}, + 'hpx_configure_module_producer': { 'kwargs': {}, 'pargs': { 'flags': [], 'nargs': '1+'}} } diff --git a/cmake/HPX_CXXModules.cmake b/cmake/HPX_CXXModules.cmake index f01bf4c3e0a1..fb7a0c23b20e 100644 --- a/cmake/HPX_CXXModules.cmake +++ b/cmake/HPX_CXXModules.cmake @@ -4,7 +4,6 @@ # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -include(HPX_AddCompileFlag) include(HPX_Message) macro(hpx_check_cxx_modules_support) @@ -65,79 +64,39 @@ if(NOT HPX_WITH_CXX_MODULES) return() endif() -# hpx_configure_module_producer( [MODULE_OUT_DIR ]) +# hpx_configure_module_producer() # -# * Ensures a stable module output dir for producer target -# * Adds compiler flags to write module cache there (Clang/GCC) -# * Creates an interface target '_if' for consumers to link to +# * Creates an interface target '_if' for consumers to link to. +# * Sets INTERFACE_CXX_SCAN_FOR_MODULES ON so that CMake's native module +# dependency tracking propagates to all consumers. +# +# CMake 3.29+ with FILE_SET CXX_MODULES handles BMI generation and +# -fmodule-output/-fprebuilt-module-path flags automatically. No manual compiler +# flags are needed here. function(hpx_configure_module_producer producer) if(NOT TARGET ${producer}) hpx_error("hpx_configure_module_producer: target '${producer}' not found") endif() - # parse optional args - set(options) - set(one_value_args MODULE_OUT_DIR) - set(multi_value_args) - cmake_parse_arguments( - _args "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN} - ) - - if(_args_MODULE_OUT_DIR) - set(_moddir "${_args_MODULE_OUT_DIR}") - else() - set(_moddir "$") - endif() - set(_iface "${producer}_if") if(NOT TARGET ${_iface}) add_library(${_iface} INTERFACE) target_link_libraries(${_iface} INTERFACE ${producer}) endif() - # Set a property so consumers can query the BMI directory via - # get_target_property. - set_target_properties( - ${_iface} PROPERTIES INTERFACE_EXPORT_MODULE_DIR "${_moddir}" - ) - - # Make sure consumers scan for the BMI - set_target_properties(${_iface} PROPERTIES INTERFACE_CXX_SCAN_FOR_MODULES On) - - if(MSVC) - # MSVC: CMake/MSVC handle IFCs automatically; create a target for - # convenience, consumers can link to this to get ordering and include info - return() - endif() - - # Compiler-specific flags to instruct where to write module cache - if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES - "AppleClang" - ) - # Clang common flags - target_compile_options(${producer} PRIVATE "-fmodule-output=${_moddir}") - elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - # GCC: modern flags - hpx_add_target_compile_option_if_available( - ${producer} PRIVATE "-fmodule-output=${_moddir}" RESULT ok - ) - if(NOT ok) - hpx_error( - "hpx_configure_module_producer: the used version of gcc does not support '-fmodule-output'" - ) - endif() - else() - hpx_warn( - "hpx_configure_module_producer: unknown compiler '${CMAKE_CXX_COMPILER_ID}'; " - "exposing EXPORT_MODULE_DIR='${_moddir}' for manual handling" - ) - endif() + # Propagate scanning requirement to consumers via the interface target. CMake + # uses this to enable its native module dependency scanning for any target + # that links to this interface. + set_target_properties(${_iface} PROPERTIES INTERFACE_CXX_SCAN_FOR_MODULES ON) endfunction() -# hpx_configure_module_consumer( ]) +# hpx_configure_module_consumer( ) +# +# * Links the consumer to the producer interface target. +# * Enables CMake's native module scanning on the consumer. # -# * propagates module-related properties from producer interface target -# * sets necessary consumer compiler flags for clang and gcc +# CMake 3.29+ automatically resolves the BMI location from the FILE_SET +# CXX_MODULES declared on the producer. No -fprebuilt-module-path needed. function(hpx_configure_module_consumer consumer producer) if(NOT TARGET ${consumer}) hpx_error("hpx_configure_module_consumer: target '${consumer}' not found") @@ -146,41 +105,20 @@ function(hpx_configure_module_consumer consumer producer) hpx_error("hpx_configure_module_consumer: target '${producer}' not found") endif() + # Imported module metadata is only picked up from direct link dependencies. + # Link the underlying module target directly when the producer follows the + # '_if' wrapper pattern. + if(producer MATCHES "_if$") + string(REGEX REPLACE "_if$" "" _producer_target "${producer}") + if(TARGET ${_producer_target}) + target_link_libraries(${consumer} PRIVATE ${_producer_target}) + endif() + endif() + target_link_libraries(${consumer} PRIVATE ${producer}) + get_target_property(_scan ${producer} INTERFACE_CXX_SCAN_FOR_MODULES) if(_scan) set_target_properties(${consumer} PROPERTIES CXX_SCAN_FOR_MODULES ${_scan}) endif() - - get_target_property(_module_dir ${producer} INTERFACE_EXPORT_MODULE_DIR) - if(_module_dir) - if(MSVC) - return() - elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID - MATCHES "AppleClang" - ) - target_compile_options( - ${consumer} PRIVATE "-fprebuilt-module-path=${_module_dir}" - ) - get_target_property(_type ${consumer} TYPE) - if((_type STREQUAL "SHARED_LIBRARY") OR (_type STREQUAL "EXECUTABLE")) - target_link_options(${consumer} PRIVATE "-fuse-ld=lld") - target_link_options(${consumer} PRIVATE "-Wl,--error-limit=0") - endif() - elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - hpx_add_target_compile_option_if_available( - ${consumer} PRIVATE "-fprebuilt-module-path=${_module_dir}" RESULT ok - ) - if(NOT ok) - hpx_error( - "hpx_configure_module_consumer: the used version of gcc does not " - "support '-fprebuilt-module-path='" - ) - endif() - else() - hpx_warn( - "hpx_configure_module_consumer: unknown compiler '${CMAKE_CXX_COMPILER_ID}'" - ) - endif() - endif() endfunction() diff --git a/cmake/HPX_CollectStdHeaders.cmake b/cmake/HPX_CollectStdHeaders.cmake index 8faaf7bbef37..4882536bd985 100644 --- a/cmake/HPX_CollectStdHeaders.cmake +++ b/cmake/HPX_CollectStdHeaders.cmake @@ -134,11 +134,9 @@ function(hpx_extract_includes_from_file module) foreach(include ${includes}) string(REGEX REPLACE "#include (<[^>]+>)" "\\1" filename ${include}) - if(NOT filename MATCHES "\\.|/") - # Check if the include is a standard library header - if(${filename} IN_LIST STANDARD_LIBRARY_HEADERS) - list(APPEND found_includes ${filename}) - endif() + if("${filename}" IN_LIST STANDARD_LIBRARY_HEADERS) + # Capture only headers explicitly listed in STANDARD_LIBRARY_HEADERS. + list(APPEND found_includes "${filename}") endif() endforeach() diff --git a/cmake/HPX_GeneratePackage.cmake b/cmake/HPX_GeneratePackage.cmake index d62821cebad8..153b95ef23d7 100644 --- a/cmake/HPX_GeneratePackage.cmake +++ b/cmake/HPX_GeneratePackage.cmake @@ -19,11 +19,15 @@ write_basic_package_version_file( COMPATIBILITY AnyNewerVersion ) -# Export HPXInternalTargets in the build directory +# Export HPXInternalTargets in the build directory. Use the EXPORT signature so +# CMake also generates the per-target C++ module metadata files. Note: +# CXX_MODULES_DIRECTORY requires CMake 3.28+, which is already the minimum +# required when HPX_WITH_CXX_MODULES is enabled. export( TARGETS ${HPX_EXPORT_INTERNAL_TARGETS} NAMESPACE HPXInternal:: FILE "${CMAKE_CURRENT_BINARY_DIR}/lib/cmake/${HPX_PACKAGE_NAME}/HPXInternalTargets.cmake" + CXX_MODULES_DIRECTORY cxx-modules ) # Export HPXInternalTargets in the install directory @@ -35,11 +39,13 @@ install( COMPONENT cmake ) -# Export HPXTargets in the build directory +# Export HPXTargets in the build directory. Use the EXPORT signature so CMake +# also generates the per-target C++ module metadata files. export( TARGETS ${HPX_EXPORT_TARGETS} NAMESPACE HPX:: FILE "${CMAKE_CURRENT_BINARY_DIR}/lib/cmake/${HPX_PACKAGE_NAME}/HPXTargets.cmake" + CXX_MODULES_DIRECTORY cxx-modules ) # Add aliases with the namespace for use within HPX diff --git a/cmake/HPX_SetupTarget.cmake b/cmake/HPX_SetupTarget.cmake index df30e1c0f7bf..9dc563f02b9a 100644 --- a/cmake/HPX_SetupTarget.cmake +++ b/cmake/HPX_SetupTarget.cmake @@ -38,6 +38,7 @@ function(hpx_setup_target target) HPX_PREFIX HEADER_ROOT SCAN_FOR_MODULES + CXX_STANDARD ) set(multi_value_args DEPENDENCIES COMPONENT_DEPENDENCIES COMPILE_FLAGS LINK_FLAGS INSTALL_FLAGS INSTALL_PDB @@ -87,6 +88,13 @@ function(hpx_setup_target target) hpx_debug("setup_target.${target}" "LINK_FLAGS: ${target_LINK_FLAGS}") endif() + if(target_CXX_STANDARD) + set_target_properties( + ${target} PROPERTIES CXX_STANDARD ${target_CXX_STANDARD} + ) + hpx_debug("setup_target.${target}" "CXX_STANDARD: ${target_CXX_STANDARD}") + endif() + if(target_NAME) set(name "${target_NAME}") else() @@ -232,9 +240,21 @@ function(hpx_setup_target target) if(HPX_WITH_CXX_MODULES AND target_SCAN_FOR_MODULES) hpx_debug("setup_target.${target} SCAN_FOR_MODULES: ON") - hpx_configure_module_consumer(${target} hpx_core_module_if) + if(TARGET hpx_core_module_if) + hpx_configure_module_consumer(${target} hpx_core_module_if) + elseif(TARGET HPXInternal::hpx_core_module_if) + hpx_configure_module_consumer(${target} HPXInternal::hpx_core_module_if) + else() + hpx_error( + "setup_target.${target}: C++ modules scanning is enabled, but neither " + "hpx_core_module_if nor HPXInternal::hpx_core_module_if exists" + ) + endif() + if(TARGET hpx_full_module_if) hpx_configure_module_consumer(${target} hpx_full_module_if) + elseif(TARGET HPXInternal::hpx_full_module_if) + hpx_configure_module_consumer(${target} HPXInternal::hpx_full_module_if) endif() else() hpx_debug("setup_target.${target} SCAN_FOR_MODULES: OFF") @@ -247,8 +267,8 @@ function(hpx_setup_target target) # If modules are enabled, Clang emits DWARF v5, which requires using lld # instead of ld. - if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES - "AppleClang" + if((NOT MSVC) AND (CMAKE_CXX_COMPILER_ID MATCHES "Clang" + OR CMAKE_CXX_COMPILER_ID MATCHES "AppleClang") ) get_target_property(_type ${target} TYPE) if((_type STREQUAL "SHARED_LIBRARY") OR (_type STREQUAL "EXECUTABLE")) diff --git a/examples/gtest_emulation/CMakeLists.txt b/examples/gtest_emulation/CMakeLists.txt index 4b2e39f1d6a7..d733a67e2384 100644 --- a/examples/gtest_emulation/CMakeLists.txt +++ b/examples/gtest_emulation/CMakeLists.txt @@ -14,10 +14,23 @@ if(EXISTS "${HPX_DIR}") if(HPX_WITH_CXX_MODULES) set(CMAKE_CXX_SCAN_FOR_MODULES ON) + # CMake only propagates imported C++ module metadata from targets that are + # linked directly by the consumer. The exported HPXInternal module targets + # are therefore linked explicitly for the external build tests. + set(hpx_cxx_module_targets) + if(TARGET HPXInternal::hpx_core_module) + list(APPEND hpx_cxx_module_targets HPXInternal::hpx_core_module) + endif() + if(TARGET HPXInternal::hpx_full_module) + list(APPEND hpx_cxx_module_targets HPXInternal::hpx_full_module) + endif() endif() # Add a static library which contains a main to emulate gtest_main add_library(static_main_lib STATIC static_main.cpp) + set_target_properties( + static_main_lib PROPERTIES CXX_STANDARD ${HPX_CXX_STANDARD} + ) # /!\ This helper interface is needed to keep the right linking order add_library(hpx_helper_interface INTERFACE) @@ -27,7 +40,14 @@ if(EXISTS "${HPX_DIR}") # Test with the main function in a separate static library add_executable(hpx_main_ext_main hpx_main_ext_main.cpp) - target_link_libraries(hpx_main_ext_main PRIVATE hpx_helper_interface) + # Keep the helper interface for link order, but link module targets directly + # to the executable so CMake can see the imported BMI metadata. + target_link_libraries( + hpx_main_ext_main PRIVATE hpx_helper_interface ${hpx_cxx_module_targets} + ) + set_target_properties( + hpx_main_ext_main PROPERTIES CXX_STANDARD ${HPX_CXX_STANDARD} + ) enable_testing() add_test(hello_world_test hpx_main_ext_main) diff --git a/examples/hello_world_component/CMakeLists.txt b/examples/hello_world_component/CMakeLists.txt index 48ad4d4cd9b7..a0249339c308 100644 --- a/examples/hello_world_component/CMakeLists.txt +++ b/examples/hello_world_component/CMakeLists.txt @@ -11,12 +11,22 @@ project(hello_world_client CXX) if(EXISTS "${HPX_DIR}") find_package(HPX REQUIRED) - if(HPX_WITH_DISTRIBUTED_RUNTIME) - add_library(hello_world_component SHARED hello_world_component.cpp) - endif() - if(HPX_WITH_CXX_MODULES) set(CMAKE_CXX_SCAN_FOR_MODULES ON) + # CMake only propagates imported C++ module metadata from targets that are + # linked directly by the consumer. The exported HPXInternal module targets + # are therefore linked explicitly for the external build tests. + set(hpx_cxx_module_targets) + if(TARGET HPXInternal::hpx_core_module) + list(APPEND hpx_cxx_module_targets HPXInternal::hpx_core_module) + endif() + if(TARGET HPXInternal::hpx_full_module) + list(APPEND hpx_cxx_module_targets HPXInternal::hpx_full_module) + endif() + endif() + + if(HPX_WITH_DISTRIBUTED_RUNTIME) + add_library(hello_world_component SHARED hello_world_component.cpp) endif() add_executable(hello_world_client hello_world_client.cpp) @@ -26,11 +36,15 @@ if(EXISTS "${HPX_DIR}") if(HPX_WITH_DISTRIBUTED_RUNTIME) target_link_libraries( hello_world_component PUBLIC HPX::hpx HPX::iostreams_component + ${hpx_cxx_module_targets} ) target_link_libraries(hello_world_component PRIVATE HPX::component) target_link_libraries(hello_world_client PRIVATE hello_world_component) endif() - target_link_libraries(hello_world_client PRIVATE HPX::hpx HPX::wrap_main) + target_link_libraries( + hello_world_client PRIVATE HPX::hpx HPX::wrap_main + ${hpx_cxx_module_targets} + ) # We still support not linking to HPX::wrap_main when # HPX_WITH_DYNAMIC_HPX_MAIN=OFF for legacy use. This can only be done using @@ -44,6 +58,7 @@ if(EXISTS "${HPX_DIR}") target_link_libraries( hello_world_client_only_hpx_init PRIVATE hello_world_component + ${hpx_cxx_module_targets} ) endif() elseif("${SETUP_TYPE}" STREQUAL "MACROS") @@ -53,10 +68,20 @@ if(EXISTS "${HPX_DIR}") COMPONENT_DEPENDENCIES iostreams DEPENDENCIES HPX::wrap_main TYPE COMPONENT + SCAN_FOR_MODULES ${HPX_WITH_CXX_MODULES} CXX_STANDARD + ${HPX_CXX_STANDARD} + ) + hpx_setup_target( + hello_world_client + DEPENDENCIES hello_world_component + SCAN_FOR_MODULES ${HPX_WITH_CXX_MODULES} CXX_STANDARD + ${HPX_CXX_STANDARD} ) - hpx_setup_target(hello_world_client DEPENDENCIES hello_world_component) else() - hpx_setup_target(hello_world_client) + hpx_setup_target( + hello_world_client SCAN_FOR_MODULES ${HPX_WITH_CXX_MODULES} + CXX_STANDARD ${HPX_CXX_STANDARD} + ) endif() else() message(FATAL_ERROR "Unknown SETUP_TYPE=\"${SETUP_TYPE}\"") diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 774a18e36ccc..90a2689f11da 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -342,10 +342,7 @@ foreach(lib ${HPX_LIBS}) set_target_properties( hpx_${lib}_module PROPERTIES POSITION_INDEPENDENT_CODE ON ) - hpx_configure_module_producer( - hpx_${lib}_module - MODULE_OUT_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/${HPX_PACKAGE_NAME}/${lib} - ) + hpx_configure_module_producer(hpx_${lib}_module) target_link_libraries( hpx_${lib}_module diff --git a/tests/unit/build/CMakeLists.txt b/tests/unit/build/CMakeLists.txt index 7387f4ba7f56..fa626f6d5613 100644 --- a/tests/unit/build/CMakeLists.txt +++ b/tests/unit/build/CMakeLists.txt @@ -10,12 +10,6 @@ if(NOT HPX_WITH_TESTS_EXTERNAL_BUILD) return() endif() -# If C++ modules are enabled, exit now (we don't know yet how to setup a -# dependent HPX project to use the generated BMIs). -if(HPX_WITH_CXX_MODULES) - return() -endif() - # Try building an external cmake based project ... function( create_cmake_test @@ -27,8 +21,9 @@ function( test_dir ) set(build_dir "${CMAKE_CURRENT_BINARY_DIR}/${name}") - set(ADDITIONAL_CMAKE_OPTIONS -DUSING_INSTALL_DIR=${using_install_dir}) - set(ADDITIONAL_CMAKE_OPTIONS -DSETUP_TYPE=${setup_type}) + set(ADDITIONAL_CMAKE_OPTIONS -DUSING_INSTALL_DIR=${using_install_dir} + -DSETUP_TYPE=${setup_type} + ) if(CMAKE_TOOLCHAIN_FILE) set(ADDITIONAL_CMAKE_OPTIONS ${ADDITIONAL_CMAKE_OPTIONS} -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} @@ -63,8 +58,11 @@ function( endif() if(HPX_WITH_CXX_MODULES) set(ADDITIONAL_CMAKE_OPTIONS - ${ADDITIONAL_CMAKE_OPTIONS} -DHPX_WITH_CXX_MODULES=ON + ${ADDITIONAL_CMAKE_OPTIONS} + -DHPX_WITH_CXX_MODULES=ON -DHPX_CMAKE_LOGLEVEL=Debug + -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS} + -DCMAKE_CXX_STANDARD=${HPX_CXX_STANDARD} ) endif() add_custom_target( @@ -79,6 +77,16 @@ function( VERBATIM ) add_dependencies(${name} hpx hpx_init hpx_wrap) + + if(HPX_WITH_CXX_MODULES) + if(TARGET hpx_core_module) + add_dependencies(${name} hpx_core_module) + endif() + if(TARGET hpx_full_module) + add_dependencies(${name} hpx_full_module) + endif() + endif() + if(HPX_WITH_DISTRIBUTED_RUNTIME) add_dependencies(${name} iostreams_component) endif() @@ -102,7 +110,7 @@ function(create_pkgconfig_test name hpx_dir) "${PROJECT_SOURCE_DIR}/examples/hello_world_component/Makefile" SRC_DIR=${PROJECT_SOURCE_DIR}/examples/hello_world_component HPX_DIR=${hpx_dir} CXX=${CMAKE_CXX_COMPILER} CC=${CMAKE_C_COMPILER} - CXX_FLAGS=${CMAKE_CXX_FLAGS_SAFE} PKG_CONFIG=${PKG_CONFIG_EXECUTABLE} + "CXX_FLAGS=${CMAKE_CXX_FLAGS_SAFE}" PKG_CONFIG=${PKG_CONFIG_EXECUTABLE} BUILD_TYPE=$ VERBATIM ) @@ -200,7 +208,10 @@ foreach(build_type ${build_types}) endforeach() add_hpx_pseudo_dependencies(tests.unit.build tests.unit.build.cmake) -if(HPX_WITH_PKGCONFIG AND HPX_WITH_DISTRIBUTED_RUNTIME) +if(HPX_WITH_PKGCONFIG + AND HPX_WITH_DISTRIBUTED_RUNTIME + AND NOT HPX_WITH_CXX_MODULES +) find_package(PkgConfig) if(PKGCONFIG_FOUND) create_pkgconfig_test(