From 5c00749c5d85c30af8d41b1504c52c43ac64aeff Mon Sep 17 00:00:00 2001 From: Daniel Tierney Date: Mon, 29 Dec 2025 15:31:37 -0500 Subject: [PATCH] cmCxxModuleUsageEffects: Collect and apply BMI compatibility requirements This gathers properties and other details affecting Built Module Interface (BMI) compatibility for C++ module importers and applies them as necessary to synthetic targets, which represent a specific module import scenario. --- Source/cmCxxModuleUsageEffects.cxx | 25 ++-- Source/cmCxxModuleUsageEffects.h | 4 + .../CXXModulesCompile/RunCMakeTest.cmake | 7 ++ .../mixed-bmi-compatibility-build-check.cmake | 111 ++++++++++++++++++ .../mixed-bmi-compatibility/CMakeLists.txt | 21 ++++ .../mixed-bmi-compatibility/consumer20.cxx | 9 ++ .../mixed-bmi-compatibility/consumer23.cxx | 9 ++ .../mixed-bmi-compatibility/importable.cxx | 6 + 8 files changed, 185 insertions(+), 7 deletions(-) create mode 100644 Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility-build-check.cmake create mode 100644 Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility/CMakeLists.txt create mode 100644 Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility/consumer20.cxx create mode 100644 Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility/consumer23.cxx create mode 100644 Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility/importable.cxx diff --git a/Source/cmCxxModuleUsageEffects.cxx b/Source/cmCxxModuleUsageEffects.cxx index aa1a13f583..8985a58520 100644 --- a/Source/cmCxxModuleUsageEffects.cxx +++ b/Source/cmCxxModuleUsageEffects.cxx @@ -2,17 +2,28 @@ file LICENSE.rst or https://cmake.org/licensing for details. */ #include "cmCxxModuleUsageEffects.h" -cmCxxModuleUsageEffects::cmCxxModuleUsageEffects( - cmGeneratorTarget const* /*gt*/) - : Hash("0000000000000000000000000000000000000000") +#include + +#include "cmCryptoHash.h" +#include "cmGeneratorTarget.h" +#include "cmTarget.h" + +cmCxxModuleUsageEffects::cmCxxModuleUsageEffects(cmGeneratorTarget const* gt) { - // TODO: collect information from the generator target as to what might - // affect module consumption. + cmCryptoHash hasher(cmCryptoHash::AlgoSHA3_512); + this->Hash = hasher.HashString(gt->GetName()); + + // Collect compile features from the consuming target. + for (auto const& feature : gt->Target->GetCompileFeaturesEntries()) { + this->CompileFeatures.emplace_back(feature); + } } -void cmCxxModuleUsageEffects::ApplyToTarget(cmTarget* /*tgt*/) +void cmCxxModuleUsageEffects::ApplyToTarget(cmTarget* tgt) { - // TODO: apply the information collected in the constructor + for (auto const& feature : this->CompileFeatures) { + tgt->AppendProperty("COMPILE_FEATURES", feature.Value, feature.Backtrace); + } } std::string const& cmCxxModuleUsageEffects::GetHash() const diff --git a/Source/cmCxxModuleUsageEffects.h b/Source/cmCxxModuleUsageEffects.h index 1c3933c578..56a85b9923 100644 --- a/Source/cmCxxModuleUsageEffects.h +++ b/Source/cmCxxModuleUsageEffects.h @@ -5,6 +5,9 @@ #include "cmConfigure.h" // IWYU pragma: keep #include +#include + +#include "cmListFileCache.h" class cmGeneratorTarget; class cmTarget; @@ -19,4 +22,5 @@ public: private: std::string Hash; + std::vector> CompileFeatures; }; diff --git a/Tests/RunCMake/CXXModulesCompile/RunCMakeTest.cmake b/Tests/RunCMake/CXXModulesCompile/RunCMakeTest.cmake index 32dd5a468f..7087423d0e 100644 --- a/Tests/RunCMake/CXXModulesCompile/RunCMakeTest.cmake +++ b/Tests/RunCMake/CXXModulesCompile/RunCMakeTest.cmake @@ -177,6 +177,13 @@ if ("named" IN_LIST CMake_TEST_MODULE_COMPILATION) run_cxx_module_test(scan_props) run_cxx_module_test(target-objects) + # mixed-bmi-compatibility requires a generator that implements per-importer + # BMI generation + if ("cxx_std_23" IN_LIST CMAKE_CXX_COMPILE_FEATURES AND + RunCMake_GENERATOR MATCHES "Ninja") + run_cxx_module_test(mixed-bmi-compatibility) + endif() + if ("cxx_std_23" IN_LIST CMAKE_CXX_COMPILE_FEATURES AND "import_std23" IN_LIST CMake_TEST_MODULE_COMPILATION) run_cxx_module_test(imp-std) diff --git a/Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility-build-check.cmake b/Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility-build-check.cmake new file mode 100644 index 0000000000..a53b3ca257 --- /dev/null +++ b/Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility-build-check.cmake @@ -0,0 +1,111 @@ +# Verify the build system generated a synthetic target/BMI for each unique importer +set(expected_consumers consumer20 consumer23) +set(linked_dir_keys "") +set(linked_dir_names "") + +if (DEFINED RunCMake_TEST_CONFIG) + set(config_dir "${RunCMake_TEST_CONFIG}") +else () + set(config_dir "Debug") +endif () + +# Get and check linked-target-dirs for each consumer +foreach (consumer IN LISTS expected_consumers) + + if (RunCMake_GENERATOR_IS_MULTI_CONFIG) + set(output_dir "${consumer}.dir/${config_dir}") + else () + set(output_dir "${consumer}.dir") + endif () + + set(depend_info_file "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/${output_dir}/CXXDependInfo.json") + if (NOT EXISTS "${depend_info_file}") + list(APPEND RunCMake_TEST_FAILED + "Could not find CXXDependInfo.json for consumer ${consumer}: checked ${depend_info_file}") + continue() + endif () + + file(READ "${depend_info_file}" depend_info_json) + + # Extract linked-target-dirs array length and first element + string(JSON linked_dirs_len LENGTH "${depend_info_json}" "linked-target-dirs") + string(JSON linked_dirs GET "${depend_info_json}" "linked-target-dirs") + + if (NOT linked_dirs_len GREATER 0) + list(APPEND RunCMake_TEST_FAILED + "Consumer '${consumer}' has no linked-target-dirs but expected synthetic target for 'importable'") + continue() + endif () + + # For this test, expect exactly one linked target dir per consumer + if (NOT linked_dirs_len EQUAL 1) + list(APPEND RunCMake_TEST_FAILED + "Expected 1 linked-target-dir for '${consumer}' but found ${linked_dirs_len}: ${linked_dirs}") + continue() + endif () + + string(JSON linked_dir GET "${depend_info_json}" "linked-target-dirs" 0) + + if (RunCMake_GENERATOR_IS_MULTI_CONFIG) + cmake_path(GET linked_dir PARENT_PATH linked_tgt_root) + else () + set(linked_tgt_root "${linked_dir}") + endif () + + cmake_path(GET linked_tgt_root FILENAME linked_tgt_name) + + # Verify it is a synthetic target for the 'importable' library + if (NOT linked_tgt_name MATCHES "^importable@synth_[A-Za-z0-9_]+\.dir$") + list(APPEND RunCMake_TEST_FAILED + "Consumer '${consumer}' should link to synthetic target dir for 'importable' but found ${linked_dir}") + continue() + endif () + + if (RunCMake_GENERATOR_IS_MULTI_CONFIG) + set(linked_output_dir "${linked_dir}/${config_dir}") + else () + set(linked_output_dir "${linked_dir}") + endif () + + # Verify the synthetic target dir exists and contains a BMI + if (NOT EXISTS "${linked_dir}") + list(APPEND RunCMake_TEST_FAILED + "Consumer '${consumer}' links to synthetic target directory that does not exist: ${linked_dir}") + continue() + endif () + + file(GLOB_RECURSE bmi_files "${linked_dir}/*.bmi") + if (NOT bmi_files) + list(APPEND RunCMake_TEST_FAILED + "No BMI files found in synthetic target directory: ${linked_dir}") + continue() + endif () + + # Record the consumers of each linked dir to verify uniqueness + string(REGEX REPLACE "[^A-Za-z0-9_]" "_" linked_dir_key "${linked_tgt_name}") + set(linked_dir_consumers_var "linked_dir_consumers_${linked_dir_key}") + if (NOT DEFINED ${linked_dir_consumers_var}) + list(APPEND linked_dir_keys "${linked_dir_key}") + list(APPEND linked_dir_names "${linked_tgt_name}") + endif () + + list(APPEND ${linked_dir_consumers_var} ${consumer}) + +endforeach () + +# Verify each synthetic target is consumed exactly once +foreach (linked_dir_key IN LISTS linked_dir_keys) + set(consumers "${linked_dir_consumers_${linked_dir_key}}") + list(LENGTH consumers linked_dir_consumer_count) + + if (linked_dir_consumer_count GREATER 1) + list(FIND linked_dir_keys ${linked_dir_key} linked_dir_index) + list(GET linked_dir_names ${linked_dir_index} linked_tgt_name) + + string(JOIN ", " linked_dir_consumers_joined ${consumers}) + list(APPEND RunCMake_TEST_FAILED + "Expected per-importer BMI generation, but '${linked_tgt_name}' is linked to by multiple targets: ${linked_dir_consumers_joined}") + endif () +endforeach () + +string(REPLACE ";" "\n " RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}") diff --git a/Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility/CMakeLists.txt b/Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility/CMakeLists.txt new file mode 100644 index 0000000000..4af50c8459 --- /dev/null +++ b/Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.24...3.28) +project(cxx_modules_mixed_bmi_compatibility CXX) + +include("${CMAKE_SOURCE_DIR}/../cxx-modules-rules.cmake") + +add_library(importable) +target_sources(importable + PUBLIC FILE_SET CXX_MODULES FILES importable.cxx) + +target_compile_features(importable PUBLIC cxx_std_20) + +add_executable(consumer20 consumer20.cxx) +target_compile_features(consumer20 PRIVATE cxx_std_20) +target_link_libraries(consumer20 PRIVATE importable) + +add_executable(consumer23 consumer23.cxx) +target_compile_features(consumer23 PRIVATE cxx_std_23) +target_link_libraries(consumer23 PRIVATE importable) + +add_test(NAME consumer20 COMMAND consumer20) +add_test(NAME consumer23 COMMAND consumer23) diff --git a/Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility/consumer20.cxx b/Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility/consumer20.cxx new file mode 100644 index 0000000000..5f0bbbc985 --- /dev/null +++ b/Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility/consumer20.cxx @@ -0,0 +1,9 @@ +#include + +import importable; + +int main() +{ + std::cout << "from_import() = " << from_import() << '\n'; + return 0; +} diff --git a/Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility/consumer23.cxx b/Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility/consumer23.cxx new file mode 100644 index 0000000000..e36852bdf8 --- /dev/null +++ b/Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility/consumer23.cxx @@ -0,0 +1,9 @@ +#include + +import importable; + +int main() +{ + std::println("from_import() = {}", from_import()); + return 0; +} diff --git a/Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility/importable.cxx b/Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility/importable.cxx new file mode 100644 index 0000000000..607680a07c --- /dev/null +++ b/Tests/RunCMake/CXXModulesCompile/mixed-bmi-compatibility/importable.cxx @@ -0,0 +1,6 @@ +export module importable; + +export int from_import() +{ + return 0; +}