Ver Fonte

cmExport*FileGenerator: support exporting C++ module properties

C++ module properties will be generated at build time, so generate code
that includes the files actually responsible for the information.
Ben Boeckel há 3 anos atrás
pai
commit
3526b8c123

+ 66 - 0
Source/cmExportBuildFileGenerator.cxx

@@ -15,6 +15,7 @@
 
 #include "cmExportSet.h"
 #include "cmFileSet.h"
+#include "cmGeneratedFileStream.h"
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
@@ -25,6 +26,7 @@
 #include "cmPolicies.h"
 #include "cmStateTypes.h"
 #include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
 #include "cmTarget.h"
 #include "cmTargetExport.h"
 #include "cmValue.h"
@@ -141,11 +143,18 @@ bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os)
     this->GenerateTargetFileSets(gte, os);
   }
 
+  this->GenerateCxxModuleInformation(os);
+
   // Generate import file content for each configuration.
   for (std::string const& c : this->Configurations) {
     this->GenerateImportConfig(os, c);
   }
 
+  // Generate import file content for each configuration.
+  for (std::string const& c : this->Configurations) {
+    this->GenerateImportCxxModuleConfigTargetInclusion(c);
+  }
+
   this->GenerateMissingTargetsCheckCode(os);
 
   return true;
@@ -479,3 +488,60 @@ std::string cmExportBuildFileGenerator::GetFileSetFiles(cmGeneratorTarget* gte,
 
   return cmJoin(resultVector, " ");
 }
+
+std::string cmExportBuildFileGenerator::GetCxxModulesDirectory() const
+{
+  return this->CxxModulesDirectory;
+}
+
+void cmExportBuildFileGenerator::GenerateCxxModuleConfigInformation(
+  std::ostream& os) const
+{
+  const char* opt = "";
+  if (this->Configurations.size() > 1) {
+    // With more than one configuration, each individual file is optional.
+    opt = " OPTIONAL";
+  }
+
+  // Generate import file content for each configuration.
+  for (std::string c : this->Configurations) {
+    if (c.empty()) {
+      c = "noconfig";
+    }
+    os << "include(\"${CMAKE_CURRENT_LIST_DIR}/cxx-modules-" << c << ".cmake\""
+       << opt << ")\n";
+  }
+}
+
+bool cmExportBuildFileGenerator::GenerateImportCxxModuleConfigTargetInclusion(
+  std::string config) const
+{
+  auto cxx_modules_dirname = this->GetCxxModulesDirectory();
+  if (cxx_modules_dirname.empty()) {
+    return true;
+  }
+
+  if (config.empty()) {
+    config = "noconfig";
+  }
+
+  std::string fileName = cmStrCat(this->FileDir, '/', cxx_modules_dirname,
+                                  "/cxx-modules-", config, ".cmake");
+
+  cmGeneratedFileStream os(fileName, true);
+  if (!os) {
+    std::string se = cmSystemTools::GetLastSystemError();
+    std::ostringstream e;
+    e << "cannot write to file \"" << fileName << "\": " << se;
+    cmSystemTools::Error(e.str());
+    return false;
+  }
+  os.SetCopyIfDifferent(true);
+
+  for (auto const* tgt : this->ExportedTargets) {
+    os << "include(\"${CMAKE_CURRENT_LIST_DIR}/target-" << tgt->GetExportName()
+       << '-' << config << ".cmake\")\n";
+  }
+
+  return true;
+}

+ 4 - 0
Source/cmExportBuildFileGenerator.h

@@ -91,6 +91,10 @@ protected:
   std::string GetFileSetFiles(cmGeneratorTarget* gte, cmFileSet* fileSet,
                               cmTargetExport* te) override;
 
+  std::string GetCxxModulesDirectory() const override;
+  void GenerateCxxModuleConfigInformation(std::ostream&) const override;
+  bool GenerateImportCxxModuleConfigTargetInclusion(std::string) const;
+
   std::pair<std::vector<std::string>, std::string> FindBuildExportInfo(
     cmGlobalGenerator* gg, const std::string& name);
 

+ 25 - 0
Source/cmExportFileGenerator.cxx

@@ -1305,3 +1305,28 @@ void cmExportFileGenerator::GenerateTargetFileSets(cmGeneratorTarget* gte,
     os << "  )\nendif()\n\n";
   }
 }
+
+void cmExportFileGenerator::GenerateCxxModuleInformation(std::ostream& os)
+{
+  auto const cxx_module_dirname = this->GetCxxModulesDirectory();
+  if (cxx_module_dirname.empty()) {
+    return;
+  }
+
+  // Write the include.
+  os << "# Include C++ module properties\n"
+     << "include(\"${CMAKE_CURRENT_LIST_DIR}/" << cxx_module_dirname
+     << "/cxx-modules.cmake\")\n\n";
+
+  // Get the path to the file we're going to write.
+  std::string path = this->MainImportFile;
+  path = cmSystemTools::GetFilenamePath(path);
+  auto trampoline_path =
+    cmStrCat(path, '/', cxx_module_dirname, "/cxx-modules.cmake");
+
+  // Include all configuration-specific include files.
+  cmGeneratedFileStream ap(trampoline_path, true);
+  ap.SetCopyIfDifferent(true);
+
+  this->GenerateCxxModuleConfigInformation(ap);
+}

+ 5 - 0
Source/cmExportFileGenerator.h

@@ -182,6 +182,8 @@ protected:
   void GenerateTargetFileSets(cmGeneratorTarget* gte, std::ostream& os,
                               cmTargetExport* te = nullptr);
 
+  void GenerateCxxModuleInformation(std::ostream& os);
+
   virtual std::string GetFileSetDirectories(cmGeneratorTarget* gte,
                                             cmFileSet* fileSet,
                                             cmTargetExport* te) = 0;
@@ -226,4 +228,7 @@ private:
 
   virtual std::string InstallNameDir(cmGeneratorTarget const* target,
                                      const std::string& config) = 0;
+
+  virtual std::string GetCxxModulesDirectory() const = 0;
+  virtual void GenerateCxxModuleConfigInformation(std::ostream& os) const = 0;
 };

+ 73 - 1
Source/cmExportInstallFileGenerator.cxx

@@ -166,10 +166,20 @@ bool cmExportInstallFileGenerator::GenerateMainFile(std::ostream& os)
 
   this->LoadConfigFiles(os);
 
+  bool result = true;
+
+  this->GenerateCxxModuleInformation(os);
+  if (requiresConfigFiles) {
+    for (std::string const& c : this->Configurations) {
+      if (!this->GenerateImportCxxModuleConfigTargetInclusion(c)) {
+        result = false;
+      }
+    }
+  }
+
   this->CleanupTemporaryVariables(os);
   this->GenerateImportedFileCheckLoop(os);
 
-  bool result = true;
   // Generate an import file for each configuration.
   // Don't do this if we only export INTERFACE_LIBRARY targets.
   if (requiresConfigFiles) {
@@ -669,3 +679,65 @@ std::string cmExportInstallFileGenerator::GetFileSetFiles(
 
   return cmJoin(resultVector, " ");
 }
+
+std::string cmExportInstallFileGenerator::GetCxxModulesDirectory() const
+{
+  return IEGen->GetCxxModuleDirectory();
+}
+
+void cmExportInstallFileGenerator::GenerateCxxModuleConfigInformation(
+  std::ostream& os) const
+{
+  // Now load per-configuration properties for them.
+  /* clang-format off */
+  os << "# Load information for each installed configuration.\n"
+        "file(GLOB _cmake_cxx_module_includes \"${CMAKE_CURRENT_LIST_DIR}/cxx-modules-*.cmake\")\n"
+        "foreach(_cmake_cxx_module_include IN LISTS _cmake_cxx_module_includes)\n"
+        "  include(\"${_cmake_cxx_module_include}\")\n"
+        "endforeach()\n"
+        "unset(_cmake_cxx_module_include)\n"
+        "unset(_cmake_cxx_module_includes)\n";
+  /* clang-format on */
+}
+
+bool cmExportInstallFileGenerator::
+  GenerateImportCxxModuleConfigTargetInclusion(std::string const& config)
+{
+  auto cxx_modules_dirname = this->GetCxxModulesDirectory();
+  if (cxx_modules_dirname.empty()) {
+    return true;
+  }
+
+  std::string filename_config = config;
+  if (filename_config.empty()) {
+    filename_config = "noconfig";
+  }
+
+  std::string const dest =
+    cmStrCat(this->FileDir, '/', cxx_modules_dirname, '/');
+  std::string fileName =
+    cmStrCat(dest, "cxx-modules-", filename_config, ".cmake");
+
+  cmGeneratedFileStream os(fileName, true);
+  if (!os) {
+    std::string se = cmSystemTools::GetLastSystemError();
+    std::ostringstream e;
+    e << "cannot write to file \"" << fileName << "\": " << se;
+    cmSystemTools::Error(e.str());
+    return false;
+  }
+  os.SetCopyIfDifferent(true);
+
+  // Record this per-config import file.
+  this->ConfigCxxModuleFiles[config] = fileName;
+
+  auto& prop_files = this->ConfigCxxModuleTargetFiles[config];
+  for (auto const* tgt : this->ExportedTargets) {
+    auto prop_filename = cmStrCat("target-", tgt->GetExportName(), '-',
+                                  filename_config, ".cmake");
+    prop_files.emplace_back(cmStrCat(dest, prop_filename));
+    os << "include(\"${CMAKE_CURRENT_LIST_DIR}/" << prop_filename << "\")\n";
+  }
+
+  return true;
+}

+ 25 - 0
Source/cmExportInstallFileGenerator.h

@@ -50,6 +50,23 @@ public:
     return this->ConfigImportFiles;
   }
 
+  /** Get the per-config C++ module file generated for each configuration.
+      This maps from the configuration name to the file temporary location
+      for installation.  */
+  std::map<std::string, std::string> const& GetConfigCxxModuleFiles()
+  {
+    return this->ConfigCxxModuleFiles;
+  }
+
+  /** Get the per-config C++ module file generated for each configuration.
+      This maps from the configuration name to the file temporary location
+      for installation for each target in the export set.  */
+  std::map<std::string, std::vector<std::string>> const&
+  GetConfigCxxModuleTargetFiles()
+  {
+    return this->ConfigCxxModuleTargetFiles;
+  }
+
   /** Compute the globbing expression used to load per-config import
       files from the main file.  */
   std::string GetConfigImportFileGlob();
@@ -100,8 +117,16 @@ protected:
   std::string GetFileSetFiles(cmGeneratorTarget* gte, cmFileSet* fileSet,
                               cmTargetExport* te) override;
 
+  std::string GetCxxModulesDirectory() const override;
+  void GenerateCxxModuleConfigInformation(std::ostream&) const override;
+  bool GenerateImportCxxModuleConfigTargetInclusion(std::string const&);
+
   cmInstallExportGenerator* IEGen;
 
   // The import file generated for each configuration.
   std::map<std::string, std::string> ConfigImportFiles;
+  // The C++ module property file generated for each configuration.
+  std::map<std::string, std::string> ConfigCxxModuleFiles;
+  // The C++ module property target files generated for each configuration.
+  std::map<std::string, std::vector<std::string>> ConfigCxxModuleTargetFiles;
 };

+ 3 - 0
Source/cmExportTryCompileFileGenerator.h

@@ -55,6 +55,9 @@ protected:
   std::string GetFileSetFiles(cmGeneratorTarget* target, cmFileSet* fileSet,
                               cmTargetExport* te) override;
 
+  std::string GetCxxModulesDirectory() const override { return {}; }
+  void GenerateCxxModuleConfigInformation(std::ostream&) const override {}
+
 private:
   std::string FindTargets(const std::string& prop,
                           const cmGeneratorTarget* tgt,

+ 69 - 0
Source/cmInstallExportGenerator.cxx

@@ -143,6 +143,75 @@ void cmInstallExportGenerator::GenerateScriptConfigs(std::ostream& os,
     os << indent << "endif()\n";
     files.clear();
   }
+
+  // Now create a configuration-specific install rule for the C++ module import
+  // property file of each configuration.
+  auto cxx_module_dest =
+    cmStrCat(this->Destination, '/', this->CxxModulesDirectory);
+  std::string config_file_example;
+  for (auto const& i : this->EFGen->GetConfigCxxModuleFiles()) {
+    config_file_example = i.second;
+    break;
+  }
+  if (!config_file_example.empty()) {
+    // Remove old per-configuration export files if the main changes.
+    std::string installedDir = cmStrCat(
+      "$ENV{DESTDIR}", ConvertToAbsoluteDestination(cxx_module_dest), '/');
+    std::string installedFile = cmStrCat(installedDir, "/cxx-modules.cmake");
+    std::string toInstallFile =
+      cmStrCat(cmSystemTools::GetFilenamePath(config_file_example),
+               "/cxx-modules.cmake");
+    os << indent << "if(EXISTS \"" << installedFile << "\")\n";
+    Indent indentN = indent.Next();
+    Indent indentNN = indentN.Next();
+    Indent indentNNN = indentNN.Next();
+    /* clang-format off */
+    os << indentN << "file(DIFFERENT _cmake_export_file_changed FILES\n"
+       << indentN << "     \"" << installedFile << "\"\n"
+       << indentN << "     \"" << toInstallFile << "\")\n";
+    os << indentN << "if(_cmake_export_file_changed)\n";
+    os << indentNN << "file(GLOB _cmake_old_config_files \"" << installedDir
+       << this->EFGen->GetConfigImportFileGlob() << "\")\n";
+    os << indentNN << "if(_cmake_old_config_files)\n";
+    os << indentNNN << "string(REPLACE \";\" \", \" _cmake_old_config_files_text \"${_cmake_old_config_files}\")\n";
+    os << indentNNN << R"(message(STATUS "Old C++ module export file \")" << installedFile
+       << "\\\" will be replaced.  Removing files [${_cmake_old_config_files_text}].\")\n";
+    os << indentNNN << "unset(_cmake_old_config_files_text)\n";
+    os << indentNNN << "file(REMOVE ${_cmake_old_config_files})\n";
+    os << indentNN << "endif()\n";
+    os << indentNN << "unset(_cmake_old_config_files)\n";
+    os << indentN << "endif()\n";
+    os << indentN << "unset(_cmake_export_file_changed)\n";
+    os << indent << "endif()\n";
+    /* clang-format on */
+
+    // All of these files are siblings; get its location to know where the
+    // "anchor" file is.
+    files.push_back(toInstallFile);
+    this->AddInstallRule(os, cxx_module_dest, cmInstallType_FILES, files,
+                         false, this->FilePermissions.c_str(), nullptr,
+                         nullptr, nullptr, indent);
+    files.clear();
+  }
+  for (auto const& i : this->EFGen->GetConfigCxxModuleFiles()) {
+    files.push_back(i.second);
+    std::string config_test = this->CreateConfigTest(i.first);
+    os << indent << "if(" << config_test << ")\n";
+    this->AddInstallRule(os, cxx_module_dest, cmInstallType_FILES, files,
+                         false, this->FilePermissions.c_str(), nullptr,
+                         nullptr, nullptr, indent.Next());
+    os << indent << "endif()\n";
+    files.clear();
+  }
+  for (auto const& i : this->EFGen->GetConfigCxxModuleTargetFiles()) {
+    std::string config_test = this->CreateConfigTest(i.first);
+    os << indent << "if(" << config_test << ")\n";
+    this->AddInstallRule(os, cxx_module_dest, cmInstallType_FILES, i.second,
+                         false, this->FilePermissions.c_str(), nullptr,
+                         nullptr, nullptr, indent.Next());
+    os << indent << "endif()\n";
+    files.clear();
+  }
 }
 
 void cmInstallExportGenerator::GenerateScriptActions(std::ostream& os,

+ 40 - 0
Tests/RunCMake/CXXModules/ExportBuildCxxModules-check.cmake

@@ -0,0 +1,40 @@
+file(READ "${RunCMake_TEST_BINARY_DIR}/lib/cmake/export-modules/export-modules-targets.cmake" export_script)
+
+if (NOT export_script MATCHES [[include\("\${CMAKE_CURRENT_LIST_DIR}/cxx-modules/cxx-modules\.cmake"\)]])
+  list(APPEND RunCMake_TEST_FAILED
+    "Could not find C++ module property script inclusion")
+endif ()
+
+file(READ "${RunCMake_TEST_BINARY_DIR}/lib/cmake/export-modules/cxx-modules/cxx-modules.cmake" trampoline_script)
+
+if (RunCMake_GENERATOR_IS_MULTI_CONFIG)
+  if (NOT trampoline_script MATCHES [[include\("\${CMAKE_CURRENT_LIST_DIR}/cxx-modules-[^.]*\.cmake" OPTIONAL\)]])
+    list(APPEND RunCMake_TEST_FAILED
+      "Could not find C++ module property per-config script inclusion(s)")
+  endif ()
+else ()
+  if (NOT trampoline_script MATCHES [[include\("\${CMAKE_CURRENT_LIST_DIR}/cxx-modules-[^.]*\.cmake"\)]])
+    list(APPEND RunCMake_TEST_FAILED
+      "Could not find C++ module property per-config script inclusion(s)")
+  endif ()
+endif ()
+
+set(any_exists 0)
+foreach (config IN ITEMS noconfig Debug Release RelWithDebInfo MinSizeRel)
+  if (NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/lib/cmake/export-modules/cxx-modules/cxx-modules-${config}.cmake")
+    continue ()
+  endif ()
+  set(any_exists 1)
+
+  file(READ "${RunCMake_TEST_BINARY_DIR}/lib/cmake/export-modules/cxx-modules/cxx-modules-${config}.cmake" config_script)
+
+  if (NOT config_script MATCHES "include\\(\"\\\${CMAKE_CURRENT_LIST_DIR}/target-export-name-${config}\\.cmake\"\\)")
+    list(APPEND RunCMake_TEST_FAILED
+      "Could not find C++ module per-target property script inclusion")
+  endif ()
+endforeach ()
+
+if (NOT any_exists)
+  list(APPEND RunCMake_TEST_FAILED
+    "No per-configuration target files exist.")
+endif ()

+ 11 - 0
Tests/RunCMake/CXXModules/ExportBuildCxxModules-stderr.txt

@@ -0,0 +1,11 @@
+CMake Warning \(dev\) at ExportBuildCxxModules.cmake:6 \(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\):
+  CMakeLists.txt:6 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Warning \(dev\):
+  C\+\+20 modules support via CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP is
+  experimental.  It is meant only for compiler developers to try.
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 22 - 0
Tests/RunCMake/CXXModules/ExportBuildCxxModules.cmake

@@ -0,0 +1,22 @@
+enable_language(CXX)
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
+set(CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE "")
+
+add_library(export-modules)
+target_sources(export-modules
+  PUBLIC
+    FILE_SET fs TYPE CXX_MODULES FILES
+      sources/module.cxx)
+target_compile_features(export-modules
+  PRIVATE
+    cxx_std_20)
+set_property(TARGET export-modules
+  PROPERTY EXPORT_NAME export-name)
+
+install(TARGETS export-modules
+  EXPORT exp
+  FILE_SET fs DESTINATION "include/cxx/export-modules")
+
+export(EXPORT exp
+  FILE "${CMAKE_BINARY_DIR}/lib/cmake/export-modules/export-modules-targets.cmake"
+  CXX_MODULES_DIRECTORY "cxx-modules")

+ 35 - 0
Tests/RunCMake/CXXModules/ExportInstallCxxModules-check.cmake

@@ -0,0 +1,35 @@
+file(READ "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/Export/eee57a7e91412f1be699e9b63fa9d601/exp.cmake" export_script)
+
+if (NOT export_script MATCHES [[include\("\${CMAKE_CURRENT_LIST_DIR}/cxx-modules/cxx-modules\.cmake"\)]])
+  list(APPEND RunCMake_TEST_FAILED
+    "Could not find C++ module property script inclusion")
+endif ()
+
+file(READ "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/Export/eee57a7e91412f1be699e9b63fa9d601/cxx-modules/cxx-modules.cmake" trampoline_script)
+
+if (NOT trampoline_script MATCHES [[file\(GLOB _cmake_cxx_module_includes "\${CMAKE_CURRENT_LIST_DIR}/cxx-modules-\*\.cmake"\)]])
+  list(APPEND RunCMake_TEST_FAILED
+    "Could not find C++ module property per-config script inclusion(s)")
+endif ()
+
+set(any_exists 0)
+foreach (config IN ITEMS noconfig Debug Release RelWithDebInfo MinSizeRel)
+  if (NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/Export/eee57a7e91412f1be699e9b63fa9d601/cxx-modules/cxx-modules-${config}.cmake")
+    continue ()
+  endif ()
+  set(any_exists 1)
+
+  file(READ "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/Export/eee57a7e91412f1be699e9b63fa9d601/cxx-modules/cxx-modules-${config}.cmake" config_script)
+
+  if (NOT config_script MATCHES "include\\(\"\\\${CMAKE_CURRENT_LIST_DIR}/target-export-name-${config}\\.cmake\"\\)")
+    list(APPEND RunCMake_TEST_FAILED
+      "Could not find C++ module per-target property script inclusion")
+  endif ()
+endforeach ()
+
+if (NOT any_exists)
+  list(APPEND RunCMake_TEST_FAILED
+    "No per-configuration target files exist.")
+endif ()
+
+string(REPLACE ";" "; " RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}")

+ 11 - 0
Tests/RunCMake/CXXModules/ExportInstallCxxModules-stderr.txt

@@ -0,0 +1,11 @@
+CMake Warning \(dev\) at ExportInstallCxxModules.cmake:6 \(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\):
+  CMakeLists.txt:6 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Warning \(dev\):
+  C\+\+20 modules support via CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP is
+  experimental.  It is meant only for compiler developers to try.
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 22 - 0
Tests/RunCMake/CXXModules/ExportInstallCxxModules.cmake

@@ -0,0 +1,22 @@
+enable_language(CXX)
+set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
+set(CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE "")
+
+add_library(export-modules)
+target_sources(export-modules
+  PUBLIC
+    FILE_SET fs TYPE CXX_MODULES FILES
+      sources/module.cxx)
+target_compile_features(export-modules
+  PRIVATE
+    cxx_std_20)
+set_property(TARGET export-modules
+  PROPERTY EXPORT_NAME export-name)
+
+install(TARGETS export-modules
+  EXPORT exp
+  FILE_SET fs DESTINATION "include/cxx/export-modules")
+
+install(EXPORT exp
+  DESTINATION "lib/cmake/export-modules"
+  CXX_MODULES_DIRECTORY "cxx-modules")

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

@@ -71,6 +71,9 @@ run_cmake(InstallBMI)
 run_cmake(InstallBMIGenericArgs)
 run_cmake(InstallBMIIgnore)
 
+run_cmake(ExportBuildCxxModules)
+run_cmake(ExportInstallCxxModules)
+
 # Actual compilation tests.
 if (NOT CMake_TEST_MODULE_COMPILATION)
   return ()