Pārlūkot izejas kodu

export: Add build-tree CPS support

Add Common Package Specification support to export(EXPORT).
Matthew Woehlke 8 mēneši atpakaļ
vecāks
revīzija
c8ccd5a9e3
91 mainītis faili ar 1031 papildinājumiem un 27 dzēšanām
  1. 35 2
      Help/command/export.rst
  2. 2 0
      Source/CMakeLists.txt
  3. 119 0
      Source/cmExportBuildPackageInfoGenerator.cxx
  4. 53 0
      Source/cmExportBuildPackageInfoGenerator.h
  5. 118 6
      Source/cmExportCommand.cxx
  6. 10 8
      Source/cmExportFileGenerator.cxx
  7. 2 0
      Source/cmExportFileGenerator.h
  8. 6 3
      Source/cmExportInstallPackageInfoGenerator.cxx
  9. 4 6
      Source/cmExportPackageInfoGenerator.cxx
  10. 1 2
      Source/cmExportPackageInfoGenerator.h
  11. 2 0
      Tests/RunCMake/CMakeLists.txt
  12. 3 0
      Tests/RunCMake/CpsExportImportBuild/CMakeLists.txt
  13. 23 0
      Tests/RunCMake/CpsExportImportBuild/RunCMakeTest.cmake
  14. 12 0
      Tests/RunCMake/CpsExportImportBuild/TestExecutable.cmake
  15. 12 0
      Tests/RunCMake/CpsExportImportBuild/TestLibrary.cmake
  16. 13 0
      Tests/RunCMake/CpsExportImportBuild/app.c
  17. 7 0
      Tests/RunCMake/CpsExportImportBuild/liba.c
  18. 13 0
      Tests/RunCMake/CpsExportImportBuild/libb.c
  19. 16 0
      Tests/RunCMake/ExportPackageInfo/Appendix-check.cmake
  20. 9 0
      Tests/RunCMake/ExportPackageInfo/Appendix.cmake
  21. 34 0
      Tests/RunCMake/ExportPackageInfo/Assertions.cmake
  22. 1 0
      Tests/RunCMake/ExportPackageInfo/BadArgs1-result.txt
  23. 10 0
      Tests/RunCMake/ExportPackageInfo/BadArgs1-stderr.txt
  24. 4 0
      Tests/RunCMake/ExportPackageInfo/BadArgs1.cmake
  25. 1 0
      Tests/RunCMake/ExportPackageInfo/BadArgs2-result.txt
  26. 16 0
      Tests/RunCMake/ExportPackageInfo/BadArgs2-stderr.txt
  27. 5 0
      Tests/RunCMake/ExportPackageInfo/BadArgs2.cmake
  28. 1 0
      Tests/RunCMake/ExportPackageInfo/BadArgs3-result.txt
  29. 10 0
      Tests/RunCMake/ExportPackageInfo/BadArgs3-stderr.txt
  30. 4 0
      Tests/RunCMake/ExportPackageInfo/BadArgs3.cmake
  31. 1 0
      Tests/RunCMake/ExportPackageInfo/BadArgs4-result.txt
  32. 28 0
      Tests/RunCMake/ExportPackageInfo/BadArgs4-stderr.txt
  33. 7 0
      Tests/RunCMake/ExportPackageInfo/BadArgs4.cmake
  34. 1 0
      Tests/RunCMake/ExportPackageInfo/BadDefaultTarget-result.txt
  35. 8 0
      Tests/RunCMake/ExportPackageInfo/BadDefaultTarget-stderr.txt
  36. 5 0
      Tests/RunCMake/ExportPackageInfo/BadDefaultTarget.cmake
  37. 1 0
      Tests/RunCMake/ExportPackageInfo/BadName-result.txt
  38. 4 0
      Tests/RunCMake/ExportPackageInfo/BadName-stderr.txt
  39. 3 0
      Tests/RunCMake/ExportPackageInfo/BadName.cmake
  40. 3 0
      Tests/RunCMake/ExportPackageInfo/CMakeLists.txt
  41. 3 0
      Tests/RunCMake/ExportPackageInfo/DependsMultiple.cmake
  42. 11 0
      Tests/RunCMake/ExportPackageInfo/DependsMultipleCommon.cmake
  43. 1 0
      Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentNamespace-result.txt
  44. 22 0
      Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentNamespace-stderr.txt
  45. 3 0
      Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentNamespace.cmake
  46. 1 0
      Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentSets-result.txt
  47. 22 0
      Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentSets-stderr.txt
  48. 5 0
      Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentSets.cmake
  49. 1 0
      Tests/RunCMake/ExportPackageInfo/DuplicateOutput-result.txt
  50. 4 0
      Tests/RunCMake/ExportPackageInfo/DuplicateOutput-stderr.txt
  51. 4 0
      Tests/RunCMake/ExportPackageInfo/DuplicateOutput.cmake
  52. 1 0
      Tests/RunCMake/ExportPackageInfo/ExperimentalGate-result.txt
  53. 2 0
      Tests/RunCMake/ExportPackageInfo/ExperimentalGate-stderr.txt
  54. 5 0
      Tests/RunCMake/ExportPackageInfo/ExperimentalGate.cmake
  55. 7 0
      Tests/RunCMake/ExportPackageInfo/ExperimentalWarning-stderr.txt
  56. 8 0
      Tests/RunCMake/ExportPackageInfo/ExperimentalWarning.cmake
  57. 24 0
      Tests/RunCMake/ExportPackageInfo/InterfaceProperties-check.cmake
  58. 15 0
      Tests/RunCMake/ExportPackageInfo/InterfaceProperties.cmake
  59. 9 0
      Tests/RunCMake/ExportPackageInfo/LowerCaseFile-check.cmake
  60. 4 0
      Tests/RunCMake/ExportPackageInfo/LowerCaseFile.cmake
  61. 16 0
      Tests/RunCMake/ExportPackageInfo/Metadata-check.cmake
  62. 11 0
      Tests/RunCMake/ExportPackageInfo/Metadata.cmake
  63. 18 0
      Tests/RunCMake/ExportPackageInfo/Minimal-check.cmake
  64. 3 0
      Tests/RunCMake/ExportPackageInfo/Minimal.cmake
  65. 21 0
      Tests/RunCMake/ExportPackageInfo/MinimalVersion-check.cmake
  66. 16 0
      Tests/RunCMake/ExportPackageInfo/MinimalVersion.cmake
  67. 1 0
      Tests/RunCMake/ExportPackageInfo/ReferencesNonExportedTarget-result.txt
  68. 3 0
      Tests/RunCMake/ExportPackageInfo/ReferencesNonExportedTarget-stderr.txt
  69. 6 0
      Tests/RunCMake/ExportPackageInfo/ReferencesNonExportedTarget.cmake
  70. 1 0
      Tests/RunCMake/ExportPackageInfo/ReferencesWronglyExportedTarget-result.txt
  71. 7 0
      Tests/RunCMake/ExportPackageInfo/ReferencesWronglyExportedTarget-stderr.txt
  72. 14 0
      Tests/RunCMake/ExportPackageInfo/ReferencesWronglyExportedTarget.cmake
  73. 1 0
      Tests/RunCMake/ExportPackageInfo/ReferencesWronglyImportedTarget-result.txt
  74. 3 0
      Tests/RunCMake/ExportPackageInfo/ReferencesWronglyImportedTarget-stderr.txt
  75. 7 0
      Tests/RunCMake/ExportPackageInfo/ReferencesWronglyImportedTarget.cmake
  76. 1 0
      Tests/RunCMake/ExportPackageInfo/ReferencesWronglyNamespacedTarget-result.txt
  77. 4 0
      Tests/RunCMake/ExportPackageInfo/ReferencesWronglyNamespacedTarget-stderr.txt
  78. 11 0
      Tests/RunCMake/ExportPackageInfo/ReferencesWronglyNamespacedTarget.cmake
  79. 22 0
      Tests/RunCMake/ExportPackageInfo/Requirements-check.cmake
  80. 20 0
      Tests/RunCMake/ExportPackageInfo/Requirements.cmake
  81. 37 0
      Tests/RunCMake/ExportPackageInfo/RunCMakeTest.cmake
  82. 11 0
      Tests/RunCMake/ExportPackageInfo/TargetTypes-check.cmake
  83. 20 0
      Tests/RunCMake/ExportPackageInfo/TargetTypes.cmake
  84. 1 0
      Tests/RunCMake/ExportPackageInfo/broken-config.cmake
  85. 3 0
      Tests/RunCMake/ExportPackageInfo/foo.cxx
  86. 1 0
      Tests/RunCMake/ExportPackageInfo/test-config.cmake
  87. 4 0
      Tests/RunCMake/ExportPackageInfo/test.cxx
  88. 1 0
      Tests/RunCMake/InstallPackageInfo/BadName-result.txt
  89. 4 0
      Tests/RunCMake/InstallPackageInfo/BadName-stderr.txt
  90. 3 0
      Tests/RunCMake/InstallPackageInfo/BadName.cmake
  91. 1 0
      Tests/RunCMake/InstallPackageInfo/RunCMakeTest.cmake

+ 35 - 2
Help/command/export.rst

@@ -89,10 +89,10 @@ Exporting Targets to Android.mk
 
 .. versionadded:: 3.7
 
-This signature exports cmake built targets to the android ndk build system
+This signature exports CMake built targets to the android ndk build system
 by creating an ``Android.mk`` file that references the prebuilt targets. The
 Android NDK supports the use of prebuilt libraries, both static and shared.
-This allows cmake to build the libraries of a project and make them available
+This allows CMake to build the libraries of a project and make them available
 to an ndk build system complete with transitive dependencies, include flags
 and defines required to use the libraries. The signature takes a list of
 targets and puts them in the ``Android.mk`` file specified by the
@@ -127,6 +127,36 @@ of the :command:`install(TARGETS)` command.
   Specify that :command:`find_dependency` calls should be exported. See
   :command:`install(EXPORT)` for details on how this works.
 
+Exporting Targets to the |CPS|
+""""""""""""""""""""""""""""""
+
+.. code-block:: cmake
+
+  export(EXPORT <export-name> PACKAGE_INFO <package-name>
+         [APPENDIX <appendix-name>]
+         [LOWER_CASE_FILE]
+         [VERSION <version>
+          [COMPAT_VERSION <version>]
+          [VERSION_SCHEMA <string>]]
+         [DEFAULT_TARGETS <target>...]
+         [DEFAULT_CONFIGURATIONS <config>...])
+
+.. versionadded:: 4.1
+.. note::
+
+  Experimental. Gated by ``CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO``.
+
+Creates a file in the |CPS|_ that may be included by outside projects to import
+targets named by ``<target>...`` from the current project's build tree.  See
+the :command:`install(PACKAGE_INFO)` command to export targets from an install
+tree.  The imported targets are implicitly in the namespace ``<package-name>``.
+
+The default file name is ``<package-name>[-<appendix-name>].cps``. If the
+``LOWER_CASE_FILE`` option is given, the file name will use the package name
+converted to lower case.
+
+See :command:`install(PACKAGE_INFO)` for a description of the other options.
+
 Exporting Packages
 ^^^^^^^^^^^^^^^^^^
 
@@ -221,3 +251,6 @@ Configure the parameters of an export. The arguments are as follows:
     this target. If specified, the generated code will check to see if the
     ``.xcframework`` exists, and if it does, it will use the ``.xcframework``
     as its imported location instead of the installed library.
+
+.. _CPS: https://cps-org.github.io/cps/
+.. |CPS| replace:: Common Package Specification

+ 2 - 0
Source/CMakeLists.txt

@@ -203,6 +203,8 @@ add_library(
   cmExportBuildCMakeConfigGenerator.cxx
   cmExportBuildFileGenerator.h
   cmExportBuildFileGenerator.cxx
+  cmExportBuildPackageInfoGenerator.h
+  cmExportBuildPackageInfoGenerator.cxx
   cmExportCMakeConfigGenerator.h
   cmExportCMakeConfigGenerator.cxx
   cmExportFileGenerator.h

+ 119 - 0
Source/cmExportBuildPackageInfoGenerator.cxx

@@ -0,0 +1,119 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmExportBuildPackageInfoGenerator.h"
+
+#include <cassert>
+#include <utility>
+#include <vector>
+
+#include <cmext/string_view>
+
+#include <cm3p/json/value.h>
+
+#include "cmGeneratorExpression.h"
+#include "cmStateTypes.h"
+#include "cmStringAlgorithms.h"
+
+cmExportBuildPackageInfoGenerator::cmExportBuildPackageInfoGenerator(
+  std::string packageName, std::string version, std::string versionCompat,
+  std::string versionSchema, std::vector<std::string> defaultTargets,
+  std::vector<std::string> defaultConfigurations)
+  : cmExportPackageInfoGenerator(
+      std::move(packageName), std::move(version), std::move(versionCompat),
+      std::move(versionSchema), std::move(defaultTargets),
+      std::move(defaultConfigurations))
+{
+  this->SetNamespace(cmStrCat(this->GetPackageName(), "::"_s));
+}
+
+bool cmExportBuildPackageInfoGenerator::GenerateMainFile(std::ostream& os)
+{
+  if (!this->CollectExports([&](cmGeneratorTarget const*) {})) {
+    return false;
+  }
+
+  if (!this->CheckDefaultTargets()) {
+    return false;
+  }
+
+  Json::Value root = this->GeneratePackageInfo();
+  root["cps_path"] = "@prefix@";
+
+  Json::Value& components = root["components"];
+
+  // Create all the imported targets.
+  for (auto const& exp : this->Exports) {
+    cmGeneratorTarget* const target = exp.Target;
+    cmStateEnums::TargetType targetType = this->GetExportTargetType(target);
+
+    Json::Value* const component =
+      this->GenerateImportTarget(components, target, targetType);
+    if (!component) {
+      return false;
+    }
+
+    ImportPropertyMap properties;
+    if (!this->PopulateInterfaceProperties(target, properties)) {
+      return false;
+    }
+    this->PopulateInterfaceLinkLibrariesProperty(
+      target, cmGeneratorExpression::InstallInterface, properties);
+
+    if (targetType != cmStateEnums::INTERFACE_LIBRARY) {
+      auto configurations = Json::Value{ Json::objectValue };
+
+      // Add per-configuration properties.
+      for (std::string const& c : this->Configurations) {
+        this->GenerateInterfacePropertiesConfig(configurations, target, c);
+      }
+
+      if (!configurations.empty()) {
+        (*component)["configurations"] = configurations;
+      }
+    }
+
+    // Set configuration-agnostic properties for component.
+    this->GenerateInterfaceProperties(*component, target, properties);
+  }
+
+  this->GeneratePackageRequires(root);
+
+  // Write the primary packing information file.
+  this->WritePackageInfo(root, os);
+
+  bool result = true;
+
+  return result;
+}
+
+void cmExportBuildPackageInfoGenerator::GenerateInterfacePropertiesConfig(
+  Json::Value& configurations, cmGeneratorTarget* target,
+  std::string const& config)
+{
+  std::string const& suffix = PropertyConfigSuffix(config);
+
+  ImportPropertyMap properties;
+
+  assert(this->GetExportTargetType(target) != cmStateEnums::INTERFACE_LIBRARY);
+  this->SetImportLocationProperty(config, suffix, target, properties);
+  if (properties.empty()) {
+    return;
+  }
+
+  this->SetImportDetailProperties(config, suffix, target, properties);
+
+  // TODO: PUBLIC_HEADER_LOCATION
+
+  Json::Value component =
+    this->GenerateInterfaceConfigProperties(suffix, properties);
+  if (!component.empty()) {
+    configurations[config] = std::move(component);
+  }
+}
+
+std::string cmExportBuildPackageInfoGenerator::GetCxxModulesDirectory() const
+{
+  // TODO: Implement a not-CMake-specific mechanism for providing module
+  // information.
+  return {};
+}

+ 53 - 0
Source/cmExportBuildPackageInfoGenerator.h

@@ -0,0 +1,53 @@
+/* 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 "cmExportBuildFileGenerator.h"
+#include "cmExportPackageInfoGenerator.h"
+
+namespace Json {
+class Value;
+}
+
+class cmGeneratorTarget;
+
+/** \class cmExportBuildPackageInfoGenerator
+ * \brief Generate a file exporting targets from a build tree.
+ *
+ * cmExportBuildCMakeConfigGenerator generates a file exporting targets from
+ * a build tree.  This exports the targets to the Common Package Specification
+ * (https://cps-org.github.io/cps/).
+ *
+ * This is used to implement the export() command.
+ */
+class cmExportBuildPackageInfoGenerator
+  : public cmExportBuildFileGenerator
+  , public cmExportPackageInfoGenerator
+{
+public:
+  cmExportBuildPackageInfoGenerator(
+    std::string packageName, std::string version, std::string versionCompat,
+    std::string versionSchema, std::vector<std::string> defaultTargets,
+    std::vector<std::string> defaultConfigurations);
+
+protected:
+  // Implement virtual methods from the superclass.
+  bool GenerateMainFile(std::ostream& os) override;
+  void GenerateImportTargetsConfig(std::ostream&, std::string const&,
+                                   std::string const&) override
+  {
+  }
+
+  void GenerateInterfacePropertiesConfig(Json::Value& configurations,
+                                         cmGeneratorTarget* target,
+                                         std::string const& config);
+
+  std::string GetCxxModulesDirectory() const override;
+  // TODO: Generate C++ module info in a not-CMake-specific format.
+};

+ 118 - 6
Source/cmExportCommand.cxx

@@ -21,8 +21,10 @@
 #include "cmExportBuildAndroidMKGenerator.h"
 #include "cmExportBuildCMakeConfigGenerator.h"
 #include "cmExportBuildFileGenerator.h"
+#include "cmExportBuildPackageInfoGenerator.h"
 #include "cmExportSet.h"
 #include "cmGeneratedFileStream.h"
+#include "cmGeneratorExpression.h"
 #include "cmGlobalGenerator.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
@@ -68,9 +70,17 @@ bool cmExportCommand(std::vector<std::string> const& args,
     ArgumentParser::NonEmpty<std::string> Namespace;
     ArgumentParser::NonEmpty<std::string> Filename;
     ArgumentParser::NonEmpty<std::string> AndroidMKFile;
+    ArgumentParser::NonEmpty<std::string> PackageName;
+    ArgumentParser::NonEmpty<std::string> Appendix;
+    ArgumentParser::NonEmpty<std::string> Version;
+    ArgumentParser::NonEmpty<std::string> VersionCompat;
+    ArgumentParser::NonEmpty<std::string> VersionSchema;
     ArgumentParser::NonEmpty<std::string> CxxModulesDirectory;
+    ArgumentParser::NonEmpty<std::vector<std::string>> DefaultTargets;
+    ArgumentParser::NonEmpty<std::vector<std::string>> DefaultConfigs;
     bool Append = false;
     bool ExportOld = false;
+    bool LowerCase = false;
 
     std::vector<std::vector<std::string>> PackageDependencyArgs;
     bool ExportPackageDependencies = false;
@@ -92,6 +102,17 @@ bool cmExportCommand(std::vector<std::string> const& args,
       parser.Bind("EXPORT_PACKAGE_DEPENDENCIES"_s,
                   &Arguments::ExportPackageDependencies);
     }
+    if (cmExperimental::HasSupportEnabled(
+          status.GetMakefile(), cmExperimental::Feature::ExportPackageInfo)) {
+      parser.Bind("PACKAGE_INFO"_s, &Arguments::PackageName);
+      parser.Bind("LOWER_CASE_FILE"_s, &Arguments::LowerCase);
+      parser.Bind("APPENDIX"_s, &Arguments::Appendix);
+      parser.Bind("VERSION"_s, &Arguments::Version);
+      parser.Bind("COMPAT_VERSION"_s, &Arguments::VersionCompat);
+      parser.Bind("VERSION_SCHEMA"_s, &Arguments::VersionSchema);
+      parser.Bind("DEFAULT_TARGETS"_s, &Arguments::DefaultTargets);
+      parser.Bind("DEFAULT_CONFIGURATIONS"_s, &Arguments::DefaultConfigs);
+    }
   } else if (args[0] == "SETUP") {
     parser.Bind("SETUP"_s, &Arguments::ExportSetName);
     if (cmExperimental::HasSupportEnabled(
@@ -200,19 +221,98 @@ bool cmExportCommand(std::vector<std::string> const& args,
     return true;
   }
 
+  if (arguments.PackageName.empty()) {
+    if (arguments.LowerCase) {
+      status.SetError("LOWER_CASE_FILE requires PACKAGE_INFO.");
+      return false;
+    }
+    if (!arguments.Appendix.empty()) {
+      status.SetError("APPENDIX requires PACKAGE_INFO.");
+      return false;
+    }
+    if (!arguments.Version.empty()) {
+      status.SetError("VERSION requires PACKAGE_INFO.");
+      return false;
+    }
+    if (!arguments.DefaultTargets.empty()) {
+      status.SetError("DEFAULT_TARGETS requires PACKAGE_INFO.");
+      return false;
+    }
+    if (!arguments.DefaultConfigs.empty()) {
+      status.SetError("DEFAULT_CONFIGURATIONS requires PACKAGE_INFO.");
+      return false;
+    }
+  } else {
+    if (!arguments.Filename.empty()) {
+      status.SetError("PACKAGE_INFO and FILE are mutually exclusive.");
+      return false;
+    }
+    if (!arguments.Namespace.empty()) {
+      status.SetError("PACKAGE_INFO and NAMESPACE are mutually exclusive.");
+      return false;
+    }
+    if (!arguments.Appendix.empty()) {
+      if (!arguments.Version.empty()) {
+        status.SetError("APPENDIX and VERSION are mutually exclusive.");
+        return false;
+      }
+      if (!arguments.DefaultTargets.empty()) {
+        status.SetError("APPENDIX and DEFAULT_TARGETS "
+                        "are mutually exclusive.");
+        return false;
+      }
+      if (!arguments.DefaultConfigs.empty()) {
+        status.SetError("APPENDIX and DEFAULT_CONFIGURATIONS "
+                        "are mutually exclusive.");
+        return false;
+      }
+    }
+  }
+  if (arguments.Version.empty()) {
+    if (!arguments.VersionCompat.empty()) {
+      status.SetError("COMPAT_VERSION requires VERSION.");
+      return false;
+    }
+    if (!arguments.VersionSchema.empty()) {
+      status.SetError("VERSION_SCHEMA requires VERSION.");
+      return false;
+    }
+  }
+
   std::string fname;
   bool android = false;
+  bool cps = false;
   if (!arguments.AndroidMKFile.empty()) {
     fname = arguments.AndroidMKFile;
     android = true;
-  }
-  if (arguments.Filename.empty() && fname.empty()) {
+  } else if (arguments.Filename.empty()) {
     if (args[0] != "EXPORT") {
       status.SetError("FILE <filename> option missing.");
       return false;
     }
-    fname = arguments.ExportSetName + ".cmake";
-  } else if (fname.empty()) {
+    if (arguments.PackageName.empty()) {
+      fname = arguments.ExportSetName + ".cmake";
+    } else {
+      // Validate the package name.
+      if (!cmGeneratorExpression::IsValidTargetName(arguments.PackageName) ||
+          arguments.PackageName.find(':') != std::string::npos) {
+        status.SetError(
+          cmStrCat(R"(PACKAGE_INFO given invalid package name ")"_s,
+                   arguments.PackageName, R"(".)"_s));
+        return false;
+      }
+
+      std::string const pkgNameOnDisk =
+        (arguments.LowerCase ? cmSystemTools::LowerCase(arguments.PackageName)
+                             : std::string{ arguments.PackageName });
+      if (arguments.Appendix.empty()) {
+        fname = cmStrCat(pkgNameOnDisk, ".cps"_s);
+      } else {
+        fname = cmStrCat(pkgNameOnDisk, '-', arguments.Appendix, ".cps"_s);
+      }
+      cps = true;
+    }
+  } else {
     // Make sure the file has a .cmake extension.
     if (cmSystemTools::GetFilenameLastExtension(arguments.Filename) !=
         ".cmake") {
@@ -298,6 +398,11 @@ bool cmExportCommand(std::vector<std::string> const& args,
   // and APPEND is not specified, if CMP0103 is OLD ignore previous definition
   // else raise an error
   if (gg->GetExportedTargetsFile(fname)) {
+    if (cps) {
+      status.SetError(cmStrCat("command already specified for the file "_s,
+                               cmSystemTools::GetFilenameName(fname), '.'));
+      return false;
+    }
     switch (mf.GetPolicyStatus(cmPolicies::CMP0103)) {
       case cmPolicies::WARN:
         mf.IssueMessage(
@@ -316,21 +421,28 @@ bool cmExportCommand(std::vector<std::string> const& args,
     }
   }
 
-  // Setup export file generation.
+  // Set up export file generation.
   std::unique_ptr<cmExportBuildFileGenerator> ebfg = nullptr;
   if (android) {
     auto ebag = cm::make_unique<cmExportBuildAndroidMKGenerator>();
+    ebag->SetNamespace(arguments.Namespace);
     ebag->SetAppendMode(arguments.Append);
     ebfg = std::move(ebag);
+  } else if (cps) {
+    auto ebpg = cm::make_unique<cmExportBuildPackageInfoGenerator>(
+      arguments.PackageName, arguments.Version, arguments.VersionCompat,
+      arguments.VersionSchema, arguments.DefaultTargets,
+      arguments.DefaultConfigs);
+    ebfg = std::move(ebpg);
   } else {
     auto ebcg = cm::make_unique<cmExportBuildCMakeConfigGenerator>();
+    ebcg->SetNamespace(arguments.Namespace);
     ebcg->SetAppendMode(arguments.Append);
     ebcg->SetExportOld(arguments.ExportOld);
     ebcg->SetExportPackageDependencies(arguments.ExportPackageDependencies);
     ebfg = std::move(ebcg);
   }
   ebfg->SetExportFile(fname.c_str());
-  ebfg->SetNamespace(arguments.Namespace);
   ebfg->SetCxxModuleDirectory(arguments.CxxModulesDirectory);
   if (exportSet) {
     ebfg->SetExportSet(exportSet);

+ 10 - 8
Source/cmExportFileGenerator.cxx

@@ -78,19 +78,21 @@ bool cmExportFileGenerator::GenerateImportFile()
   return this->GenerateImportFile(*foutPtr);
 }
 
-void cmExportFileGenerator::GenerateImportConfig(std::ostream& os,
-                                                 std::string const& config)
+std::string cmExportFileGenerator::PropertyConfigSuffix(
+  std::string const& config)
 {
   // Construct the property configuration suffix.
-  std::string suffix = "_";
-  if (!config.empty()) {
-    suffix += cmSystemTools::UpperCase(config);
-  } else {
-    suffix += "NOCONFIG";
+  if (config.empty()) {
+    return "_NOCONFIG";
   }
+  return cmStrCat('_', cmSystemTools::UpperCase(config));
+}
 
+void cmExportFileGenerator::GenerateImportConfig(std::ostream& os,
+                                                 std::string const& config)
+{
   // Generate the per-config target information.
-  this->GenerateImportTargetsConfig(os, config, suffix);
+  this->GenerateImportTargetsConfig(os, config, PropertyConfigSuffix(config));
 }
 
 bool cmExportFileGenerator::PopulateInterfaceProperties(

+ 2 - 0
Source/cmExportFileGenerator.h

@@ -170,6 +170,8 @@ protected:
   bool AddTargetNamespace(std::string& input, cmGeneratorTarget const* target,
                           cmLocalGenerator const* lg);
 
+  static std::string PropertyConfigSuffix(std::string const& config);
+
   // The namespace in which the exports are placed in the generated file.
   std::string Namespace;
 

+ 6 - 3
Source/cmExportInstallPackageInfoGenerator.cxx

@@ -71,8 +71,8 @@ bool cmExportInstallPackageInfoGenerator::GenerateMainFile(std::ostream& os)
   }
   root["cps_path"] = packagePath;
 
-  bool requiresConfigFiles = false;
   // Create all the imported targets.
+  bool requiresConfigFiles = false;
   for (cmTargetExport const* te : allTargets) {
     cmGeneratorTarget* gt = te->Target;
     cmStateEnums::TargetType targetType = this->GetExportTargetType(te);
@@ -139,8 +139,11 @@ void cmExportInstallPackageInfoGenerator::GenerateImportTargetsConfig(
     this->PopulateImportProperties(config, suffix, te.get(), properties,
                                    importedLocations);
 
-    this->GenerateInterfaceConfigProperties(components, te->Target, suffix,
-                                            properties);
+    Json::Value component =
+      this->GenerateInterfaceConfigProperties(suffix, properties);
+    if (!component.empty()) {
+      components[te->Target->GetExportName()] = std::move(component);
+    }
   }
 
   this->WritePackageInfo(root, os);

+ 4 - 6
Source/cmExportPackageInfoGenerator.cxx

@@ -277,7 +277,7 @@ bool cmExportPackageInfoGenerator::NoteLinkedTarget(
     return true;
   }
 
-  // Target belongs to multiple namespaces or multiple export sets.
+  // Target belongs to another export from this build.
   auto const& exportInfo = this->FindExportInfo(linkedTarget);
   if (exportInfo.Namespaces.size() == 1 && exportInfo.Sets.size() == 1) {
     auto const& linkNamespace = *exportInfo.Namespaces.begin();
@@ -302,6 +302,7 @@ bool cmExportPackageInfoGenerator::NoteLinkedTarget(
     return true;
   }
 
+  // Target belongs to multiple namespaces or multiple export sets.
   // cmExportFileGenerator::HandleMissingTarget should have complained about
   // this already.
   return false;
@@ -424,8 +425,7 @@ void cmExportPackageInfoGenerator::GenerateInterfaceListProperty(
   }
 }
 
-void cmExportPackageInfoGenerator::GenerateInterfaceConfigProperties(
-  Json::Value& components, cmGeneratorTarget const* target,
+Json::Value cmExportPackageInfoGenerator::GenerateInterfaceConfigProperties(
   std::string const& suffix, ImportPropertyMap const& properties) const
 {
   Json::Value component;
@@ -456,7 +456,5 @@ void cmExportPackageInfoGenerator::GenerateInterfaceConfigProperties(
     }
   }
 
-  if (!component.empty()) {
-    components[target->GetExportName()] = component;
-  }
+  return component;
 }

+ 1 - 2
Source/cmExportPackageInfoGenerator.h

@@ -62,8 +62,7 @@ protected:
   bool GenerateInterfaceProperties(Json::Value& component,
                                    cmGeneratorTarget const* target,
                                    ImportPropertyMap const& properties) const;
-  void GenerateInterfaceConfigProperties(
-    Json::Value& components, cmGeneratorTarget const* target,
+  Json::Value GenerateInterfaceConfigProperties(
     std::string const& suffix, ImportPropertyMap const& properties) const;
 
   cm::string_view GetImportPrefixWithSlash() const override;

+ 2 - 0
Tests/RunCMake/CMakeLists.txt

@@ -397,6 +397,7 @@ set_property(TEST RunCMake.CompilerId APPEND PROPERTY LABELS "CUDA" "HIP" "ISPC"
 add_RunCMake_test(CompilerTest ${CMake_TEST_LANG_VARS})
 set_property(TEST RunCMake.CompilerTest APPEND PROPERTY LABELS "CUDA" "HIP" "ISPC" "Fortran")
 add_RunCMake_test(Configure -DMSVC_IDE=${MSVC_IDE})
+add_RunCMake_test(CpsExportImportBuild)
 add_RunCMake_test(CpsExportImportInstall)
 add_RunCMake_test(DisallowedCommands)
 if("${CMAKE_GENERATOR}" MATCHES "Unix Makefiles|Ninja")
@@ -1205,6 +1206,7 @@ add_RunCMake_test(AutoExportDll
   )
 
 add_RunCMake_test(AndroidMK)
+add_RunCMake_test(ExportPackageInfo)
 add_RunCMake_test(InstallPackageInfo)
 
 if(CMake_TEST_ANDROID_NDK OR CMake_TEST_ANDROID_STANDALONE_TOOLCHAIN)

+ 3 - 0
Tests/RunCMake/CpsExportImportBuild/CMakeLists.txt

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 4.0.20250220)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)

+ 23 - 0
Tests/RunCMake/CpsExportImportBuild/RunCMakeTest.cmake

@@ -0,0 +1,23 @@
+include(RunCMake)
+
+set(RunCMake_TEST_OPTIONS
+  -Wno-dev
+  "-DCMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO:STRING=b80be207-778e-46ba-8080-b23bba22639e"
+  "-DCMAKE_EXPERIMENTAL_FIND_CPS_PACKAGES:STRING=e82e467b-f997-4464-8ace-b00808fff261"
+  )
+
+function(build_project test)
+  set(RunCMake_TEST_NO_CLEAN FALSE)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-build)
+  if (NOT RunCMake_GENERATOR_IS_MULTI_CONFIG)
+    list(APPEND RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Release)
+  endif()
+
+  run_cmake(${test})
+
+  set(RunCMake_TEST_NO_CLEAN TRUE)
+  run_cmake_command(${test}-build ${CMAKE_COMMAND} --build . --config Release)
+endfunction()
+
+build_project(TestLibrary)
+build_project(TestExecutable)

+ 12 - 0
Tests/RunCMake/CpsExportImportBuild/TestExecutable.cmake

@@ -0,0 +1,12 @@
+project(TestLibrary C)
+
+set(liba_DIR "${CMAKE_BINARY_DIR}/../TestLibrary-build")
+set(libb_DIR "${CMAKE_BINARY_DIR}/../TestLibrary-build")
+
+find_package(libb REQUIRED COMPONENTS libb)
+
+add_executable(app app.c)
+
+target_link_libraries(app PUBLIC libb::libb)
+
+install(TARGETS app DESTINATION bin)

+ 12 - 0
Tests/RunCMake/CpsExportImportBuild/TestLibrary.cmake

@@ -0,0 +1,12 @@
+project(TestLibrary C)
+
+add_library(liba SHARED liba.c)
+add_library(libb SHARED libb.c)
+
+target_link_libraries(libb PUBLIC liba)
+
+install(TARGETS liba EXPORT liba DESTINATION lib)
+export(EXPORT liba PACKAGE_INFO liba)
+
+install(TARGETS libb EXPORT libb DESTINATION lib)
+export(EXPORT libb PACKAGE_INFO libb)

+ 13 - 0
Tests/RunCMake/CpsExportImportBuild/app.c

@@ -0,0 +1,13 @@
+#include <stdio.h>
+
+extern
+#ifdef _WIN32
+__declspec(dllimport)
+#endif
+int ask(void);
+
+int main(void)
+{
+  printf("%i\n", ask());
+  return 0;
+}

+ 7 - 0
Tests/RunCMake/CpsExportImportBuild/liba.c

@@ -0,0 +1,7 @@
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int answer(void)
+{
+  return 42;
+}

+ 13 - 0
Tests/RunCMake/CpsExportImportBuild/libb.c

@@ -0,0 +1,13 @@
+extern
+#ifdef _WIN32
+__declspec(dllimport)
+#endif
+int answer(void);
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int ask(void)
+{
+  return answer();
+}

+ 16 - 0
Tests/RunCMake/ExportPackageInfo/Appendix-check.cmake

@@ -0,0 +1,16 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/Appendix-build")
+
+file(READ "${out_dir}/foo.cps" content)
+expect_value("${content}" "foo" "name")
+expect_value("${content}" "interface" "components" "mammal" "type")
+expect_value("${content}" "1.0" "version")
+
+file(READ "${out_dir}/foo-dog.cps" content)
+expect_value("${content}" "foo" "name")
+expect_value("${content}" "interface" "components" "canine" "type")
+expect_missing("${content}" "version")
+
+expect_array("${content}" 1 "components" "canine" "requires")
+expect_value("${content}" ":mammal" "components" "canine" "requires" 0)

+ 9 - 0
Tests/RunCMake/ExportPackageInfo/Appendix.cmake

@@ -0,0 +1,9 @@
+add_library(mammal INTERFACE)
+add_library(canine INTERFACE)
+target_link_libraries(canine INTERFACE mammal)
+
+install(TARGETS mammal EXPORT mammal DESTINATION .)
+install(TARGETS canine EXPORT canine DESTINATION .)
+
+export(EXPORT mammal PACKAGE_INFO foo VERSION 1.0)
+export(EXPORT canine PACKAGE_INFO foo APPENDIX dog)

+ 34 - 0
Tests/RunCMake/ExportPackageInfo/Assertions.cmake

@@ -0,0 +1,34 @@
+macro(_expect entity op actual expected)
+  if(NOT "${actual}" ${op} "${expected}")
+    list(JOIN ARGN "." name)
+    set(RunCMake_TEST_FAILED
+      "Attribute '${name}' ${entity} '${actual}' does not match expected ${entity} '${expected}'" PARENT_SCOPE)
+    return()
+  endif()
+endmacro()
+
+function(expect_value content expected_value)
+  string(JSON actual_value GET "${content}" ${ARGN})
+  _expect("value" STREQUAL "${actual_value}" "${expected_value}" ${ARGN})
+endfunction()
+
+function(expect_array content expected_length)
+  string(JSON actual_type TYPE "${content}" ${ARGN})
+  _expect("type" STREQUAL "${actual_type}" "ARRAY" ${ARGN})
+
+  string(JSON actual_length LENGTH "${content}" ${ARGN})
+  _expect("length" EQUAL "${actual_length}" "${expected_length}" ${ARGN})
+endfunction()
+
+function(expect_null content)
+  string(JSON actual_type TYPE "${content}" ${ARGN})
+  _expect("type" STREQUAL "${actual_type}" "NULL" ${ARGN})
+endfunction()
+
+function(expect_missing content)
+  string(JSON value ERROR_VARIABLE error GET "${content}" ${ARGN})
+  if(NOT value MATCHES "^(.*-)?NOTFOUND$")
+    set(RunCMake_TEST_FAILED
+      "Attribute '${ARGN}' is unexpectedly present" PARENT_SCOPE)
+  endif()
+endfunction()

+ 1 - 0
Tests/RunCMake/ExportPackageInfo/BadArgs1-result.txt

@@ -0,0 +1 @@
+1

+ 10 - 0
Tests/RunCMake/ExportPackageInfo/BadArgs1-stderr.txt

@@ -0,0 +1,10 @@
+CMake Error at BadArgs1\.cmake:3 \(export\):
+  export COMPAT_VERSION requires VERSION\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Error at BadArgs1\.cmake:4 \(export\):
+  export VERSION_SCHEMA requires VERSION\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)

+ 4 - 0
Tests/RunCMake/ExportPackageInfo/BadArgs1.cmake

@@ -0,0 +1,4 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+export(EXPORT foo PACKAGE_INFO foo COMPAT_VERSION 1.0)
+export(EXPORT foo PACKAGE_INFO foo VERSION_SCHEMA simple)

+ 1 - 0
Tests/RunCMake/ExportPackageInfo/BadArgs2-result.txt

@@ -0,0 +1 @@
+1

+ 16 - 0
Tests/RunCMake/ExportPackageInfo/BadArgs2-stderr.txt

@@ -0,0 +1,16 @@
+CMake Error at BadArgs2\.cmake:3 \(export\):
+  export APPENDIX and VERSION are mutually exclusive\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Error at BadArgs2\.cmake:4 \(export\):
+  export APPENDIX and DEFAULT_TARGETS are mutually exclusive\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Error at BadArgs2\.cmake:5 \(export\):
+  export APPENDIX and DEFAULT_CONFIGURATIONS are mutually exclusive\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)

+ 5 - 0
Tests/RunCMake/ExportPackageInfo/BadArgs2.cmake

@@ -0,0 +1,5 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+export(EXPORT foo PACKAGE_INFO foo APPENDIX test VERSION 1.0)
+export(EXPORT foo PACKAGE_INFO foo APPENDIX test DEFAULT_TARGETS foo)
+export(EXPORT foo PACKAGE_INFO foo APPENDIX test DEFAULT_CONFIGURATIONS Release)

+ 1 - 0
Tests/RunCMake/ExportPackageInfo/BadArgs3-result.txt

@@ -0,0 +1 @@
+1

+ 10 - 0
Tests/RunCMake/ExportPackageInfo/BadArgs3-stderr.txt

@@ -0,0 +1,10 @@
+CMake Error at BadArgs3\.cmake:3 \(export\):
+  export PACKAGE_INFO and FILE are mutually exclusive\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Error at BadArgs3\.cmake:4 \(export\):
+  export PACKAGE_INFO and NAMESPACE are mutually exclusive\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)

+ 4 - 0
Tests/RunCMake/ExportPackageInfo/BadArgs3.cmake

@@ -0,0 +1,4 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+export(EXPORT foo PACKAGE_INFO foo FILE foo.cps)
+export(EXPORT foo PACKAGE_INFO foo NAMESPACE foo::)

+ 1 - 0
Tests/RunCMake/ExportPackageInfo/BadArgs4-result.txt

@@ -0,0 +1 @@
+1

+ 28 - 0
Tests/RunCMake/ExportPackageInfo/BadArgs4-stderr.txt

@@ -0,0 +1,28 @@
+CMake Error at BadArgs4\.cmake:3 \(export\):
+  export LOWER_CASE_FILE requires PACKAGE_INFO\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Error at BadArgs4\.cmake:4 \(export\):
+  export APPENDIX requires PACKAGE_INFO\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Error at BadArgs4\.cmake:5 \(export\):
+  export VERSION requires PACKAGE_INFO\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Error at BadArgs4\.cmake:6 \(export\):
+  export DEFAULT_TARGETS requires PACKAGE_INFO\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Error at BadArgs4\.cmake:7 \(export\):
+  export DEFAULT_CONFIGURATIONS requires PACKAGE_INFO\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)

+ 7 - 0
Tests/RunCMake/ExportPackageInfo/BadArgs4.cmake

@@ -0,0 +1,7 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+export(EXPORT foo LOWER_CASE_FILE)
+export(EXPORT foo APPENDIX test)
+export(EXPORT foo VERSION 1.0)
+export(EXPORT foo DEFAULT_TARGETS foo)
+export(EXPORT foo DEFAULT_CONFIGURATIONS Release)

+ 1 - 0
Tests/RunCMake/ExportPackageInfo/BadDefaultTarget-result.txt

@@ -0,0 +1 @@
+1

+ 8 - 0
Tests/RunCMake/ExportPackageInfo/BadDefaultTarget-stderr.txt

@@ -0,0 +1,8 @@
+CMake Error in CMakeLists.txt:
+  Package "test" specifies DEFAULT_TARGETS "dog", which is not a target in
+  the export set "foo".
+
+
+CMake Error in CMakeLists.txt:
+  Package "test" specifies DEFAULT_TARGETS "cat", which is not a target in
+  the export set "foo".

+ 5 - 0
Tests/RunCMake/ExportPackageInfo/BadDefaultTarget.cmake

@@ -0,0 +1,5 @@
+add_library(foo INTERFACE)
+add_library(dog INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+install(TARGETS dog EXPORT dog DESTINATION .)
+export(EXPORT foo PACKAGE_INFO test DEFAULT_TARGETS dog cat)

+ 1 - 0
Tests/RunCMake/ExportPackageInfo/BadName-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/ExportPackageInfo/BadName-stderr.txt

@@ -0,0 +1,4 @@
+CMake Error at BadName\.cmake:3 \(export\):
+  export PACKAGE_INFO given invalid package name "%foo"\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)

+ 3 - 0
Tests/RunCMake/ExportPackageInfo/BadName.cmake

@@ -0,0 +1,3 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+export(EXPORT foo PACKAGE_INFO "%foo")

+ 3 - 0
Tests/RunCMake/ExportPackageInfo/CMakeLists.txt

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 4.0.20240220)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)

+ 3 - 0
Tests/RunCMake/ExportPackageInfo/DependsMultiple.cmake

@@ -0,0 +1,3 @@
+project(DependsMultiple CXX)
+set(NAMESPACE foo::)
+include(DependsMultipleCommon.cmake)

+ 11 - 0
Tests/RunCMake/ExportPackageInfo/DependsMultipleCommon.cmake

@@ -0,0 +1,11 @@
+add_library(foo foo.cxx)
+add_library(bar foo.cxx)
+target_link_libraries(bar foo)
+
+install(TARGETS foo EXPORT foo)
+export(EXPORT foo NAMESPACE ${NAMESPACE})
+export(EXPORT foo PACKAGE_INFO foo)
+
+install(TARGETS bar EXPORT bar)
+export(EXPORT bar)
+export(EXPORT bar PACKAGE_INFO bar)

+ 1 - 0
Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentNamespace-result.txt

@@ -0,0 +1 @@
+1

+ 22 - 0
Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentNamespace-stderr.txt

@@ -0,0 +1,22 @@
+CMake Error in CMakeLists\.txt:
+  export called with target "bar" which requires target "foo" that is not in
+  this export set, but in another export set which is exported multiple times
+  with different namespaces:.*
+  .*/Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentNamespace-build/foo\.cmake,.*
+  .*/Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentNamespace-build/foo\.cps\.
+
+  An exported target cannot depend upon another target which is exported in
+  more than one export set or with more than one namespace\.  Consider
+  consolidating the exports of the "foo" target to a single export\.
+
+
+CMake Error in CMakeLists\.txt:
+  export called with target "bar" which requires target "foo" that is not in
+  this export set, but in another export set which is exported multiple times
+  with different namespaces:.*
+  .*/Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentNamespace-build/foo\.cmake,.*
+  .*/Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentNamespace-build/foo\.cps\.
+
+  An exported target cannot depend upon another target which is exported in
+  more than one export set or with more than one namespace\.  Consider
+  consolidating the exports of the "foo" target to a single export\.

+ 3 - 0
Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentNamespace.cmake

@@ -0,0 +1,3 @@
+project(DependsMultipleDifferentNamespace CXX)
+set(NAMESPACE "")
+include(DependsMultipleCommon.cmake)

+ 1 - 0
Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentSets-result.txt

@@ -0,0 +1 @@
+1

+ 22 - 0
Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentSets-stderr.txt

@@ -0,0 +1,22 @@
+CMake Error in CMakeLists\.txt:
+  export called with target "bar" which requires target "foo" that is not in
+  this export set, but in multiple other export sets:.*
+  .*/Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentSets-build/foo-alt\.cmake,.*
+  .*/Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentSets-build/foo\.cmake,.*
+  .*/Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentSets-build/foo\.cps\.
+
+  An exported target cannot depend upon another target which is exported in
+  more than one export set or with more than one namespace\.  Consider
+  consolidating the exports of the "foo" target to a single export\.
+
+
+CMake Error in CMakeLists\.txt:
+  export called with target "bar" which requires target "foo" that is not in
+  this export set, but in multiple other export sets:.*
+  .*/Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentSets-build/foo-alt\.cmake,.*
+  .*/Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentSets-build/foo\.cmake,.*
+  .*/Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentSets-build/foo\.cps\.
+
+  An exported target cannot depend upon another target which is exported in
+  more than one export set or with more than one namespace\.  Consider
+  consolidating the exports of the "foo" target to a single export\.

+ 5 - 0
Tests/RunCMake/ExportPackageInfo/DependsMultipleDifferentSets.cmake

@@ -0,0 +1,5 @@
+project(DependsMultipleDifferentSets CXX)
+include(DependsMultipleCommon.cmake)
+
+install(TARGETS foo EXPORT foo-alt)
+export(EXPORT foo-alt)

+ 1 - 0
Tests/RunCMake/ExportPackageInfo/DuplicateOutput-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/ExportPackageInfo/DuplicateOutput-stderr.txt

@@ -0,0 +1,4 @@
+CMake Error at DuplicateOutput\.cmake:4 \(export\):
+  export command already specified for the file foo\.cps\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)

+ 4 - 0
Tests/RunCMake/ExportPackageInfo/DuplicateOutput.cmake

@@ -0,0 +1,4 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+export(EXPORT foo PACKAGE_INFO foo)
+export(EXPORT foo PACKAGE_INFO foo)

+ 1 - 0
Tests/RunCMake/ExportPackageInfo/ExperimentalGate-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/ExportPackageInfo/ExperimentalGate-stderr.txt

@@ -0,0 +1,2 @@
+CMake Error at ExperimentalGate.cmake:5 \(export\):
+  export Unknown argument: "PACKAGE_INFO"\.

+ 5 - 0
Tests/RunCMake/ExportPackageInfo/ExperimentalGate.cmake

@@ -0,0 +1,5 @@
+unset(CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO)
+
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+export(EXPORT foo PACKAGE_INFO foo)

+ 7 - 0
Tests/RunCMake/ExportPackageInfo/ExperimentalWarning-stderr.txt

@@ -0,0 +1,7 @@
+CMake Warning \(dev\) at ExperimentalWarning.cmake:8 \(export\):
+  CMake's support for exporting package information in the Common Package
+  Specification format is experimental\.  It is meant only for experimentation
+  and feedback to CMake developers\.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.

+ 8 - 0
Tests/RunCMake/ExportPackageInfo/ExperimentalWarning.cmake

@@ -0,0 +1,8 @@
+set(
+  CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO
+  "b80be207-778e-46ba-8080-b23bba22639e"
+  )
+
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+export(EXPORT foo PACKAGE_INFO foo)

+ 24 - 0
Tests/RunCMake/ExportPackageInfo/InterfaceProperties-check.cmake

@@ -0,0 +1,24 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/InterfaceProperties-build")
+
+file(READ "${out_dir}/foo.cps" content)
+expect_value("${content}" "foo" "name")
+
+string(JSON component GET "${content}" "components" "foo")
+
+expect_value("${component}" "interface" "type")
+expect_array("${component}" 1 "includes")
+expect_value("${component}" "${CMAKE_CURRENT_LIST_DIR}/foo" "includes" 0)
+expect_array("${component}" 1 "compile_features")
+expect_value("${component}" "c++23" "compile_features" 0)
+expect_array("${component}" 1 "compile_flags")
+expect_value("${component}" "-ffast-math" "compile_flags" 0)
+expect_null("${component}" "compile_definitions" "*" "FOO")
+expect_value("${component}" "BAR" "compile_definitions" "*" "BAR")
+expect_array("${component}" 1 "link_directories")
+expect_value("${component}" "/opt/foo/lib" "link_directories" 0)
+expect_array("${component}" 1 "link_flags")
+expect_value("${component}" "--needed" "link_flags" 0)
+expect_array("${component}" 1 "link_libraries")
+expect_value("${component}" "/usr/lib/libm.so" "link_libraries" 0)

+ 15 - 0
Tests/RunCMake/ExportPackageInfo/InterfaceProperties.cmake

@@ -0,0 +1,15 @@
+add_library(foo INTERFACE)
+
+target_compile_features(foo INTERFACE cxx_std_23)
+target_compile_options(foo INTERFACE -ffast-math)
+target_compile_definitions(foo INTERFACE -DFOO -DBAR=BAR)
+target_include_directories(
+  foo INTERFACE
+  $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/foo>
+  )
+target_link_directories(foo INTERFACE /opt/foo/lib)
+target_link_options(foo INTERFACE --needed)
+target_link_libraries(foo INTERFACE /usr/lib/libm.so)
+
+install(TARGETS foo EXPORT foo DESTINATION .)
+export(EXPORT foo PACKAGE_INFO foo)

+ 9 - 0
Tests/RunCMake/ExportPackageInfo/LowerCaseFile-check.cmake

@@ -0,0 +1,9 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/LowerCaseFile-build")
+
+file(READ "${out_dir}/lowercase.cps" content)
+expect_value("${content}" "LowerCase" "name")
+
+file(READ "${out_dir}/PreserveCase.cps" content)
+expect_value("${content}" "PreserveCase" "name")

+ 4 - 0
Tests/RunCMake/ExportPackageInfo/LowerCaseFile.cmake

@@ -0,0 +1,4 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+export(EXPORT foo PACKAGE_INFO LowerCase LOWER_CASE_FILE)
+export(EXPORT foo PACKAGE_INFO PreserveCase)

+ 16 - 0
Tests/RunCMake/ExportPackageInfo/Metadata-check.cmake

@@ -0,0 +1,16 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/Metadata-build")
+
+file(READ "${out_dir}/foo.cps" content)
+expect_value("${content}" "foo" "name")
+expect_value("${content}" "1.2.3" "version")
+expect_value("${content}" "1.2.0" "compat_version")
+expect_value("${content}" "simple" "version_schema")
+
+expect_array("${content}" 1 "default_components")
+expect_value("${content}" "foo" "default_components" 0)
+
+expect_array("${content}" 2 "configurations")
+expect_value("${content}" "release" "configurations" 0)
+expect_value("${content}" "debug" "configurations" 1)

+ 11 - 0
Tests/RunCMake/ExportPackageInfo/Metadata.cmake

@@ -0,0 +1,11 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+export(
+  EXPORT foo
+  PACKAGE_INFO foo
+  VERSION 1.2.3
+  VERSION_SCHEMA simple
+  COMPAT_VERSION 1.2.0
+  DEFAULT_TARGETS foo
+  DEFAULT_CONFIGURATIONS release debug
+  )

+ 18 - 0
Tests/RunCMake/ExportPackageInfo/Minimal-check.cmake

@@ -0,0 +1,18 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/Minimal-build")
+
+file(READ "${out_dir}/foo.cps" content)
+expect_value("${content}" "foo" "name")
+expect_value("${content}" "interface" "components" "foo" "type")
+expect_missing("${content}" "version")
+expect_missing("${content}" "configurations")
+expect_missing("${content}" "default_targets")
+expect_missing("${content}" "components" "foo" "compile_definitions")
+expect_missing("${content}" "components" "foo" "compile_features")
+expect_missing("${content}" "components" "foo" "compile_flags")
+expect_missing("${content}" "components" "foo" "link_directories")
+expect_missing("${content}" "components" "foo" "link_features")
+expect_missing("${content}" "components" "foo" "link_flags")
+expect_missing("${content}" "components" "foo" "link_libraries")
+expect_missing("${content}" "components" "foo" "requires")

+ 3 - 0
Tests/RunCMake/ExportPackageInfo/Minimal.cmake

@@ -0,0 +1,3 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+export(EXPORT foo PACKAGE_INFO foo)

+ 21 - 0
Tests/RunCMake/ExportPackageInfo/MinimalVersion-check.cmake

@@ -0,0 +1,21 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/MinimalVersion-build")
+
+file(READ "${out_dir}/foo1.cps" content)
+expect_value("${content}" "foo1" "name")
+expect_value("${content}" "1.0" "version")
+expect_missing("${content}" "compat_version")
+expect_missing("${content}" "version_schema")
+
+file(READ "${out_dir}/foo2.cps" content)
+expect_value("${content}" "foo2" "name")
+expect_value("${content}" "1.5" "version")
+expect_value("${content}" "1.0" "compat_version")
+expect_missing("${content}" "version_schema")
+
+file(READ "${out_dir}/foo3.cps" content)
+expect_value("${content}" "foo3" "name")
+expect_value("${content}" "1.0" "version")
+expect_missing("${content}" "compat_version")
+expect_value("${content}" "simple" "version_schema")

+ 16 - 0
Tests/RunCMake/ExportPackageInfo/MinimalVersion.cmake

@@ -0,0 +1,16 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+
+export(EXPORT foo
+  PACKAGE_INFO foo1
+  VERSION 1.0)
+
+export(EXPORT foo
+  PACKAGE_INFO foo2
+  VERSION 1.5
+  COMPAT_VERSION 1.0)
+
+export(EXPORT foo
+  PACKAGE_INFO foo3
+  VERSION 1.0
+  VERSION_SCHEMA simple)

+ 1 - 0
Tests/RunCMake/ExportPackageInfo/ReferencesNonExportedTarget-result.txt

@@ -0,0 +1 @@
+1

+ 3 - 0
Tests/RunCMake/ExportPackageInfo/ReferencesNonExportedTarget-stderr.txt

@@ -0,0 +1,3 @@
+CMake Error in CMakeLists.txt:
+  export called with target "canine" which requires target "mammal" that is
+  not in any export set.

+ 6 - 0
Tests/RunCMake/ExportPackageInfo/ReferencesNonExportedTarget.cmake

@@ -0,0 +1,6 @@
+add_library(mammal INTERFACE)
+add_library(canine INTERFACE)
+target_link_libraries(canine INTERFACE mammal)
+
+install(TARGETS canine EXPORT dog DESTINATION .)
+export(EXPORT dog PACKAGE_INFO dog)

+ 1 - 0
Tests/RunCMake/ExportPackageInfo/ReferencesWronglyExportedTarget-result.txt

@@ -0,0 +1 @@
+1

+ 7 - 0
Tests/RunCMake/ExportPackageInfo/ReferencesWronglyExportedTarget-stderr.txt

@@ -0,0 +1,7 @@
+CMake Error in CMakeLists.txt:
+  Target "test" references target "foo", which does not use the standard
+  namespace separator.  This is not allowed.
+.*
+CMake Error in CMakeLists.txt:
+  Target "test" references target "bar_bar", which does not use the standard
+  namespace separator.  This is not allowed.

+ 14 - 0
Tests/RunCMake/ExportPackageInfo/ReferencesWronglyExportedTarget.cmake

@@ -0,0 +1,14 @@
+add_library(foo INTERFACE)
+add_library(bar INTERFACE)
+
+add_library(test INTERFACE)
+target_link_libraries(test INTERFACE foo bar)
+
+install(TARGETS foo EXPORT foo DESTINATION .)
+install(TARGETS bar EXPORT bar DESTINATION .)
+
+export(EXPORT foo)
+export(EXPORT bar NAMESPACE bar_)
+
+install(TARGETS test EXPORT test DESTINATION .)
+export(EXPORT test PACKAGE_INFO test)

+ 1 - 0
Tests/RunCMake/ExportPackageInfo/ReferencesWronglyImportedTarget-result.txt

@@ -0,0 +1 @@
+1

+ 3 - 0
Tests/RunCMake/ExportPackageInfo/ReferencesWronglyImportedTarget-stderr.txt

@@ -0,0 +1,3 @@
+CMake Error in CMakeLists.txt:
+  Target "foo" references imported target "bar" which does not come from any
+  known package.

+ 7 - 0
Tests/RunCMake/ExportPackageInfo/ReferencesWronglyImportedTarget.cmake

@@ -0,0 +1,7 @@
+add_library(bar INTERFACE IMPORTED)
+
+add_library(foo INTERFACE)
+target_link_libraries(foo INTERFACE bar)
+
+install(TARGETS foo EXPORT foo DESTINATION .)
+export(EXPORT foo PACKAGE_INFO foo)

+ 1 - 0
Tests/RunCMake/ExportPackageInfo/ReferencesWronglyNamespacedTarget-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/ExportPackageInfo/ReferencesWronglyNamespacedTarget-stderr.txt

@@ -0,0 +1,4 @@
+CMake Error in CMakeLists.txt:
+  Target "foo" references target "wrong::lib", which comes from the "broken"
+  package, but does not belong to the package's canonical namespace.  This is
+  not allowed.

+ 11 - 0
Tests/RunCMake/ExportPackageInfo/ReferencesWronglyNamespacedTarget.cmake

@@ -0,0 +1,11 @@
+find_package(
+  broken REQUIRED CONFIG
+  NO_DEFAULT_PATH
+  PATHS ${CMAKE_CURRENT_LIST_DIR}
+  )
+
+add_library(foo INTERFACE)
+target_link_libraries(foo INTERFACE wrong::lib)
+
+install(TARGETS foo EXPORT foo DESTINATION .)
+export(EXPORT foo PACKAGE_INFO foo)

+ 22 - 0
Tests/RunCMake/ExportPackageInfo/Requirements-check.cmake

@@ -0,0 +1,22 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/Requirements-build")
+
+file(READ "${out_dir}/foo.cps" content)
+expect_value("${content}" "foo" "name")
+expect_value("${content}" "interface" "components" "libb" "type")
+
+file(READ "${out_dir}/bar.cps" content)
+expect_value("${content}" "bar" "name")
+expect_array("${content}"      1 "requires"  "foo" "components")
+expect_value("${content}" "libb" "requires"  "foo" "components" 0)
+expect_array("${content}"      1 "requires" "test" "components")
+expect_value("${content}" "liba" "requires" "test" "components" 0)
+expect_value("${content}" "interface" "components" "libc" "type")
+expect_value("${content}" "interface" "components" "libd" "type")
+
+string(JSON component GET "${content}" "components" "libd")
+expect_array("${component}" 3 "requires")
+expect_value("${component}" "test:liba" "requires" 0)
+expect_value("${component}"  "foo:libb" "requires" 1)
+expect_value("${component}"     ":libc" "requires" 2)

+ 20 - 0
Tests/RunCMake/ExportPackageInfo/Requirements.cmake

@@ -0,0 +1,20 @@
+find_package(
+  test REQUIRED CONFIG
+  NO_DEFAULT_PATH
+  PATHS ${CMAKE_CURRENT_LIST_DIR}
+  )
+
+add_library(libb INTERFACE)
+add_library(libc INTERFACE)
+add_library(libd INTERFACE)
+
+add_library(foo ALIAS libb)
+add_library(bar ALIAS libc)
+
+target_link_libraries(libd INTERFACE test::liba foo bar)
+
+install(TARGETS libb EXPORT foo DESTINATION .)
+export(EXPORT foo PACKAGE_INFO foo)
+
+install(TARGETS libc libd EXPORT bar DESTINATION .)
+export(EXPORT bar PACKAGE_INFO bar)

+ 37 - 0
Tests/RunCMake/ExportPackageInfo/RunCMakeTest.cmake

@@ -0,0 +1,37 @@
+include(RunCMake)
+
+# Test experimental gate
+run_cmake(ExperimentalGate)
+run_cmake(ExperimentalWarning)
+
+# Enable experimental feature and suppress warnings
+set(RunCMake_TEST_OPTIONS
+  -Wno-dev
+  "-DCMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO:STRING=b80be207-778e-46ba-8080-b23bba22639e"
+  )
+
+# Test incorrect usage
+run_cmake(BadArgs1)
+run_cmake(BadArgs2)
+run_cmake(BadArgs3)
+run_cmake(BadArgs4)
+run_cmake(BadName)
+run_cmake(DuplicateOutput)
+run_cmake(BadDefaultTarget)
+run_cmake(ReferencesNonExportedTarget)
+run_cmake(ReferencesWronglyExportedTarget)
+run_cmake(ReferencesWronglyImportedTarget)
+run_cmake(ReferencesWronglyNamespacedTarget)
+run_cmake(DependsMultipleDifferentNamespace)
+run_cmake(DependsMultipleDifferentSets)
+
+# Test functionality
+run_cmake(Appendix)
+run_cmake(InterfaceProperties)
+run_cmake(Metadata)
+run_cmake(Minimal)
+run_cmake(MinimalVersion)
+run_cmake(LowerCaseFile)
+run_cmake(Requirements)
+run_cmake(TargetTypes)
+run_cmake(DependsMultiple)

+ 11 - 0
Tests/RunCMake/ExportPackageInfo/TargetTypes-check.cmake

@@ -0,0 +1,11 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/TargetTypes-build")
+
+file(READ "${out_dir}/foo.cps" content)
+expect_value("${content}" "foo" "name")
+expect_value("${content}" "archive"    "components" "foo-static" "type")
+expect_value("${content}" "dylib"      "components" "foo-shared" "type")
+expect_value("${content}" "module"     "components" "foo-module" "type")
+expect_value("${content}" "interface"  "components" "bar"        "type")
+expect_value("${content}" "executable" "components" "test"       "type")

+ 20 - 0
Tests/RunCMake/ExportPackageInfo/TargetTypes.cmake

@@ -0,0 +1,20 @@
+project(TargetTypes CXX)
+
+add_library(foo-static STATIC foo.cxx)
+add_library(foo-shared SHARED foo.cxx)
+add_library(foo-module MODULE foo.cxx)
+add_library(bar INTERFACE)
+add_executable(test test.cxx)
+
+install(
+  TARGETS
+    foo-static
+    foo-shared
+    foo-module
+    bar
+    test
+  EXPORT foo
+  DESTINATION .
+  )
+
+export(EXPORT foo PACKAGE_INFO foo)

+ 1 - 0
Tests/RunCMake/ExportPackageInfo/broken-config.cmake

@@ -0,0 +1 @@
+add_library(wrong::lib INTERFACE IMPORTED)

+ 3 - 0
Tests/RunCMake/ExportPackageInfo/foo.cxx

@@ -0,0 +1,3 @@
+void foo()
+{
+}

+ 1 - 0
Tests/RunCMake/ExportPackageInfo/test-config.cmake

@@ -0,0 +1 @@
+add_library(test::liba INTERFACE IMPORTED)

+ 4 - 0
Tests/RunCMake/ExportPackageInfo/test.cxx

@@ -0,0 +1,4 @@
+int main()
+{
+  return 0;
+}

+ 1 - 0
Tests/RunCMake/InstallPackageInfo/BadName-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/InstallPackageInfo/BadName-stderr.txt

@@ -0,0 +1,4 @@
+CMake Error at BadName\.cmake:3 \(install\):
+  install PACKAGE_INFO given invalid package name "%foo"\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)

+ 3 - 0
Tests/RunCMake/InstallPackageInfo/BadName.cmake

@@ -0,0 +1,3 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+install(PACKAGE_INFO "%foo" EXPORT foo)

+ 1 - 0
Tests/RunCMake/InstallPackageInfo/RunCMakeTest.cmake

@@ -16,6 +16,7 @@ run_cmake(BadArgs2)
 run_cmake(BadArgs3)
 run_cmake(BadArgs4)
 run_cmake(BadArgs5)
+run_cmake(BadName)
 run_cmake(BadDefaultTarget)
 run_cmake(ReferencesNonExportedTarget)
 run_cmake(ReferencesWronglyExportedTarget)