浏览代码

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>]
   install(TARGETS targets... [EXPORT <export-name>]
           [RUNTIME_DEPENDENCIES args...|RUNTIME_DEPENDENCY_SET <set-name>]
           [RUNTIME_DEPENDENCIES args...|RUNTIME_DEPENDENCY_SET <set-name>]
           [[ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE|
           [[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>]
            [DESTINATION <dir>]
            [PERMISSIONS permissions...]
            [PERMISSIONS permissions...]
            [CONFIGURATIONS [Debug|Release|...]]
            [CONFIGURATIONS [Debug|Release|...]]
@@ -215,6 +215,18 @@ that may be installed:
   ``/blah/include/myproj/here.h`` with a base directory ``/blah/include``
   ``/blah/include/myproj/here.h`` with a base directory ``/blah/include``
   would be installed to ``myproj/here.h`` below the destination.
   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
 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
 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
 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
   cmInstallCommand.h
   cmInstallCommandArguments.cxx
   cmInstallCommandArguments.cxx
   cmInstallCommandArguments.h
   cmInstallCommandArguments.h
+  cmInstallCxxModuleBmiGenerator.cxx
+  cmInstallCxxModuleBmiGenerator.h
   cmInstallFilesCommand.cxx
   cmInstallFilesCommand.cxx
   cmInstallFilesCommand.h
   cmInstallFilesCommand.h
   cmInstallProgramsCommand.cxx
   cmInstallProgramsCommand.cxx

+ 16 - 0
Source/cmFileAPICodemodel.cxx

@@ -27,6 +27,7 @@
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
 #include "cmGlobalGenerator.h"
+#include "cmInstallCxxModuleBmiGenerator.h"
 #include "cmInstallDirectoryGenerator.h"
 #include "cmInstallDirectoryGenerator.h"
 #include "cmInstallExportGenerator.h"
 #include "cmInstallExportGenerator.h"
 #include "cmInstallFileSetGenerator.h"
 #include "cmInstallFileSetGenerator.h"
@@ -1092,6 +1093,21 @@ Json::Value DirectoryObject::DumpInstaller(cmInstallGenerator* gen)
     if (installFileSet->GetOptional()) {
     if (installFileSet->GetOptional()) {
       installer["isOptional"] = true;
       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.
   // Add fields common to all install generators.

+ 60 - 5
Source/cmInstallCommand.cxx

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

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

@@ -213,6 +213,16 @@ def check_directory(c):
                 assert is_int(at["index"])
                 assert is_int(at["index"])
                 assert c["targets"][at["index"]]["name"] == et["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:
             if e["backtrace"] is not None:
                 expected_keys.append("backtrace")
                 expected_keys.append("backtrace")
                 check_backtrace(d, a["backtrace"], e["backtrace"])
                 check_backtrace(d, a["backtrace"], e["backtrace"])

+ 1 - 0
bootstrap

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