瀏覽代碼

install: support `CXX_MODULES_BMI` installation bits

Ben Boeckel 3 年之前
父節點
當前提交
29118091dc

+ 13 - 1
Help/command/install.rst

@@ -132,7 +132,7 @@ Installing Targets
   install(TARGETS targets... [EXPORT <export-name>]
           [RUNTIME_DEPENDENCIES args...|RUNTIME_DEPENDENCY_SET <set-name>]
           [[ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE|
-            PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE|FILE_SET <set-name>]
+            PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE|FILE_SET <set-name>|CXX_MODULES_BMI]
            [DESTINATION <dir>]
            [PERMISSIONS permissions...]
            [CONFIGURATIONS [Debug|Release|...]]
@@ -215,6 +215,18 @@ that may be installed:
   ``/blah/include/myproj/here.h`` with a base directory ``/blah/include``
   would be installed to ``myproj/here.h`` below the destination.
 
+``CXX_MODULES_BMI``
+
+.. note ::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API``
+
+  Any module files from C++ modules from ``PUBLIC`` sources in a file set of
+  type ``CXX_MODULES`` will be installed to the given ``DESTINATION``. All
+  modules are placed directly in the destination as no directory structure is
+  derived from the names of the modules. An empty ``DESTINATION`` may be used
+  to suppress installing these files (for use in generic code).
+
 For each of these arguments given, the arguments following them only apply
 to the target or file type specified in the argument. If none is given, the
 installation properties apply to all target types. If only one is given then

+ 2 - 0
Source/CMakeLists.txt

@@ -607,6 +607,8 @@ set(SRCS
   cmInstallCommand.h
   cmInstallCommandArguments.cxx
   cmInstallCommandArguments.h
+  cmInstallCxxModuleBmiGenerator.cxx
+  cmInstallCxxModuleBmiGenerator.h
   cmInstallFilesCommand.cxx
   cmInstallFilesCommand.h
   cmInstallProgramsCommand.cxx

+ 16 - 0
Source/cmFileAPICodemodel.cxx

@@ -27,6 +27,7 @@
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
+#include "cmInstallCxxModuleBmiGenerator.h"
 #include "cmInstallDirectoryGenerator.h"
 #include "cmInstallExportGenerator.h"
 #include "cmInstallFileSetGenerator.h"
@@ -1092,6 +1093,21 @@ Json::Value DirectoryObject::DumpInstaller(cmInstallGenerator* gen)
     if (installFileSet->GetOptional()) {
       installer["isOptional"] = true;
     }
+  } else if (auto* cxxModuleBmi =
+               dynamic_cast<cmInstallCxxModuleBmiGenerator*>(gen)) {
+    installer["type"] = "cxxModuleBmi";
+    installer["destination"] = cxxModuleBmi->GetDestination(this->Config);
+
+    auto const* target = cxxModuleBmi->GetTarget();
+    installer["cxxModuleBmiTarget"] = Json::objectValue;
+    installer["cxxModuleBmiTarget"]["id"] = TargetId(target, this->TopBuild);
+    installer["cxxModuleBmiTarget"]["index"] = this->TargetIndexMap[target];
+
+    // FIXME: Parse FilePermissions.
+    // FIXME: Parse MessageLevel.
+    if (cxxModuleBmi->GetOptional()) {
+      installer["isOptional"] = true;
+    }
   }
 
   // Add fields common to all install generators.

+ 60 - 5
Source/cmInstallCommand.cxx

@@ -20,11 +20,13 @@
 
 #include "cmArgumentParser.h"
 #include "cmExecutionStatus.h"
+#include "cmExperimental.h"
 #include "cmExportSet.h"
 #include "cmFileSet.h"
 #include "cmGeneratorExpression.h"
 #include "cmGlobalGenerator.h"
 #include "cmInstallCommandArguments.h"
+#include "cmInstallCxxModuleBmiGenerator.h"
 #include "cmInstallDirectoryGenerator.h"
 #include "cmInstallExportGenerator.h"
 #include "cmInstallFileSetGenerator.h"
@@ -110,6 +112,8 @@ public:
     const cmInstallCommandArguments* args) const;
   std::string GetLibraryDestination(
     const cmInstallCommandArguments* args) const;
+  std::string GetCxxModulesBmiDestination(
+    const cmInstallCommandArguments* args) const;
   std::string GetIncludeDestination(
     const cmInstallCommandArguments* args) const;
   std::string GetSysconfDestination(
@@ -413,6 +417,7 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
     std::vector<std::string> PublicHeader;
     std::vector<std::string> Resource;
     std::vector<std::vector<std::string>> FileSets;
+    std::vector<std::string> CxxModulesBmi;
   };
 
   static auto const argHelper =
@@ -427,7 +432,8 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
       .Bind("PRIVATE_HEADER"_s, &ArgVectors::PrivateHeader)
       .Bind("PUBLIC_HEADER"_s, &ArgVectors::PublicHeader)
       .Bind("RESOURCE"_s, &ArgVectors::Resource)
-      .Bind("FILE_SET"_s, &ArgVectors::FileSets);
+      .Bind("FILE_SET"_s, &ArgVectors::FileSets)
+      .Bind("CXX_MODULES_BMI"_s, &ArgVectors::CxxModulesBmi);
 
   std::vector<std::string> genericArgVector;
   ArgVectors const argVectors = argHelper.Parse(args, &genericArgVector);
@@ -466,6 +472,7 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
   cmInstallCommandIncludesArgument includesArgs;
   std::vector<cmInstallCommandFileSetArguments> fileSetArgs(
     argVectors.FileSets.size(), { helper.DefaultComponentName });
+  cmInstallCommandArguments cxxModuleBmiArgs(helper.DefaultComponentName);
 
   // now parse the args for specific parts of the target (e.g. LIBRARY,
   // RUNTIME, ARCHIVE etc.
@@ -489,6 +496,15 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
     fileSetArgs[i] = std::move(fileSetArg);
   }
 
+  bool const supportCxx20FileSetTypes = cmExperimental::HasSupportEnabled(
+    *helper.Makefile, cmExperimental::Feature::CxxModuleCMakeApi);
+  if (!supportCxx20FileSetTypes) {
+    std::copy(argVectors.CxxModulesBmi.begin(), argVectors.CxxModulesBmi.end(),
+              std::back_inserter(unknownArgs));
+  } else {
+    cxxModuleBmiArgs.Parse(argVectors.CxxModulesBmi, &unknownArgs);
+  }
+
   if (!unknownArgs.empty()) {
     // Unknown argument.
     status.SetError(
@@ -509,6 +525,7 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
   for (auto& fileSetArg : fileSetArgs) {
     fileSetArg.SetGenericArguments(&genericArgs);
   }
+  cxxModuleBmiArgs.SetGenericArguments(&genericArgs);
 
   success = success && archiveArgs.Finalize();
   success = success && libraryArgs.Finalize();
@@ -522,6 +539,9 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
   for (auto& fileSetArg : fileSetArgs) {
     success = success && fileSetArg.Finalize();
   }
+  if (supportCxx20FileSetTypes) {
+    success = success && cxxModuleBmiArgs.Finalize();
+  }
 
   if (!success) {
     return false;
@@ -535,7 +555,8 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
       publicHeaderArgs.GetNamelinkOnly() || resourceArgs.GetNamelinkOnly() ||
       std::any_of(fileSetArgs.begin(), fileSetArgs.end(),
                   [](const cmInstallCommandFileSetArguments& fileSetArg)
-                    -> bool { return fileSetArg.GetNamelinkOnly(); })) {
+                    -> bool { return fileSetArg.GetNamelinkOnly(); }) ||
+      cxxModuleBmiArgs.GetNamelinkOnly()) {
     status.SetError(
       "TARGETS given NAMELINK_ONLY option not in LIBRARY group.  "
       "The NAMELINK_ONLY option may be specified only following LIBRARY.");
@@ -547,7 +568,8 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
       publicHeaderArgs.GetNamelinkSkip() || resourceArgs.GetNamelinkSkip() ||
       std::any_of(fileSetArgs.begin(), fileSetArgs.end(),
                   [](const cmInstallCommandFileSetArguments& fileSetArg)
-                    -> bool { return fileSetArg.GetNamelinkSkip(); })) {
+                    -> bool { return fileSetArg.GetNamelinkSkip(); }) ||
+      cxxModuleBmiArgs.GetNamelinkSkip()) {
     status.SetError(
       "TARGETS given NAMELINK_SKIP option not in LIBRARY group.  "
       "The NAMELINK_SKIP option may be specified only following LIBRARY.");
@@ -563,7 +585,8 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
       resourceArgs.HasNamelinkComponent() ||
       std::any_of(fileSetArgs.begin(), fileSetArgs.end(),
                   [](const cmInstallCommandFileSetArguments& fileSetArg)
-                    -> bool { return fileSetArg.HasNamelinkComponent(); })) {
+                    -> bool { return fileSetArg.HasNamelinkComponent(); }) ||
+      cxxModuleBmiArgs.HasNamelinkComponent()) {
     status.SetError(
       "TARGETS given NAMELINK_COMPONENT option not in LIBRARY group.  "
       "The NAMELINK_COMPONENT option may be specified only following "
@@ -582,7 +605,8 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
       !publicHeaderArgs.GetType().empty() || !resourceArgs.GetType().empty() ||
       std::any_of(fileSetArgs.begin(), fileSetArgs.end(),
                   [](const cmInstallCommandFileSetArguments& fileSetArg)
-                    -> bool { return !fileSetArg.GetType().empty(); })) {
+                    -> bool { return !fileSetArg.GetType().empty(); }) ||
+      !cxxModuleBmiArgs.GetType().empty()) {
     status.SetError(
       "TARGETS given TYPE option. The TYPE option may only be specified in "
       " install(FILES) and install(DIRECTORIES).");
@@ -705,6 +729,7 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
   bool installsPublicHeader = false;
   bool installsResource = false;
   std::vector<bool> installsFileSet(fileSetArgs.size(), false);
+  bool installsCxxModuleBmi = false;
 
   // Generate install script code to install the given targets.
   for (cmTarget* ti : targets) {
@@ -721,6 +746,7 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
     std::unique_ptr<cmInstallFilesGenerator> publicHeaderGenerator;
     std::unique_ptr<cmInstallFilesGenerator> resourceGenerator;
     std::vector<std::unique_ptr<cmInstallFileSetGenerator>> fileSetGenerators;
+    std::unique_ptr<cmInstallCxxModuleBmiGenerator> cxxModuleBmiGenerator;
 
     // Avoid selecting default destinations for PUBLIC_HEADER and
     // PRIVATE_HEADER if any artifacts are specified.
@@ -759,6 +785,7 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
         for (auto const& gen : fileSetGenerators) {
           te->FileSetGenerators[gen->GetFileSet()] = gen.get();
         }
+        te->CxxModuleBmiGenerator = cxxModuleBmiGenerator.get();
         target.AddInstallIncludeDirectories(
           *te, cmMakeRange(includesArgs.GetIncludeDirs()));
         te->NamelinkOnly = namelinkOnly;
@@ -1104,6 +1131,19 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
       }
     }
 
+    if (supportCxx20FileSetTypes &&
+        !cxxModuleBmiArgs.GetDestination().empty()) {
+      cxxModuleBmiGenerator = cm::make_unique<cmInstallCxxModuleBmiGenerator>(
+        target.GetName(),
+        helper.GetCxxModulesBmiDestination(&cxxModuleBmiArgs),
+        cxxModuleBmiArgs.GetPermissions(),
+        cxxModuleBmiArgs.GetConfigurations(), cxxModuleBmiArgs.GetComponent(),
+        cmInstallGenerator::SelectMessageLevel(target.GetMakefile()),
+        cxxModuleBmiArgs.GetExcludeFromAll(), cxxModuleBmiArgs.GetOptional(),
+        helper.Makefile->GetBacktrace());
+      target.SetHaveInstallRule(true);
+    }
+
     // Add this install rule to an export if one was specified.
     if (!addTargetExport()) {
       return false;
@@ -1120,6 +1160,7 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
     installsPrivateHeader = installsPrivateHeader || privateHeaderGenerator;
     installsPublicHeader = installsPublicHeader || publicHeaderGenerator;
     installsResource = installsResource || resourceGenerator;
+    installsCxxModuleBmi = installsCxxModuleBmi || cxxModuleBmiGenerator;
 
     helper.Makefile->AddInstallGenerator(std::move(archiveGenerator));
     helper.Makefile->AddInstallGenerator(std::move(libraryGenerator));
@@ -1134,6 +1175,7 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
     for (auto& gen : fileSetGenerators) {
       helper.Makefile->AddInstallGenerator(std::move(gen));
     }
+    helper.Makefile->AddInstallGenerator(std::move(cxxModuleBmiGenerator));
   }
 
   if (runtimeDependenciesArgVector && !runtimeDependencySet->Empty()) {
@@ -1191,6 +1233,10 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
         fileSetArgs[i].GetComponent());
     }
   }
+  if (installsCxxModuleBmi) {
+    helper.Makefile->GetGlobalGenerator()->AddInstallComponent(
+      cxxModuleBmiArgs.GetComponent());
+  }
 
   return true;
 }
@@ -2279,6 +2325,15 @@ std::string Helper::GetLibraryDestination(
   return this->GetDestination(args, "CMAKE_INSTALL_LIBDIR", "lib");
 }
 
+std::string Helper::GetCxxModulesBmiDestination(
+  const cmInstallCommandArguments* args) const
+{
+  if (args) {
+    return args->GetDestination();
+  }
+  return {};
+}
+
 std::string Helper::GetIncludeDestination(
   const cmInstallCommandArguments* args) const
 {

+ 75 - 0
Source/cmInstallCxxModuleBmiGenerator.cxx

@@ -0,0 +1,75 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmInstallCxxModuleBmiGenerator.h"
+
+#include <ostream>
+#include <utility>
+
+#include "cmGeneratorExpression.h"
+#include "cmGeneratorTarget.h"
+#include "cmGlobalGenerator.h"
+#include "cmListFileCache.h"
+#include "cmLocalGenerator.h"
+#include "cmOutputConverter.h"
+#include "cmStringAlgorithms.h"
+
+cmInstallCxxModuleBmiGenerator::cmInstallCxxModuleBmiGenerator(
+  std::string target, std::string const& dest, std::string file_permissions,
+  std::vector<std::string> const& configurations, std::string const& component,
+  MessageLevel message, bool exclude_from_all, bool optional,
+  cmListFileBacktrace backtrace)
+  : cmInstallGenerator(dest, configurations, component, message,
+                       exclude_from_all, false, std::move(backtrace))
+  , TargetName(std::move(target))
+  , FilePermissions(std::move(file_permissions))
+  , Optional(optional)
+{
+  this->ActionsPerConfig = true;
+}
+
+cmInstallCxxModuleBmiGenerator::~cmInstallCxxModuleBmiGenerator() = default;
+
+bool cmInstallCxxModuleBmiGenerator::Compute(cmLocalGenerator* lg)
+{
+  this->LocalGenerator = lg;
+
+  this->Target = lg->FindLocalNonAliasGeneratorTarget(this->TargetName);
+  if (!this->Target) {
+    // If no local target has been found, find it in the global scope.
+    this->Target =
+      lg->GetGlobalGenerator()->FindGeneratorTarget(this->TargetName);
+  }
+
+  return true;
+}
+
+std::string cmInstallCxxModuleBmiGenerator::GetScriptLocation(
+  std::string const& config) const
+{
+  char const* config_name = config.c_str();
+  if (config.empty()) {
+    config_name = "noconfig";
+  }
+  return cmStrCat(this->Target->GetSupportDirectory(),
+                  "/install-cxx-module-bmi-", config_name, ".cmake");
+}
+
+std::string cmInstallCxxModuleBmiGenerator::GetDestination(
+  std::string const& config) const
+{
+  return cmGeneratorExpression::Evaluate(this->Destination,
+                                         this->LocalGenerator, config);
+}
+
+void cmInstallCxxModuleBmiGenerator::GenerateScriptForConfig(
+  std::ostream& os, const std::string& config, Indent indent)
+{
+  auto const& loc = this->GetScriptLocation(config);
+  if (loc.empty()) {
+    return;
+  }
+  os << indent << "include(\""
+     << cmOutputConverter::EscapeForCMake(
+          loc, cmOutputConverter::WrapQuotes::NoWrap)
+     << "\" OPTIONAL)\n";
+}

+ 52 - 0
Source/cmInstallCxxModuleBmiGenerator.h

@@ -0,0 +1,52 @@
+/* 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 <iosfwd>
+#include <string>
+#include <vector>
+
+#include "cmInstallGenerator.h"
+#include "cmScriptGenerator.h"
+
+class cmGeneratorTarget;
+class cmListFileBacktrace;
+class cmLocalGenerator;
+
+/** \class cmInstallCxxModuleBmiGenerator
+ * \brief Generate C++ module BMI installation rules.
+ */
+class cmInstallCxxModuleBmiGenerator : public cmInstallGenerator
+{
+public:
+  cmInstallCxxModuleBmiGenerator(
+    std::string target, std::string const& dest, std::string file_permissions,
+    std::vector<std::string> const& configurations,
+    std::string const& component, MessageLevel message, bool exclude_from_all,
+    bool optional, cmListFileBacktrace backtrace);
+  ~cmInstallCxxModuleBmiGenerator() override;
+
+  bool Compute(cmLocalGenerator* lg) override;
+
+  std::string const& GetFilePermissions() const
+  {
+    return this->FilePermissions;
+  }
+  std::string GetDestination(std::string const& config) const;
+  std::string GetScriptLocation(std::string const& config) const;
+  cmGeneratorTarget const* GetTarget() const { return this->Target; }
+  bool GetOptional() const { return this->Optional; }
+  MessageLevel GetMessageLevel() const { return this->Message; }
+
+protected:
+  void GenerateScriptForConfig(std::ostream& os, const std::string& config,
+                               Indent indent) override;
+
+  std::string const TargetName;
+  cmGeneratorTarget const* Target = nullptr;
+  cmLocalGenerator* LocalGenerator = nullptr;
+  std::string const FilePermissions;
+  bool const Optional;
+};

+ 2 - 0
Source/cmTargetExport.h

@@ -8,6 +8,7 @@
 
 class cmFileSet;
 class cmGeneratorTarget;
+class cmInstallCxxModuleBmiGenerator;
 class cmInstallFileSetGenerator;
 class cmInstallFilesGenerator;
 class cmInstallTargetGenerator;
@@ -32,6 +33,7 @@ public:
   cmInstallTargetGenerator* BundleGenerator;
   cmInstallFilesGenerator* HeaderGenerator;
   std::map<cmFileSet*, cmInstallFileSetGenerator*> FileSetGenerators;
+  cmInstallCxxModuleBmiGenerator* CxxModuleBmiGenerator;
   ///@}
 
   bool NamelinkOnly = false;

+ 24 - 0
Tests/RunCMake/CXXModules/InstallBMI-check.cmake

@@ -0,0 +1,24 @@
+file(READ "${RunCMake_TEST_BINARY_DIR}/cmake_install.cmake" install_script)
+
+if (NOT install_script MATCHES [[\(CMAKE_INSTALL_COMPONENT STREQUAL "bmi" OR NOT CMAKE_INSTALL_COMPONENT\)]])
+  list(APPEND RunCMake_TEST_FAILED
+    "Could not find BMI install script component for `bmi`")
+endif ()
+
+if (NOT install_script MATCHES [[include\("[^)]*/CMakeFiles/install-bmi\.dir/install-cxx-module-bmi-[^.]*\.cmake" OPTIONAL\)]])
+  list(APPEND RunCMake_TEST_FAILED
+    "Could not find BMI install script inclusion")
+endif ()
+
+if (NOT install_script MATCHES [[\(CMAKE_INSTALL_COMPONENT STREQUAL "bmi-optional"\)]])
+  list(APPEND RunCMake_TEST_FAILED
+    "Could not find BMI install script component for `bmi-optional`")
+endif ()
+
+if (NOT install_script MATCHES [[\(CMAKE_INSTALL_COMPONENT STREQUAL "bmi-only-debug" OR NOT CMAKE_INSTALL_COMPONENT\)
+  if\(CMAKE_INSTALL_CONFIG_NAME MATCHES "\^\(\[Dd\]\[Ee\]\[Bb\]\[Uu\]\[Gg\]\)\$"\)]])
+  list(APPEND RunCMake_TEST_FAILED
+    "Could not find BMI install script component for `bmi-only-debug`")
+endif ()
+
+string(REPLACE ";" "; " RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}")

+ 6 - 0
Tests/RunCMake/CXXModules/InstallBMI-stderr.txt

@@ -0,0 +1,6 @@
+CMake Warning \(dev\) at InstallBMI.cmake:8 \(install\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:6 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 23 - 0
Tests/RunCMake/CXXModules/InstallBMI.cmake

@@ -0,0 +1,23 @@
+enable_language(CXX)
+
+add_library(install-bmi)
+target_sources(install-bmi
+  PRIVATE
+    sources/cxx-anchor.cxx)
+
+install(TARGETS install-bmi
+  CXX_MODULES_BMI
+    DESTINATION "lib/bmi"
+    COMPONENT "bmi")
+
+install(TARGETS install-bmi
+  CXX_MODULES_BMI
+    DESTINATION "lib/bmi"
+    EXCLUDE_FROM_ALL
+    COMPONENT "bmi-optional")
+
+install(TARGETS install-bmi
+  CXX_MODULES_BMI
+    DESTINATION "lib/bmi"
+    CONFIGURATIONS Debug
+    COMPONENT "bmi-only-debug")

+ 8 - 0
Tests/RunCMake/CXXModules/InstallBMIGenericArgs-check.cmake

@@ -0,0 +1,8 @@
+file(READ "${RunCMake_TEST_BINARY_DIR}/cmake_install.cmake" install_script)
+
+if (NOT install_script MATCHES [[include\("[^)]*/CMakeFiles/install-bmi-generic-args\.dir/install-cxx-module-bmi-[^.]*\.cmake" OPTIONAL\)]])
+  list(APPEND RunCMake_TEST_FAILED
+    "Could not find BMI install script inclusion")
+endif ()
+
+string(REPLACE ";" "; " RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}")

+ 6 - 0
Tests/RunCMake/CXXModules/InstallBMIGenericArgs-stderr.txt

@@ -0,0 +1,6 @@
+CMake Warning \(dev\) at InstallBMIGenericArgs.cmake:8 \(install\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:6 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 9 - 0
Tests/RunCMake/CXXModules/InstallBMIGenericArgs.cmake

@@ -0,0 +1,9 @@
+enable_language(CXX)
+
+add_library(install-bmi-generic-args)
+target_sources(install-bmi-generic-args
+  PRIVATE
+    sources/cxx-anchor.cxx)
+
+install(TARGETS install-bmi-generic-args
+  DESTINATION "bin")

+ 13 - 0
Tests/RunCMake/CXXModules/InstallBMIIgnore-check.cmake

@@ -0,0 +1,13 @@
+file(READ "${RunCMake_TEST_BINARY_DIR}/cmake_install.cmake" install_script)
+
+if (install_script MATCHES [[\(CMAKE_INSTALL_COMPONENT STREQUAL "bmi" OR NOT CMAKE_INSTALL_COMPONENT\)]])
+  list(APPEND RunCMake_TEST_FAILED
+    "Found BMI install script component for `bmi`")
+endif ()
+
+if (install_script MATCHES [[include\("[^)]*/CMakeFiles/install-bmi-ignore\.dir/install-cxx-module-bmi-[^.]*\.cmake" OPTIONAL\)]])
+  list(APPEND RunCMake_TEST_FAILED
+    "Found BMI install script inclusion")
+endif ()
+
+string(REPLACE ";" "; " RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}")

+ 6 - 0
Tests/RunCMake/CXXModules/InstallBMIIgnore-stderr.txt

@@ -0,0 +1,6 @@
+CMake Warning \(dev\) at InstallBMIIgnore.cmake:5 \(install\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:6 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 9 - 0
Tests/RunCMake/CXXModules/InstallBMIIgnore.cmake

@@ -0,0 +1,9 @@
+enable_language(CXX)
+
+add_library(install-bmi-ignore INTERFACE)
+
+install(TARGETS install-bmi-ignore
+  CXX_MODULES_BMI
+    # An empty destination ignores BMI installation.
+    DESTINATION ""
+    COMPONENT "bmi")

+ 8 - 0
Tests/RunCMake/CXXModules/InstallBMINoGenericArgs-check.cmake

@@ -0,0 +1,8 @@
+file(READ "${RunCMake_TEST_BINARY_DIR}/cmake_install.cmake" install_script)
+
+if (install_script MATCHES [[include\("[^)]*/CMakeFiles/install-bmi-generic-args\.dir/install-cxx-module-bmi-[^.]*\.cmake" OPTIONAL\)]])
+  list(APPEND RunCMake_TEST_FAILED
+    "Found BMI install script inclusion")
+endif ()
+
+string(REPLACE ";" "; " RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}")

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

@@ -67,6 +67,10 @@ foreach (fileset_type IN LISTS fileset_types)
   run_cmake("NotCXXSource${fileset_type}")
 endforeach ()
 
+run_cmake(InstallBMI)
+run_cmake(InstallBMIGenericArgs)
+run_cmake(InstallBMIIgnore)
+
 # Actual compilation tests.
 if (NOT CMake_TEST_MODULE_COMPILATION)
   return ()

+ 10 - 0
Tests/RunCMake/FileAPI/codemodel-v2-check.py

@@ -213,6 +213,16 @@ def check_directory(c):
                 assert is_int(at["index"])
                 assert c["targets"][at["index"]]["name"] == et["index"]
 
+            if e.get("cxxModuleBmiTarget", None) is not None:
+                expected_keys.append("cxxModuleBmiTarget")
+                et = e["cxxModuleBmiTarget"]
+                at = a["cxxModuleBmiTarget"]
+                assert is_dict(at)
+                assert sorted(at.keys()) == ["id", "index"]
+                assert matches(at["id"], et["id"])
+                assert is_int(at["index"])
+                assert c["targets"][at["index"]]["name"] == et["index"]
+
             if e["backtrace"] is not None:
                 expected_keys.append("backtrace")
                 check_backtrace(d, a["backtrace"], e["backtrace"])

+ 1 - 0
bootstrap

@@ -393,6 +393,7 @@ CMAKE_CXX_SOURCES="\
   cmIncludeRegularExpressionCommand \
   cmInstallCommand \
   cmInstallCommandArguments \
+  cmInstallCxxModuleBmiGenerator \
   cmInstallDirectoryGenerator \
   cmInstallExportGenerator \
   cmInstallFileSetGenerator \