Browse Source

Merge topic 'imported-cxxmodules'

48ee946fdc cmExperimental: recycle the C++ modules API UUID
1a1806a71b gitlab-ci: declare `bmionly` support for modules where possible
457a12f3f9 Tests/RunCMake/CXXModules: add tests which use modules from imported targets
9b9ec70b54 Ninja: generate scanning and build rules for C++20 module synthetic targets
80ef50a191 CXXModules: add a variable for BMI-only compilation
80d6544398 cxxmodules: generate synthetic targets as an initial pass
3dc6676ecc cmSyntheticTargetCache: add a struct for synthetic target caching
cb356b540c cmCxxModuleUsageEffects: add a class to capture module usage effects
...

Acked-by: Kitware Robot <[email protected]>
Tested-by: buildbot <[email protected]>
Merge-request: !8535
Brad King 2 years ago
parent
commit
0788accdfc
76 changed files with 1681 additions and 75 deletions
  1. 1 1
      .gitlab/ci/configure_fedora38_ninja_clang.cmake
  2. 1 1
      .gitlab/ci/configure_fedora38_ninja_multi_clang.cmake
  3. 1 1
      .gitlab/ci/configure_linux_gcc_cxx_modules_ninja.cmake
  4. 1 1
      .gitlab/ci/configure_linux_gcc_cxx_modules_ninja_multi.cmake
  5. 1 1
      .gitlab/ci/configure_windows_clang_ninja.cmake
  6. 1 1
      .gitlab/ci/configure_windows_msvc_cxx_modules_common.cmake
  7. 1 0
      .gitlab/ci/cxx_modules_rules_gcc.cmake
  8. 7 1
      Help/dev/experimental.rst
  9. 5 0
      Help/manual/cmake-properties.7.rst
  10. 14 0
      Help/prop_tgt/IMPORTED_CXX_MODULES_COMPILE_DEFINITIONS.rst
  11. 13 0
      Help/prop_tgt/IMPORTED_CXX_MODULES_COMPILE_FEATURES.rst
  12. 13 0
      Help/prop_tgt/IMPORTED_CXX_MODULES_COMPILE_OPTIONS.rst
  13. 14 0
      Help/prop_tgt/IMPORTED_CXX_MODULES_INCLUDE_DIRECTORIES.rst
  14. 11 0
      Help/prop_tgt/IMPORTED_CXX_MODULES_LINK_LIBRARIES.rst
  15. 1 0
      Modules/Compiler/Clang-CXX.cmake
  16. 1 0
      Modules/Compiler/MSVC-CXX.cmake
  17. 5 0
      Source/CMakeLists.txt
  18. 21 0
      Source/cmCxxModuleUsageEffects.cxx
  19. 22 0
      Source/cmCxxModuleUsageEffects.h
  20. 44 9
      Source/cmDyndepCollation.cxx
  21. 5 0
      Source/cmDyndepCollation.h
  22. 1 1
      Source/cmExperimental.cxx
  23. 9 0
      Source/cmExportBuildFileGenerator.cxx
  24. 72 0
      Source/cmExportFileGenerator.cxx
  25. 3 0
      Source/cmExportFileGenerator.h
  26. 7 0
      Source/cmExportInstallFileGenerator.cxx
  27. 1 0
      Source/cmFileAPICodemodel.cxx
  28. 7 0
      Source/cmFileSet.cxx
  29. 2 0
      Source/cmFileSet.h
  30. 133 17
      Source/cmGeneratorTarget.cxx
  31. 13 1
      Source/cmGeneratorTarget.h
  32. 40 0
      Source/cmGlobalGenerator.cxx
  33. 2 0
      Source/cmGlobalGenerator.h
  34. 9 3
      Source/cmGlobalNinjaGenerator.cxx
  35. 76 0
      Source/cmImportedCxxModuleInfo.cxx
  36. 37 0
      Source/cmImportedCxxModuleInfo.h
  37. 85 12
      Source/cmNinjaNormalTargetGenerator.cxx
  38. 3 0
      Source/cmNinjaNormalTargetGenerator.h
  39. 214 8
      Source/cmNinjaTargetGenerator.cxx
  40. 11 1
      Source/cmNinjaTargetGenerator.h
  41. 15 0
      Source/cmSyntheticTargetCache.h
  42. 227 6
      Source/cmTarget.cxx
  43. 4 0
      Source/cmTarget.h
  44. 1 0
      Source/cmVisualStudio10TargetGenerator.cxx
  45. 1 1
      Tests/RunCMake/CXXModules/CMakeLists.txt
  46. 28 0
      Tests/RunCMake/CXXModules/RunCMakeTest.cmake
  47. 1 1
      Tests/RunCMake/CXXModules/examples/cxx-modules-rules.cmake
  48. 1 1
      Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build/test/CMakeLists.txt
  49. 1 1
      Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install/test/CMakeLists.txt
  50. 1 1
      Tests/RunCMake/CXXModules/examples/export-interface-build/test/CMakeLists.txt
  51. 1 1
      Tests/RunCMake/CXXModules/examples/export-interface-install/test/CMakeLists.txt
  52. 1 1
      Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build/test/CMakeLists.txt
  53. 1 1
      Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install/test/CMakeLists.txt
  54. 4 0
      Tests/RunCMake/CXXModules/examples/export-usage-build-stderr.txt
  55. 110 0
      Tests/RunCMake/CXXModules/examples/export-usage-build/CMakeLists.txt
  56. 6 0
      Tests/RunCMake/CXXModules/examples/export-usage-build/forward.cxx
  57. 10 0
      Tests/RunCMake/CXXModules/examples/export-usage-build/importable.cxx
  58. 6 0
      Tests/RunCMake/CXXModules/examples/export-usage-build/private.cxx
  59. 69 0
      Tests/RunCMake/CXXModules/examples/export-usage-build/test/CMakeLists.txt
  60. 4 0
      Tests/RunCMake/CXXModules/examples/export-usage-install-stderr.txt
  61. 114 0
      Tests/RunCMake/CXXModules/examples/export-usage-install/CMakeLists.txt
  62. 6 0
      Tests/RunCMake/CXXModules/examples/export-usage-install/forward.cxx
  63. 10 0
      Tests/RunCMake/CXXModules/examples/export-usage-install/importable.cxx
  64. 6 0
      Tests/RunCMake/CXXModules/examples/export-usage-install/private.cxx
  65. 69 0
      Tests/RunCMake/CXXModules/examples/export-usage-install/test/CMakeLists.txt
  66. 7 0
      Tests/RunCMake/CXXModules/examples/import-modules-export-bmi-and-interface-build-stderr.txt
  67. 7 0
      Tests/RunCMake/CXXModules/examples/import-modules-export-bmi-and-interface-install-stderr.txt
  68. 7 0
      Tests/RunCMake/CXXModules/examples/import-modules-export-interface-build-stderr.txt
  69. 7 0
      Tests/RunCMake/CXXModules/examples/import-modules-export-interface-install-stderr.txt
  70. 7 0
      Tests/RunCMake/CXXModules/examples/import-modules-export-interface-no-properties-build-stderr.txt
  71. 7 0
      Tests/RunCMake/CXXModules/examples/import-modules-export-interface-no-properties-install-stderr.txt
  72. 24 0
      Tests/RunCMake/CXXModules/examples/import-modules/CMakeLists.txt
  73. 6 0
      Tests/RunCMake/CXXModules/examples/import-modules/use.cxx
  74. 1 1
      Tests/RunCMake/target_sources/FileSetDefaultWrongTypeExperimental.cmake
  75. 1 1
      Tests/RunCMake/target_sources/FileSetWrongTypeExperimental.cmake
  76. 2 0
      bootstrap

+ 1 - 1
.gitlab/ci/configure_fedora38_ninja_clang.cmake

@@ -1,4 +1,4 @@
-set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared" CACHE STRING "")
+set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared,bmionly" CACHE STRING "")
 set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_clang.cmake" CACHE STRING "")
 
 include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora38_common_clang.cmake")

+ 1 - 1
.gitlab/ci/configure_fedora38_ninja_multi_clang.cmake

@@ -1,4 +1,4 @@
-set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared" CACHE STRING "")
+set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared,bmionly" CACHE STRING "")
 set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_clang.cmake" CACHE STRING "")
 
 include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora38_common_clang.cmake")

+ 1 - 1
.gitlab/ci/configure_linux_gcc_cxx_modules_ninja.cmake

@@ -1,4 +1,4 @@
-set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi" CACHE STRING "")
+set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,bmionly" CACHE STRING "")
 set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_gcc.cmake" CACHE STRING "")
 
 include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake")

+ 1 - 1
.gitlab/ci/configure_linux_gcc_cxx_modules_ninja_multi.cmake

@@ -1,4 +1,4 @@
-set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi" CACHE STRING "")
+set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,bmionly" CACHE STRING "")
 set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_gcc.cmake" CACHE STRING "")
 
 include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake")

+ 1 - 1
.gitlab/ci/configure_windows_clang_ninja.cmake

@@ -1,5 +1,5 @@
 if("$ENV{CMAKE_CI_BUILD_NAME}" MATCHES "(^|_)gnu(_|$)")
-  set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared" CACHE STRING "")
+  set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared,bmionly" CACHE STRING "")
   set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_clang.cmake" CACHE STRING "")
 endif()
 include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_clang_common.cmake")

+ 1 - 1
.gitlab/ci/configure_windows_msvc_cxx_modules_common.cmake

@@ -1,2 +1,2 @@
-set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,shared,export_bmi,install_bmi" CACHE STRING "")
+set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,shared,export_bmi,install_bmi,bmionly" CACHE STRING "")
 set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_msvc.cmake" CACHE STRING "")

+ 1 - 0
.gitlab/ci/cxx_modules_rules_gcc.cmake

@@ -17,3 +17,4 @@ string(CONCAT CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FLAG
   " -fdeps-format=p1689r5"
   # Force C++ as a language.
   " -x c++")
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_BMI_ONLY_FLAG "-fmodule-only")

+ 7 - 1
Help/dev/experimental.rst

@@ -18,7 +18,7 @@ C++20 Module APIs
 =================
 
 Variable: ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
-Value: ``a816ed09-43d1-40e5-bc8c-1a2824ee194e``
+Value: ``ac01f462-0f5f-432a-86aa-acef252918a6``
 
 In order to support C++20 modules, there are a number of behaviors that have
 CMake APIs to provide the required features to build and export them from a
@@ -99,6 +99,10 @@ dependencies to the file specified by the ``<DYNDEP_FILE>`` placeholder. The
 ``CMAKE_EXPERIMENTAL_CXX_SCANDEP_DEPFILE_FORMAT`` file may be set to ``msvc``
 for scandep rules which use ``msvc``-style dependency reporting.
 
+In order to support ``IMPORTED`` targets with associated C++20 module sources,
+the ``CMAKE_EXPERIMENTAL_CXX_MODULE_BMI_ONLY_FLAG`` variable must be provided
+to have the compiler only output a BMI instead of a BMI and an object file.
+
 The module dependencies should be written in the format described
 by the `P1689r5`_ paper.
 
@@ -113,6 +117,8 @@ For compilers that generate module maps, tell CMake as follows:
   set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FORMAT "gcc")
   set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FLAG
     "${compiler_flags_for_module_map} -fmodule-mapper=<MODULE_MAP_FILE>")
+  set(CMAKE_EXPERIMENTAL_CXX_MODULE_BMI_ONLY_FLAG
+    "-fmodule-only")
 
 Currently, the only supported formats are, ``clang``, ``gcc``, and ``msvc``.
 The ``gcc`` format is described in the GCC documentation, but the relevant

+ 5 - 0
Help/manual/cmake-properties.7.rst

@@ -240,6 +240,11 @@ Properties on Targets
    /prop_tgt/IMPORTED
    /prop_tgt/IMPORTED_COMMON_LANGUAGE_RUNTIME
    /prop_tgt/IMPORTED_CONFIGURATIONS
+   /prop_tgt/IMPORTED_CXX_MODULES_COMPILE_DEFINITIONS
+   /prop_tgt/IMPORTED_CXX_MODULES_COMPILE_FEATURES
+   /prop_tgt/IMPORTED_CXX_MODULES_COMPILE_OPTIONS
+   /prop_tgt/IMPORTED_CXX_MODULES_INCLUDE_DIRECTORIES
+   /prop_tgt/IMPORTED_CXX_MODULES_LINK_LIBRARIES
    /prop_tgt/IMPORTED_GLOBAL
    /prop_tgt/IMPORTED_IMPLIB
    /prop_tgt/IMPORTED_IMPLIB_CONFIG

+ 14 - 0
Help/prop_tgt/IMPORTED_CXX_MODULES_COMPILE_DEFINITIONS.rst

@@ -0,0 +1,14 @@
+IMPORTED_CXX_MODULES_COMPILE_DEFINITIONS
+----------------------------------------
+
+.. versionadded:: 3.28
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+Preprocessor definitions for compiling an ``IMPORTED`` target's C++ module
+sources.
+
+CMake will automatically drop some definitions that are not supported
+by the native build tool.

+ 13 - 0
Help/prop_tgt/IMPORTED_CXX_MODULES_COMPILE_FEATURES.rst

@@ -0,0 +1,13 @@
+IMPORTED_CXX_MODULES_COMPILE_FEATURES
+-------------------------------------
+
+.. versionadded:: 3.28
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+Compiler features enabled for this ``IMPORTED`` target's C++ modules.
+
+The value of this property is used by the generators to set the include
+paths for the compiler.

+ 13 - 0
Help/prop_tgt/IMPORTED_CXX_MODULES_COMPILE_OPTIONS.rst

@@ -0,0 +1,13 @@
+IMPORTED_CXX_MODULES_COMPILE_OPTIONS
+------------------------------------
+
+.. versionadded:: 3.28
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+List of options to pass to the compiler for this ``IMPORTED`` target's C++
+modules.
+
+.. include:: ../command/OPTIONS_SHELL.txt

+ 14 - 0
Help/prop_tgt/IMPORTED_CXX_MODULES_INCLUDE_DIRECTORIES.rst

@@ -0,0 +1,14 @@
+IMPORTED_CXX_MODULES_INCLUDE_DIRECTORIES
+----------------------------------------
+
+.. versionadded:: 3.28
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+List of preprocessor include file search directories when compiling C++
+modules for ``IMPORTED`` targets.
+
+The value of this property is used by the generators to set the include
+paths for the compiler.

+ 11 - 0
Help/prop_tgt/IMPORTED_CXX_MODULES_LINK_LIBRARIES.rst

@@ -0,0 +1,11 @@
+IMPORTED_CXX_MODULES_LINK_LIBRARIES
+-----------------------------------
+
+.. versionadded:: 3.28
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+List of direct dependencies to use for usage requirements for C++ modules in
+the target's C++ modules.

+ 1 - 0
Modules/Compiler/Clang-CXX.cmake

@@ -43,5 +43,6 @@ if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 16.0)
       " > <DYNDEP_FILE>")
     set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FORMAT "clang")
     set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FLAG "@<MODULE_MAP_FILE>")
+    set(CMAKE_EXPERIMENTAL_CXX_MODULE_BMI_ONLY_FLAG "--precompile")
   endif()
 endif()

+ 1 - 0
Modules/Compiler/MSVC-CXX.cmake

@@ -87,4 +87,5 @@ if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "19.34")
   set(CMAKE_EXPERIMENTAL_CXX_SCANDEP_DEPFILE_FORMAT "msvc")
   set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FORMAT "msvc")
   set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FLAG "@<MODULE_MAP_FILE>")
+  set(CMAKE_EXPERIMENTAL_CXX_MODULE_BMI_ONLY_FLAG "-ifcOnly;-ifcOutput;<OBJECT>")
 endif ()

+ 5 - 0
Source/CMakeLists.txt

@@ -171,6 +171,8 @@ add_library(
   cmCustomCommandTypes.h
   cmCxxModuleMapper.cxx
   cmCxxModuleMapper.h
+  cmCxxModuleUsageEffects.cxx
+  cmCxxModuleUsageEffects.h
   cmDefinitions.cxx
   cmDefinitions.h
   cmDependencyProvider.h
@@ -299,6 +301,8 @@ add_library(
   cmGraphAdjacencyList.h
   cmGraphVizWriter.cxx
   cmGraphVizWriter.h
+  cmImportedCxxModuleInfo.cxx
+  cmImportedCxxModuleInfo.h
   cmInstallGenerator.h
   cmInstallGenerator.cxx
   cmInstallGetRuntimeDependenciesGenerator.h
@@ -424,6 +428,7 @@ add_library(
   cmStateTypes.h
   cmStringAlgorithms.cxx
   cmStringAlgorithms.h
+  cmSyntheticTargetCache.h
   cmSystemTools.cxx
   cmSystemTools.h
   cmTarget.cxx

+ 21 - 0
Source/cmCxxModuleUsageEffects.cxx

@@ -0,0 +1,21 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmCxxModuleUsageEffects.h"
+
+cmCxxModuleUsageEffects::cmCxxModuleUsageEffects(
+  cmGeneratorTarget const* /*gt*/)
+  : Hash("0000000000000000000000000000000000000000")
+{
+  // TODO: collect information from the generator target as to what might
+  // affect module consumption.
+}
+
+void cmCxxModuleUsageEffects::ApplyToTarget(cmTarget* /*tgt*/)
+{
+  // TODO: apply the information collected in the constructor
+}
+
+std::string const& cmCxxModuleUsageEffects::GetHash() const
+{
+  return this->Hash;
+}

+ 22 - 0
Source/cmCxxModuleUsageEffects.h

@@ -0,0 +1,22 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <string>
+
+class cmGeneratorTarget;
+class cmTarget;
+
+class cmCxxModuleUsageEffects
+{
+public:
+  cmCxxModuleUsageEffects(cmGeneratorTarget const* gt);
+
+  void ApplyToTarget(cmTarget* tgt);
+  std::string const& GetHash() const;
+
+private:
+  std::string Hash;
+};

+ 44 - 9
Source/cmDyndepCollation.cxx

@@ -73,20 +73,36 @@ Json::Value CollationInformationCxxModules(
                                   gt->LocalGenerator, config, gt);
     }
 
-    std::map<std::string, cmSourceFile const*> sf_map;
+    enum class CompileType
     {
-      std::vector<cmSourceFile const*> objectSources;
-      gt->GetObjectSources(objectSources, config);
-      for (auto const* sf : objectSources) {
+      ObjectAndBmi,
+      BmiOnly,
+    };
+    std::map<std::string, std::pair<cmSourceFile const*, CompileType>> sf_map;
+    {
+      auto fill_sf_map = [gt, tgt, &sf_map](cmSourceFile const* sf,
+                                            CompileType type) {
         auto full_path = sf->GetFullPath();
         if (full_path.empty()) {
           gt->Makefile->IssueMessage(
             MessageType::INTERNAL_ERROR,
             cmStrCat("Target \"", tgt->GetName(),
                      "\" has a full path-less source file."));
-          continue;
+          return;
         }
-        sf_map[full_path] = sf;
+        sf_map[full_path] = std::make_pair(sf, type);
+      };
+
+      std::vector<cmSourceFile const*> objectSources;
+      gt->GetObjectSources(objectSources, config);
+      for (auto const* sf : objectSources) {
+        fill_sf_map(sf, CompileType::ObjectAndBmi);
+      }
+
+      std::vector<cmSourceFile const*> cxxModuleSources;
+      gt->GetCxxModuleSources(cxxModuleSources, config);
+      for (auto const* sf : cxxModuleSources) {
+        fill_sf_map(sf, CompileType::BmiOnly);
       }
     }
 
@@ -113,7 +129,8 @@ Json::Value CollationInformationCxxModules(
           continue;
         }
 
-        auto const* sf = lookup->second;
+        auto const* sf = lookup->second.first;
+        CompileType const ct = lookup->second.second;
 
         if (!sf) {
           gt->Makefile->IssueMessage(
@@ -123,11 +140,14 @@ Json::Value CollationInformationCxxModules(
           continue;
         }
 
-        auto obj_path = cb.ObjectFilePath(sf, config);
+        auto obj_path = ct == CompileType::ObjectAndBmi
+          ? cb.ObjectFilePath(sf, config)
+          : cb.BmiFilePath(sf, config);
         Json::Value& tdi_module_info = tdi_cxx_module_info[obj_path] =
           Json::objectValue;
 
         tdi_module_info["source"] = file;
+        tdi_module_info["bmi-only"] = ct == CompileType::BmiOnly;
         tdi_module_info["relative-directory"] = files_per_dir.first;
         tdi_module_info["name"] = file_set->GetName();
         tdi_module_info["type"] = file_set->GetType();
@@ -269,10 +289,11 @@ void cmDyndepCollation::AddCollationInformation(
 struct CxxModuleFileSet
 {
   std::string Name;
+  bool BmiOnly = false;
   std::string RelativeDirectory;
   std::string SourcePath;
   std::string Type;
-  cmFileSetVisibility Visibility;
+  cmFileSetVisibility Visibility = cmFileSetVisibility::Private;
   cm::optional<std::string> Destination;
 };
 
@@ -356,6 +377,7 @@ cmDyndepCollation::ParseExportInfo(Json::Value const& tdi)
       CxxModuleFileSet& fsi = export_info->ObjectToFileSet[i.key().asString()];
       auto const& tdi_cxx_module_info = *i;
       fsi.Name = tdi_cxx_module_info["name"].asString();
+      fsi.BmiOnly = tdi_cxx_module_info["bmi-only"].asBool();
       fsi.RelativeDirectory =
         tdi_cxx_module_info["relative-directory"].asString();
       if (!fsi.RelativeDirectory.empty() &&
@@ -644,3 +666,16 @@ bool cmDyndepCollation::IsObjectPrivate(
   auto const& file_set = fileset_info_itr->second;
   return !cmFileSetVisibilityIsForInterface(file_set.Visibility);
 }
+
+bool cmDyndepCollation::IsBmiOnly(cmCxxModuleExportInfo const& exportInfo,
+                                  std::string const& object)
+{
+#ifdef _WIN32
+  auto object_path = object;
+  cmSystemTools::ConvertToUnixSlashes(object_path);
+#else
+  auto const& object_path = object;
+#endif
+  auto fs = exportInfo.ObjectToFileSet.find(object_path);
+  return (fs != exportInfo.ObjectToFileSet.end()) && fs->second.BmiOnly;
+}

+ 5 - 0
Source/cmDyndepCollation.h

@@ -23,6 +23,8 @@ struct cmDyndepGeneratorCallbacks
 {
   std::function<std::string(cmSourceFile const* sf, std::string const& config)>
     ObjectFilePath;
+  std::function<std::string(cmSourceFile const* sf, std::string const& config)>
+    BmiFilePath;
 };
 
 struct cmDyndepMetadataCallbacks
@@ -51,4 +53,7 @@ struct cmDyndepCollation
                                   cmDyndepMetadataCallbacks const& cb);
   static bool IsObjectPrivate(std::string const& object,
                               cmCxxModuleExportInfo const& export_info);
+
+  static bool IsBmiOnly(cmCxxModuleExportInfo const& exportInfo,
+                        std::string const& object);
 };

+ 1 - 1
Source/cmExperimental.cxx

@@ -21,7 +21,7 @@ namespace {
 cmExperimental::FeatureData LookupTable[] = {
   // CxxModuleCMakeApi
   { "CxxModuleCMakeApi",
-    "a816ed09-43d1-40e5-bc8c-1a2824ee194e",
+    "ac01f462-0f5f-432a-86aa-acef252918a6",
     "CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API",
     "CMake's C++ module support is experimental. It is meant only for "
     "experimentation and feedback to CMake developers.",

+ 9 - 0
Source/cmExportBuildFileGenerator.cxx

@@ -126,6 +126,15 @@ bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os)
                                     properties);
 
     std::string errorMessage;
+    if (!this->PopulateCxxModuleExportProperties(
+          gte, properties, cmGeneratorExpression::BuildInterface,
+          errorMessage)) {
+      this->LG->GetGlobalGenerator()->GetCMakeInstance()->IssueMessage(
+        MessageType::FATAL_ERROR, errorMessage,
+        this->LG->GetMakefile()->GetBacktrace());
+      return false;
+    }
+
     if (!this->PopulateExportProperties(gte, properties, errorMessage)) {
       this->LG->GetGlobalGenerator()->GetCMakeInstance()->IssueMessage(
         MessageType::FATAL_ERROR, errorMessage,

+ 72 - 0
Source/cmExportFileGenerator.cxx

@@ -9,6 +9,7 @@
 #include <utility>
 
 #include <cm/memory>
+#include <cmext/string_view>
 
 #include "cmsys/FStream.hxx"
 
@@ -1255,6 +1256,77 @@ void cmExportFileGenerator::GenerateImportedFileChecksCode(
   os << ")\n\n";
 }
 
+bool cmExportFileGenerator::PopulateCxxModuleExportProperties(
+  cmGeneratorTarget const* gte, ImportPropertyMap& properties,
+  cmGeneratorExpression::PreprocessContext ctx, std::string& errorMessage)
+{
+  if (!gte->HaveCxx20ModuleSources(&errorMessage)) {
+    return true;
+  }
+
+  const cm::static_string_view exportedDirectModuleProperties[] = {
+    "CXX_EXTENSIONS"_s,
+  };
+  for (auto const& propName : exportedDirectModuleProperties) {
+    auto const propNameStr = std::string(propName);
+    cmValue prop = gte->Target->GetComputedProperty(
+      propNameStr, *gte->Target->GetMakefile());
+    if (!prop) {
+      prop = gte->Target->GetProperty(propNameStr);
+    }
+    if (prop) {
+      properties[propNameStr] = cmGeneratorExpression::Preprocess(*prop, ctx);
+    }
+  }
+
+  const cm::static_string_view exportedModuleProperties[] = {
+    "INCLUDE_DIRECTORIES"_s,
+    "COMPILE_DEFINITIONS"_s,
+    "COMPILE_OPTIONS"_s,
+    "COMPILE_FEATURES"_s,
+  };
+  for (auto const& propName : exportedModuleProperties) {
+    auto const propNameStr = std::string(propName);
+    cmValue prop = gte->Target->GetComputedProperty(
+      propNameStr, *gte->Target->GetMakefile());
+    if (!prop) {
+      prop = gte->Target->GetProperty(propNameStr);
+    }
+    if (prop) {
+      auto const exportedPropName =
+        cmStrCat("IMPORTED_CXX_MODULES_", propName);
+      properties[exportedPropName] =
+        cmGeneratorExpression::Preprocess(*prop, ctx);
+    }
+  }
+
+  const cm::static_string_view exportedLinkModuleProperties[] = {
+    "LINK_LIBRARIES"_s,
+  };
+  for (auto const& propName : exportedLinkModuleProperties) {
+    auto const propNameStr = std::string(propName);
+    cmValue prop = gte->Target->GetComputedProperty(
+      propNameStr, *gte->Target->GetMakefile());
+    if (!prop) {
+      prop = gte->Target->GetProperty(propNameStr);
+    }
+    if (prop) {
+      auto const exportedPropName =
+        cmStrCat("IMPORTED_CXX_MODULES_", propName);
+      auto value = cmGeneratorExpression::Preprocess(*prop, ctx);
+      this->ResolveTargetsInGeneratorExpressions(
+        value, gte, cmExportFileGenerator::ReplaceFreeTargets);
+      std::vector<std::string> wrappedValues;
+      for (auto& item : cmList{ value }) {
+        wrappedValues.push_back(cmStrCat("$<COMPILE_ONLY:", item, '>'));
+      }
+      properties[exportedPropName] = cmJoin(wrappedValues, ";");
+    }
+  }
+
+  return true;
+}
+
 bool cmExportFileGenerator::PopulateExportProperties(
   cmGeneratorTarget const* gte, ImportPropertyMap& properties,
   std::string& errorMessage)

+ 3 - 0
Source/cmExportFileGenerator.h

@@ -175,6 +175,9 @@ protected:
   virtual void GenerateRequiredCMakeVersion(std::ostream& os,
                                             const char* versionString);
 
+  bool PopulateCxxModuleExportProperties(
+    cmGeneratorTarget const* gte, ImportPropertyMap& properties,
+    cmGeneratorExpression::PreprocessContext ctx, std::string& errorMessage);
   bool PopulateExportProperties(cmGeneratorTarget const* gte,
                                 ImportPropertyMap& properties,
                                 std::string& errorMessage);

+ 7 - 0
Source/cmExportInstallFileGenerator.cxx

@@ -126,6 +126,13 @@ bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os)
       gt, cmGeneratorExpression::InstallInterface, properties);
 
     std::string errorMessage;
+    if (!this->PopulateCxxModuleExportProperties(
+          gt, properties, cmGeneratorExpression::InstallInterface,
+          errorMessage)) {
+      cmSystemTools::Error(errorMessage);
+      return false;
+    }
+
     if (!this->PopulateExportProperties(gt, properties, errorMessage)) {
       cmSystemTools::Error(errorMessage);
       return false;

+ 1 - 0
Source/cmFileAPICodemodel.cxx

@@ -1678,6 +1678,7 @@ Json::Value Target::DumpSource(cmGeneratorTarget::SourceAndKind const& sk,
   }
 
   switch (sk.Kind) {
+    case cmGeneratorTarget::SourceKindCxxModuleSource:
     case cmGeneratorTarget::SourceKindObjectSource: {
       source["compileGroupIndex"] =
         this->AddSourceCompileGroup(sk.Source.Value, si);

+ 7 - 0
Source/cmFileSet.cxx

@@ -7,6 +7,7 @@
 #include <utility>
 #include <vector>
 
+#include <cmext/algorithm>
 #include <cmext/string_view>
 
 #include "cmsys/RegularExpression.hxx"
@@ -88,6 +89,12 @@ cmFileSet::cmFileSet(cmake& cmakeInstance, std::string name, std::string type,
 {
 }
 
+void cmFileSet::CopyEntries(cmFileSet const* fs)
+{
+  cm::append(this->DirectoryEntries, fs->DirectoryEntries);
+  cm::append(this->FileEntries, fs->FileEntries);
+}
+
 void cmFileSet::ClearDirectoryEntries()
 {
   this->DirectoryEntries.clear();

+ 2 - 0
Source/cmFileSet.h

@@ -41,6 +41,8 @@ public:
   const std::string& GetType() const { return this->Type; }
   cmFileSetVisibility GetVisibility() const { return this->Visibility; }
 
+  void CopyEntries(cmFileSet const* fs);
+
   void ClearDirectoryEntries();
   void AddDirectoryEntry(BT<std::string> directories);
   const std::vector<BT<std::string>>& GetDirectoryEntries() const

+ 133 - 17
Source/cmGeneratorTarget.cxx

@@ -27,7 +27,9 @@
 
 #include "cmAlgorithms.h"
 #include "cmComputeLinkInformation.h"
+#include "cmCryptoHash.h"
 #include "cmCustomCommandGenerator.h"
+#include "cmCxxModuleUsageEffects.h"
 #include "cmEvaluatedTargetProperty.h"
 #include "cmExperimental.h"
 #include "cmFileSet.h"
@@ -52,6 +54,7 @@
 #include "cmStandardLevelResolver.h"
 #include "cmState.h"
 #include "cmStringAlgorithms.h"
+#include "cmSyntheticTargetCache.h"
 #include "cmSystemTools.h"
 #include "cmTarget.h"
 #include "cmTargetLinkLibraryType.h"
@@ -1071,6 +1074,12 @@ void cmGeneratorTarget::GetHeaderSources(
   IMPLEMENT_VISIT(SourceKindHeader);
 }
 
+void cmGeneratorTarget::GetCxxModuleSources(
+  std::vector<cmSourceFile const*>& data, const std::string& config) const
+{
+  IMPLEMENT_VISIT(SourceKindCxxModuleSource);
+}
+
 void cmGeneratorTarget::GetExtraSources(std::vector<cmSourceFile const*>& data,
                                         const std::string& config) const
 {
@@ -1953,8 +1962,12 @@ void cmGeneratorTarget::ComputeKindedSources(KindedSources& files,
     // Compute the kind (classification) of this source file.
     SourceKind kind;
     std::string ext = cmSystemTools::LowerCase(sf->GetExtension());
+    cmFileSet const* fs = this->GetFileSetForSource(config, sf);
     if (sf->GetCustomCommand()) {
       kind = SourceKindCustomCommand;
+    } else if (!this->Target->IsNormal() && !this->Target->IsImported() &&
+               fs && (fs->GetType() == "CXX_MODULES"_s)) {
+      kind = SourceKindCxxModuleSource;
     } else if (this->Target->GetType() == cmStateEnums::UTILITY ||
                this->Target->GetType() == cmStateEnums::INTERFACE_LIBRARY
                // XXX(clang-tidy): https://bugs.llvm.org/show_bug.cgi?id=44165
@@ -8220,6 +8233,96 @@ void ComputeLinkImplTransitive(cmGeneratorTarget const* self,
 }
 }
 
+bool cmGeneratorTarget::DiscoverSyntheticTargets(cmSyntheticTargetCache& cache,
+                                                 std::string const& config)
+{
+  cmOptionalLinkImplementation impl;
+  this->ComputeLinkImplementationLibraries(config, impl, this,
+                                           LinkInterfaceFor::Link);
+
+  cmCxxModuleUsageEffects usage(this);
+
+  auto& SyntheticDeps = this->Configs[config].SyntheticDeps;
+
+  for (auto const& entry : impl.Libraries) {
+    auto const* gt = entry.Target;
+    if (!gt || !gt->IsImported()) {
+      continue;
+    }
+
+    if (gt->HaveCxx20ModuleSources()) {
+      auto hasher = cmCryptoHash::New("SHA3_512");
+      constexpr size_t HASH_TRUNCATION = 12;
+      auto dirhash = hasher->HashString(
+        gt->GetLocalGenerator()->GetCurrentBinaryDirectory());
+      std::string safeName = gt->GetName();
+      cmSystemTools::ReplaceString(safeName, ":", "_");
+      auto targetIdent =
+        hasher->HashString(cmStrCat("@d_", dirhash, "@u_", usage.GetHash()));
+      std::string targetName =
+        cmStrCat(safeName, "@synth_", targetIdent.substr(0, HASH_TRUNCATION));
+
+      // Check the cache to see if this instance of the imported target has
+      // already been created.
+      auto cached = cache.CxxModuleTargets.find(targetName);
+      cmGeneratorTarget const* synthDep = nullptr;
+      if (cached == cache.CxxModuleTargets.end()) {
+        auto const* model = gt->Target;
+        auto* mf = gt->Makefile;
+        auto* lg = gt->GetLocalGenerator();
+        auto* tgt = mf->AddSynthesizedTarget(cmStateEnums::INTERFACE_LIBRARY,
+                                             targetName);
+
+        // Copy relevant information from the existing IMPORTED target.
+
+        // Copy policies to the target.
+        tgt->CopyPolicyStatuses(model);
+
+        // Copy file sets.
+        {
+          auto fsNames = model->GetAllFileSetNames();
+          for (auto const& fsName : fsNames) {
+            auto const* fs = model->GetFileSet(fsName);
+            if (!fs) {
+              mf->IssueMessage(MessageType::INTERNAL_ERROR,
+                               cmStrCat("Failed to find file set named '",
+                                        fsName, "' on target '",
+                                        tgt->GetName(), '\''));
+              continue;
+            }
+            auto* newFs = tgt
+                            ->GetOrCreateFileSet(fs->GetName(), fs->GetType(),
+                                                 fs->GetVisibility())
+                            .first;
+            newFs->CopyEntries(fs);
+          }
+        }
+
+        // Copy imported C++ module properties.
+        tgt->CopyImportedCxxModulesEntries(model);
+
+        // Copy other properties which may affect the C++ module BMI
+        // generation.
+        tgt->CopyImportedCxxModulesProperties(model);
+
+        // Apply usage requirements to the target.
+        usage.ApplyToTarget(tgt);
+
+        // Create the generator target and attach it to the local generator.
+        auto gtp = cm::make_unique<cmGeneratorTarget>(tgt, lg);
+        synthDep = gtp.get();
+        lg->AddGeneratorTarget(std::move(gtp));
+      } else {
+        synthDep = cached->second;
+      }
+
+      SyntheticDeps[gt].push_back(synthDep);
+    }
+  }
+
+  return true;
+}
+
 void cmGeneratorTarget::ComputeLinkImplementationLibraries(
   const std::string& config, cmOptionalLinkImplementation& impl,
   cmGeneratorTarget const* head, LinkInterfaceFor implFor) const
@@ -8227,6 +8330,7 @@ void cmGeneratorTarget::ComputeLinkImplementationLibraries(
   cmLocalGenerator const* lg = this->LocalGenerator;
   cmMakefile const* mf = lg->GetMakefile();
   cmBTStringRange entryRange = this->Target->GetLinkImplementationEntries();
+  auto const& synthTargetsForConfig = this->Configs[config].SyntheticDeps;
   // Collect libraries directly linked in this configuration.
   for (auto const& entry : entryRange) {
     // Keep this logic in sync with ExpandLinkItems.
@@ -8316,7 +8420,15 @@ void cmGeneratorTarget::ComputeLinkImplementationLibraries(
       // The entry is meant for this configuration.
       cmLinkItem item =
         this->ResolveLinkItem(BT<std::string>(name, entry.Backtrace), lg);
-      if (!item.Target) {
+      if (item.Target) {
+        auto depsForTarget = synthTargetsForConfig.find(item.Target);
+        if (depsForTarget != synthTargetsForConfig.end()) {
+          for (auto const* depForTarget : depsForTarget->second) {
+            cmLinkItem synthItem(depForTarget, item.Cross, item.Backtrace);
+            impl.Libraries.emplace_back(std::move(synthItem), false);
+          }
+        }
+      } else {
         // Report explicitly linked object files separately.
         std::string const& maybeObj = item.AsStr();
         if (cmSystemTools::FileIsFullPath(maybeObj)) {
@@ -8930,24 +9042,28 @@ bool cmGeneratorTarget::HaveFortranSources(std::string const& config) const
                      });
 }
 
-bool cmGeneratorTarget::HaveCxx20ModuleSources() const
+bool cmGeneratorTarget::HaveCxx20ModuleSources(std::string* errorMessage) const
 {
   auto const& fs_names = this->Target->GetAllFileSetNames();
-  return std::any_of(fs_names.begin(), fs_names.end(),
-                     [this](std::string const& name) -> bool {
-                       auto const* file_set = this->Target->GetFileSet(name);
-                       if (!file_set) {
-                         this->Makefile->IssueMessage(
-                           MessageType::INTERNAL_ERROR,
-                           cmStrCat("Target \"", this->Target->GetName(),
-                                    "\" is tracked to have file set \"", name,
-                                    "\", but it was not found."));
-                         return false;
-                       }
-
-                       auto const& fs_type = file_set->GetType();
-                       return fs_type == "CXX_MODULES"_s;
-                     });
+  return std::any_of(
+    fs_names.begin(), fs_names.end(),
+    [this, errorMessage](std::string const& name) -> bool {
+      auto const* file_set = this->Target->GetFileSet(name);
+      if (!file_set) {
+        auto message = cmStrCat("Target \"", this->Target->GetName(),
+                                "\" is tracked to have file set \"", name,
+                                "\", but it was not found.");
+        if (errorMessage) {
+          *errorMessage = std::move(message);
+        } else {
+          this->Makefile->IssueMessage(MessageType::INTERNAL_ERROR, message);
+        }
+        return false;
+      }
+
+      auto const& fs_type = file_set->GetType();
+      return fs_type == "CXX_MODULES"_s;
+    });
 }
 
 cmGeneratorTarget::Cxx20SupportLevel cmGeneratorTarget::HaveCxxModuleSupport(

+ 13 - 1
Source/cmGeneratorTarget.h

@@ -31,6 +31,7 @@ class cmGlobalGenerator;
 class cmLocalGenerator;
 class cmMakefile;
 class cmSourceFile;
+struct cmSyntheticTargetCache;
 class cmTarget;
 
 struct cmGeneratorExpressionContext;
@@ -116,6 +117,7 @@ public:
     SourceKindCertificate,
     SourceKindCustomCommand,
     SourceKindExternalObject,
+    SourceKindCxxModuleSource,
     SourceKindExtra,
     SourceKindHeader,
     SourceKindIDL,
@@ -186,6 +188,8 @@ public:
                           const std::string& config) const;
   void GetHeaderSources(std::vector<cmSourceFile const*>&,
                         const std::string& config) const;
+  void GetCxxModuleSources(std::vector<cmSourceFile const*>&,
+                           const std::string& config) const;
   void GetExtraSources(std::vector<cmSourceFile const*>&,
                        const std::string& config) const;
   void GetCustomCommands(std::vector<cmSourceFile const*>&,
@@ -929,6 +933,9 @@ public:
 
   std::string GetImportedXcFrameworkPath(const std::string& config) const;
 
+  bool DiscoverSyntheticTargets(cmSyntheticTargetCache& cache,
+                                std::string const& config);
+
 private:
   void AddSourceCommon(const std::string& src, bool before = false);
 
@@ -1264,8 +1271,11 @@ public:
    *
    * This will inspect the target itself to see if C++20 module
    * support is expected to work based on its sources.
+   *
+   * If `errorMessage` is given a non-`nullptr`, any error message will be
+   * stored in it, otherwise the error will be reported directly.
    */
-  bool HaveCxx20ModuleSources() const;
+  bool HaveCxx20ModuleSources(std::string* errorMessage = nullptr) const;
 
   enum class Cxx20SupportLevel
   {
@@ -1301,6 +1311,8 @@ private:
   {
     bool BuiltFileSetCache = false;
     std::map<std::string, cmFileSet const*> FileSetCache;
+    std::map<cmGeneratorTarget const*, std::vector<cmGeneratorTarget const*>>
+      SyntheticDeps;
   };
   mutable std::map<std::string, InfoByConfig> Configs;
 };

+ 40 - 0
Source/cmGlobalGenerator.cxx

@@ -55,6 +55,7 @@
 #include "cmStateDirectory.h"
 #include "cmStateTypes.h"
 #include "cmStringAlgorithms.h"
+#include "cmSyntheticTargetCache.h"
 #include "cmSystemTools.h"
 #include "cmValue.h"
 #include "cmVersion.h"
@@ -1560,6 +1561,17 @@ bool cmGlobalGenerator::Compute()
   }
 #endif
 
+  // Iterate through all targets and set up C++20 module targets.
+  // Create target templates for each imported target with C++20 modules.
+  // INTERFACE library with BMI-generating rules and a collation step?
+  // Maybe INTERFACE libraries with modules files should just do BMI-only?
+  // Make `add_dependencies(imported_target
+  // $<$<TARGET_NAME_IF_EXISTS:uses_imported>:synth1>
+  // $<$<TARGET_NAME_IF_EXISTS:other_uses_imported>:synth2>)`
+  if (!this->DiscoverSyntheticTargets()) {
+    return false;
+  }
+
   // Add generator specific helper commands
   for (const auto& localGen : this->LocalGenerators) {
     localGen->AddHelperCommands();
@@ -1784,6 +1796,34 @@ void cmGlobalGenerator::ComputeTargetOrder(cmGeneratorTarget const* gt,
   entry->second = index++;
 }
 
+bool cmGlobalGenerator::DiscoverSyntheticTargets()
+{
+  cmSyntheticTargetCache cache;
+
+  for (auto const& gen : this->LocalGenerators) {
+    // Because DiscoverSyntheticTargets() adds generator targets, we need to
+    // cache the existing list of generator targets before starting.
+    std::vector<cmGeneratorTarget*> genTargets;
+    genTargets.reserve(gen->GetGeneratorTargets().size());
+    for (auto const& tgt : gen->GetGeneratorTargets()) {
+      genTargets.push_back(tgt.get());
+    }
+
+    for (auto* tgt : genTargets) {
+      std::vector<std::string> const& configs =
+        tgt->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
+
+      for (auto const& config : configs) {
+        if (!tgt->DiscoverSyntheticTargets(cache, config)) {
+          return false;
+        }
+      }
+    }
+  }
+
+  return true;
+}
+
 bool cmGlobalGenerator::AddHeaderSetVerification()
 {
   for (auto const& gen : this->LocalGenerators) {

+ 2 - 0
Source/cmGlobalGenerator.h

@@ -662,6 +662,8 @@ protected:
 
   virtual bool CheckALLOW_DUPLICATE_CUSTOM_TARGETS() const;
 
+  bool DiscoverSyntheticTargets();
+
   bool AddHeaderSetVerification();
 
   bool AddAutomaticSources();

+ 9 - 3
Source/cmGlobalNinjaGenerator.cxx

@@ -2643,7 +2643,9 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
   for (cmScanDepInfo const& object : objects) {
     for (auto const& p : object.Provides) {
       std::string mod;
-      if (!p.CompiledModulePath.empty()) {
+      if (cmDyndepCollation::IsBmiOnly(export_info, object.PrimaryOutput)) {
+        mod = object.PrimaryOutput;
+      } else if (!p.CompiledModulePath.empty()) {
         // The scanner provided the path to the module file.
         mod = p.CompiledModulePath;
         if (!cmSystemTools::FileIsFullPath(mod)) {
@@ -2714,8 +2716,12 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
       build.Outputs[0] = this->ConvertToNinjaPath(object.PrimaryOutput);
       build.ImplicitOuts.clear();
       for (auto const& p : object.Provides) {
-        build.ImplicitOuts.push_back(
-          this->ConvertToNinjaPath(mod_files[p.LogicalName].BmiPath));
+        auto const implicitOut =
+          this->ConvertToNinjaPath(mod_files[p.LogicalName].BmiPath);
+        // Ignore the `provides` when the BMI is the output.
+        if (implicitOut != build.Outputs[0]) {
+          build.ImplicitOuts.emplace_back(implicitOut);
+        }
       }
       build.ImplicitDeps.clear();
       for (auto const& r : object.Requires) {

+ 76 - 0
Source/cmImportedCxxModuleInfo.cxx

@@ -0,0 +1,76 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmImportedCxxModuleInfo.h"
+
+#include <cstddef>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "cmCryptoHash.h"
+#include "cmList.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+
+bool ImportedCxxModuleLookup::Initialized() const
+{
+  return this->DoneInit;
+}
+
+void ImportedCxxModuleLookup::Initialize(std::string const& importedModules)
+{
+  for (auto const& entry : cmList{ importedModules }) {
+    auto nameSep = entry.find('=');
+    if (nameSep == std::string::npos) {
+      // Invalid entry; ignore.
+      continue;
+    }
+
+    auto name = entry.substr(0, nameSep);
+
+    auto sourceSep = entry.find(',', nameSep);
+    std::string source;
+    if (sourceSep == std::string::npos) {
+      source = entry.substr(nameSep + 1);
+    } else {
+      source = entry.substr(nameSep + 1, sourceSep - nameSep - 1);
+    }
+
+    std::vector<std::string> bmis;
+    if (sourceSep != std::string::npos) {
+      auto bmiPaths = entry.substr(sourceSep + 1);
+      bmis = cmSystemTools::SplitString(bmiPaths, ',');
+    }
+
+    this->ImportedInfo.emplace(source,
+                               ImportedCxxModuleInfo{ name, std::move(bmis) });
+  }
+
+  this->DoneInit = true;
+}
+
+std::string ImportedCxxModuleLookup::BmiNameForSource(std::string const& path)
+{
+  auto genit = this->GeneratorInfo.find(path);
+  if (genit != this->GeneratorInfo.end()) {
+    return genit->second.BmiName;
+  }
+
+  auto importit = this->ImportedInfo.find(path);
+  std::string bmiName;
+  auto hasher = cmCryptoHash::New("SHA3_512");
+  constexpr size_t HASH_TRUNCATION = 12;
+  if (importit != this->ImportedInfo.end()) {
+    auto safename = hasher->HashString(importit->second.Name);
+    bmiName = cmStrCat(safename.substr(0, HASH_TRUNCATION), ".bmi");
+  } else {
+    auto dirhash = hasher->HashString(path);
+    bmiName = cmStrCat(dirhash.substr(0, HASH_TRUNCATION), ".bmi");
+  }
+
+  this->GeneratorInfo.emplace(
+    path, ImportedCxxModuleGeneratorInfo{ &importit->second, bmiName });
+  return bmiName;
+}

+ 37 - 0
Source/cmImportedCxxModuleInfo.h

@@ -0,0 +1,37 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <map>
+#include <string>
+#include <vector>
+
+struct ImportedCxxModuleInfo
+{
+  std::string const Name;
+  std::vector<std::string> const AvailableBmis;
+};
+
+struct ImportedCxxModuleGeneratorInfo
+{
+  ImportedCxxModuleInfo const* ImportedInfo;
+  std::string const BmiName;
+};
+
+struct ImportedCxxModuleLookup
+{
+  ImportedCxxModuleLookup() = default;
+  ~ImportedCxxModuleLookup() = default;
+
+  bool Initialized() const;
+  void Initialize(std::string const& importedModules);
+
+  std::string BmiNameForSource(std::string const& path);
+
+private:
+  bool DoneInit = false;
+  std::map<std::string, ImportedCxxModuleInfo> ImportedInfo;
+  std::map<std::string, ImportedCxxModuleGeneratorInfo> GeneratorInfo;
+};

+ 85 - 12
Source/cmNinjaNormalTargetGenerator.cxx

@@ -62,12 +62,15 @@ cmNinjaNormalTargetGenerator::~cmNinjaNormalTargetGenerator() = default;
 
 void cmNinjaNormalTargetGenerator::Generate(const std::string& config)
 {
-  std::string lang = this->GeneratorTarget->GetLinkerLanguage(config);
-  if (this->TargetLinkLanguage(config).empty()) {
-    cmSystemTools::Error(
-      cmStrCat("CMake can not determine linker language for target: ",
-               this->GetGeneratorTarget()->GetName()));
-    return;
+  if (this->GetGeneratorTarget()->GetType() !=
+      cmStateEnums::INTERFACE_LIBRARY) {
+    std::string lang = this->GeneratorTarget->GetLinkerLanguage(config);
+    if (this->TargetLinkLanguage(config).empty()) {
+      cmSystemTools::Error(
+        cmStrCat("CMake can not determine linker language for target: ",
+                 this->GetGeneratorTarget()->GetName()));
+      return;
+    }
   }
 
   // Write the rules for each language.
@@ -87,6 +90,34 @@ void cmNinjaNormalTargetGenerator::Generate(const std::string& config)
 
   if (this->GetGeneratorTarget()->GetType() == cmStateEnums::OBJECT_LIBRARY) {
     this->WriteObjectLibStatement(config);
+  } else if (this->GetGeneratorTarget()->GetType() ==
+             cmStateEnums::INTERFACE_LIBRARY) {
+    bool haveCxxModuleSources = false;
+    if (this->GetGeneratorTarget()->HaveCxx20ModuleSources()) {
+      haveCxxModuleSources = true;
+    }
+
+    if (!haveCxxModuleSources) {
+      cmSystemTools::Error(cmStrCat(
+        "Ninja does not support INTERFACE libraries without C++ module "
+        "sources as a normal target: ",
+        this->GetGeneratorTarget()->GetName()));
+      return;
+    }
+
+    firstForConfig = true;
+    for (auto const& fileConfig : this->GetConfigNames()) {
+      if (!this->GetGlobalGenerator()
+             ->GetCrossConfigs(fileConfig)
+             .count(config)) {
+        continue;
+      }
+      if (haveCxxModuleSources) {
+        this->WriteCxxModuleLibraryStatement(config, fileConfig,
+                                             firstForConfig);
+      }
+      firstForConfig = false;
+    }
   } else {
     firstForConfig = true;
     for (auto const& fileConfig : this->GetConfigNames()) {
@@ -123,12 +154,26 @@ void cmNinjaNormalTargetGenerator::WriteLanguagesRules(
 #endif
 
   // Write rules for languages compiled in this target.
-  std::set<std::string> languages;
-  std::vector<cmSourceFile const*> sourceFiles;
-  this->GetGeneratorTarget()->GetObjectSources(sourceFiles, config);
-  if (this->HaveRequiredLanguages(sourceFiles, languages)) {
-    for (std::string const& language : languages) {
-      this->WriteLanguageRules(language, config);
+  {
+    std::set<std::string> languages;
+    std::vector<cmSourceFile const*> sourceFiles;
+    this->GetGeneratorTarget()->GetObjectSources(sourceFiles, config);
+    if (this->HaveRequiredLanguages(sourceFiles, languages)) {
+      for (std::string const& language : languages) {
+        this->WriteLanguageRules(language, config);
+      }
+    }
+  }
+
+  // Write rules for languages in BMI-only rules.
+  {
+    std::set<std::string> languages;
+    std::vector<cmSourceFile const*> sourceFiles;
+    this->GetGeneratorTarget()->GetCxxModuleSources(sourceFiles, config);
+    if (this->HaveRequiredLanguages(sourceFiles, languages)) {
+      for (std::string const& language : languages) {
+        this->WriteLanguageRules(language, config);
+      }
     }
   }
 }
@@ -1637,6 +1682,34 @@ void cmNinjaNormalTargetGenerator::WriteObjectLibStatement(
     this->GetTargetName(), this->GetGeneratorTarget(), config);
 }
 
+void cmNinjaNormalTargetGenerator::WriteCxxModuleLibraryStatement(
+  const std::string& config, const std::string& /*fileConfig*/,
+  bool firstForConfig)
+{
+  // TODO: How to use `fileConfig` properly?
+
+  // Write a phony output that depends on the scanning output.
+  {
+    cmNinjaBuild build("phony");
+    build.Comment =
+      cmStrCat("Imported C++ module library ", this->GetTargetName());
+    this->GetLocalGenerator()->AppendTargetOutputs(this->GetGeneratorTarget(),
+                                                   build.Outputs, config);
+    if (firstForConfig) {
+      this->GetLocalGenerator()->AppendTargetOutputs(
+        this->GetGeneratorTarget(),
+        this->GetGlobalGenerator()->GetByproductsForCleanTarget(config),
+        config);
+    }
+    build.ExplicitDeps.emplace_back(this->GetDyndepFilePath("CXX", config));
+    this->GetGlobalGenerator()->WriteBuild(this->GetCommonFileStream(), build);
+  }
+
+  // Add aliases for the target name.
+  this->GetGlobalGenerator()->AddTargetAlias(
+    this->GetTargetName(), this->GetGeneratorTarget(), config);
+}
+
 cmGeneratorTarget::Names cmNinjaNormalTargetGenerator::TargetNames(
   const std::string& config) const
 {

+ 3 - 0
Source/cmNinjaNormalTargetGenerator.h

@@ -49,6 +49,9 @@ private:
                                       const std::string& output);
 
   void WriteObjectLibStatement(const std::string& config);
+  void WriteCxxModuleLibraryStatement(const std::string& config,
+                                      const std::string& fileConfig,
+                                      bool firstForConfig);
 
   std::vector<std::string> ComputeLinkCmd(const std::string& config);
   std::vector<std::string> ComputeDeviceLinkCmd();

+ 214 - 8
Source/cmNinjaTargetGenerator.cxx

@@ -28,6 +28,7 @@
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
+#include "cmGlobalCommonGenerator.h"
 #include "cmGlobalNinjaGenerator.h"
 #include "cmList.h"
 #include "cmLocalGenerator.h"
@@ -59,8 +60,13 @@ std::unique_ptr<cmNinjaTargetGenerator> cmNinjaTargetGenerator::New(
     case cmStateEnums::OBJECT_LIBRARY:
       return cm::make_unique<cmNinjaNormalTargetGenerator>(target);
 
-    case cmStateEnums::UTILITY:
     case cmStateEnums::INTERFACE_LIBRARY:
+      if (target->HaveCxx20ModuleSources()) {
+        return cm::make_unique<cmNinjaNormalTargetGenerator>(target);
+      }
+      CM_FALLTHROUGH;
+
+    case cmStateEnums::UTILITY:
     case cmStateEnums::GLOBAL_TARGET:
       return cm::make_unique<cmNinjaUtilityTargetGenerator>(target);
 
@@ -167,7 +173,7 @@ std::string cmNinjaTargetGenerator::OrderDependsTargetForTarget(
 // Refactor it.
 std::string cmNinjaTargetGenerator::ComputeFlagsForObject(
   cmSourceFile const* source, const std::string& language,
-  const std::string& config)
+  const std::string& config, const std::string& objectFileName)
 {
   std::unordered_map<std::string, std::string> pchSources;
   std::vector<std::string> architectures =
@@ -247,6 +253,18 @@ std::string cmNinjaTargetGenerator::ComputeFlagsForObject(
                  "\nin a file set of type \"", fs->GetType(),
                  R"(" but the source is not classified as a "CXX" source.)"));
     }
+
+    if (!this->GeneratorTarget->Target->IsNormal()) {
+      auto flag = this->GetMakefile()->GetSafeDefinition(
+        "CMAKE_EXPERIMENTAL_CXX_MODULE_BMI_ONLY_FLAG");
+      cmRulePlaceholderExpander::RuleVariables compileObjectVars;
+      compileObjectVars.Object = objectFileName.c_str();
+      auto rulePlaceholderExpander =
+        this->GetLocalGenerator()->CreateRulePlaceholderExpander();
+      rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(),
+                                                   flag, compileObjectVars);
+      this->LocalGenerator->AppendCompileOptions(flags, flag);
+    }
   }
 
   return flags;
@@ -394,6 +412,31 @@ std::string cmNinjaTargetGenerator::GetObjectFilePath(
   return path;
 }
 
+std::string cmNinjaTargetGenerator::GetBmiFilePath(
+  cmSourceFile const* source, const std::string& config) const
+{
+  std::string path = this->LocalGenerator->GetHomeRelativeOutputPath();
+  if (!path.empty()) {
+    path += '/';
+  }
+
+  auto& importedConfigInfo = this->Configs.at(config).ImportedCxxModules;
+  if (!importedConfigInfo.Initialized()) {
+    std::string configUpper = cmSystemTools::UpperCase(config);
+    std::string propName = cmStrCat("IMPORTED_CXX_MODULES_", configUpper);
+    auto value = this->GeneratorTarget->GetSafeProperty(propName);
+    importedConfigInfo.Initialize(value);
+  }
+
+  std::string bmiName =
+    importedConfigInfo.BmiNameForSource(source->GetFullPath());
+
+  path += cmStrCat(
+    this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget),
+    this->GetGlobalGenerator()->ConfigDirectory(config), '/', bmiName);
+  return path;
+}
+
 std::string cmNinjaTargetGenerator::GetClangTidyReplacementsFilePath(
   std::string const& directory, cmSourceFile const& source,
   std::string const& config) const
@@ -1027,6 +1070,16 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements(
     }
   }
 
+  {
+    std::vector<cmSourceFile const*> bmiOnlySources;
+    this->GeneratorTarget->GetCxxModuleSources(bmiOnlySources, config);
+
+    for (cmSourceFile const* sf : bmiOnlySources) {
+      this->WriteCxxModuleBmiBuildStatement(sf, config, fileConfig,
+                                            firstForConfig);
+    }
+  }
+
   for (auto const& langScanningFiles : this->Configs[config].ScanningInfo) {
     std::string const& language = langScanningFiles.first;
     std::vector<ScanningFiles> const& scanningFiles = langScanningFiles.second;
@@ -1149,22 +1202,22 @@ cmNinjaBuild GetScanBuildStatement(const std::string& ruleName,
   scanBuild.Variables["OBJ_FILE"] = objectFileName;
 
   // Tell dependency scanner where to store dyndep intermediate results.
-  std::string const& ddiFile = cmStrCat(objectFileName, ".ddi");
-  scanBuild.Variables["DYNDEP_INTERMEDIATE_FILE"] = ddiFile;
+  std::string ddiFileName = cmStrCat(objectFileName, ".ddi");
+  scanBuild.Variables["DYNDEP_INTERMEDIATE_FILE"] = ddiFileName;
 
   // Outputs of the scan/preprocessor build statement.
   if (compilePP) {
     scanBuild.Outputs.push_back(ppFileName);
-    scanBuild.ImplicitOuts.push_back(ddiFile);
+    scanBuild.ImplicitOuts.push_back(ddiFileName);
   } else {
-    scanBuild.Outputs.push_back(ddiFile);
+    scanBuild.Outputs.push_back(ddiFileName);
     scanBuild.Variables["PREPROCESSED_OUTPUT_FILE"] = ppFileName;
     if (!compilationPreprocesses) {
       // Compilation does not preprocess and we are not compiling an
       // already-preprocessed source.  Make compilation depend on the scan
       // results to honor implicit dependencies discovered during scanning
       // (such as Fortran INCLUDE directives).
-      objBuild.ImplicitDeps.emplace_back(ddiFile);
+      objBuild.ImplicitDeps.emplace_back(ddiFileName);
     }
   }
 
@@ -1214,7 +1267,8 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
   cmNinjaBuild objBuild(this->LanguageCompilerRule(
     language, config, needDyndep ? WithScanning::Yes : WithScanning::No));
   cmNinjaVars& vars = objBuild.Variables;
-  vars["FLAGS"] = this->ComputeFlagsForObject(source, language, config);
+  vars["FLAGS"] =
+    this->ComputeFlagsForObject(source, language, config, objectFileName);
   vars["DEFINES"] = this->ComputeDefines(source, language, config);
   vars["INCLUDES"] = this->ComputeIncludes(source, language, config);
 
@@ -1545,6 +1599,155 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
   }
 }
 
+void cmNinjaTargetGenerator::WriteCxxModuleBmiBuildStatement(
+  cmSourceFile const* source, const std::string& config,
+  const std::string& fileConfig, bool firstForConfig)
+{
+  std::string const language = source->GetLanguage();
+  if (language != "CXX"_s) {
+    this->GetMakefile()->IssueMessage(
+      MessageType::FATAL_ERROR,
+      cmStrCat("Source file '", source->GetFullPath(), "' of target '",
+               this->GetTargetName(), "' is a '", language,
+               "' source but must be 'CXX' in order to have a BMI build "
+               "statement generated."));
+    return;
+  }
+
+  std::string const sourceFilePath = this->GetCompiledSourceNinjaPath(source);
+  std::string const bmiDir = this->ConvertToNinjaPath(
+    cmStrCat(this->GeneratorTarget->GetSupportDirectory(),
+             this->GetGlobalGenerator()->ConfigDirectory(config)));
+  std::string const bmiFileName =
+    this->ConvertToNinjaPath(this->GetBmiFilePath(source, config));
+  std::string const bmiFileDir = cmSystemTools::GetFilenamePath(bmiFileName);
+
+  int const commandLineLengthLimit = this->ForceResponseFile() ? -1 : 0;
+
+  cmNinjaBuild bmiBuild(
+    this->LanguageCompilerRule(language, config, WithScanning::Yes));
+  cmNinjaVars& vars = bmiBuild.Variables;
+  vars["FLAGS"] =
+    this->ComputeFlagsForObject(source, language, config, bmiFileName);
+  vars["DEFINES"] = this->ComputeDefines(source, language, config);
+  vars["INCLUDES"] = this->ComputeIncludes(source, language, config);
+
+  if (this->GetMakefile()->GetSafeDefinition(
+        cmStrCat("CMAKE_", language, "_DEPFILE_FORMAT")) != "msvc"_s) {
+    bool replaceExt(false);
+    if (!language.empty()) {
+      std::string repVar =
+        cmStrCat("CMAKE_", language, "_DEPFILE_EXTENSION_REPLACE");
+      replaceExt = this->Makefile->IsOn(repVar);
+    }
+    if (!replaceExt) {
+      // use original code
+      vars["DEP_FILE"] = this->GetLocalGenerator()->ConvertToOutputFormat(
+        cmStrCat(bmiFileName, ".d"), cmOutputConverter::SHELL);
+    } else {
+      // Replace the original source file extension with the
+      // depend file extension.
+      std::string dependFileName = cmStrCat(
+        cmSystemTools::GetFilenameWithoutLastExtension(bmiFileName), ".d");
+      vars["DEP_FILE"] = this->GetLocalGenerator()->ConvertToOutputFormat(
+        cmStrCat(bmiFileDir, '/', dependFileName), cmOutputConverter::SHELL);
+    }
+  }
+
+  std::string d =
+    this->GeneratorTarget->GetClangTidyExportFixesDirectory(language);
+  if (!d.empty()) {
+    this->GlobalCommonGenerator->AddClangTidyExportFixesDir(d);
+    std::string fixesFile =
+      this->GetClangTidyReplacementsFilePath(d, *source, config);
+    this->GlobalCommonGenerator->AddClangTidyExportFixesFile(fixesFile);
+    cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(fixesFile));
+    fixesFile = this->ConvertToNinjaPath(fixesFile);
+    vars["CLANG_TIDY_EXPORT_FIXES"] = fixesFile;
+  }
+
+  if (firstForConfig) {
+    this->ExportObjectCompileCommand(
+      language, sourceFilePath, bmiDir, bmiFileName, bmiFileDir, vars["FLAGS"],
+      vars["DEFINES"], vars["INCLUDES"], config);
+  }
+
+  bmiBuild.Outputs.push_back(bmiFileName);
+  bmiBuild.ExplicitDeps.push_back(sourceFilePath);
+
+  std::vector<std::string> depList;
+
+  std::vector<std::string> architectures =
+    this->GeneratorTarget->GetAppleArchs(config, language);
+  if (architectures.empty()) {
+    architectures.emplace_back();
+  }
+
+  bmiBuild.OrderOnlyDeps.push_back(this->OrderDependsTargetForTarget(config));
+
+  // For some cases we scan to dynamically discover dependencies.
+  std::string modmapFormat;
+  if (true) {
+    std::string const modmapFormatVar =
+      cmStrCat("CMAKE_EXPERIMENTAL_", language, "_MODULE_MAP_FORMAT");
+    modmapFormat = this->Makefile->GetSafeDefinition(modmapFormatVar);
+  }
+
+  {
+    bool const compilePPWithDefines = this->CompileWithDefines(language);
+
+    std::string scanRuleName = this->LanguageScanRule(language, config);
+    std::string ppFileName = cmStrCat(bmiFileName, ".ddi.i");
+
+    cmNinjaBuild ppBuild = GetScanBuildStatement(
+      scanRuleName, ppFileName, false, compilePPWithDefines, true, bmiBuild,
+      vars, bmiFileName, this->LocalGenerator);
+
+    ScanningFiles scanningFiles;
+
+    if (firstForConfig) {
+      scanningFiles.ScanningOutput = cmStrCat(bmiFileName, ".ddi");
+    }
+
+    this->addPoolNinjaVariable("JOB_POOL_COMPILE", this->GetGeneratorTarget(),
+                               ppBuild.Variables);
+
+    this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig),
+                                           ppBuild, commandLineLengthLimit);
+
+    std::string const dyndep = this->GetDyndepFilePath(language, config);
+    bmiBuild.OrderOnlyDeps.push_back(dyndep);
+    vars["dyndep"] = dyndep;
+
+    if (!modmapFormat.empty()) {
+      std::string ddModmapFile = cmStrCat(bmiFileName, ".modmap");
+      vars["DYNDEP_MODULE_MAP_FILE"] = ddModmapFile;
+      scanningFiles.ModuleMapFile = std::move(ddModmapFile);
+    }
+
+    if (!scanningFiles.IsEmpty()) {
+      this->Configs[config].ScanningInfo[language].emplace_back(scanningFiles);
+    }
+  }
+
+  this->EnsureParentDirectoryExists(bmiFileName);
+
+  vars["OBJECT_DIR"] = this->GetLocalGenerator()->ConvertToOutputFormat(
+    bmiDir, cmOutputConverter::SHELL);
+  vars["OBJECT_FILE_DIR"] = this->GetLocalGenerator()->ConvertToOutputFormat(
+    bmiFileDir, cmOutputConverter::SHELL);
+
+  this->addPoolNinjaVariable("JOB_POOL_COMPILE", this->GetGeneratorTarget(),
+                             vars);
+
+  this->SetMsvcTargetPdbVariable(vars, config);
+
+  bmiBuild.RspFile = cmStrCat(bmiFileName, ".rsp");
+
+  this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig),
+                                         bmiBuild, commandLineLengthLimit);
+}
+
 void cmNinjaTargetGenerator::WriteTargetDependInfo(std::string const& lang,
                                                    const std::string& config)
 {
@@ -1605,6 +1808,9 @@ void cmNinjaTargetGenerator::WriteTargetDependInfo(std::string const& lang,
   cb.ObjectFilePath = [this](cmSourceFile const* sf, std::string const& cnf) {
     return this->GetObjectFilePath(sf, cnf);
   };
+  cb.BmiFilePath = [this](cmSourceFile const* sf, std::string const& cnf) {
+    return this->GetBmiFilePath(sf, cnf);
+  };
 
 #if !defined(CMAKE_BOOTSTRAP)
   cmDyndepCollation::AddCollationInformation(tdi, this->GeneratorTarget,

+ 11 - 1
Source/cmNinjaTargetGenerator.h

@@ -15,6 +15,7 @@
 
 #include "cmCommonTargetGenerator.h"
 #include "cmGlobalNinjaGenerator.h"
+#include "cmImportedCxxModuleInfo.h"
 #include "cmNinjaTypes.h"
 #include "cmOSXBundleGenerator.h"
 
@@ -91,7 +92,8 @@ protected:
    */
   std::string ComputeFlagsForObject(cmSourceFile const* source,
                                     const std::string& language,
-                                    const std::string& config);
+                                    const std::string& config,
+                                    const std::string& objectFileName);
 
   void AddIncludeFlags(std::string& flags, std::string const& lang,
                        const std::string& config) override;
@@ -129,6 +131,8 @@ protected:
   /// @return the object file path for the given @a source.
   std::string GetObjectFilePath(cmSourceFile const* source,
                                 const std::string& config) const;
+  std::string GetBmiFilePath(cmSourceFile const* source,
+                             const std::string& config) const;
 
   /// @return the preprocessed source file path for the given @a source.
   std::string GetPreprocessedFilePath(cmSourceFile const* source,
@@ -163,6 +167,10 @@ protected:
   void WriteObjectBuildStatements(const std::string& config,
                                   const std::string& fileConfig,
                                   bool firstForConfig);
+  void WriteCxxModuleBmiBuildStatement(cmSourceFile const* source,
+                                       const std::string& config,
+                                       const std::string& fileConfig,
+                                       bool firstForConfig);
   void WriteObjectBuildStatement(cmSourceFile const* source,
                                  const std::string& config,
                                  const std::string& fileConfig,
@@ -239,6 +247,8 @@ private:
     cmNinjaDeps Objects;
     // Dyndep Support
     std::map<std::string, std::vector<ScanningFiles>> ScanningInfo;
+    // Imported C++ module info.
+    mutable ImportedCxxModuleLookup ImportedCxxModules;
     // Swift Support
     Json::Value SwiftOutputMap;
     std::vector<cmCustomCommand const*> CustomCommands;

+ 15 - 0
Source/cmSyntheticTargetCache.h

@@ -0,0 +1,15 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <map>
+#include <string>
+
+class cmGeneratorTarget;
+
+struct cmSyntheticTargetCache
+{
+  std::map<std::string, cmGeneratorTarget const*> CxxModuleTargets;
+};

+ 227 - 6
Source/cmTarget.cxx

@@ -246,9 +246,9 @@ struct UsageRequirementProperty
   {
   }
 
-  void CopyFromDirectory(cmBTStringRange directoryEntries)
+  void CopyFromEntries(cmBTStringRange entries)
   {
-    return cm::append(this->Entries, directoryEntries);
+    return cm::append(this->Entries, entries);
   }
 
   enum class Action
@@ -673,6 +673,11 @@ public:
   UsageRequirementProperty InterfaceLinkLibraries;
   UsageRequirementProperty InterfaceLinkLibrariesDirect;
   UsageRequirementProperty InterfaceLinkLibrariesDirectExclude;
+  UsageRequirementProperty ImportedCxxModulesIncludeDirectories;
+  UsageRequirementProperty ImportedCxxModulesCompileDefinitions;
+  UsageRequirementProperty ImportedCxxModulesCompileFeatures;
+  UsageRequirementProperty ImportedCxxModulesCompileOptions;
+  UsageRequirementProperty ImportedCxxModulesLinkLibraries;
 
   FileSetType HeadersFileSets;
   FileSetType CxxModulesFileSets;
@@ -723,6 +728,14 @@ cmTargetInternals::cmTargetInternals()
   , InterfaceLinkLibrariesDirect("INTERFACE_LINK_LIBRARIES_DIRECT"_s)
   , InterfaceLinkLibrariesDirectExclude(
       "INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE"_s)
+  , ImportedCxxModulesIncludeDirectories(
+      "IMPORTED_CXX_MODULES_INCLUDE_DIRECTORIES"_s)
+  , ImportedCxxModulesCompileDefinitions(
+      "IMPORTED_CXX_MODULES_COMPILE_DEFINITIONS"_s)
+  , ImportedCxxModulesCompileFeatures(
+      "IMPORTED_CXX_MODULES_COMPILE_FEATURES"_s)
+  , ImportedCxxModulesCompileOptions("IMPORTED_CXX_MODULES_COMPILE_OPTIONS"_s)
+  , ImportedCxxModulesLinkLibraries("IMPORTED_CXX_MODULES_LINK_LIBRARIES"_s)
   , HeadersFileSets("HEADERS"_s, "HEADER_DIRS"_s, "HEADER_SET"_s,
                     "HEADER_DIRS_"_s, "HEADER_SET_"_s, "Header"_s,
                     "The default header set"_s, "Header set"_s,
@@ -951,7 +964,7 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
   if (this->IsNormal()) {
     // Initialize the INCLUDE_DIRECTORIES property based on the current value
     // of the same directory property:
-    this->impl->IncludeDirectories.CopyFromDirectory(
+    this->impl->IncludeDirectories.CopyFromEntries(
       this->impl->Makefile->GetIncludeDirectoriesEntries());
 
     {
@@ -960,11 +973,11 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
                                                   sysInc.end());
     }
 
-    this->impl->CompileOptions.CopyFromDirectory(
+    this->impl->CompileOptions.CopyFromEntries(
       this->impl->Makefile->GetCompileOptionsEntries());
-    this->impl->LinkOptions.CopyFromDirectory(
+    this->impl->LinkOptions.CopyFromEntries(
       this->impl->Makefile->GetLinkOptionsEntries());
-    this->impl->LinkDirectories.CopyFromDirectory(
+    this->impl->LinkDirectories.CopyFromEntries(
       this->impl->Makefile->GetLinkDirectoriesEntries());
   }
 
@@ -1735,6 +1748,186 @@ cmBTStringRange cmTarget::GetLinkInterfaceDirectExcludeEntries() const
   return cmMakeRange(this->impl->InterfaceLinkLibrariesDirectExclude.Entries);
 }
 
+void cmTarget::CopyPolicyStatuses(cmTarget const* tgt)
+{
+  // Normal targets cannot be the target of a copy.
+  assert(!this->IsNormal());
+  // Imported targets cannot be the target of a copy.
+  assert(!this->IsImported());
+  // Only imported targets can be the source of a copy.
+  assert(tgt->IsImported());
+
+  this->impl->PolicyMap = tgt->impl->PolicyMap;
+}
+
+void cmTarget::CopyImportedCxxModulesEntries(cmTarget const* tgt)
+{
+  // Normal targets cannot be the target of a copy.
+  assert(!this->IsNormal());
+  // Imported targets cannot be the target of a copy.
+  assert(!this->IsImported());
+  // Only imported targets can be the source of a copy.
+  assert(tgt->IsImported());
+
+  this->impl->IncludeDirectories.Entries.clear();
+  this->impl->IncludeDirectories.CopyFromEntries(
+    cmMakeRange(tgt->impl->ImportedCxxModulesIncludeDirectories.Entries));
+  this->impl->CompileDefinitions.Entries.clear();
+  this->impl->CompileDefinitions.CopyFromEntries(
+    cmMakeRange(tgt->impl->ImportedCxxModulesCompileDefinitions.Entries));
+  this->impl->CompileFeatures.Entries.clear();
+  this->impl->CompileFeatures.CopyFromEntries(
+    cmMakeRange(tgt->impl->ImportedCxxModulesCompileFeatures.Entries));
+  this->impl->CompileOptions.Entries.clear();
+  this->impl->CompileOptions.CopyFromEntries(
+    cmMakeRange(tgt->impl->ImportedCxxModulesCompileOptions.Entries));
+  this->impl->LinkLibraries.Entries.clear();
+  this->impl->LinkLibraries.CopyFromEntries(
+    cmMakeRange(tgt->impl->LinkLibraries.Entries));
+
+  // Copy the C++ module fileset entries from `tgt`'s `INTERFACE` to this
+  // target's `PRIVATE`.
+  this->impl->CxxModulesFileSets.SelfEntries.Entries.clear();
+  this->impl->CxxModulesFileSets.SelfEntries.Entries =
+    tgt->impl->CxxModulesFileSets.InterfaceEntries.Entries;
+}
+
+void cmTarget::CopyImportedCxxModulesProperties(cmTarget const* tgt)
+{
+  // Normal targets cannot be the target of a copy.
+  assert(!this->IsNormal());
+  // Imported targets cannot be the target of a copy.
+  assert(!this->IsImported());
+  // Only imported targets can be the source of a copy.
+  assert(tgt->IsImported());
+
+  // The list of properties that are relevant here include:
+  // - compilation-specific properties for any language or platform
+  // - compilation-specific properties for C++
+  // - build graph-specific properties that affect compilation
+  // - IDE metadata properties
+  // - static analysis properties
+
+  static const std::string propertiesToCopy[] = {
+    // Compilation properties
+    "DEFINE_SYMBOL",
+    "DEPRECATION",
+    "NO_SYSTEM_FROM_IMPORTED",
+    "POSITION_INDEPENDENT_CODE",
+    "VISIBILITY_INLINES_HIDDEN",
+    // -- Platforms
+    // ---- Android
+    "ANDROID_API",
+    "ANDROID_API_MIN",
+    "ANDROID_ARCH",
+    "ANDROID_STL_TYPE",
+    // ---- macOS
+    "OSX_ARCHITECTURES",
+    // ---- Windows
+    "MSVC_DEBUG_INFORMATION_FORMAT",
+    "MSVC_RUNTIME_LIBRARY",
+    "VS_PLATFORM_TOOLSET",
+    // ---- OpenWatcom
+    "WATCOM_RUNTIME_LIBRARY",
+    // -- Language
+    // ---- C++
+    "CXX_COMPILER_LAUNCHER",
+    "CXX_STANDARD",
+    "CXX_STANDARD_REQUIRED",
+    "CXX_EXTENSIONS",
+    "CXX_VISIBILITY_PRESET",
+
+    // Static analysis
+    "CXX_CLANG_TIDY",
+    "CXX_CLANG_TIDY_EXPORT_FIXES_DIR",
+    "CXX_CPPLINT",
+    "CXX_CPPCHECK",
+    "CXX_INCLUDE_WHAT_YOU_USE",
+
+    // Build graph properties
+    "EXCLUDE_FROM_ALL",
+    "EXCLUDE_FROM_DEFAULT_BUILD",
+    "OPTIMIZE_DEPENDENCIES",
+    // -- Ninja
+    "JOB_POOL_COMPILE",
+    // -- Visual Studio
+    "VS_NO_COMPILE_BATCHING",
+    "VS_PROJECT_IMPORT",
+
+    // Metadata
+    "EchoString",
+    "EXPORT_COMPILE_COMMANDS",
+    "FOLDER",
+    "LABELS",
+    "PROJECT_LABEL",
+    "SYSTEM",
+  };
+
+  auto copyProperty = [this, tgt](std::string const& prop) -> cmValue {
+    cmValue value = tgt->GetProperty(prop);
+    // Always set the property; it may have been explicitly unset.
+    this->SetProperty(prop, value);
+    return value;
+  };
+
+  for (auto const& prop : propertiesToCopy) {
+    copyProperty(prop);
+  }
+
+  static const cm::static_string_view perConfigPropertiesToCopy[] = {
+    "EXCLUDE_FROM_DEFAULT_BUILD_"_s,
+    "IMPORTED_CXX_MODULES_"_s,
+    "MAP_IMPORTED_CONFIG_"_s,
+    "OSX_ARCHITECTURES_"_s,
+  };
+
+  std::vector<std::string> configNames =
+    this->impl->Makefile->GetGeneratorConfigs(cmMakefile::ExcludeEmptyConfig);
+  for (std::string const& configName : configNames) {
+    std::string configUpper = cmSystemTools::UpperCase(configName);
+    for (auto const& perConfigProp : perConfigPropertiesToCopy) {
+      copyProperty(cmStrCat(perConfigProp, configUpper));
+    }
+  }
+
+  if (this->GetGlobalGenerator()->IsXcode()) {
+    cmValue xcodeGenerateScheme = copyProperty("XCODE_GENERATE_SCHEME");
+
+    // TODO: Make sure these show up on the imported target in the first place
+    // XCODE_ATTRIBUTE_???
+
+    if (xcodeGenerateScheme.IsOn()) {
+#ifdef __APPLE__
+      static const std::string xcodeSchemePropertiesToCopy[] = {
+        // FIXME: Do all of these apply? Do they matter?
+        "XCODE_SCHEME_ADDRESS_SANITIZER",
+        "XCODE_SCHEME_ADDRESS_SANITIZER_USE_AFTER_RETURN",
+        "XCODE_SCHEME_DISABLE_MAIN_THREAD_CHECKER",
+        "XCODE_SCHEME_DYNAMIC_LIBRARY_LOADS",
+        "XCODE_SCHEME_DYNAMIC_LINKER_API_USAGE",
+        "XCODE_SCHEME_ENABLE_GPU_API_VALIDATION",
+        "XCODE_SCHEME_ENABLE_GPU_SHADER_VALIDATION",
+        "XCODE_SCHEME_GUARD_MALLOC",
+        "XCODE_SCHEME_LAUNCH_CONFIGURATION",
+        "XCODE_SCHEME_MAIN_THREAD_CHECKER_STOP",
+        "XCODE_SCHEME_MALLOC_GUARD_EDGES",
+        "XCODE_SCHEME_MALLOC_SCRIBBLE",
+        "XCODE_SCHEME_MALLOC_STACK",
+        "XCODE_SCHEME_THREAD_SANITIZER",
+        "XCODE_SCHEME_THREAD_SANITIZER_STOP",
+        "XCODE_SCHEME_UNDEFINED_BEHAVIOUR_SANITIZER",
+        "XCODE_SCHEME_UNDEFINED_BEHAVIOUR_SANITIZER_STOP",
+        "XCODE_SCHEME_ZOMBIE_OBJECTS",
+      };
+
+      for (auto const& xcodeProperty : xcodeSchemePropertiesToCopy) {
+        copyProperty(xcodeProperty);
+      }
+#endif
+    }
+  }
+}
+
 cmBTStringRange cmTarget::GetHeaderSetsEntries() const
 {
   return cmMakeRange(this->impl->HeadersFileSets.SelfEntries.Entries);
@@ -1777,6 +1970,11 @@ MAKE_PROP(IMPORTED);
 MAKE_PROP(IMPORTED_GLOBAL);
 MAKE_PROP(INCLUDE_DIRECTORIES);
 MAKE_PROP(LINK_OPTIONS);
+MAKE_PROP(IMPORTED_CXX_MODULES_INCLUDE_DIRECTORIES);
+MAKE_PROP(IMPORTED_CXX_MODULES_COMPILE_DEFINITIONS);
+MAKE_PROP(IMPORTED_CXX_MODULES_COMPILE_FEATURES);
+MAKE_PROP(IMPORTED_CXX_MODULES_COMPILE_OPTIONS);
+MAKE_PROP(IMPORTED_CXX_MODULES_LINK_LIBRARIES);
 MAKE_PROP(LINK_DIRECTORIES);
 MAKE_PROP(LINK_LIBRARIES);
 MAKE_PROP(MANUALLY_ADDED_DEPENDENCIES);
@@ -1846,6 +2044,11 @@ void cmTarget::SetProperty(const std::string& prop, cmValue value)
     &this->impl->InterfaceLinkLibraries,
     &this->impl->InterfaceLinkLibrariesDirect,
     &this->impl->InterfaceLinkLibrariesDirectExclude,
+    &this->impl->ImportedCxxModulesIncludeDirectories,
+    &this->impl->ImportedCxxModulesCompileDefinitions,
+    &this->impl->ImportedCxxModulesCompileFeatures,
+    &this->impl->ImportedCxxModulesCompileOptions,
+    &this->impl->ImportedCxxModulesLinkLibraries,
   };
 
   for (auto* usageRequirement : usageRequirements) {
@@ -2019,6 +2222,11 @@ void cmTarget::AppendProperty(const std::string& prop,
     &this->impl->InterfaceLinkLibraries,
     &this->impl->InterfaceLinkLibrariesDirect,
     &this->impl->InterfaceLinkLibrariesDirectExclude,
+    &this->impl->ImportedCxxModulesIncludeDirectories,
+    &this->impl->ImportedCxxModulesCompileDefinitions,
+    &this->impl->ImportedCxxModulesCompileFeatures,
+    &this->impl->ImportedCxxModulesCompileOptions,
+    &this->impl->ImportedCxxModulesLinkLibraries,
   };
 
   for (auto* usageRequirement : usageRequirements) {
@@ -2445,6 +2653,11 @@ cmValue cmTarget::GetProperty(const std::string& prop) const
     propINTERFACE_LINK_LIBRARIES,
     propINTERFACE_LINK_LIBRARIES_DIRECT,
     propINTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE,
+    propIMPORTED_CXX_MODULES_INCLUDE_DIRECTORIES,
+    propIMPORTED_CXX_MODULES_COMPILE_DEFINITIONS,
+    propIMPORTED_CXX_MODULES_COMPILE_FEATURES,
+    propIMPORTED_CXX_MODULES_COMPILE_OPTIONS,
+    propIMPORTED_CXX_MODULES_LINK_LIBRARIES,
   };
   if (specialProps.count(prop)) {
     if (prop == propC_STANDARD || prop == propCXX_STANDARD ||
@@ -2470,6 +2683,11 @@ cmValue cmTarget::GetProperty(const std::string& prop) const
       &this->impl->InterfaceLinkLibraries,
       &this->impl->InterfaceLinkLibrariesDirect,
       &this->impl->InterfaceLinkLibrariesDirectExclude,
+      &this->impl->ImportedCxxModulesIncludeDirectories,
+      &this->impl->ImportedCxxModulesCompileDefinitions,
+      &this->impl->ImportedCxxModulesCompileFeatures,
+      &this->impl->ImportedCxxModulesCompileOptions,
+      &this->impl->ImportedCxxModulesLinkLibraries,
     };
 
     for (auto const* usageRequirement : usageRequirements) {
@@ -2675,6 +2893,9 @@ bool cmTarget::CanCompileSources() const
   if (this->IsImported()) {
     return false;
   }
+  if (this->IsSynthetic()) {
+    return true;
+  }
   switch (this->GetType()) {
     case cmStateEnums::EXECUTABLE:
     case cmStateEnums::STATIC_LIBRARY:

+ 4 - 0
Source/cmTarget.h

@@ -291,6 +291,10 @@ public:
   cmBTStringRange GetLinkInterfaceDirectEntries() const;
   cmBTStringRange GetLinkInterfaceDirectExcludeEntries() const;
 
+  void CopyPolicyStatuses(cmTarget const* tgt);
+  void CopyImportedCxxModulesEntries(cmTarget const* tgt);
+  void CopyImportedCxxModulesProperties(cmTarget const* tgt);
+
   cmBTStringRange GetHeaderSetsEntries() const;
   cmBTStringRange GetCxxModuleSetsEntries() const;
 

+ 1 - 0
Source/cmVisualStudio10TargetGenerator.cxx

@@ -2517,6 +2517,7 @@ void cmVisualStudio10TargetGenerator::WriteAllSources(Elem& e0)
       case cmGeneratorTarget::SourceKindModuleDefinition:
         tool = "None";
         break;
+      case cmGeneratorTarget::SourceKindCxxModuleSource:
       case cmGeneratorTarget::SourceKindUnityBatched:
       case cmGeneratorTarget::SourceKindObjectSource: {
         const std::string& lang = si.Source->GetLanguage();

+ 1 - 1
Tests/RunCMake/CXXModules/CMakeLists.txt

@@ -1,6 +1,6 @@
 cmake_minimum_required(VERSION 3.23)
 project(${RunCMake_TEST} NONE)
 
-set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "a816ed09-43d1-40e5-bc8c-1a2824ee194e")
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "ac01f462-0f5f-432a-86aa-acef252918a6")
 
 include(${RunCMake_TEST}.cmake)

+ 28 - 0
Tests/RunCMake/CXXModules/RunCMakeTest.cmake

@@ -187,7 +187,20 @@ endif ()
 if ("export_bmi" IN_LIST CMake_TEST_MODULE_COMPILATION)
   run_cxx_module_test(export-interface-no-properties-build)
   run_cxx_module_test(export-interface-build)
+  run_cxx_module_test(export-usage-build)
   run_cxx_module_test(export-bmi-and-interface-build)
+
+  if ("collation" IN_LIST CMake_TEST_MODULE_COMPILATION AND
+      "bmionly" IN_LIST CMake_TEST_MODULE_COMPILATION)
+    set(test_suffix export-interface-build)
+    run_cxx_module_test(import-modules "import-modules-${test_suffix}" "-DCMAKE_PREFIX_PATH=${RunCMake_BINARY_DIR}/examples/${test_suffix}-build")
+
+    set(test_suffix export-interface-no-properties-build)
+    run_cxx_module_test(import-modules "import-modules-${test_suffix}" "-DCMAKE_PREFIX_PATH=${RunCMake_BINARY_DIR}/examples/${test_suffix}-build" -DNO_PROPERTIES=1)
+
+    set(test_suffix export-bmi-and-interface-build)
+    run_cxx_module_test(import-modules "import-modules-${test_suffix}" "-DCMAKE_PREFIX_PATH=${RunCMake_BINARY_DIR}/examples/${test_suffix}-build" -DWITH_BMIS=1)
+  endif ()
 endif ()
 
 # All of the following tests perform installation.
@@ -201,6 +214,21 @@ if ("install_bmi" IN_LIST CMake_TEST_MODULE_COMPILATION)
   if ("export_bmi" IN_LIST CMake_TEST_MODULE_COMPILATION)
     run_cxx_module_test(export-interface-no-properties-install)
     run_cxx_module_test(export-interface-install)
+    run_cxx_module_test(export-usage-install)
     run_cxx_module_test(export-bmi-and-interface-install)
+
+    if ("collation" IN_LIST CMake_TEST_MODULE_COMPILATION AND
+        "bmionly" IN_LIST CMake_TEST_MODULE_COMPILATION)
+      set(RunCMake_CXXModules_INSTALL 0)
+      set(test_suffix export-interface-install)
+      run_cxx_module_test(import-modules "import-modules-${test_suffix}" "-DCMAKE_PREFIX_PATH=${RunCMake_BINARY_DIR}/examples/${test_suffix}-install")
+
+      set(test_suffix export-interface-no-properties-install)
+      run_cxx_module_test(import-modules "import-modules-${test_suffix}" "-DCMAKE_PREFIX_PATH=${RunCMake_BINARY_DIR}/examples/${test_suffix}-install" -DNO_PROPERTIES=1)
+
+      set(test_suffix export-bmi-and-interface-install)
+      run_cxx_module_test(import-modules "import-modules-${test_suffix}" "-DCMAKE_PREFIX_PATH=${RunCMake_BINARY_DIR}/examples/${test_suffix}-install" -DWITH_BMIS=1)
+      set(RunCMake_CXXModules_INSTALL 1)
+    endif ()
   endif ()
 endif ()

+ 1 - 1
Tests/RunCMake/CXXModules/examples/cxx-modules-rules.cmake

@@ -1,4 +1,4 @@
-set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "a816ed09-43d1-40e5-bc8c-1a2824ee194e")
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "ac01f462-0f5f-432a-86aa-acef252918a6")
 
 if (NOT EXISTS "${CMake_TEST_MODULE_COMPILATION_RULES}")
   message(FATAL_ERROR

+ 1 - 1
Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build/test/CMakeLists.txt

@@ -1,7 +1,7 @@
 cmake_minimum_required(VERSION 3.24)
 project(cxx_modules_library NONE)
 
-set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "a816ed09-43d1-40e5-bc8c-1a2824ee194e")
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "ac01f462-0f5f-432a-86aa-acef252918a6")
 
 find_package(export_bmi_and_interfaces REQUIRED)
 

+ 1 - 1
Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install/test/CMakeLists.txt

@@ -1,7 +1,7 @@
 cmake_minimum_required(VERSION 3.24)
 project(cxx_modules_library NONE)
 
-set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "a816ed09-43d1-40e5-bc8c-1a2824ee194e")
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "ac01f462-0f5f-432a-86aa-acef252918a6")
 
 find_package(export_bmi_and_interfaces REQUIRED)
 

+ 1 - 1
Tests/RunCMake/CXXModules/examples/export-interface-build/test/CMakeLists.txt

@@ -1,7 +1,7 @@
 cmake_minimum_required(VERSION 3.24)
 project(cxx_modules_library NONE)
 
-set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "a816ed09-43d1-40e5-bc8c-1a2824ee194e")
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "ac01f462-0f5f-432a-86aa-acef252918a6")
 
 find_package(export_interfaces REQUIRED)
 

+ 1 - 1
Tests/RunCMake/CXXModules/examples/export-interface-install/test/CMakeLists.txt

@@ -1,7 +1,7 @@
 cmake_minimum_required(VERSION 3.24)
 project(cxx_modules_library NONE)
 
-set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "a816ed09-43d1-40e5-bc8c-1a2824ee194e")
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "ac01f462-0f5f-432a-86aa-acef252918a6")
 
 find_package(export_interfaces REQUIRED)
 

+ 1 - 1
Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build/test/CMakeLists.txt

@@ -1,7 +1,7 @@
 cmake_minimum_required(VERSION 3.24)
 project(cxx_modules_library NONE)
 
-set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "a816ed09-43d1-40e5-bc8c-1a2824ee194e")
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "ac01f462-0f5f-432a-86aa-acef252918a6")
 
 find_package(export_interfaces_no_properties REQUIRED)
 

+ 1 - 1
Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install/test/CMakeLists.txt

@@ -1,7 +1,7 @@
 cmake_minimum_required(VERSION 3.24)
 project(cxx_modules_library NONE)
 
-set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "a816ed09-43d1-40e5-bc8c-1a2824ee194e")
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "ac01f462-0f5f-432a-86aa-acef252918a6")
 
 find_package(export_interfaces_no_properties REQUIRED)
 

+ 4 - 0
Tests/RunCMake/CXXModules/examples/export-usage-build-stderr.txt

@@ -0,0 +1,4 @@
+CMake Warning \(dev\) at CMakeLists.txt:7 \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 110 - 0
Tests/RunCMake/CXXModules/examples/export-usage-build/CMakeLists.txt

@@ -0,0 +1,110 @@
+cmake_minimum_required(VERSION 3.24)
+project(cxx_modules_export_usage CXX)
+
+include("${CMAKE_SOURCE_DIR}/../cxx-modules-rules.cmake")
+
+add_library(export_usage STATIC)
+target_sources(export_usage
+  PRIVATE
+    forward.cxx
+  PRIVATE
+    FILE_SET modules_private TYPE CXX_MODULES
+      BASE_DIRS
+        "${CMAKE_CURRENT_SOURCE_DIR}"
+      FILES
+        private.cxx
+  PUBLIC
+    FILE_SET modules TYPE CXX_MODULES
+      BASE_DIRS
+        "${CMAKE_CURRENT_SOURCE_DIR}"
+      FILES
+        importable.cxx)
+target_compile_features(export_usage PUBLIC cxx_std_20)
+
+list(APPEND CMAKE_CXX_KNOWN_FEATURES
+  exported
+  buildiface
+  installiface
+  buildlocaliface)
+
+target_include_directories(export_usage
+  PRIVATE
+    "/usr/exported"
+    "$<BUILD_INTERFACE:/usr/buildiface>"
+    "$<INSTALL_INTERFACE:/usr/installiface>"
+    "$<BUILD_LOCAL_INTERFACE:/usr/buildlocaliface>")
+target_compile_definitions(export_usage
+  PRIVATE
+    "exported"
+    "$<BUILD_INTERFACE:buildiface>"
+    "$<INSTALL_INTERFACE:installiface>"
+    "$<BUILD_LOCAL_INTERFACE:buildlocaliface>")
+target_compile_features(export_usage
+  PRIVATE
+    "cxx_std_11"
+    "$<BUILD_INTERFACE:cxx_std_14>"
+    "$<INSTALL_INTERFACE:cxx_std_17>"
+    "$<BUILD_LOCAL_INTERFACE:cxx_std_20>")
+
+if (MSVC)
+  set(variable_flag "-constexpr:depth")
+else ()
+  set(variable_flag "-fconstexpr-depth=")
+endif ()
+
+target_compile_options(export_usage
+  PRIVATE
+    "${variable_flag}100"
+    "$<BUILD_INTERFACE:${variable_flag}200>"
+    "$<INSTALL_INTERFACE:${variable_flag}300>"
+    "$<BUILD_LOCAL_INTERFACE:${variable_flag}400>")
+
+add_library(export_used INTERFACE)
+add_library(export_build INTERFACE)
+add_library(export_install INTERFACE)
+add_library(export_never INTERFACE)
+
+target_link_libraries(export_usage
+  PRIVATE
+    "export_used"
+    "$<BUILD_INTERFACE:export_build>"
+    "$<INSTALL_INTERFACE:export_install>"
+    "$<BUILD_LOCAL_INTERFACE:export_never>")
+
+install(TARGETS export_usage
+  EXPORT CXXModules
+  FILE_SET modules DESTINATION "lib/cxx/miu")
+export(EXPORT CXXModules
+  NAMESPACE CXXModules::
+  FILE "${CMAKE_CURRENT_BINARY_DIR}/export_usage-targets.cmake")
+install(TARGETS export_used export_build export_install
+  EXPORT CXXModulesDeps)
+export(EXPORT CXXModulesDeps
+  NAMESPACE CXXModules::
+  FILE "${CMAKE_CURRENT_BINARY_DIR}/export_usage-dep-targets.cmake")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/export_usage-config.cmake"
+  "include(\"\${CMAKE_CURRENT_LIST_DIR}/export_usage-dep-targets.cmake\")
+include(\"\${CMAKE_CURRENT_LIST_DIR}/export_usage-targets.cmake\")
+set(\${CMAKE_FIND_PACKAGE_NAME}_FOUND 1)
+")
+
+set(generator
+  -G "${CMAKE_GENERATOR}")
+if (CMAKE_GENERATOR_TOOLSET)
+  list(APPEND generator
+    -T "${CMAKE_GENERATOR_TOOLSET}")
+endif ()
+if (CMAKE_GENERATOR_PLATFORM)
+  list(APPEND generator
+    -A "${CMAKE_GENERATOR_PLATFORM}")
+endif ()
+
+add_test(NAME export_usage_build
+  COMMAND
+    "${CMAKE_COMMAND}"
+    "-Dexpected_dir=${CMAKE_CURRENT_SOURCE_DIR}"
+    "-Dexport_interfaces_flag=${variable_flag}"
+    "-Dexport_usage_DIR=${CMAKE_CURRENT_BINARY_DIR}"
+    ${generator}
+    -S "${CMAKE_CURRENT_SOURCE_DIR}/test"
+    -B "${CMAKE_CURRENT_BINARY_DIR}/test")

+ 6 - 0
Tests/RunCMake/CXXModules/examples/export-usage-build/forward.cxx

@@ -0,0 +1,6 @@
+import priv;
+
+int forwarding()
+{
+  return from_private();
+}

+ 10 - 0
Tests/RunCMake/CXXModules/examples/export-usage-build/importable.cxx

@@ -0,0 +1,10 @@
+export module importable;
+
+extern "C++" {
+int forwarding();
+}
+
+export int from_import()
+{
+  return forwarding();
+}

+ 6 - 0
Tests/RunCMake/CXXModules/examples/export-usage-build/private.cxx

@@ -0,0 +1,6 @@
+export module priv;
+
+export int from_private()
+{
+  return 0;
+}

+ 69 - 0
Tests/RunCMake/CXXModules/examples/export-usage-build/test/CMakeLists.txt

@@ -0,0 +1,69 @@
+cmake_minimum_required(VERSION 3.24)
+project(cxx_modules_library NONE)
+
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "ac01f462-0f5f-432a-86aa-acef252918a6")
+
+find_package(export_usage REQUIRED)
+
+if (NOT TARGET CXXModules::export_usage)
+  message(FATAL_ERROR
+    "Missing imported target")
+endif ()
+
+if (NOT TARGET CXXModules::export_used)
+  message(FATAL_ERROR
+    "Missing imported target")
+endif ()
+
+if (NOT TARGET CXXModules::export_build)
+  message(FATAL_ERROR
+    "Missing imported target")
+endif ()
+
+if (NOT TARGET CXXModules::export_install)
+  message(FATAL_ERROR
+    "Missing imported target")
+endif ()
+
+if (TARGET CXXModules::export_never)
+  message(FATAL_ERROR
+    "Extra imported target")
+endif ()
+
+function (check_property expected property)
+  get_property(actual TARGET CXXModules::export_usage
+    PROPERTY "${property}")
+  if (NOT actual STREQUAL expected)
+    message(SEND_ERROR
+      "Mismatch for ${property}:\n  expected: ${expected}\n  actual: ${actual}")
+  endif ()
+endfunction ()
+
+check_property("/usr/exported;/usr/buildiface" "IMPORTED_CXX_MODULES_INCLUDE_DIRECTORIES")
+check_property("exported;buildiface" "IMPORTED_CXX_MODULES_COMPILE_DEFINITIONS")
+check_property("cxx_std_20;cxx_std_11;cxx_std_14" "IMPORTED_CXX_MODULES_COMPILE_FEATURES")
+check_property("${export_interfaces_flag}100;${export_interfaces_flag}200" "IMPORTED_CXX_MODULES_COMPILE_OPTIONS")
+check_property("$<COMPILE_ONLY:CXXModules::export_used>;$<COMPILE_ONLY:CXXModules::export_build>" "IMPORTED_CXX_MODULES_LINK_LIBRARIES")
+
+# Extract the export-dependent targets from the export file.
+file(STRINGS "${export_usage_DIR}/export_usage-targets.cmake" usage_dependent_targets
+  REGEX "foreach._target ")
+# Rudimentary argument splitting.
+string(REPLACE " " ";" usage_dependent_targets "${usage_dependent_targets}")
+# Keep only "target" names.
+list(FILTER usage_dependent_targets INCLUDE REGEX "CXXModules::")
+# Strip quotes.
+string(REPLACE "\"" "" usage_dependent_targets "${usage_dependent_targets}")
+
+if (NOT "CXXModules::export_used" IN_LIST usage_dependent_targets)
+  message(SEND_ERROR
+    "The main export does not require the 'CXXModules::export_used' target")
+endif ()
+if (NOT "CXXModules::export_build" IN_LIST usage_dependent_targets)
+  message(SEND_ERROR
+    "The main export does not require the 'CXXModules::export_build' target")
+endif ()
+if ("CXXModules::export_install" IN_LIST usage_dependent_targets)
+  message(SEND_ERROR
+    "The main export requires the 'CXXModules::export_install' target")
+endif ()

+ 4 - 0
Tests/RunCMake/CXXModules/examples/export-usage-install-stderr.txt

@@ -0,0 +1,4 @@
+CMake Warning \(dev\) at CMakeLists.txt:7 \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 114 - 0
Tests/RunCMake/CXXModules/examples/export-usage-install/CMakeLists.txt

@@ -0,0 +1,114 @@
+cmake_minimum_required(VERSION 3.24)
+project(cxx_modules_export_usage CXX)
+
+include("${CMAKE_SOURCE_DIR}/../cxx-modules-rules.cmake")
+
+add_library(export_usage STATIC)
+target_sources(export_usage
+  PRIVATE
+    forward.cxx
+  PRIVATE
+    FILE_SET modules_private TYPE CXX_MODULES
+      BASE_DIRS
+        "${CMAKE_CURRENT_SOURCE_DIR}"
+      FILES
+        private.cxx
+  PUBLIC
+    FILE_SET modules TYPE CXX_MODULES
+      BASE_DIRS
+        "${CMAKE_CURRENT_SOURCE_DIR}"
+      FILES
+        importable.cxx)
+target_compile_features(export_usage PUBLIC cxx_std_20)
+
+list(APPEND CMAKE_CXX_KNOWN_FEATURES
+  exported
+  buildiface
+  installiface
+  buildlocaliface)
+
+target_include_directories(export_usage
+  PRIVATE
+    "/usr/exported"
+    "$<BUILD_INTERFACE:/usr/buildiface>"
+    "$<INSTALL_INTERFACE:/usr/installiface>"
+    "$<BUILD_LOCAL_INTERFACE:/usr/buildlocaliface>")
+target_compile_definitions(export_usage
+  PRIVATE
+    "exported"
+    "$<BUILD_INTERFACE:buildiface>"
+    "$<INSTALL_INTERFACE:installiface>"
+    "$<BUILD_LOCAL_INTERFACE:buildlocaliface>")
+target_compile_features(export_usage
+  PRIVATE
+    "cxx_std_11"
+    "$<BUILD_INTERFACE:cxx_std_14>"
+    "$<INSTALL_INTERFACE:cxx_std_17>"
+    "$<BUILD_LOCAL_INTERFACE:cxx_std_20>")
+
+if (MSVC)
+  set(variable_flag "-constexpr:depth")
+else ()
+  set(variable_flag "-fconstexpr-depth=")
+endif ()
+
+target_compile_options(export_usage
+  PRIVATE
+    "${variable_flag}100"
+    "$<BUILD_INTERFACE:${variable_flag}200>"
+    "$<INSTALL_INTERFACE:${variable_flag}300>"
+    "$<BUILD_LOCAL_INTERFACE:${variable_flag}400>")
+
+add_library(export_used INTERFACE)
+add_library(export_build INTERFACE)
+add_library(export_install INTERFACE)
+add_library(export_never INTERFACE)
+
+target_link_libraries(export_usage
+  PRIVATE
+    "export_used"
+    "$<BUILD_INTERFACE:export_build>"
+    "$<INSTALL_INTERFACE:export_install>"
+    "$<BUILD_LOCAL_INTERFACE:export_never>")
+
+install(TARGETS export_usage
+  EXPORT CXXModules
+  FILE_SET modules DESTINATION "lib/cxx/miu")
+install(EXPORT CXXModules
+  NAMESPACE CXXModules::
+  DESTINATION "lib/cmake/export_usage"
+  FILE "export_usage-targets.cmake")
+install(TARGETS export_used export_build export_install
+  EXPORT CXXModulesDeps)
+install(EXPORT CXXModulesDeps
+  NAMESPACE CXXModules::
+  DESTINATION "lib/cmake/export_usage"
+  FILE "export_usage-dep-targets.cmake")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/export_usage-config.cmake"
+  "include(\"\${CMAKE_CURRENT_LIST_DIR}/export_usage-dep-targets.cmake\")
+include(\"\${CMAKE_CURRENT_LIST_DIR}/export_usage-targets.cmake\")
+set(\${CMAKE_FIND_PACKAGE_NAME}_FOUND 1)
+")
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/export_usage-config.cmake"
+  DESTINATION "lib/cmake/export_usage")
+
+set(generator
+  -G "${CMAKE_GENERATOR}")
+if (CMAKE_GENERATOR_TOOLSET)
+  list(APPEND generator
+    -T "${CMAKE_GENERATOR_TOOLSET}")
+endif ()
+if (CMAKE_GENERATOR_PLATFORM)
+  list(APPEND generator
+    -A "${CMAKE_GENERATOR_PLATFORM}")
+endif ()
+
+add_test(NAME export_usage_build
+  COMMAND
+    "${CMAKE_COMMAND}"
+    "-Dexpected_dir=${CMAKE_INSTALL_PREFIX}/lib/cxx/miu"
+    "-Dexport_interfaces_flag=${variable_flag}"
+    "-Dexport_usage_DIR=${CMAKE_INSTALL_PREFIX}/lib/cmake/export_usage"
+    ${generator}
+    -S "${CMAKE_CURRENT_SOURCE_DIR}/test"
+    -B "${CMAKE_CURRENT_BINARY_DIR}/test")

+ 6 - 0
Tests/RunCMake/CXXModules/examples/export-usage-install/forward.cxx

@@ -0,0 +1,6 @@
+import priv;
+
+int forwarding()
+{
+  return from_private();
+}

+ 10 - 0
Tests/RunCMake/CXXModules/examples/export-usage-install/importable.cxx

@@ -0,0 +1,10 @@
+export module importable;
+
+extern "C++" {
+int forwarding();
+}
+
+export int from_import()
+{
+  return forwarding();
+}

+ 6 - 0
Tests/RunCMake/CXXModules/examples/export-usage-install/private.cxx

@@ -0,0 +1,6 @@
+export module priv;
+
+export int from_private()
+{
+  return 0;
+}

+ 69 - 0
Tests/RunCMake/CXXModules/examples/export-usage-install/test/CMakeLists.txt

@@ -0,0 +1,69 @@
+cmake_minimum_required(VERSION 3.24)
+project(cxx_modules_library NONE)
+
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "ac01f462-0f5f-432a-86aa-acef252918a6")
+
+find_package(export_usage REQUIRED)
+
+if (NOT TARGET CXXModules::export_usage)
+  message(FATAL_ERROR
+    "Missing imported target")
+endif ()
+
+if (NOT TARGET CXXModules::export_used)
+  message(FATAL_ERROR
+    "Missing imported target")
+endif ()
+
+if (NOT TARGET CXXModules::export_build)
+  message(FATAL_ERROR
+    "Missing imported target")
+endif ()
+
+if (NOT TARGET CXXModules::export_install)
+  message(FATAL_ERROR
+    "Missing imported target")
+endif ()
+
+if (TARGET CXXModules::export_never)
+  message(FATAL_ERROR
+    "Extra imported target")
+endif ()
+
+function (check_property expected property)
+  get_property(actual TARGET CXXModules::export_usage
+    PROPERTY "${property}")
+  if (NOT actual STREQUAL expected)
+    message(SEND_ERROR
+      "Mismatch for ${property}:\n  expected: ${expected}\n  actual  : ${actual}")
+  endif ()
+endfunction ()
+
+check_property("/usr/exported;/usr/installiface" "IMPORTED_CXX_MODULES_INCLUDE_DIRECTORIES")
+check_property("exported;installiface" "IMPORTED_CXX_MODULES_COMPILE_DEFINITIONS")
+check_property("cxx_std_20;cxx_std_11;cxx_std_17" "IMPORTED_CXX_MODULES_COMPILE_FEATURES")
+check_property("${export_interfaces_flag}100;${export_interfaces_flag}300" "IMPORTED_CXX_MODULES_COMPILE_OPTIONS")
+check_property("$<COMPILE_ONLY:CXXModules::export_used>;$<COMPILE_ONLY:CXXModules::export_install>" "IMPORTED_CXX_MODULES_LINK_LIBRARIES")
+
+# Extract the export-dependent targets from the export file.
+file(STRINGS "${export_usage_DIR}/export_usage-targets.cmake" usage_dependent_targets
+  REGEX "foreach._target ")
+# Rudimentary argument splitting.
+string(REPLACE " " ";" usage_dependent_targets "${usage_dependent_targets}")
+# Keep only "target" names.
+list(FILTER usage_dependent_targets INCLUDE REGEX "CXXModules::")
+# Strip quotes.
+string(REPLACE "\"" "" usage_dependent_targets "${usage_dependent_targets}")
+
+if (NOT "CXXModules::export_used" IN_LIST usage_dependent_targets)
+  message(SEND_ERROR
+    "The main export does not require the 'CXXModules::export_used' target")
+endif ()
+if ("CXXModules::export_build" IN_LIST usage_dependent_targets)
+  message(SEND_ERROR
+    "The main export requires the 'CXXModules::export_build' target")
+endif ()
+if (NOT "CXXModules::export_install" IN_LIST usage_dependent_targets)
+  message(SEND_ERROR
+    "The main export does not require the 'CXXModules::export_install' target")
+endif ()

+ 7 - 0
Tests/RunCMake/CXXModules/examples/import-modules-export-bmi-and-interface-build-stderr.txt

@@ -0,0 +1,7 @@
+CMake Warning \(dev\) at .*/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build-build/export_bmi_and_interfaces-targets.cmake:[0-9]* \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  .*/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build-build/export_bmi_and_interfaces-config.cmake:1 \(include\)
+  CMakeLists.txt:15 \(find_package\)
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 7 - 0
Tests/RunCMake/CXXModules/examples/import-modules-export-bmi-and-interface-install-stderr.txt

@@ -0,0 +1,7 @@
+CMake Warning \(dev\) at .*/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install-install/lib/cmake/export_bmi_and_interfaces/export_bmi_and_interfaces-targets.cmake:[0-9]* \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  .*/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install-install/lib/cmake/export_bmi_and_interfaces/export_bmi_and_interfaces-config.cmake:1 \(include\)
+  CMakeLists.txt:15 \(find_package\)
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 7 - 0
Tests/RunCMake/CXXModules/examples/import-modules-export-interface-build-stderr.txt

@@ -0,0 +1,7 @@
+CMake Warning \(dev\) at .*/Tests/RunCMake/CXXModules/examples/export-interface-build-build/export_interfaces-targets.cmake:[0-9]* \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  .*/Tests/RunCMake/CXXModules/examples/export-interface-build-build/export_interfaces-config.cmake:1 \(include\)
+  CMakeLists.txt:15 \(find_package\)
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 7 - 0
Tests/RunCMake/CXXModules/examples/import-modules-export-interface-install-stderr.txt

@@ -0,0 +1,7 @@
+CMake Warning \(dev\) at .*/Tests/RunCMake/CXXModules/examples/export-interface-install-install/lib/cmake/export_interfaces/export_interfaces-targets.cmake:[0-9]* \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  .*/Tests/RunCMake/CXXModules/examples/export-interface-install-install/lib/cmake/export_interfaces/export_interfaces-config.cmake:1 \(include\)
+  CMakeLists.txt:15 \(find_package\)
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 7 - 0
Tests/RunCMake/CXXModules/examples/import-modules-export-interface-no-properties-build-stderr.txt

@@ -0,0 +1,7 @@
+CMake Warning \(dev\) at .*/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build-build/export_interfaces_no_properties-targets.cmake:[0-9]* \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  .*/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build-build/export_interfaces_no_properties-config.cmake:1 \(include\)
+  CMakeLists.txt:15 \(find_package\)
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 7 - 0
Tests/RunCMake/CXXModules/examples/import-modules-export-interface-no-properties-install-stderr.txt

@@ -0,0 +1,7 @@
+CMake Warning \(dev\) at .*/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install-install/lib/cmake/export_interfaces_no_properties/export_interfaces_no_properties-targets.cmake:[0-9]* \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  .*/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install-install/lib/cmake/export_interfaces_no_properties/export_interfaces_no_properties-config.cmake:1 \(include\)
+  CMakeLists.txt:15 \(find_package\)
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 24 - 0
Tests/RunCMake/CXXModules/examples/import-modules/CMakeLists.txt

@@ -0,0 +1,24 @@
+cmake_minimum_required(VERSION 3.24)
+project(cxx_modules_import_interfaces CXX)
+
+include("${CMAKE_SOURCE_DIR}/../cxx-modules-rules.cmake")
+
+if (NO_PROPERTIES)
+  set(package_name "export_interfaces_no_properties")
+elseif (WITH_BMIS)
+  set(package_name "export_bmi_and_interfaces")
+else ()
+  set(package_name "export_interfaces")
+endif ()
+set(target_name "CXXModules::${package_name}")
+
+find_package("${package_name}" REQUIRED)
+
+add_executable(use_import_interfaces)
+target_sources(use_import_interfaces
+  PRIVATE
+    use.cxx)
+target_compile_features(use_import_interfaces PRIVATE cxx_std_20)
+target_link_libraries(use_import_interfaces PRIVATE "${target_name}")
+
+add_test(NAME use_import_interfaces COMMAND use_import_interfaces)

+ 6 - 0
Tests/RunCMake/CXXModules/examples/import-modules/use.cxx

@@ -0,0 +1,6 @@
+import importable;
+
+int main(int argc, char* argv[])
+{
+  return from_import();
+}

+ 1 - 1
Tests/RunCMake/target_sources/FileSetDefaultWrongTypeExperimental.cmake

@@ -1,6 +1,6 @@
 enable_language(C)
 
-set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "a816ed09-43d1-40e5-bc8c-1a2824ee194e")
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "ac01f462-0f5f-432a-86aa-acef252918a6")
 
 add_library(lib1 STATIC empty.c)
 target_sources(lib1 PRIVATE FILE_SET UNKNOWN)

+ 1 - 1
Tests/RunCMake/target_sources/FileSetWrongTypeExperimental.cmake

@@ -1,6 +1,6 @@
 enable_language(C)
 
-set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "a816ed09-43d1-40e5-bc8c-1a2824ee194e")
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "ac01f462-0f5f-432a-86aa-acef252918a6")
 
 add_library(lib1 STATIC empty.c)
 target_sources(lib1 PRIVATE FILE_SET a TYPE UNKNOWN)

+ 2 - 0
bootstrap

@@ -333,6 +333,7 @@ CMAKE_CXX_SOURCES="\
   cmCustomCommandGenerator \
   cmCustomCommandLines \
   cmCxxModuleMapper \
+  cmCxxModuleUsageEffects \
   cmDefinePropertyCommand \
   cmDefinitions \
   cmDocumentationFormatter \
@@ -392,6 +393,7 @@ CMAKE_CXX_SOURCES="\
   cmGlobVerificationManager \
   cmHexFileConverter \
   cmIfCommand \
+  cmImportedCxxModuleInfo \
   cmIncludeCommand \
   cmIncludeGuardCommand \
   cmIncludeDirectoryCommand \