Browse Source

Merge topic 'package-info-from-install-export'

34343922a5 install: Add ability to generate CPS from install(EXPORT)

Acked-by: Kitware Robot <[email protected]>
Tested-by: buildbot <[email protected]>
Merge-request: !11315
Brad King 3 weeks ago
parent
commit
515bb02443
29 changed files with 517 additions and 1 deletions
  1. 15 1
      Help/command/install.rst
  2. 1 0
      Help/manual/cmake-variables.7.rst
  3. 122 0
      Help/variable/CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO.rst
  4. 9 0
      Source/cmExperimental.cxx
  5. 1 0
      Source/cmExperimental.h
  6. 168 0
      Source/cmInstallCommand.cxx
  7. 1 0
      Tests/RunCMake/CMakeLists.txt
  8. 39 0
      Tests/RunCMake/InstallExportsAsPackageInfo/Assertions.cmake
  9. 1 0
      Tests/RunCMake/InstallExportsAsPackageInfo/BadDirective-result.txt
  10. 5 0
      Tests/RunCMake/InstallExportsAsPackageInfo/BadDirective-stderr.txt
  11. 2 0
      Tests/RunCMake/InstallExportsAsPackageInfo/BadDirective.cmake
  12. 3 0
      Tests/RunCMake/InstallExportsAsPackageInfo/CMakeLists.txt
  13. 6 0
      Tests/RunCMake/InstallExportsAsPackageInfo/ExperimentalGate.cmake
  14. 7 0
      Tests/RunCMake/InstallExportsAsPackageInfo/ExperimentalWarning-stderr.txt
  15. 13 0
      Tests/RunCMake/InstallExportsAsPackageInfo/ExperimentalWarning.cmake
  16. 27 0
      Tests/RunCMake/InstallExportsAsPackageInfo/LowerCase-check.cmake
  17. 12 0
      Tests/RunCMake/InstallExportsAsPackageInfo/LowerCase.cmake
  18. 1 0
      Tests/RunCMake/InstallExportsAsPackageInfo/MissingAppendixName-result.txt
  19. 5 0
      Tests/RunCMake/InstallExportsAsPackageInfo/MissingAppendixName-stderr.txt
  20. 2 0
      Tests/RunCMake/InstallExportsAsPackageInfo/MissingAppendixName.cmake
  21. 1 0
      Tests/RunCMake/InstallExportsAsPackageInfo/MissingExport-result.txt
  22. 3 0
      Tests/RunCMake/InstallExportsAsPackageInfo/MissingExport-stderr.txt
  23. 2 0
      Tests/RunCMake/InstallExportsAsPackageInfo/MissingExport.cmake
  24. 1 0
      Tests/RunCMake/InstallExportsAsPackageInfo/MissingPackageName-result.txt
  25. 5 0
      Tests/RunCMake/InstallExportsAsPackageInfo/MissingPackageName-stderr.txt
  26. 2 0
      Tests/RunCMake/InstallExportsAsPackageInfo/MissingPackageName.cmake
  27. 22 0
      Tests/RunCMake/InstallExportsAsPackageInfo/RunCMakeTest.cmake
  28. 19 0
      Tests/RunCMake/InstallExportsAsPackageInfo/SampleExport-check.cmake
  29. 22 0
      Tests/RunCMake/InstallExportsAsPackageInfo/SampleExport.cmake

+ 15 - 1
Help/command/install.rst

@@ -979,7 +979,7 @@ Signatures
 
     Experimental. Gated by ``CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO``.
 
-  Installs a |CPS|_ file exporting targets for dependent projects:
+  Installs a |CPS|_ ("CPS") file exporting targets for dependent projects:
 
   .. code-block:: cmake
 
@@ -1105,6 +1105,20 @@ Signatures
   use of ``LOWER_CASE_FILE`` should be consistent between the main package and
   any appendices.
 
+  .. note::
+    Because it is intended to be portable across multiple build tools, CPS
+    may not support all features that are allowed in CMake-script exports.  In
+    particular, support for generator expressions in interface properties is
+    limited at this time to configuration-dependent expressions.
+
+  .. note::
+    This is the recommended way to generate |CPS| package information for a
+    project.  For distributors whose users may require CPS package information
+    when making changes to the project's build files is not practical, the
+    :variable:`CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO` variable may be used to
+    generate ``.cps`` files from :command:`install(EXPORT)` calls.  Refer to
+    the variable's documentation for usage and caveats.
+
 .. signature::
   install(RUNTIME_DEPENDENCY_SET <set-name> [...])
 

+ 1 - 0
Help/manual/cmake-variables.7.rst

@@ -263,6 +263,7 @@ Variables that Change Behavior
    /variable/CMAKE_INCLUDE_PATH
    /variable/CMAKE_INSTALL_DEFAULT_COMPONENT_NAME
    /variable/CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS
+   /variable/CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO
    /variable/CMAKE_INSTALL_MESSAGE
    /variable/CMAKE_INSTALL_PREFIX
    /variable/CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT

+ 122 - 0
Help/variable/CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO.rst

@@ -0,0 +1,122 @@
+CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO
+-------------------------------------
+
+.. versionadded:: 4.3
+
+.. note::
+
+   This variable is meaningful only when experimental support has been enabled
+   by the ``CMAKE_EXPERIMENTAL_FIXME`` gate.
+
+A list of directives instructing CMake to install |CPS| package information
+when exported target information is installed via :command:`install(EXPORT)`.
+The value is treated as a list, with each directive having the form
+``<export-name>:<package-name>[/[l][a<appendix-name>][/<destination>]]``.
+Slashes are used to separate different components of the directive.
+
+Note that this feature is intended for package distributors, and should
+**only** be used when editing a project's CMake script is not feasible.
+Developers should use :command:`install(PACKAGE_INFO)` directly.
+
+Additionally, because ``CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO`` functions by
+emulating a call to :command:`install(PACKAGE_INFO)`, using it with a project
+that is already calling :command:`install(PACKAGE_INFO)` directly may result
+in conflicting installation directives, which will usually cause the project's
+configure step to fail.
+
+The meaning of the values is as follows:
+
+``<export-name>``
+  Name of the export for which package information should be installed.
+
+``<package-name>``
+  Name of the package for which to generate package information.  This is also
+  the name that users would use in a :command:`find_package` call.
+
+``l``
+  Optional.  Specifies that the name of the package information file on disk
+  should be lower case.  See the ``LOWER_CASE_FILE`` option of
+  :command:`install(PACKAGE_INFO)`.
+
+``a<appendix-name>``
+  Optional.  Specifies that an appendix ``<appendix-name>`` should be created
+  rather than a root package description.  See the ``APPENDIX`` option of
+  :command:`install(PACKAGE_INFO)`.  Note that additional information
+  (see below) cannot be added to appendices.
+
+``<destination>``
+  Optional.  Specifies the destination to which the package information file
+  should be installed.  See the ``DESTINATION`` option of
+  :command:`install(PACKAGE_INFO)`.  Note that the default is a
+  platform-specific location that is appropriate for |CPS| files in most
+  instances, *not* the ``DESTINATION`` of the :command:`install(EXPORT)`
+  which the directive matched.
+
+For non-appendices, CMake will also infer additional information from several
+CMake variables of the form ``<export-name>_EXPORT_PACKAGE_INFO_<var>``.  The
+values of these are first processed as if by :command:`string(CONFIGURE)` with
+the ``@ONLY`` option.  These are optional, and their effect is equivalent to
+passing their value to the ``<var>`` option of the
+:command:`install(PACKAGE_INFO)` command.
+
+The additional variables are:
+
+  * ``<export-name>_EXPORT_PACKAGE_INFO_VERSION``
+  * ``<export-name>_EXPORT_PACKAGE_INFO_COMPAT_VERSION``
+  * ``<export-name>_EXPORT_PACKAGE_INFO_VERSION_SCHEMA``
+  * ``<export-name>_EXPORT_PACKAGE_INFO_LICENSE``
+  * ``<export-name>_EXPORT_PACKAGE_INFO_DEFAULT_LICENSE``
+  * ``<export-name>_EXPORT_PACKAGE_INFO_DEFAULT_CONFIGURATIONS``
+
+Ideally, the version should be set to ``@PROJECT_VERSION@``.  However,
+some projects may not use the ``VERSION`` option of the :command:`project`
+command.
+
+Example
+^^^^^^^
+
+Consider the following (simplified) project:
+
+.. code-block:: cmake
+
+  project(Example VERSION 1.2.3 SPDX_LICENSE "BSD-3-Clause")
+
+  add_library(foo ...)
+  add_library(bar ...)
+
+  install(TARGETS foo EXPORT required-targets)
+  install(TARGETS bar EXPORT optional-targets)
+
+  install(EXPORT required-targets FILE example-targets.cmake ...)
+  install(EXPORT optional-targets FILE example-optional-targets.cmake ...)
+
+In this example, let ``example-targets.cmake`` be a file which is always
+installed, and ``example-optional-targets.cmake`` be a file which is
+optionally installed (e.g. is distributed as part of a separate package which
+depends on the 'base' package).
+
+Now, imagine we are a distributor that wants to make |CPS| package information
+files available to our users, but we do not want to modify the project's build
+files.  We can do this by passing the following arguments to CMake:
+
+.. code-block::
+
+  -DCMAKE_EXPERIMENTAL_FIXME=<elided>
+  -DCMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO=\
+    required-targets:Example/l;\
+    optional-targets:Example/laoptional
+  -Drequired-targets_EXPORT_PACKAGE_INFO_VERSION=@Example_VERSION@
+  -Drequired-targets_EXPORT_PACKAGE_INFO_LICENSE=@Example_SPDX_LICENSE@
+
+(Whitespace and line continuation characters, added for readability, should
+be removed in real usage. Arguments may need to be quoted to prevent being
+reinterpreted by the command shell.)
+
+This will cause CMake to also create and install the files ``example.cps`` and
+``example-optional.cps`` which describe the ``Example`` package.  We could
+also specify the version and license information using substitutions provided
+by the package build system (e.g. rpm, dpkg) if the project does not provide
+this information via the :command:`project` command.
+
+.. _CPS: https://cps-org.github.io/cps/
+.. |CPS| replace:: Common Package Specification

+ 9 - 0
Source/cmExperimental.cxx

@@ -55,6 +55,15 @@ cmExperimental::FeatureData const LookupTable[] = {
     "experimentation and feedback to CMake developers.",
     {},
     cmExperimental::TryCompileCondition::Always },
+  // MappedPackageInfo
+  { "MappedPackageInfo",
+    "ababa1b5-7099-495f-a9cd-e22d38f274f2",
+    "CMAKE_EXPERIMENTAL_MAPPED_PACKAGE_INFO",
+    "CMake's support for generating package information in the Common Package "
+    "Specification format from CMake script exports is experimental. It is "
+    "meant only for experimentation and feedback to CMake developers.",
+    {},
+    cmExperimental::TryCompileCondition::Always },
   // ExportBuildDatabase
   { "ExportBuildDatabase",
     "73194a1d-c0b5-41b9-9190-a4512925e192",

+ 1 - 0
Source/cmExperimental.h

@@ -21,6 +21,7 @@ public:
     CxxImportStd,
     ImportPackageInfo,
     ExportPackageInfo,
+    MappedPackageInfo,
     ExportBuildDatabase,
     Instrumentation,
 

+ 168 - 0
Source/cmInstallCommand.cxx

@@ -2035,6 +2035,152 @@ bool HandleExportAndroidMKMode(std::vector<std::string> const& args,
 #endif
 }
 
+#ifndef CMAKE_BOOTSTRAP
+cm::optional<cm::string_view> MatchExport(cm::string_view directive,
+                                          std::string const& exportName)
+{
+  std::string::size_type const l = exportName.size();
+  if (directive.substr(0, l) == exportName) {
+    if (directive.size() > l && directive[l] == ':') {
+      return directive.substr(l + 1);
+    }
+  }
+  return cm::nullopt;
+}
+
+void AssignValue(std::string& dest, std::string const& value)
+{
+  dest = value;
+}
+
+void AssignValue(std::vector<std::string>& dest, std::string const& value)
+{
+  dest = cmList{ value }.data();
+}
+
+template <typename T>
+void GetExportArgumentFromVariable(cmMakefile const* makefile,
+                                   cmExportSet const& exportSet,
+                                   cm::string_view suffix, T& variable)
+{
+  std::string const& name =
+    cmStrCat(exportSet.GetName(), "_EXPORT_PACKAGE_INFO_"_s, suffix);
+  if (cmValue const& value = makefile->GetDefinition(name)) {
+    std::string realValue;
+    makefile->ConfigureString(value, realValue, true, false);
+    AssignValue(variable, realValue);
+  }
+}
+
+bool HandleMappedPackageInfo(
+  cmExportSet& exportSet, cm::string_view directive, Helper& helper,
+  cmInstallCommandArguments const& installCommandArgs,
+  cmExecutionStatus& status, cmInstallGenerator::MessageLevel message,
+  std::string const& cxxModulesDirectory)
+{
+  cmPackageInfoArguments arguments;
+
+  // Extract information from the directive.
+  std::string::size_type const n = directive.find('/');
+  if (n != std::string::npos) {
+    arguments.PackageName = std::string{ directive.substr(0, n) };
+    directive = directive.substr(n + 1);
+
+    if (!directive.empty() && directive[0] == 'l') {
+      arguments.LowerCase = true;
+      directive = directive.substr(1);
+    }
+
+    if (!directive.empty() && directive[0] == 'a') {
+      std::string::size_type const d = directive.find('/');
+      if (d != std::string::npos) {
+        arguments.Appendix = std::string{ directive.substr(1, d - 1) };
+        directive = directive.substr(d);
+      } else {
+        arguments.Appendix = std::string{ directive.substr(1) };
+        directive = {};
+      }
+
+      if (arguments.Appendix.empty()) {
+        status.SetError(cmStrCat(
+          "CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO given APPENDIX "
+          R"(directive for export ")"_s,
+          exportSet.GetName(), R"(", but no appendix name was provided.)"_s));
+        return false;
+      }
+    }
+
+    if (!directive.empty()) {
+      if (directive[0] != '/') {
+        status.SetError(
+          cmStrCat("CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO given unrecognized "
+                   R"(directive  ")"_s,
+                   directive, R"(".)"_s));
+        return false;
+      }
+
+      directive = directive.substr(1);
+    }
+  } else {
+    arguments.PackageName = std::string{ directive };
+    directive = {};
+  }
+
+  if (arguments.PackageName.empty()) {
+    status.SetError(
+      cmStrCat("CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO missing package name "
+               R"(for export ")"_s,
+               exportSet.GetName(), R"(".)"_s));
+    return false;
+  }
+
+  // Build destination.
+  std::string dest = std::string{ directive };
+  if (dest.empty()) {
+    if (helper.Makefile->GetSafeDefinition("CMAKE_SYSTEM_NAME") == "Windows") {
+      dest = std::string{ "cps"_s };
+    } else {
+      dest = cmStrCat(helper.GetLibraryDestination(nullptr), "/cps/",
+                      arguments.GetPackageDirName());
+    }
+  }
+
+  if (arguments.Appendix.empty()) {
+    // Get additional export information from variables.
+    GetExportArgumentFromVariable( // BR
+      helper.Makefile, exportSet, "VERSION"_s, arguments.Version);
+    GetExportArgumentFromVariable( // BR
+      helper.Makefile, exportSet, "COMPAT_VERSION"_s, arguments.VersionCompat);
+    GetExportArgumentFromVariable( // BR
+      helper.Makefile, exportSet, "VERSION_SCHEMA"_s, arguments.VersionSchema);
+    GetExportArgumentFromVariable( // BR
+      helper.Makefile, exportSet, "LICENSE"_s, arguments.License);
+    GetExportArgumentFromVariable( // BR
+      helper.Makefile, exportSet, "DEFAULT_LICENSE"_s,
+      arguments.DefaultLicense);
+    GetExportArgumentFromVariable( // BR
+      helper.Makefile, exportSet, "DEFAULT_CONFIGURATIONS"_s,
+      arguments.DefaultConfigs);
+  }
+
+  // Sanity-check export information.
+  if (!arguments.Check(status)) {
+    return false;
+  }
+
+  // Create the package info generator.
+  helper.Makefile->AddInstallGenerator(
+    cm::make_unique<cmInstallPackageInfoExportGenerator>(
+      &exportSet, dest, installCommandArgs.GetPermissions(),
+      installCommandArgs.GetConfigurations(),
+      installCommandArgs.GetComponent(), message,
+      installCommandArgs.GetExcludeFromAll(), std::move(arguments),
+      cxxModulesDirectory, helper.Makefile->GetBacktrace()));
+
+  return true;
+}
+#endif
+
 bool HandleExportMode(std::vector<std::string> const& args,
                       cmExecutionStatus& status)
 {
@@ -2135,6 +2281,28 @@ bool HandleExportMode(std::vector<std::string> const& args,
   helper.Makefile->GetGlobalGenerator()->AddInstallComponent(
     ica.GetComponent());
 
+#ifndef CMAKE_BOOTSTRAP
+  // Check if PACKAGE_INFO export has been requested for this export set.
+  if (cmExperimental::HasSupportEnabled(
+        status.GetMakefile(), cmExperimental::Feature::ExportPackageInfo) &&
+      cmExperimental::HasSupportEnabled(
+        status.GetMakefile(), cmExperimental::Feature::MappedPackageInfo)) {
+    if (cmValue const& piExports = helper.Makefile->GetDefinition(
+          "CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO")) {
+      for (auto const& pie : cmList{ piExports }) {
+        cm::optional<cm::string_view> const directive = MatchExport(pie, exp);
+        if (directive) {
+          if (!HandleMappedPackageInfo(exportSet, *directive, helper, ica,
+                                       status, message,
+                                       cxx_modules_directory)) {
+            return false;
+          }
+        }
+      }
+    }
+  }
+#endif
+
   // Create the export install generator.
   helper.Makefile->AddInstallGenerator(
     cm::make_unique<cmInstallCMakeConfigExportGenerator>(

+ 1 - 0
Tests/RunCMake/CMakeLists.txt

@@ -1335,6 +1335,7 @@ add_RunCMake_test(AutoExportDll
 add_RunCMake_test(AndroidMK)
 add_RunCMake_test(ExportPackageInfo)
 add_RunCMake_test(InstallPackageInfo)
+add_RunCMake_test(InstallExportsAsPackageInfo)
 
 if(CMake_TEST_ANDROID_NDK OR CMake_TEST_ANDROID_STANDALONE_TOOLCHAIN)
   if(NOT "${CMAKE_GENERATOR}" MATCHES "Make|Ninja|Visual Studio 1[456]")

+ 39 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/Assertions.cmake

@@ -0,0 +1,39 @@
+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_object content)
+  string(JSON actual_type TYPE "${content}" ${ARGN})
+  _expect("type" STREQUAL "${actual_type}" "OBJECT" ${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/InstallExportsAsPackageInfo/BadDirective-result.txt

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

+ 5 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/BadDirective-stderr.txt

@@ -0,0 +1,5 @@
+CMake Error at BadDirective\.cmake:2 \(install\):
+  install CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO given unrecognized directive
+  "error"\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)

+ 2 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/BadDirective.cmake

@@ -0,0 +1,2 @@
+set(CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO foo:foo/error)
+install(EXPORT foo FILE foo-targets.cmake DESTINATION .)

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

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

+ 6 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/ExperimentalGate.cmake

@@ -0,0 +1,6 @@
+unset(CMAKE_EXPERIMENTAL_MAPPED_PACKAGE_INFO)
+set(CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO foo:)
+
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+install(EXPORT foo FILE foo-targets.cmake DESTINATION .)

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

@@ -0,0 +1,7 @@
+CMake Warning \(dev\) at ExperimentalWarning\.cmake:13 \(install\):
+  CMake's support for generating package information in the Common Package
+  Specification format from CMake script exports 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\.

+ 13 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/ExperimentalWarning.cmake

@@ -0,0 +1,13 @@
+set(
+  CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO
+  "b80be207-778e-46ba-8080-b23bba22639e"
+  )
+set(
+  CMAKE_EXPERIMENTAL_MAPPED_PACKAGE_INFO
+  "ababa1b5-7099-495f-a9cd-e22d38f274f2"
+  )
+unset(CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO)
+
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+install(EXPORT foo FILE foo-targets.cmake DESTINATION .)

+ 27 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/LowerCase-check.cmake

@@ -0,0 +1,27 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/LowerCase-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983")
+
+function(expect_in_list list value)
+  list(FIND ${list} "${value}" index)
+  if(${index} EQUAL -1)
+    set(RunCMake_TEST_FAILED
+      "Expected '${value}' in ${list} ('${${list}}'), but it was not found" PARENT_SCOPE)
+  endif()
+endfunction()
+
+file(GLOB files
+  LIST_DIRECTORIES false
+  RELATIVE "${out_dir}"
+  "${out_dir}/*.cps"
+)
+expect_in_list(files "farm.cps")
+expect_in_list(files "farm-extra.cps")
+
+file(READ "${out_dir}/farm.cps" content)
+expect_value("${content}" "Farm" "name")
+expect_value("${content}" "interface" "components" "Cow" "type")
+
+file(READ "${out_dir}/farm-extra.cps" content)
+expect_value("${content}" "Farm" "name")
+expect_value("${content}" "interface" "components" "Pig" "type")

+ 12 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/LowerCase.cmake

@@ -0,0 +1,12 @@
+set(CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO
+  cow:Farm/l/cps
+  pig:Farm/laextra/cps
+)
+
+add_library(Cow INTERFACE)
+add_library(Pig INTERFACE)
+
+install(TARGETS Cow EXPORT cow)
+install(TARGETS Pig EXPORT pig)
+install(EXPORT cow FILE farm-targets.cmake DESTINATION .)
+install(EXPORT pig FILE farm-targets-extra.cmake DESTINATION .)

+ 1 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/MissingAppendixName-result.txt

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

+ 5 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/MissingAppendixName-stderr.txt

@@ -0,0 +1,5 @@
+CMake Error at MissingAppendixName.cmake:2 \(install\):
+  install CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO given APPENDIX directive for
+  export "foo", but no appendix name was provided\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)

+ 2 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/MissingAppendixName.cmake

@@ -0,0 +1,2 @@
+set(CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO foo:foo/a)
+install(EXPORT foo FILE foo-targets.cmake DESTINATION .)

+ 1 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/MissingExport-result.txt

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

+ 3 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/MissingExport-stderr.txt

@@ -0,0 +1,3 @@
+CMake Error: INSTALL\(PACKAGE_INFO\) given unknown export "foo"
+CMake Error: INSTALL\(EXPORT\) given unknown export "foo"
+CMake Generate step failed\.  Build files cannot be regenerated correctly\.

+ 2 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/MissingExport.cmake

@@ -0,0 +1,2 @@
+set(CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO foo:foo)
+install(EXPORT foo FILE foo-targets.cmake DESTINATION .)

+ 1 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/MissingPackageName-result.txt

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

+ 5 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/MissingPackageName-stderr.txt

@@ -0,0 +1,5 @@
+CMake Error at MissingPackageName.cmake:2 \(install\):
+  install CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO missing package name for
+  export "foo"\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:3 \(include\)

+ 2 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/MissingPackageName.cmake

@@ -0,0 +1,2 @@
+set(CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO foo:)
+install(EXPORT foo FILE foo-targets.cmake DESTINATION .)

+ 22 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/RunCMakeTest.cmake

@@ -0,0 +1,22 @@
+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"
+  "-DCMAKE_EXPERIMENTAL_MAPPED_PACKAGE_INFO:STRING=ababa1b5-7099-495f-a9cd-e22d38f274f2"
+  )
+
+# Test incorrect usage
+run_cmake(MissingPackageName)
+run_cmake(MissingAppendixName)
+run_cmake(MissingExport)
+run_cmake(BadDirective)
+
+# Test functionality
+run_cmake(SampleExport)
+run_cmake(LowerCase)

+ 19 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/SampleExport-check.cmake

@@ -0,0 +1,19 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/SampleExport-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983")
+
+file(READ "${out_dir}/farm.cps" content)
+expect_value("${content}" "farm" "name")
+expect_value("${content}" "interface" "components" "cow" "type")
+expect_value("${content}" "1.2.3" "version")
+expect_value("${content}" "1.1.0" "compat_version")
+expect_value("${content}" "simple" "version_schema")
+expect_value("${content}" "Apache-2.0" "license")
+expect_value("${content}" "BSD-3-Clause" "default_license")
+expect_array("${content}" 2 "configurations")
+expect_value("${content}" "Small" "configurations" 0)
+expect_value("${content}" "Large" "configurations" 1)
+
+file(READ "${out_dir}/farm-extra.cps" content)
+expect_value("${content}" "farm" "name")
+expect_value("${content}" "interface" "components" "pig" "type")

+ 22 - 0
Tests/RunCMake/InstallExportsAsPackageInfo/SampleExport.cmake

@@ -0,0 +1,22 @@
+cmake_minimum_required(VERSION 4.2)
+
+project(farm VERSION 1.2.3 COMPAT_VERSION 1.1.0)
+
+set(CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO
+  cow:farm//cps
+  pig:farm/aextra/cps
+)
+set(cow_EXPORT_PACKAGE_INFO_VERSION @farm_VERSION@)
+set(cow_EXPORT_PACKAGE_INFO_COMPAT_VERSION @farm_COMPAT_VERSION@)
+set(cow_EXPORT_PACKAGE_INFO_VERSION_SCHEMA "simple")
+set(cow_EXPORT_PACKAGE_INFO_LICENSE "Apache-2.0")
+set(cow_EXPORT_PACKAGE_INFO_DEFAULT_LICENSE "BSD-3-Clause")
+set(cow_EXPORT_PACKAGE_INFO_DEFAULT_CONFIGURATIONS "Small;Large")
+
+add_library(cow INTERFACE)
+add_library(pig INTERFACE)
+
+install(TARGETS cow EXPORT cow)
+install(TARGETS pig EXPORT pig)
+install(EXPORT cow FILE farm-targets.cmake DESTINATION .)
+install(EXPORT pig FILE farm-targets-extra.cmake DESTINATION .)