Merge topic 'if-space-dldt'

2c0d16df7b Help/command/if: fix formatting in precedence order overview

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !9777
diff --git a/CompileFlags.cmake b/CompileFlags.cmake
index f94e079..3080daa 100644
--- a/CompileFlags.cmake
+++ b/CompileFlags.cmake
@@ -37,7 +37,13 @@
   string(APPEND CMAKE_EXE_LINKER_FLAGS " -Xlinker -stack:20000000")
 endif()
 
-#silence duplicate symbol warnings on AIX
+# Silence "Additional optimization may be attained by recompiling and
+# specifying MAXMEM option" warning on XLC (AIX)
+if(CMAKE_CXX_COMPILER_ID MATCHES "^(XL|XLClang)$")
+  string(APPEND CMAKE_CXX_FLAGS " -qmaxmem=-1")
+endif()
+
+# Silence duplicate symbol warnings on AIX
 if(CMAKE_SYSTEM_NAME MATCHES "AIX")
   if(NOT CMAKE_COMPILER_IS_GNUCXX)
     set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -bhalt:5 ")
diff --git a/Help/command/install.rst b/Help/command/install.rst
index a2e7491..b199147 100644
--- a/Help/command/install.rst
+++ b/Help/command/install.rst
@@ -19,6 +19,7 @@
   install(`SCRIPT`_ <file> [...])
   install(`CODE`_ <code> [...])
   install(`EXPORT`_ <export-name> [...])
+  install(`PACKAGE_INFO`_ <package-name> [...])
   install(`RUNTIME_DEPENDENCY_SET`_ <set-name> [...])
 
 Introduction
@@ -905,6 +906,61 @@
   ``mp_myexe`` as if the target were built in its own tree.
 
 .. signature::
+  install(PACKAGE_INFO <package-name> [...])
+
+  .. versionadded:: 3.31
+  .. note::
+
+    Experimental. Gated by ``CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO``.
+
+  Installs a |CPS|_ file exporting targets for dependent projects:
+
+  .. code-block:: cmake
+
+    install(PACKAGE_INFO <package-name> EXPORT <export-name>
+            [APPENDIX <appendix-name>]
+            [DESTINATION <dir>]
+            [LOWER_CASE_FILE]
+            [VERSION <version>
+             [COMPAT_VERSION <version>]
+             [VERSION_SCHEMA <string>]]
+            [DEFAULT_TARGETS <target>...]
+            [DEFAULT_CONFIGURATIONS <config>...]
+            [PERMISSIONS <permission>...]
+            [CONFIGURATIONS <config>...]
+            [COMPONENT <component>]
+            [EXCLUDE_FROM_ALL])
+
+  The ``PACKAGE_INFO`` form generates and installs a |CPS| file which describes
+  installed targets such that they can be consumed by another project.
+  Target installations are associated with the export ``<export-name>``
+  using the ``EXPORT`` option of the :command:`install(TARGETS)` signature
+  documented above.  Unlike :command:`install(EXPORT)`, this information is not
+  expressed in CMake code, and can be consumed by tools other than CMake.  When
+  imported into another CMake project, the imported targets will be prefixed
+  with ``<package-name>::``.  By default, the generated file will be called
+  ``<package-name>[-<appendix-name>].cps``.  If ``LOWER_CASE_FILE`` is given,
+  the package name as it appears on disk (in both the file name and install
+  destination) will be first converted to lower case.
+
+  If ``DESTINATION`` is not specified, a platform-specific default is used.
+
+  If ``APPENDIX`` is specified, rather than generating a top level package
+  specification, the specified targets will be exported as an appendix to the
+  named package.  Appendices may be used to separate less commonly used targets
+  (along with their external dependencies) from the rest of a package.  This
+  enables consumers to ignore transitive dependencies for targets that they
+  don't use, and also allows a single logical "package" to be composed of
+  artifacts produced by multiple build trees.
+
+  Appendices are not permitted to change basic package metadata; therefore,
+  none of ``VERSION``, ``COMPAT_VERSION``, ``VERSION_SCHEMA``,
+  ``DEFAULT_TARGETS`` or ``DEFAULT_CONFIGURATIONS`` may be specified in
+  combination with ``APPENDIX``.  Additionally, it is strongly recommended that
+  use of ``LOWER_CASE_FILE`` should be consistent between the main package and
+  any appendices.
+
+.. signature::
   install(RUNTIME_DEPENDENCY_SET <set-name> [...])
 
   .. versionadded:: 3.21
@@ -1096,3 +1152,6 @@
   This is an environment variable rather than a CMake variable. It allows you
   to change the installation prefix on UNIX systems. See :envvar:`DESTDIR` for
   details.
+
+.. _CPS: https://cps-org.github.io/cps/
+.. |CPS| replace:: Common Package Specification
diff --git a/Help/dev/experimental.rst b/Help/dev/experimental.rst
index 35ea34f..fb33112 100644
--- a/Help/dev/experimental.rst
+++ b/Help/dev/experimental.rst
@@ -39,6 +39,23 @@
   using the ``CMAKE_EXPORT_FIND_PACKAGE_NAME`` variable and/or
 ``EXPORT_FIND_PACKAGE_NAME`` target property.
 
+Export |CPS| Package Information
+================================
+
+In order to activate support for this experimental feature, set
+
+* variable ``CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO`` to
+* value ``b80be207-778e-46ba-8080-b23bba22639e``.
+
+This UUID may change in future versions of CMake.  Be sure to use the value
+documented here by the source tree of the version of CMake with which you are
+experimenting.
+
+When activated, this experimental feature provides the following:
+
+* The experimental ``install(PACKAGE_INFO)`` command is available to export
+  package information in the |CPS|_ format.
+
 C++ ``import std`` support
 ==========================
 
@@ -60,3 +77,6 @@
 
 * Targets with the property set to a true value and at least ``cxx_std_23``
   may use ``import std;`` in any scanned C++ source file.
+
+.. _CPS: https://cps-org.github.io/cps/
+.. |CPS| replace:: Common Package Specification
diff --git a/Help/release/3.30.rst b/Help/release/3.30.rst
index 2d57249..ca35d1f 100644
--- a/Help/release/3.30.rst
+++ b/Help/release/3.30.rst
@@ -254,3 +254,11 @@
 * These versions made no changes to documented features or interfaces.
   Some implementation updates were made to support ecosystem changes
   and/or fix regressions.
+
+.. 3.30.3 (unreleased)
+
+  * The :module:`FindPython` and :module:`FindPython3` modules now define,
+    respectively, the ``Python_DEFINITIONS`` and  ``Python3_DEFINITIONS``
+    variables on Windows to support development with the free threaded
+    version of Python.  The :prop_tgt:`INTERFACE_COMPILE_DEFINITIONS` target
+    property is also defined for the various targets provided by these modules.
diff --git a/Modules/CheckCCompilerFlag.cmake b/Modules/CheckCCompilerFlag.cmake
index 696f7a3..0a658c4 100644
--- a/Modules/CheckCCompilerFlag.cmake
+++ b/Modules/CheckCCompilerFlag.cmake
@@ -5,7 +5,7 @@
 CheckCCompilerFlag
 ------------------
 
-Check whether the C compiler supports a given flag.
+Check once whether the C compiler supports a given flag.
 
 .. command:: check_c_compiler_flag
 
@@ -13,24 +13,25 @@
 
     check_c_compiler_flag(<flag> <resultVar>)
 
-  Check that the ``<flag>`` is accepted by the compiler without
-  a diagnostic.  Stores the result in an internal cache entry
-  named ``<resultVar>``.
+Check once that the ``<flag>`` is accepted by the compiler without a diagnostic.
+The result is stored in the internal cache variable specified by
+``<resultVar>``, with boolean ``true`` for success and boolean ``false`` for
+failure.
 
-A positive result from this check indicates only that the compiler did not
-issue a diagnostic message when given the flag.  Whether the flag has any
-effect or even a specific one is beyond the scope of this module.
+``true`` indicates only that the compiler did not issue a diagnostic message
+when given the flag. Whether the flag has any effect is beyond the scope of
+this module.
 
-The check is only performed once, with the result cached in the variable named
-by ``<resultVar>``. Every subsequent CMake run will reuse this cached value
-rather than performing the check again, even if the ``<code>`` changes. In
-order to force the check to be re-evaluated, the variable named by
-``<resultVar>`` must be manually removed from the cache.
+Internally, :command:`try_compile` is used to perform the check. If
+:variable:`CMAKE_TRY_COMPILE_TARGET_TYPE` is set to ``EXECUTABLE`` (default),
+the check compiles and links an executable program. If set to
+``STATIC_LIBRARY``, the check is compiled but not linked.
 
 See also :command:`check_compiler_flag` for a more general command syntax.
 
 The compile and link commands can be influenced by setting any of the
-following variables prior to calling ``check_c_compiler_flag()``
+following variables prior to calling ``check_c_compiler_flag()``. Unknown flags
+in these variables can case a false negative result.
 
 .. include:: /module/CMAKE_REQUIRED_FLAGS.txt
 
diff --git a/Modules/CheckCXXCompilerFlag.cmake b/Modules/CheckCXXCompilerFlag.cmake
index 90cd488..1465b9f 100644
--- a/Modules/CheckCXXCompilerFlag.cmake
+++ b/Modules/CheckCXXCompilerFlag.cmake
@@ -5,7 +5,7 @@
 CheckCXXCompilerFlag
 ------------------------
 
-Check whether the CXX compiler supports a given flag.
+Check once whether the CXX compiler supports a given flag.
 
 .. command:: check_cxx_compiler_flag
 
@@ -13,20 +13,37 @@
 
     check_cxx_compiler_flag(<flag> <var>)
 
-  Check that the ``<flag>`` is accepted by the compiler without
-  a diagnostic.  Stores the result in an internal cache entry
-  named ``<var>``.
+Check once that the ``<flag>`` is accepted by the compiler without a diagnostic.
+The result is stored in the internal cache variable specified by
+``<resultVar>``, with boolean ``true`` for success and boolean ``false`` for
+failure.
 
-A positive result from this check indicates only that the compiler did not
-issue a diagnostic message when given the flag.  Whether the flag has any
-effect or even a specific one is beyond the scope of this module.
+``true`` indicates only that the compiler did not issue a diagnostic message
+when given the flag. Whether the flag has any effect is beyond the scope of
+this module.
+
+Internally, :command:`try_compile` is used to perform the check. If
+:variable:`CMAKE_TRY_COMPILE_TARGET_TYPE` is set to ``EXECUTABLE`` (default),
+the check compiles and links an executable program. If set to
+``STATIC_LIBRARY``, the check is compiled but not linked.
 
 See also :command:`check_compiler_flag` for a more general command syntax.
 
-.. note::
-  Since the :command:`try_compile` command forwards flags from variables
-  like :variable:`CMAKE_CXX_FLAGS <CMAKE_<LANG>_FLAGS>`, unknown flags
-  in such variables may cause a false negative for this check.
+The compile and link commands can be influenced by setting any of the
+following variables prior to calling ``check_cxx_compiler_flag()``. Unknown flags
+in these variables can case a false negative result.
+
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/CheckCompilerFlag.cmake b/Modules/CheckCompilerFlag.cmake
index 0f2ec4c..6252767 100644
--- a/Modules/CheckCompilerFlag.cmake
+++ b/Modules/CheckCompilerFlag.cmake
@@ -7,7 +7,7 @@
 
 .. versionadded:: 3.19
 
-Check whether the compiler supports a given flag.
+Check once whether the ``<lang>`` compiler supports a given flag.
 
 .. command:: check_compiler_flag
 
@@ -15,21 +15,23 @@
 
     check_compiler_flag(<lang> <flag> <resultVar>)
 
-Check that the ``<flag>`` is accepted by the compiler without a diagnostic.
-Stores the result in an internal cache entry named ``<resultVar>``.
+Check once that the ``<flag>`` is accepted by the ``<lang>`` compiler without
+a diagnostic. The result is stored in the internal cache variable specified by
+``<resultVar>``, with boolean ``true`` for success and boolean ``false`` for
+failure.
 
-A positive result from this check indicates only that the compiler did not
-issue a diagnostic message when given the flag.  Whether the flag has any
-effect or even a specific one is beyond the scope of this module.
+``true`` indicates only that the compiler did not issue a diagnostic message
+when given the flag. Whether the flag has any effect is beyond the scope of
+this module.
 
-The check is only performed once, with the result cached in the variable named
-by ``<resultVar>``. Every subsequent CMake run will reuse this cached value
-rather than performing the check again, even if the ``<code>`` changes. In
-order to force the check to be re-evaluated, the variable named by
-``<resultVar>`` must be manually removed from the cache.
+Internally, :command:`try_compile` is used to perform the check. If
+:variable:`CMAKE_TRY_COMPILE_TARGET_TYPE` is set to ``EXECUTABLE`` (default),
+the check compiles and links an executable program. If set to
+``STATIC_LIBRARY``, the check is compiled but not linked.
 
 The compile and link commands can be influenced by setting any of the
-following variables prior to calling ``check_compiler_flag()``
+following variables prior to calling ``check_compiler_flag()``. Unknown flags
+in these variables can case a false negative result.
 
 .. include:: /module/CMAKE_REQUIRED_FLAGS.txt
 
diff --git a/Modules/CheckFortranCompilerFlag.cmake b/Modules/CheckFortranCompilerFlag.cmake
index 94090ef..7ab1247 100644
--- a/Modules/CheckFortranCompilerFlag.cmake
+++ b/Modules/CheckFortranCompilerFlag.cmake
@@ -7,7 +7,7 @@
 
 .. versionadded:: 3.3
 
-Check whether the Fortran compiler supports a given flag.
+Check once whether the Fortran compiler supports a given flag.
 
 .. command:: check_fortran_compiler_flag
 
@@ -15,24 +15,25 @@
 
     check_fortran_compiler_flag(<flag> <resultVar>)
 
-  Check that the ``<flag>`` is accepted by the compiler without
-  a diagnostic.  Stores the result in an internal cache entry
-  named ``<resultVar>``.
+Check once that the ``<flag>`` is accepted by the compiler without a diagnostic.
+The result is stored in the internal cache variable specified by
+``<resultVar>``, with boolean ``true`` for success and boolean ``false`` for
+failure.
 
-A positive result from this check indicates only that the compiler did not
-issue a diagnostic message when given the flag.  Whether the flag has any
-effect or even a specific one is beyond the scope of this module.
+``true`` indicates only that the compiler did not issue a diagnostic message
+when given the flag. Whether the flag has any effect is beyond the scope of
+this module.
 
-The check is only performed once, with the result cached in the variable named
-by ``<resultVar>``. Every subsequent CMake run will reuse this cached value
-rather than performing the check again, even if the ``<code>`` changes. In
-order to force the check to be re-evaluated, the variable named by
-``<resultVar>`` must be manually removed from the cache.
+Internally, :command:`try_compile` is used to perform the check. If
+:variable:`CMAKE_TRY_COMPILE_TARGET_TYPE` is set to ``EXECUTABLE`` (default),
+the check compiles and links an executable program. If set to
+``STATIC_LIBRARY``, the check is compiled but not linked.
 
 See also :command:`check_compiler_flag` for a more general command syntax.
 
 The compile and link commands can be influenced by setting any of the
-following variables prior to calling ``check_fortran_compiler_flag()``
+following variables prior to calling ``check_fortran_compiler_flag()``. Unknown
+flags in these variables can case a false negative result.
 
 .. include:: /module/CMAKE_REQUIRED_FLAGS.txt
 
diff --git a/Modules/CheckFunctionExists.cmake b/Modules/CheckFunctionExists.cmake
index e7c47a4..76fa76a 100644
--- a/Modules/CheckFunctionExists.cmake
+++ b/Modules/CheckFunctionExists.cmake
@@ -5,7 +5,7 @@
 CheckFunctionExists
 -------------------
 
-Check if a C function can be linked
+Check once if a C function can be linked from system libraries.
 
 .. command:: check_function_exists
 
@@ -14,8 +14,7 @@
     check_function_exists(<function> <variable>)
 
   Checks that the ``<function>`` is provided by libraries on the system and store
-  the result in a ``<variable>``, which will be created as an internal
-  cache variable.
+  the result in internal cache variable ``<variable>``.
 
 The following variables may be set before calling this macro to modify the
 way the check is run:
@@ -34,8 +33,8 @@
 
 .. note::
 
-  Prefer using :Module:`CheckSymbolExists` instead of this module,
-  for the following reasons:
+  Prefer using :module:`CheckSymbolExists` or :module:`CheckSourceCompiles`
+  instead of this module, for the following reasons:
 
   * ``check_function_exists()`` can't detect functions that are inlined
     in headers or specified as a macro.
diff --git a/Modules/CheckLibraryExists.cmake b/Modules/CheckLibraryExists.cmake
index 8340500..d48d7ec 100644
--- a/Modules/CheckLibraryExists.cmake
+++ b/Modules/CheckLibraryExists.cmake
@@ -5,7 +5,7 @@
 CheckLibraryExists
 ------------------
 
-Check if the function exists.
+Check once if the function exists in system or specified library.
 
 .. command:: CHECK_LIBRARY_EXISTS
 
@@ -18,10 +18,11 @@
     LIBRARY  - the name of the library you are looking for
     FUNCTION - the name of the function
     LOCATION - location where the library should be found
-    VARIABLE - variable to store the result
-               Will be created as an internal cache variable.
+    VARIABLE - internal cache variable to store the result
 
-
+Prefer using :module:`CheckSymbolExists` or :module:`CheckSourceCompiles`
+instead of this module for more robust detection if a function is available in
+a library.
 
 The following variables may be set before calling this macro to modify
 the way the check is run:
diff --git a/Modules/CheckOBJCCompilerFlag.cmake b/Modules/CheckOBJCCompilerFlag.cmake
index f87949b..102b137 100644
--- a/Modules/CheckOBJCCompilerFlag.cmake
+++ b/Modules/CheckOBJCCompilerFlag.cmake
@@ -7,7 +7,7 @@
 
 .. versionadded:: 3.16
 
-Check whether the Objective-C compiler supports a given flag.
+Check once whether the Objective-C compiler supports a given flag.
 
 .. command:: check_objc_compiler_flag
 
@@ -15,24 +15,25 @@
 
     check_objc_compiler_flag(<flag> <resultVar>)
 
-  Check that the ``<flag>`` is accepted by the compiler without
-  a diagnostic.  Stores the result in an internal cache entry
-  named ``<resultVar>``.
+Check once that the ``<flag>`` is accepted by the compiler without a diagnostic.
+The result is stored in the internal cache variable specified by
+``<resultVar>``, with boolean ``true`` for success and boolean ``false`` for
+failure.
 
-A positive result from this check indicates only that the compiler did not
-issue a diagnostic message when given the flag.  Whether the flag has any
-effect or even a specific one is beyond the scope of this module.
+``true`` indicates only that the compiler did not issue a diagnostic message
+when given the flag. Whether the flag has any effect is beyond the scope of
+this module.
 
-The check is only performed once, with the result cached in the variable named
-by ``<resultVar>``. Every subsequent CMake run will reuse this cached value
-rather than performing the check again, even if the ``<code>`` changes. In
-order to force the check to be re-evaluated, the variable named by
-``<resultVar>`` must be manually removed from the cache.
+Internally, :command:`try_compile` is used to perform the check. If
+:variable:`CMAKE_TRY_COMPILE_TARGET_TYPE` is set to ``EXECUTABLE`` (default),
+the check compiles and links an executable program. If set to
+``STATIC_LIBRARY``, the check is compiled but not linked.
 
 See also :command:`check_compiler_flag` for a more general command syntax.
 
 The compile and link commands can be influenced by setting any of the
-following variables prior to calling ``check_objc_compiler_flag()``
+following variables prior to calling ``check_objc_compiler_flag()``. Unknown
+flags in these variables can case a false negative result.
 
 .. include:: /module/CMAKE_REQUIRED_FLAGS.txt
 
diff --git a/Modules/CheckOBJCXXCompilerFlag.cmake b/Modules/CheckOBJCXXCompilerFlag.cmake
index 5091a75..21daa5c 100644
--- a/Modules/CheckOBJCXXCompilerFlag.cmake
+++ b/Modules/CheckOBJCXXCompilerFlag.cmake
@@ -7,7 +7,7 @@
 
 .. versionadded:: 3.16
 
-Check whether the Objective-C++ compiler supports a given flag.
+Check once whether the Objective-C++ compiler supports a given flag.
 
 .. command:: check_objcxx_compiler_flag
 
@@ -15,24 +15,25 @@
 
     check_objcxx_compiler_flag(<flag> <resultVar>)
 
-  Check that the ``<flag>`` is accepted by the compiler without
-  a diagnostic.  Stores the result in an internal cache entry
-  named ``<resultVar>``.
+Check once that the ``<flag>`` is accepted by the compiler without a diagnostic.
+The result is stored in the internal cache variable specified by
+``<resultVar>``, with boolean ``true`` for success and boolean ``false`` for
+failure.
 
-A positive result from this check indicates only that the compiler did not
-issue a diagnostic message when given the flag.  Whether the flag has any
-effect or even a specific one is beyond the scope of this module.
+``true`` indicates only that the compiler did not issue a diagnostic message
+when given the flag. Whether the flag has any effect is beyond the scope of
+this module.
 
-The check is only performed once, with the result cached in the variable named
-by ``<resultVar>``. Every subsequent CMake run will reuse this cached value
-rather than performing the check again, even if the ``<code>`` changes. In
-order to force the check to be re-evaluated, the variable named by
-``<resultVar>`` must be manually removed from the cache.
+Internally, :command:`try_compile` is used to perform the check. If
+:variable:`CMAKE_TRY_COMPILE_TARGET_TYPE` is set to ``EXECUTABLE`` (default),
+the check compiles and links an executable program. If set to
+``STATIC_LIBRARY``, the check is compiled but not linked.
 
 See also :command:`check_compiler_flag` for a more general command syntax.
 
 The compile and link commands can be influenced by setting any of the
-following variables prior to calling ``check_objcxx_compiler_flag()``
+following variables prior to calling ``check_objcxx_compiler_flag()``. Unknown
+flags in these variables can case a false negative result.
 
 .. include:: /module/CMAKE_REQUIRED_FLAGS.txt
 
diff --git a/Modules/FindIconv.cmake b/Modules/FindIconv.cmake
index 879ff16f..876af8d 100644
--- a/Modules/FindIconv.cmake
+++ b/Modules/FindIconv.cmake
@@ -134,6 +134,8 @@
 
   find_path(Iconv_INCLUDE_DIR
     NAMES "iconv.h"
+    PATH_SUFFIXES
+      gnu-libiconv # GNU libiconv on Alpine Linux has header in a subdirectory.
     DOC "iconv include directory")
   set(Iconv_LIBRARY_NAMES "iconv" "libiconv")
   mark_as_advanced(Iconv_INCLUDE_DIR)
diff --git a/Modules/FindPython.cmake b/Modules/FindPython.cmake
index 7f11999..0c5958a 100644
--- a/Modules/FindPython.cmake
+++ b/Modules/FindPython.cmake
@@ -224,6 +224,11 @@
 
   The Python include directories.
 
+``Python_DEFINITIONS``
+  .. versionadded:: 3.30.3
+
+  The Python preprocessor definitions.
+
 ``Python_DEBUG_POSTFIX``
   .. versionadded.. 3.30
 
diff --git a/Modules/FindPython/Support.cmake b/Modules/FindPython/Support.cmake
index 1386117..35a1d67 100644
--- a/Modules/FindPython/Support.cmake
+++ b/Modules/FindPython/Support.cmake
@@ -3740,6 +3740,14 @@
                                 _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_RELEASE
                                 _${_PYTHON_PREFIX}_RUNTIME_LIBRARY_DEBUG)
     endif()
+
+    if (WIN32 AND _${_PYTHON_PREFIX}_LIBRARY_RELEASE MATCHES "t${CMAKE_IMPORT_LIBRARY_SUFFIX}$")
+      # On windows, header file is shared between the different implementations
+      # So Py_GIL_DISABLED should be set explicitly
+      set (${_PYTHON_PREFIX}_DEFINITIONS Py_GIL_DISABLED=1)
+    else()
+      unset (${_PYTHON_PREFIX}_DEFINITIONS)
+    endif()
   endif()
 
   if ("SABI_LIBRARY" IN_LIST _${_PYTHON_PREFIX}_FIND_DEVELOPMENT_ARTIFACTS)
@@ -3769,6 +3777,14 @@
                                 _${_PYTHON_PREFIX}_RUNTIME_SABI_LIBRARY_RELEASE
                                 _${_PYTHON_PREFIX}_RUNTIME_SABI_LIBRARY_DEBUG)
     endif()
+
+    if (WIN32 AND _${_PYTHON_PREFIX}_SABI_LIBRARY_RELEASE MATCHES "t${CMAKE_IMPORT_LIBRARY_SUFFIX}$")
+      # On windows, header file is shared between the different implementations
+      # So Py_GIL_DISABLED should be set explicitly
+      set (${_PYTHON_PREFIX}_DEFINITIONS Py_GIL_DISABLED=1)
+    else()
+      unset (${_PYTHON_PREFIX}_DEFINITIONS)
+    endif()
   endif()
 
   if (_${_PYTHON_PREFIX}_LIBRARY_RELEASE OR _${_PYTHON_PREFIX}_SABI_LIBRARY_RELEASE OR _${_PYTHON_PREFIX}_INCLUDE_DIR)
@@ -3903,11 +3919,12 @@
 
   # Workaround Intel MKL library outputting a message in stdout, which cause
   # incorrect detection of numpy.get_include() and numpy.__version__
-  # See https://github.com/numpy/numpy/issues/23775
-  if(DEFINED ENV{MKL_ENABLE_INSTRUCTIONS})
-    set(_${_PYTHON_PREFIX}_BACKUP_ENV_VAR_MKL_ENABLE_INSTRUCTIONS ENV{MKL_ENABLE_INSTRUCTIONS})
+  # See https://github.com/numpy/numpy/issues/23775 and
+  # https://gitlab.kitware.com/cmake/cmake/-/issues/26240
+  if(NOT DEFINED ENV{MKL_ENABLE_INSTRUCTIONS})
+    set(_${_PYTHON_PREFIX}_UNSET_ENV_VAR_MKL_ENABLE_INSTRUCTIONS YES)
+    set(ENV{MKL_ENABLE_INSTRUCTIONS} "SSE4_2")
   endif()
-  set(ENV{MKL_ENABLE_INSTRUCTIONS} "SSE4_2")
 
   if (NOT _${_PYTHON_PREFIX}_NumPy_INCLUDE_DIR)
     execute_process(COMMAND ${${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c
@@ -3949,11 +3966,9 @@
     set (${_PYTHON_PREFIX}_NumPy_FOUND FALSE)
   endif()
 
-  # Restore previous value of MKL_ENABLE_INSTRUCTIONS
-  if(DEFINED _${_PYTHON_PREFIX}_BACKUP_ENV_VAR_MKL_ENABLE_INSTRUCTIONS)
-    set(ENV{MKL_ENABLE_INSTRUCTIONS} ${_${_PYTHON_PREFIX}_BACKUP_ENV_VAR_MKL_ENABLE_INSTRUCTIONS})
-    unset(_${_PYTHON_PREFIX}_BACKUP_ENV_VAR_MKL_ENABLE_INSTRUCTIONS)
-  else()
+  # Unset MKL_ENABLE_INSTRUCTIONS if set by us
+  if(DEFINED _${_PYTHON_PREFIX}_UNSET_ENV_VAR_MKL_ENABLE_INSTRUCTIONS)
+    unset(_${_PYTHON_PREFIX}_UNSET_ENV_VAR_MKL_ENABLE_INSTRUCTIONS)
     unset(ENV{MKL_ENABLE_INSTRUCTIONS})
   endif()
 
@@ -4067,6 +4082,12 @@
       set_property (TARGET ${__name}
                     PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${${_PYTHON_PREFIX}_INCLUDE_DIRS}")
 
+      if (${_PYTHON_PREFIX}_DEFINITIONS)
+        set_property (TARGET ${__name}
+                      PROPERTY INTERFACE_COMPILE_DEFINITIONS "${${_PYTHON_PREFIX}_DEFINITIONS}")
+      endif()
+
+
       if (${_PYTHON_PREFIX}_${_PREFIX}LIBRARY_RELEASE AND ${_PYTHON_PREFIX}_RUNTIME_${_PREFIX}LIBRARY_RELEASE)
         # System manage shared libraries in two parts: import and runtime
         if (${_PYTHON_PREFIX}_${_PREFIX}LIBRARY_RELEASE AND ${_PYTHON_PREFIX}_${_PREFIX}LIBRARY_DEBUG)
@@ -4123,6 +4144,11 @@
       set_property (TARGET ${__name}
                     PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${${_PYTHON_PREFIX}_INCLUDE_DIRS}")
 
+      if (${_PYTHON_PREFIX}_DEFINITIONS)
+        set_property (TARGET ${__name}
+                      PROPERTY INTERFACE_COMPILE_DEFINITIONS "${${_PYTHON_PREFIX}_DEFINITIONS}")
+      endif()
+
       # When available, enforce shared library generation with undefined symbols
       if (APPLE)
         set_property (TARGET ${__name}
diff --git a/Modules/FindPython3.cmake b/Modules/FindPython3.cmake
index 5214583..9796828 100644
--- a/Modules/FindPython3.cmake
+++ b/Modules/FindPython3.cmake
@@ -226,6 +226,11 @@
 
   The Python 3 include directories.
 
+``Python3_DEFINITIONS``
+  .. versionadded:: 3.30.3
+
+  The Python 3 preprocessor definitions.
+
 ``Python3_DEBUG_POSTFIX``
   .. versionadded.. 3.30
 
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 6841f1d..67360bc 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -217,6 +217,10 @@
   cmExportInstallCMakeConfigGenerator.cxx
   cmExportInstallFileGenerator.h
   cmExportInstallFileGenerator.cxx
+  cmExportInstallPackageInfoGenerator.h
+  cmExportInstallPackageInfoGenerator.cxx
+  cmExportPackageInfoGenerator.h
+  cmExportPackageInfoGenerator.cxx
   cmExportTryCompileFileGenerator.h
   cmExportTryCompileFileGenerator.cxx
   cmExportSet.h
@@ -336,6 +340,8 @@
   cmInstallFilesGenerator.cxx
   cmInstallImportedRuntimeArtifactsGenerator.h
   cmInstallImportedRuntimeArtifactsGenerator.cxx
+  cmInstallPackageInfoExportGenerator.h
+  cmInstallPackageInfoExportGenerator.cxx
   cmInstallRuntimeDependencySet.h
   cmInstallRuntimeDependencySet.cxx
   cmInstallRuntimeDependencySetGenerator.h
diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake
index 530abaa..fe78b30 100644
--- a/Source/CMakeVersion.cmake
+++ b/Source/CMakeVersion.cmake
@@ -1,7 +1,7 @@
 # CMake version number components.
 set(CMake_VERSION_MAJOR 3)
 set(CMake_VERSION_MINOR 30)
-set(CMake_VERSION_PATCH 20240827)
+set(CMake_VERSION_PATCH 20240828)
 #set(CMake_VERSION_RC 0)
 set(CMake_VERSION_IS_DIRTY 0)
 
diff --git a/Source/cmExperimental.cxx b/Source/cmExperimental.cxx
index a2e6e70..4504c07 100644
--- a/Source/cmExperimental.cxx
+++ b/Source/cmExperimental.cxx
@@ -46,6 +46,16 @@
     {},
     cmExperimental::TryCompileCondition::Always,
     false },
+  // ExportPackageInfo
+  { "ExportPackageInfo",
+    "b80be207-778e-46ba-8080-b23bba22639e",
+    "CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO",
+    "CMake's support for exporting package information in the Common Package "
+    "Specification format. It is meant only for experimentation and feedback "
+    "to CMake developers.",
+    {},
+    cmExperimental::TryCompileCondition::Always,
+    false },
 };
 static_assert(sizeof(LookupTable) / sizeof(LookupTable[0]) ==
                 static_cast<size_t>(cmExperimental::Feature::Sentinel),
diff --git a/Source/cmExperimental.h b/Source/cmExperimental.h
index 05764f9..46a9bc4 100644
--- a/Source/cmExperimental.h
+++ b/Source/cmExperimental.h
@@ -20,6 +20,7 @@
     ExportPackageDependencies,
     WindowsKernelModeDriver,
     CxxImportStd,
+    ExportPackageInfo,
 
     Sentinel,
   };
diff --git a/Source/cmExportFileGenerator.cxx b/Source/cmExportFileGenerator.cxx
index 24ed273..5e9461d 100644
--- a/Source/cmExportFileGenerator.cxx
+++ b/Source/cmExportFileGenerator.cxx
@@ -331,6 +331,14 @@
   }
 }
 
+bool cmExportFileGenerator::NoteLinkedTarget(
+  cmGeneratorTarget const* /*target*/, std::string const& /*linkedName*/,
+  cmGeneratorTarget const* /*linkedTarget*/)
+{
+  // Default implementation does nothing; only needed by some generators.
+  return true;
+}
+
 bool cmExportFileGenerator::AddTargetNamespace(std::string& input,
                                                cmGeneratorTarget const* target,
                                                cmLocalGenerator const* lg)
@@ -352,8 +360,9 @@
 
   if (tgt->IsImported()) {
     input = tgt->GetName();
-    return true;
+    return this->NoteLinkedTarget(target, input, tgt);
   }
+
   if (this->ExportedTargets.find(tgt) != this->ExportedTargets.end()) {
     input = this->Namespace + tgt->GetExportName();
   } else {
@@ -365,7 +374,8 @@
       input = tgt->GetName();
     }
   }
-  return true;
+
+  return this->NoteLinkedTarget(target, input, tgt);
 }
 
 void cmExportFileGenerator::ResolveTargetsInGeneratorExpressions(
diff --git a/Source/cmExportFileGenerator.h b/Source/cmExportFileGenerator.h
index f765493..b1c9ce3 100644
--- a/Source/cmExportFileGenerator.h
+++ b/Source/cmExportFileGenerator.h
@@ -95,6 +95,11 @@
                                            std::string const& config,
                                            std::string const& suffix) = 0;
 
+  /** Record a target referenced by an exported target. */
+  virtual bool NoteLinkedTarget(cmGeneratorTarget const* target,
+                                std::string const& linkedName,
+                                cmGeneratorTarget const* linkedTarget);
+
   /** Each subclass knows how to deal with a target that is  missing from an
    *  export set.  */
   virtual void HandleMissingTarget(std::string& link_libs,
diff --git a/Source/cmExportInstallPackageInfoGenerator.cxx b/Source/cmExportInstallPackageInfoGenerator.cxx
new file mode 100644
index 0000000..b9b715e
--- /dev/null
+++ b/Source/cmExportInstallPackageInfoGenerator.cxx
@@ -0,0 +1,197 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmExportInstallPackageInfoGenerator.h"
+
+#include <memory>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include <cm3p/json/value.h>
+
+#include "cmExportSet.h"
+#include "cmGeneratorExpression.h"
+#include "cmGeneratorTarget.h"
+#include "cmInstallExportGenerator.h"
+#include "cmLocalGenerator.h"
+#include "cmMakefile.h"
+#include "cmStateTypes.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+#include "cmTarget.h"
+#include "cmTargetExport.h"
+
+cmExportInstallPackageInfoGenerator::cmExportInstallPackageInfoGenerator(
+  cmInstallExportGenerator* iegen, 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))
+  , cmExportInstallFileGenerator(iegen)
+{
+}
+
+std::string cmExportInstallPackageInfoGenerator::GetConfigImportFileGlob()
+  const
+{
+  std::string glob = cmStrCat(this->FileBase, "@*", this->FileExt);
+  return glob;
+}
+
+std::string const& cmExportInstallPackageInfoGenerator::GetExportName() const
+{
+  return this->GetPackageName();
+}
+
+bool cmExportInstallPackageInfoGenerator::GenerateMainFile(std::ostream& os)
+{
+  std::vector<cmTargetExport const*> allTargets;
+  {
+    auto visitor = [&](cmTargetExport const* te) { allTargets.push_back(te); };
+
+    if (!this->CollectExports(visitor)) {
+      return false;
+    }
+  }
+
+  if (!this->CheckDefaultTargets()) {
+    return false;
+  }
+
+  Json::Value root = this->GeneratePackageInfo();
+  Json::Value& components = root["components"];
+
+  // Compute the relative import prefix for the file
+  std::string const& packagePath = this->GenerateImportPrefix();
+  if (packagePath.empty()) {
+    return false;
+  }
+  root["cps_path"] = packagePath;
+
+  bool requiresConfigFiles = false;
+  // Create all the imported targets.
+  for (cmTargetExport const* te : allTargets) {
+    cmGeneratorTarget* gt = te->Target;
+    cmStateEnums::TargetType targetType = this->GetExportTargetType(te);
+
+    Json::Value* const component =
+      this->GenerateImportTarget(components, gt, targetType);
+    if (!component) {
+      return false;
+    }
+
+    ImportPropertyMap properties;
+    if (!this->PopulateInterfaceProperties(te, properties)) {
+      return false;
+    }
+    this->PopulateInterfaceLinkLibrariesProperty(
+      gt, cmGeneratorExpression::InstallInterface, properties);
+
+    if (targetType != cmStateEnums::INTERFACE_LIBRARY) {
+      requiresConfigFiles = true;
+    }
+
+    // Set configuration-agnostic properties for component.
+    this->GenerateInterfaceProperties(*component, gt, properties);
+  }
+
+  this->GeneratePackageRequires(root);
+
+  // Write the primary packing information file.
+  this->WritePackageInfo(root, os);
+
+  bool result = true;
+
+  // Generate an import file for each configuration.
+  if (requiresConfigFiles) {
+    for (std::string const& c : this->Configurations) {
+      if (!this->GenerateImportFileConfig(c)) {
+        result = false;
+      }
+    }
+  }
+
+  return result;
+}
+
+void cmExportInstallPackageInfoGenerator::GenerateImportTargetsConfig(
+  std::ostream& os, std::string const& config, std::string const& suffix)
+{
+  Json::Value root;
+  root["name"] = this->GetPackageName();
+  root["configuration"] = config;
+
+  Json::Value& components = root["components"];
+
+  for (auto const& te : this->GetExportSet()->GetTargetExports()) {
+    // Collect import properties for this target.
+    if (this->GetExportTargetType(te.get()) ==
+        cmStateEnums::INTERFACE_LIBRARY) {
+      continue;
+    }
+
+    ImportPropertyMap properties;
+    std::set<std::string> importedLocations;
+
+    this->PopulateImportProperties(config, suffix, te.get(), properties,
+                                   importedLocations);
+
+    this->GenerateInterfaceConfigProperties(components, te->Target, suffix,
+                                            properties);
+  }
+
+  this->WritePackageInfo(root, os);
+}
+
+std::string cmExportInstallPackageInfoGenerator::GenerateImportPrefix() const
+{
+  std::string expDest = this->IEGen->GetDestination();
+  if (cmSystemTools::FileIsFullPath(expDest)) {
+    std::string const& installPrefix =
+      this->IEGen->GetLocalGenerator()->GetMakefile()->GetSafeDefinition(
+        "CMAKE_INSTALL_PREFIX");
+    if (cmHasPrefix(expDest, installPrefix)) {
+      auto n = installPrefix.length();
+      while (n < expDest.length() && expDest[n] == '/') {
+        ++n;
+      }
+      expDest = expDest.substr(n);
+    } else {
+      this->ReportError(
+        cmStrCat("install(PACKAGE_INFO \"", this->GetExportName(),
+                 "\" ...) specifies DESTINATION \"", expDest,
+                 "\" which is not a subdirectory of the install prefix."));
+      return {};
+    }
+  }
+
+  if (expDest.empty()) {
+    return this->GetInstallPrefix();
+  }
+  return cmStrCat(this->GetImportPrefixWithSlash(), expDest);
+}
+
+std::string cmExportInstallPackageInfoGenerator::InstallNameDir(
+  cmGeneratorTarget const* target, std::string const& config)
+{
+  std::string install_name_dir;
+
+  cmMakefile* mf = target->Target->GetMakefile();
+  if (mf->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME")) {
+    install_name_dir =
+      target->GetInstallNameDirForInstallTree(config, "@prefix@");
+  }
+
+  return install_name_dir;
+}
+
+std::string cmExportInstallPackageInfoGenerator::GetCxxModulesDirectory() const
+{
+  // TODO: Implement a not-CMake-specific mechanism for providing module
+  // information.
+  // return IEGen->GetCxxModuleDirectory();
+  return {};
+}
diff --git a/Source/cmExportInstallPackageInfoGenerator.h b/Source/cmExportInstallPackageInfoGenerator.h
new file mode 100644
index 0000000..5861b05
--- /dev/null
+++ b/Source/cmExportInstallPackageInfoGenerator.h
@@ -0,0 +1,66 @@
+/* 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 "cmExportInstallFileGenerator.h"
+#include "cmExportPackageInfoGenerator.h"
+
+class cmGeneratorTarget;
+class cmInstallExportGenerator;
+
+/** \class cmExportInstallPackageInfoGenerator
+ * \brief Generate files exporting targets from an install tree.
+ *
+ * cmExportInstallPackageInfoGenerator generates files exporting targets from
+ * an installation tree.  The files are placed in a temporary location for
+ * installation by cmInstallExportGenerator.  The file format is the Common
+ * Package Specification (https://cps-org.github.io/cps/).
+ *
+ * One main file is generated that describes the imported targets.  Additional,
+ * per-configuration files describe target locations and settings for each
+ * configuration.
+ *
+ * This is used to implement the INSTALL(PACKAGE_INFO) command.
+ */
+class cmExportInstallPackageInfoGenerator
+  : public cmExportPackageInfoGenerator
+  , public cmExportInstallFileGenerator
+{
+public:
+  /** Construct with the export installer that will install the
+      files.  */
+  cmExportInstallPackageInfoGenerator(
+    cmInstallExportGenerator* iegen, std::string packageName,
+    std::string version, std::string versionCompat, std::string versionSchema,
+    std::vector<std::string> defaultTargets,
+    std::vector<std::string> defaultConfigurations);
+
+  /** Compute the globbing expression used to load per-config import
+      files from the main file.  */
+  std::string GetConfigImportFileGlob() const override;
+
+protected:
+  std::string const& GetExportName() const override;
+
+  // Implement virtual methods from the superclass.
+  bool GenerateMainFile(std::ostream& os) override;
+  void GenerateImportTargetsConfig(std::ostream& os, std::string const& config,
+                                   std::string const& suffix) override;
+
+  char GetConfigFileNameSeparator() const override { return '@'; }
+
+  /** Generate the cps_path, which determines the import prefix.  */
+  std::string GenerateImportPrefix() const;
+
+  std::string InstallNameDir(cmGeneratorTarget const* target,
+                             std::string const& config) override;
+
+  std::string GetCxxModulesDirectory() const override;
+  // TODO: Generate C++ module info in a not-CMake-specific format.
+};
diff --git a/Source/cmExportPackageInfoGenerator.cxx b/Source/cmExportPackageInfoGenerator.cxx
new file mode 100644
index 0000000..7625953
--- /dev/null
+++ b/Source/cmExportPackageInfoGenerator.cxx
@@ -0,0 +1,452 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmExportPackageInfoGenerator.h"
+
+#include <memory>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include <cm/string_view>
+#include <cmext/algorithm>
+#include <cmext/string_view>
+
+#include <cm3p/json/value.h>
+#include <cm3p/json/writer.h>
+
+#include "cmExportSet.h"
+#include "cmFindPackageStack.h"
+#include "cmGeneratorExpression.h"
+#include "cmGeneratorTarget.h"
+#include "cmList.h"
+#include "cmMakefile.h"
+#include "cmMessageType.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+#include "cmTarget.h"
+#include "cmValue.h"
+
+constexpr char const* cmExportPackageInfoGenerator::CPS_VERSION_STR;
+
+cmExportPackageInfoGenerator::cmExportPackageInfoGenerator(
+  std::string packageName, std::string version, std::string versionCompat,
+  std::string versionSchema, std::vector<std::string> defaultTargets,
+  std::vector<std::string> defaultConfigurations)
+  : PackageName(std::move(packageName))
+  , PackageVersion(std::move(version))
+  , PackageVersionCompat(std::move(versionCompat))
+  , PackageVersionSchema(std::move(versionSchema))
+  , DefaultTargets(std::move(defaultTargets))
+  , DefaultConfigurations(std::move(defaultConfigurations))
+{
+}
+
+cm::string_view cmExportPackageInfoGenerator::GetImportPrefixWithSlash() const
+{
+  return "@prefix@/"_s;
+}
+
+bool cmExportPackageInfoGenerator::GenerateImportFile(std::ostream& os)
+{
+  return this->GenerateMainFile(os);
+}
+
+void cmExportPackageInfoGenerator::WritePackageInfo(
+  Json::Value const& packageInfo, std::ostream& os) const
+{
+  Json::StreamWriterBuilder builder;
+  builder["indentation"] = "  ";
+  builder["commentStyle"] = "None";
+  std::unique_ptr<Json::StreamWriter> const writer(builder.newStreamWriter());
+  writer->write(packageInfo, &os);
+}
+
+namespace {
+template <typename T>
+void buildArray(Json::Value& object, std::string const& property,
+                T const& values)
+{
+  if (!values.empty()) {
+    Json::Value& array = object[property];
+    for (auto const& item : values) {
+      array.append(item);
+    }
+  }
+}
+}
+
+bool cmExportPackageInfoGenerator::CheckDefaultTargets() const
+{
+  bool result = true;
+  std::set<std::string> exportedTargetNames;
+  for (auto const* te : this->ExportedTargets) {
+    exportedTargetNames.emplace(te->GetExportName());
+  }
+
+  for (auto const& name : this->DefaultTargets) {
+    if (!cm::contains(exportedTargetNames, name)) {
+      this->ReportError(
+        cmStrCat("Package \"", this->GetPackageName(),
+                 "\" specifies DEFAULT_TARGETS \"", name,
+                 "\", which is not a target in the export set \"",
+                 this->GetExportSet()->GetName(), "\"."));
+      result = false;
+    }
+  }
+
+  return result;
+}
+
+Json::Value cmExportPackageInfoGenerator::GeneratePackageInfo() const
+{
+  Json::Value package;
+
+  package["name"] = this->GetPackageName();
+  package["cps_version"] = this->CPS_VERSION_STR;
+
+  if (!this->PackageVersion.empty()) {
+    package["version"] = this->PackageVersion;
+    if (!this->PackageVersion.empty()) {
+      package["compat_version"] = this->PackageVersionCompat;
+    }
+    if (!this->PackageVersion.empty()) {
+      package["version_schema"] = this->PackageVersionSchema;
+    }
+  }
+
+  buildArray(package, "default_components", this->DefaultTargets);
+  buildArray(package, "configurations", this->DefaultConfigurations);
+
+  // TODO: description, website, license
+
+  return package;
+}
+
+void cmExportPackageInfoGenerator::GeneratePackageRequires(
+  Json::Value& package) const
+{
+  if (!this->Requirements.empty()) {
+    Json::Value& requirements = package["requires"];
+    for (auto const& requirement : this->Requirements) {
+      // TODO: version, hint
+      requirements[requirement] = Json::Value{};
+    }
+  }
+}
+
+Json::Value* cmExportPackageInfoGenerator::GenerateImportTarget(
+  Json::Value& components, cmGeneratorTarget const* target,
+  cmStateEnums::TargetType targetType) const
+{
+  auto const& name = target->GetExportName();
+  if (name.empty()) {
+    return nullptr;
+  }
+
+  Json::Value& component = components[name];
+  Json::Value& type = component["type"];
+  switch (targetType) {
+    case cmStateEnums::EXECUTABLE:
+      type = "executable";
+      break;
+    case cmStateEnums::STATIC_LIBRARY:
+      type = "archive";
+      break;
+    case cmStateEnums::SHARED_LIBRARY:
+      type = "dylib";
+      break;
+    case cmStateEnums::MODULE_LIBRARY:
+      type = "module";
+      break;
+    case cmStateEnums::INTERFACE_LIBRARY:
+      type = "interface";
+      break;
+    default:
+      type = "unknown";
+      break;
+  }
+  return &component;
+}
+
+bool cmExportPackageInfoGenerator::GenerateInterfaceProperties(
+  Json::Value& component, cmGeneratorTarget const* target,
+  ImportPropertyMap const& properties) const
+{
+  bool result = true;
+
+  this->GenerateInterfaceLinkProperties(result, component, target, properties);
+
+  this->GenerateInterfaceCompileFeatures(result, component, target,
+                                         properties);
+  this->GenerateInterfaceCompileDefines(result, component, target, properties);
+
+  this->GenerateInterfaceListProperty(result, component, target,
+                                      "compile_flags", "COMPILE_OPTIONS"_s,
+                                      properties);
+  this->GenerateInterfaceListProperty(result, component, target, "link_flags",
+                                      "LINK_OPTIONS"_s, properties);
+  this->GenerateInterfaceListProperty(result, component, target,
+                                      "link_directories", "LINK_DIRECTORIES"_s,
+                                      properties);
+  this->GenerateInterfaceListProperty(result, component, target, "includes",
+                                      "INCLUDE_DIRECTORIES"_s, properties);
+
+  // TODO: description, license
+
+  return result;
+}
+
+namespace {
+bool forbidGeneratorExpressions(std::string const& propertyName,
+                                std::string const& propertyValue,
+                                cmGeneratorTarget const* target)
+{
+  std::string const& evaluatedValue = cmGeneratorExpression::Preprocess(
+    propertyValue, cmGeneratorExpression::StripAllGeneratorExpressions);
+  if (evaluatedValue != propertyValue) {
+    target->Makefile->IssueMessage(
+      MessageType::FATAL_ERROR,
+      cmStrCat("Property \"", propertyName, "\" of target \"",
+               target->GetName(),
+               "\" contains a generator expression. This is not allowed."));
+    return false;
+  }
+  return true;
+}
+}
+
+bool cmExportPackageInfoGenerator::NoteLinkedTarget(
+  cmGeneratorTarget const* target, std::string const& linkedName,
+  cmGeneratorTarget const* linkedTarget)
+{
+  if (cm::contains(this->ExportedTargets, linkedTarget)) {
+    // Target is internal to this package.
+    this->LinkTargets.emplace(linkedName,
+                              cmStrCat(':', linkedTarget->GetExportName()));
+    return true;
+  }
+
+  if (linkedTarget->IsImported()) {
+    // Target is imported from a found package.
+    auto pkgName = [linkedTarget]() -> std::string {
+      auto const& pkgStack = linkedTarget->Target->GetFindPackageStack();
+      if (!pkgStack.Empty()) {
+        return pkgStack.Top().Name;
+      }
+
+      return linkedTarget->Target->GetProperty("EXPORT_FIND_PACKAGE_NAME");
+    }();
+
+    if (pkgName.empty()) {
+      target->Makefile->IssueMessage(
+        MessageType::FATAL_ERROR,
+        cmStrCat("Target \"", target->GetName(),
+                 "\" references imported target \"", linkedName,
+                 "\" which does not come from any known package."));
+      return false;
+    }
+
+    auto const& prefix = cmStrCat(pkgName, "::");
+    if (!cmHasPrefix(linkedName, prefix)) {
+      target->Makefile->IssueMessage(
+        MessageType::FATAL_ERROR,
+        cmStrCat("Target \"", target->GetName(), "\" references target \"",
+                 linkedName, "\", which comes from the \"", pkgName,
+                 "\" package, but does not belong to the package's "
+                 "canonical namespace. This is not allowed."));
+      return false;
+    }
+
+    // TODO: Record package version, hint.
+    this->Requirements.emplace(pkgName);
+    this->LinkTargets.emplace(
+      linkedName, cmStrCat(pkgName, ':', linkedName.substr(prefix.length())));
+    return true;
+  }
+
+  // Target belongs to another export from this build.
+  auto const& exportInfo = this->FindExportInfo(linkedTarget);
+  if (exportInfo.first.size() == 1) {
+    auto const& linkNamespace = exportInfo.second;
+    if (!cmHasSuffix(linkNamespace, "::")) {
+      target->Makefile->IssueMessage(
+        MessageType::FATAL_ERROR,
+        cmStrCat("Target \"", target->GetName(), "\" references target \"",
+                 linkedName,
+                 "\", which does not use the standard namespace separator. "
+                 "This is not allowed."));
+      return false;
+    }
+
+    auto pkgName =
+      cm::string_view{ linkNamespace.data(), linkNamespace.size() - 2 };
+
+    if (pkgName == this->GetPackageName()) {
+      this->LinkTargets.emplace(linkedName,
+                                cmStrCat(':', linkedTarget->GetExportName()));
+    } else {
+      this->Requirements.emplace(pkgName);
+      this->LinkTargets.emplace(
+        linkedName, cmStrCat(pkgName, ':', linkedTarget->GetExportName()));
+    }
+    return true;
+  }
+
+  // cmExportFileGenerator::HandleMissingTarget should have complained about
+  // this already. (In fact, we probably shouldn't ever get here.)
+  return false;
+}
+
+void cmExportPackageInfoGenerator::GenerateInterfaceLinkProperties(
+  bool& result, Json::Value& component, cmGeneratorTarget const* target,
+  ImportPropertyMap const& properties) const
+{
+  auto const& iter = properties.find("INTERFACE_LINK_LIBRARIES");
+  if (iter == properties.end()) {
+    return;
+  }
+
+  // TODO: Support $<LINK_ONLY>.
+  if (!forbidGeneratorExpressions(iter->first, iter->second, target)) {
+    result = false;
+    return;
+  }
+
+  std::vector<std::string> buildRequires;
+  // std::vector<std::string> linkRequires; TODO
+  std::vector<std::string> linkLibraries;
+
+  for (auto const& name : cmList{ iter->second }) {
+    auto const& ti = this->LinkTargets.find(name);
+    if (ti != this->LinkTargets.end()) {
+      if (ti->second.empty()) {
+        result = false;
+      } else {
+        buildRequires.emplace_back(ti->second);
+      }
+    } else {
+      linkLibraries.emplace_back(name);
+    }
+  }
+
+  buildArray(component, "requires", buildRequires);
+  // buildArray(component, "link_requires", linkRequires); TODO
+  buildArray(component, "link_libraries", linkLibraries);
+}
+
+void cmExportPackageInfoGenerator::GenerateInterfaceCompileFeatures(
+  bool& result, Json::Value& component, cmGeneratorTarget const* target,
+  ImportPropertyMap const& properties) const
+{
+  auto const& iter = properties.find("INTERFACE_COMPILE_FEATURES");
+  if (iter == properties.end()) {
+    return;
+  }
+
+  if (!forbidGeneratorExpressions(iter->first, iter->second, target)) {
+    result = false;
+    return;
+  }
+
+  std::set<std::string> features;
+  for (auto const& value : cmList{ iter->second }) {
+    if (cmHasLiteralPrefix(value, "c_std_")) {
+      auto suffix = cm::string_view{ value }.substr(6, 2);
+      features.emplace(cmStrCat("cxx", suffix));
+    } else if (cmHasLiteralPrefix(value, "cxx_std_")) {
+      auto suffix = cm::string_view{ value }.substr(8, 2);
+      features.emplace(cmStrCat("c++", suffix));
+    }
+  }
+
+  buildArray(component, "compile_features", features);
+}
+
+void cmExportPackageInfoGenerator::GenerateInterfaceCompileDefines(
+  bool& result, Json::Value& component, cmGeneratorTarget const* target,
+  ImportPropertyMap const& properties) const
+{
+  auto const& iter = properties.find("INTERFACE_COMPILE_DEFINITIONS");
+  if (iter == properties.end()) {
+    return;
+  }
+
+  // TODO: Support language-specific defines.
+  if (!forbidGeneratorExpressions(iter->first, iter->second, target)) {
+    result = false;
+    return;
+  }
+
+  Json::Value defines;
+  for (auto const& def : cmList{ iter->second }) {
+    auto const n = def.find('=');
+    if (n == std::string::npos) {
+      defines[def] = Json::Value{};
+    } else {
+      defines[def.substr(0, n)] = def.substr(n + 1);
+    }
+  }
+
+  if (!defines.empty()) {
+    component["compile_definitions"]["*"] = std::move(defines);
+  }
+}
+
+void cmExportPackageInfoGenerator::GenerateInterfaceListProperty(
+  bool& result, Json::Value& component, cmGeneratorTarget const* target,
+  std::string const& outName, cm::string_view inName,
+  ImportPropertyMap const& properties) const
+{
+  auto const& prop = cmStrCat("INTERFACE_", inName);
+  auto const& iter = properties.find(prop);
+  if (iter == properties.end()) {
+    return;
+  }
+
+  if (!forbidGeneratorExpressions(prop, iter->second, target)) {
+    result = false;
+    return;
+  }
+
+  Json::Value& array = component[outName];
+  for (auto const& value : cmList{ iter->second }) {
+    array.append(value);
+  }
+}
+
+void cmExportPackageInfoGenerator::GenerateInterfaceConfigProperties(
+  Json::Value& components, cmGeneratorTarget const* target,
+  std::string const& suffix, ImportPropertyMap const& properties) const
+{
+  Json::Value component;
+  auto const suffixLength = suffix.length();
+
+  for (auto const& p : properties) {
+    if (!cmHasSuffix(p.first, suffix)) {
+      continue;
+    }
+    auto const n = p.first.length() - suffixLength - 9;
+    auto const prop = cm::string_view{ p.first }.substr(9, n);
+
+    if (prop == "LOCATION") {
+      component["location"] = p.second;
+    } else if (prop == "IMPLIB") {
+      component["link_location"] = p.second;
+    } else if (prop == "LINK_INTERFACE_LANGUAGES") {
+      std::vector<std::string> languages;
+      for (auto const& lang : cmList{ p.second }) {
+        auto ll = cmSystemTools::LowerCase(lang);
+        if (ll == "cxx") {
+          languages.emplace_back("cpp");
+        } else {
+          languages.emplace_back(std::move(ll));
+        }
+      }
+      buildArray(component, "link_languages", languages);
+    }
+  }
+
+  if (!component.empty()) {
+    components[target->GetExportName()] = component;
+  }
+}
diff --git a/Source/cmExportPackageInfoGenerator.h b/Source/cmExportPackageInfoGenerator.h
new file mode 100644
index 0000000..1fb1703
--- /dev/null
+++ b/Source/cmExportPackageInfoGenerator.h
@@ -0,0 +1,116 @@
+/* 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 <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <cm/string_view>
+
+#include "cmExportFileGenerator.h"
+#include "cmStateTypes.h"
+
+class cmGeneratorTarget;
+namespace Json {
+class Value;
+}
+
+/** \class cmExportPackageInfoGenerator
+ * \brief Generate Common Package Specification package information files
+ * exporting targets from a build or install tree.
+ *
+ * cmExportPackageInfoGenerator is the superclass for
+ * cmExportBuildPackageInfoGenerator and cmExportInstallPackageInfoGenerator.
+ * It contains common code generation routines for the two kinds of export
+ * implementations.
+ */
+class cmExportPackageInfoGenerator : virtual public cmExportFileGenerator
+{
+public:
+  cmExportPackageInfoGenerator(std::string packageName, std::string version,
+                               std::string versionCompat,
+                               std::string versionSchema,
+                               std::vector<std::string> defaultTargets,
+                               std::vector<std::string> defaultConfigurations);
+
+  using cmExportFileGenerator::GenerateImportFile;
+
+protected:
+  std::string const& GetPackageName() const { return this->PackageName; }
+
+  void WritePackageInfo(Json::Value const& packageInfo,
+                        std::ostream& os) const;
+
+  // Methods to implement export file code generation.
+  bool GenerateImportFile(std::ostream& os) override;
+
+  bool CheckDefaultTargets() const;
+
+  Json::Value GeneratePackageInfo() const;
+  Json::Value* GenerateImportTarget(Json::Value& components,
+                                    cmGeneratorTarget const* target,
+                                    cmStateEnums::TargetType targetType) const;
+
+  void GeneratePackageRequires(Json::Value& package) const;
+
+  using ImportPropertyMap = std::map<std::string, std::string>;
+  bool GenerateInterfaceProperties(Json::Value& component,
+                                   cmGeneratorTarget const* target,
+                                   ImportPropertyMap const& properties) const;
+  void GenerateInterfaceConfigProperties(
+    Json::Value& components, cmGeneratorTarget const* target,
+    std::string const& suffix, ImportPropertyMap const& properties) const;
+
+  cm::string_view GetImportPrefixWithSlash() const override;
+
+  std::string GetCxxModuleFile(std::string const& /*name*/) const override
+  {
+    // TODO
+    return {};
+  }
+
+  void GenerateCxxModuleConfigInformation(std::string const& /*name*/,
+                                          std::ostream& /*os*/) const override
+  {
+    // TODO
+  }
+
+  bool NoteLinkedTarget(cmGeneratorTarget const* target,
+                        std::string const& linkedName,
+                        cmGeneratorTarget const* linkedTarget) override;
+
+private:
+  void GenerateInterfaceLinkProperties(
+    bool& result, Json::Value& component, cmGeneratorTarget const* target,
+    ImportPropertyMap const& properties) const;
+
+  void GenerateInterfaceCompileFeatures(
+    bool& result, Json::Value& component, cmGeneratorTarget const* target,
+    ImportPropertyMap const& properties) const;
+
+  void GenerateInterfaceCompileDefines(
+    bool& result, Json::Value& component, cmGeneratorTarget const* target,
+    ImportPropertyMap const& properties) const;
+
+  void GenerateInterfaceListProperty(
+    bool& result, Json::Value& component, cmGeneratorTarget const* target,
+    std::string const& outName, cm::string_view inName,
+    ImportPropertyMap const& properties) const;
+
+  std::string const PackageName;
+  std::string const PackageVersion;
+  std::string const PackageVersionCompat;
+  std::string const PackageVersionSchema;
+  std::vector<std::string> DefaultTargets;
+  std::vector<std::string> DefaultConfigurations;
+
+  std::map<std::string, std::string> LinkTargets;
+  std::set<std::string> Requirements;
+
+  static constexpr char const* CPS_VERSION_STR = "0.12.0";
+};
diff --git a/Source/cmInstallCommand.cxx b/Source/cmInstallCommand.cxx
index 3160585..f4cc4c3 100644
--- a/Source/cmInstallCommand.cxx
+++ b/Source/cmInstallCommand.cxx
@@ -36,6 +36,7 @@
 #include "cmInstallGenerator.h"
 #include "cmInstallGetRuntimeDependenciesGenerator.h"
 #include "cmInstallImportedRuntimeArtifactsGenerator.h"
+#include "cmInstallPackageInfoExportGenerator.h"
 #include "cmInstallRuntimeDependencySet.h"
 #include "cmInstallRuntimeDependencySetGenerator.h"
 #include "cmInstallScriptGenerator.h"
@@ -2162,6 +2163,143 @@
   return true;
 }
 
+bool HandlePackageInfoMode(std::vector<std::string> const& args,
+                           cmExecutionStatus& status)
+{
+#ifndef CMAKE_BOOTSTRAP
+  if (!cmExperimental::HasSupportEnabled(
+        status.GetMakefile(), cmExperimental::Feature::ExportPackageInfo)) {
+    status.SetError("does not recognize sub-command PACKAGE_INFO");
+    return false;
+  }
+
+  Helper helper(status);
+
+  // This is the PACKAGE_INFO mode.
+  cmInstallCommandArguments ica(helper.DefaultComponentName);
+
+  ArgumentParser::NonEmpty<std::string> pkg;
+  ArgumentParser::NonEmpty<std::string> appendix;
+  ArgumentParser::NonEmpty<std::string> exportName;
+  bool lowerCase = false;
+  ArgumentParser::NonEmpty<std::string> version;
+  ArgumentParser::NonEmpty<std::string> versionCompat;
+  ArgumentParser::NonEmpty<std::string> versionSchema;
+  ArgumentParser::NonEmpty<std::vector<std::string>> defaultTargets;
+  ArgumentParser::NonEmpty<std::vector<std::string>> defaultConfigs;
+  ArgumentParser::NonEmpty<std::string> cxxModulesDirectory;
+
+  // TODO: Support DESTINATION.
+  ica.Bind("PACKAGE_INFO"_s, pkg);
+  ica.Bind("EXPORT"_s, exportName);
+  ica.Bind("APPENDIX"_s, appendix);
+  ica.Bind("LOWER_CASE_FILE"_s, lowerCase);
+  ica.Bind("VERSION"_s, version);
+  ica.Bind("COMPAT_VERSION"_s, versionCompat);
+  ica.Bind("VERSION_SCHEMA"_s, versionSchema);
+  ica.Bind("DEFAULT_TARGETS"_s, defaultTargets);
+  ica.Bind("DEFAULT_CONFIGURATIONS"_s, defaultConfigs);
+  // ica.Bind("CXX_MODULES_DIRECTORY"_s, cxxModulesDirectory); TODO?
+
+  std::vector<std::string> unknownArgs;
+  ica.Parse(args, &unknownArgs);
+
+  if (!unknownArgs.empty()) {
+    // Unknown argument.
+    status.SetError(
+      cmStrCat(args[0], " given unknown argument \"", unknownArgs[0], "\"."));
+    return false;
+  }
+
+  if (!ica.Finalize()) {
+    return false;
+  }
+
+  if (exportName.empty()) {
+    status.SetError(cmStrCat(args[0], " missing EXPORT."));
+    return false;
+  }
+
+  if (version.empty()) {
+    if (!versionCompat.empty()) {
+      status.SetError("COMPAT_VERSION requires VERSION.");
+      return false;
+    }
+    if (!versionSchema.empty()) {
+      status.SetError("VERSION_SCHEMA requires VERSION.");
+      return false;
+    }
+  } else {
+    if (!appendix.empty()) {
+      status.SetError("APPENDIX and VERSION are mutually exclusive.");
+      return false;
+    }
+  }
+  if (!appendix.empty()) {
+    if (!defaultTargets.empty()) {
+      status.SetError("APPENDIX and DEFAULT_TARGETS are mutually exclusive.");
+      return false;
+    }
+    if (!defaultConfigs.empty()) {
+      status.SetError("APPENDIX and DEFAULT_CONFIGURATIONS "
+                      "are mutually exclusive.");
+      return false;
+    }
+  }
+
+  // Validate the package name.
+  if (!cmGeneratorExpression::IsValidTargetName(pkg) ||
+      pkg.find(':') != std::string::npos) {
+    status.SetError(
+      cmStrCat(args[0], " given invalid package name \"", pkg, "\"."));
+    return false;
+  }
+
+  // Construct the case-normalized package name and the file name.
+  std::string const pkgNameOnDisk =
+    (lowerCase ? cmSystemTools::LowerCase(pkg) : pkg);
+  std::string pkgFileName = [&]() -> std::string {
+    if (appendix.empty()) {
+      return cmStrCat(pkgNameOnDisk, ".cps");
+    }
+    return cmStrCat(pkgNameOnDisk, '-', appendix, ".cps");
+  }();
+
+  // Get or construct the destination path.
+  std::string dest = ica.GetDestination();
+  if (dest.empty()) {
+    if (helper.Makefile->GetSafeDefinition("CMAKE_SYSTEM_NAME") == "Windows") {
+      dest = std::string{ "cps"_s };
+    } else {
+      dest = cmStrCat(helper.GetLibraryDestination(nullptr), "/cps/",
+                      pkgNameOnDisk);
+    }
+  }
+
+  cmExportSet& exportSet =
+    helper.Makefile->GetGlobalGenerator()->GetExportSets()[exportName];
+
+  cmInstallGenerator::MessageLevel message =
+    cmInstallGenerator::SelectMessageLevel(helper.Makefile);
+
+  // Create the export install generator.
+  helper.Makefile->AddInstallGenerator(
+    cm::make_unique<cmInstallPackageInfoExportGenerator>(
+      &exportSet, dest, ica.GetPermissions(), ica.GetConfigurations(),
+      ica.GetComponent(), message, ica.GetExcludeFromAll(),
+      std::move(pkgFileName), std::move(pkg), std::move(version),
+      std::move(versionCompat), std::move(versionSchema),
+      std::move(defaultTargets), std::move(defaultConfigs),
+      std::move(cxxModulesDirectory), helper.Makefile->GetBacktrace()));
+
+  return true;
+#else
+  static_cast<void>(args);
+  status.SetError("PACKAGE_INFO not supported in bootstrap cmake");
+  return false;
+#endif
+}
+
 bool HandleRuntimeDependencySetMode(std::vector<std::string> const& args,
                                     cmExecutionStatus& status)
 {
@@ -2525,6 +2663,7 @@
     { "DIRECTORY"_s, HandleDirectoryMode },
     { "EXPORT"_s, HandleExportMode },
     { "EXPORT_ANDROID_MK"_s, HandleExportAndroidMKMode },
+    { "PACKAGE_INFO"_s, HandlePackageInfoMode },
     { "RUNTIME_DEPENDENCY_SET"_s, HandleRuntimeDependencySetMode },
   };
 
diff --git a/Source/cmInstallPackageInfoExportGenerator.cxx b/Source/cmInstallPackageInfoExportGenerator.cxx
new file mode 100644
index 0000000..4ff045b
--- /dev/null
+++ b/Source/cmInstallPackageInfoExportGenerator.cxx
@@ -0,0 +1,36 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmInstallPackageInfoExportGenerator.h"
+
+#include <utility>
+
+#include <cm/memory>
+
+#include "cmExportInstallFileGenerator.h"
+#include "cmExportInstallPackageInfoGenerator.h"
+#include "cmListFileCache.h"
+
+class cmExportSet;
+
+cmInstallPackageInfoExportGenerator::cmInstallPackageInfoExportGenerator(
+  cmExportSet* exportSet, std::string destination, std::string filePermissions,
+  std::vector<std::string> const& configurations, std::string component,
+  MessageLevel message, bool excludeFromAll, std::string filename,
+  std::string packageName, std::string version, std::string versionCompat,
+  std::string versionSchema, std::vector<std::string> defaultTargets,
+  std::vector<std::string> defaultConfigurations,
+  std::string cxxModulesDirectory, cmListFileBacktrace backtrace)
+  : cmInstallExportGenerator(
+      exportSet, std::move(destination), std::move(filePermissions),
+      configurations, std::move(component), message, excludeFromAll,
+      std::move(filename), packageName + "::", std::move(cxxModulesDirectory),
+      std::move(backtrace))
+{
+  this->EFGen = cm::make_unique<cmExportInstallPackageInfoGenerator>(
+    this, std::move(packageName), std::move(version), std::move(versionCompat),
+    std::move(versionSchema), std::move(defaultTargets),
+    std::move(defaultConfigurations));
+}
+
+cmInstallPackageInfoExportGenerator::~cmInstallPackageInfoExportGenerator() =
+  default;
diff --git a/Source/cmInstallPackageInfoExportGenerator.h b/Source/cmInstallPackageInfoExportGenerator.h
new file mode 100644
index 0000000..c79df84
--- /dev/null
+++ b/Source/cmInstallPackageInfoExportGenerator.h
@@ -0,0 +1,36 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "cmInstallExportGenerator.h"
+
+class cmExportSet;
+class cmListFileBacktrace;
+
+/** \class cmInstallPackageInfoGenerator
+ * \brief Generate rules for creating CPS package info files.
+ */
+class cmInstallPackageInfoExportGenerator : public cmInstallExportGenerator
+{
+public:
+  cmInstallPackageInfoExportGenerator(
+    cmExportSet* exportSet, std::string destination,
+    std::string filePermissions,
+    std::vector<std::string> const& configurations, std::string component,
+    MessageLevel message, bool excludeFromAll, std::string filename,
+    std::string packageName, std::string version, std::string versionCompat,
+    std::string versionSchema, std::vector<std::string> defaultTargets,
+    std::vector<std::string> defaultConfigurations,
+    std::string cxxModulesDirectory, cmListFileBacktrace backtrace);
+  cmInstallPackageInfoExportGenerator(
+    cmInstallPackageInfoExportGenerator const&) = delete;
+  ~cmInstallPackageInfoExportGenerator() override;
+
+  cmInstallPackageInfoExportGenerator& operator=(
+    cmInstallPackageInfoExportGenerator const&) = delete;
+
+  char const* InstallSubcommand() const override { return "PACKAGE_INFO"; }
+};
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index 1f7b8c5..65295cc 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -1177,6 +1177,7 @@
   )
 
 add_RunCMake_test(AndroidMK)
+add_RunCMake_test(PackageInfo)
 
 if(CMake_TEST_ANDROID_NDK OR CMake_TEST_ANDROID_STANDALONE_TOOLCHAIN)
   if(NOT "${CMAKE_GENERATOR}" MATCHES "Make|Ninja|Visual Studio 1[456]")
diff --git a/Tests/RunCMake/PackageInfo/Appendix-check.cmake b/Tests/RunCMake/PackageInfo/Appendix-check.cmake
new file mode 100644
index 0000000..864e731
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/Appendix-check.cmake
@@ -0,0 +1,16 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/Appendix-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983")
+
+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)
diff --git a/Tests/RunCMake/PackageInfo/Appendix.cmake b/Tests/RunCMake/PackageInfo/Appendix.cmake
new file mode 100644
index 0000000..fe67778
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/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 .)
+
+install(PACKAGE_INFO foo DESTINATION cps EXPORT mammal VERSION 1.0)
+install(PACKAGE_INFO foo DESTINATION cps EXPORT canine APPENDIX dog)
diff --git a/Tests/RunCMake/PackageInfo/Assertions.cmake b/Tests/RunCMake/PackageInfo/Assertions.cmake
new file mode 100644
index 0000000..8157618
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/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()
diff --git a/Tests/RunCMake/PackageInfo/BadArgs1-result.txt b/Tests/RunCMake/PackageInfo/BadArgs1-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/BadArgs1-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/PackageInfo/BadArgs1-stderr.txt b/Tests/RunCMake/PackageInfo/BadArgs1-stderr.txt
new file mode 100644
index 0000000..92ba6fb
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/BadArgs1-stderr.txt
@@ -0,0 +1,2 @@
+CMake Error at BadArgs1.cmake:3 \(install\):
+  install COMPAT_VERSION requires VERSION.
diff --git a/Tests/RunCMake/PackageInfo/BadArgs1.cmake b/Tests/RunCMake/PackageInfo/BadArgs1.cmake
new file mode 100644
index 0000000..b99997c
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/BadArgs1.cmake
@@ -0,0 +1,3 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+install(PACKAGE_INFO test EXPORT foo COMPAT_VERSION 1.0)
diff --git a/Tests/RunCMake/PackageInfo/BadArgs2-result.txt b/Tests/RunCMake/PackageInfo/BadArgs2-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/BadArgs2-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/PackageInfo/BadArgs2-stderr.txt b/Tests/RunCMake/PackageInfo/BadArgs2-stderr.txt
new file mode 100644
index 0000000..636335c
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/BadArgs2-stderr.txt
@@ -0,0 +1,2 @@
+CMake Error at BadArgs2.cmake:3 \(install\):
+  install VERSION_SCHEMA requires VERSION.
diff --git a/Tests/RunCMake/PackageInfo/BadArgs2.cmake b/Tests/RunCMake/PackageInfo/BadArgs2.cmake
new file mode 100644
index 0000000..265d93c
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/BadArgs2.cmake
@@ -0,0 +1,3 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+install(PACKAGE_INFO test EXPORT foo VERSION_SCHEMA simple)
diff --git a/Tests/RunCMake/PackageInfo/BadArgs3-result.txt b/Tests/RunCMake/PackageInfo/BadArgs3-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/BadArgs3-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/PackageInfo/BadArgs3-stderr.txt b/Tests/RunCMake/PackageInfo/BadArgs3-stderr.txt
new file mode 100644
index 0000000..11e1a8c
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/BadArgs3-stderr.txt
@@ -0,0 +1,2 @@
+CMake Error at BadArgs3.cmake:3 \(install\):
+  install APPENDIX and VERSION are mutually exclusive.
diff --git a/Tests/RunCMake/PackageInfo/BadArgs3.cmake b/Tests/RunCMake/PackageInfo/BadArgs3.cmake
new file mode 100644
index 0000000..5f57f6a
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/BadArgs3.cmake
@@ -0,0 +1,3 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+install(PACKAGE_INFO test EXPORT foo APPENDIX test VERSION 1.0)
diff --git a/Tests/RunCMake/PackageInfo/BadArgs4-result.txt b/Tests/RunCMake/PackageInfo/BadArgs4-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/BadArgs4-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/PackageInfo/BadArgs4-stderr.txt b/Tests/RunCMake/PackageInfo/BadArgs4-stderr.txt
new file mode 100644
index 0000000..067a07b
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/BadArgs4-stderr.txt
@@ -0,0 +1,2 @@
+CMake Error at BadArgs4.cmake:3 \(install\):
+  install APPENDIX and DEFAULT_TARGETS are mutually exclusive.
diff --git a/Tests/RunCMake/PackageInfo/BadArgs4.cmake b/Tests/RunCMake/PackageInfo/BadArgs4.cmake
new file mode 100644
index 0000000..426d10b
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/BadArgs4.cmake
@@ -0,0 +1,3 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+install(PACKAGE_INFO test EXPORT foo APPENDIX test DEFAULT_TARGETS foo)
diff --git a/Tests/RunCMake/PackageInfo/BadArgs5-result.txt b/Tests/RunCMake/PackageInfo/BadArgs5-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/BadArgs5-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/PackageInfo/BadArgs5-stderr.txt b/Tests/RunCMake/PackageInfo/BadArgs5-stderr.txt
new file mode 100644
index 0000000..4f7d285
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/BadArgs5-stderr.txt
@@ -0,0 +1,2 @@
+CMake Error at BadArgs5.cmake:3 \(install\):
+  install APPENDIX and DEFAULT_CONFIGURATIONS are mutually exclusive.
diff --git a/Tests/RunCMake/PackageInfo/BadArgs5.cmake b/Tests/RunCMake/PackageInfo/BadArgs5.cmake
new file mode 100644
index 0000000..356c856
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/BadArgs5.cmake
@@ -0,0 +1,3 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+install(PACKAGE_INFO test EXPORT foo APPENDIX test DEFAULT_CONFIGURATIONS test)
diff --git a/Tests/RunCMake/PackageInfo/BadDefaultTarget-result.txt b/Tests/RunCMake/PackageInfo/BadDefaultTarget-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/BadDefaultTarget-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/PackageInfo/BadDefaultTarget-stderr.txt b/Tests/RunCMake/PackageInfo/BadDefaultTarget-stderr.txt
new file mode 100644
index 0000000..6467a14
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/BadDefaultTarget-stderr.txt
@@ -0,0 +1,2 @@
+CMake Error: Package "test" specifies DEFAULT_TARGETS "dog", which is not a target in the export set "foo".
+CMake Error: Package "test" specifies DEFAULT_TARGETS "cat", which is not a target in the export set "foo".
diff --git a/Tests/RunCMake/PackageInfo/BadDefaultTarget.cmake b/Tests/RunCMake/PackageInfo/BadDefaultTarget.cmake
new file mode 100644
index 0000000..d3d993a
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/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 .)
+install(PACKAGE_INFO test EXPORT foo DEFAULT_TARGETS dog cat)
diff --git a/Tests/RunCMake/PackageInfo/CMakeLists.txt b/Tests/RunCMake/PackageInfo/CMakeLists.txt
new file mode 100644
index 0000000..dda37d8
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.30)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/PackageInfo/ExperimentalGate-result.txt b/Tests/RunCMake/PackageInfo/ExperimentalGate-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/ExperimentalGate-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/PackageInfo/ExperimentalGate-stderr.txt b/Tests/RunCMake/PackageInfo/ExperimentalGate-stderr.txt
new file mode 100644
index 0000000..40799b7
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/ExperimentalGate-stderr.txt
@@ -0,0 +1,2 @@
+CMake Error at ExperimentalGate.cmake:5 \(install\):
+  install does not recognize sub-command PACKAGE_INFO
diff --git a/Tests/RunCMake/PackageInfo/ExperimentalGate.cmake b/Tests/RunCMake/PackageInfo/ExperimentalGate.cmake
new file mode 100644
index 0000000..327d3bb
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/ExperimentalGate.cmake
@@ -0,0 +1,5 @@
+unset(CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO)
+
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+install(PACKAGE_INFO foo DESTINATION cps EXPORT foo)
diff --git a/Tests/RunCMake/PackageInfo/ExperimentalWarning-stderr.txt b/Tests/RunCMake/PackageInfo/ExperimentalWarning-stderr.txt
new file mode 100644
index 0000000..960d541
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/ExperimentalWarning-stderr.txt
@@ -0,0 +1,7 @@
+CMake Warning \(dev\) at ExperimentalWarning.cmake:8 \(install\):
+  CMake's support for exporting package information in the Common Package
+  Specification format.  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.
diff --git a/Tests/RunCMake/PackageInfo/ExperimentalWarning.cmake b/Tests/RunCMake/PackageInfo/ExperimentalWarning.cmake
new file mode 100644
index 0000000..df6604c
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/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 .)
+install(PACKAGE_INFO foo DESTINATION cps EXPORT foo)
diff --git a/Tests/RunCMake/PackageInfo/InterfaceProperties-check.cmake b/Tests/RunCMake/PackageInfo/InterfaceProperties-check.cmake
new file mode 100644
index 0000000..2c3272e
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/InterfaceProperties-check.cmake
@@ -0,0 +1,24 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/InterfaceProperties-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983")
+
+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}" "@prefix@/include/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)
diff --git a/Tests/RunCMake/PackageInfo/InterfaceProperties.cmake b/Tests/RunCMake/PackageInfo/InterfaceProperties.cmake
new file mode 100644
index 0000000..42edc21
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/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
+  $<INSTALL_INTERFACE:include/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 .)
+install(PACKAGE_INFO foo DESTINATION cps EXPORT foo)
diff --git a/Tests/RunCMake/PackageInfo/LowerCaseFile-check.cmake b/Tests/RunCMake/PackageInfo/LowerCaseFile-check.cmake
new file mode 100644
index 0000000..d8de372
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/LowerCaseFile-check.cmake
@@ -0,0 +1,9 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/LowerCaseFile-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983")
+
+file(READ "${out_dir}/lowercase.cps" content)
+expect_value("${content}" "LowerCase" "name")
+
+file(READ "${out_dir}/PreserveCase.cps" content)
+expect_value("${content}" "PreserveCase" "name")
diff --git a/Tests/RunCMake/PackageInfo/LowerCaseFile.cmake b/Tests/RunCMake/PackageInfo/LowerCaseFile.cmake
new file mode 100644
index 0000000..dc6827f
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/LowerCaseFile.cmake
@@ -0,0 +1,4 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+install(PACKAGE_INFO LowerCase DESTINATION cps EXPORT foo LOWER_CASE_FILE)
+install(PACKAGE_INFO PreserveCase DESTINATION cps EXPORT foo)
diff --git a/Tests/RunCMake/PackageInfo/Metadata-check.cmake b/Tests/RunCMake/PackageInfo/Metadata-check.cmake
new file mode 100644
index 0000000..8db8c29
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/Metadata-check.cmake
@@ -0,0 +1,16 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/Metadata-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983")
+
+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)
diff --git a/Tests/RunCMake/PackageInfo/Metadata.cmake b/Tests/RunCMake/PackageInfo/Metadata.cmake
new file mode 100644
index 0000000..f8fc9b8
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/Metadata.cmake
@@ -0,0 +1,12 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+install(
+  PACKAGE_INFO foo
+  DESTINATION cps
+  EXPORT foo
+  VERSION 1.2.3
+  VERSION_SCHEMA simple
+  COMPAT_VERSION 1.2.0
+  DEFAULT_TARGETS foo
+  DEFAULT_CONFIGURATIONS release debug
+  )
diff --git a/Tests/RunCMake/PackageInfo/Minimal-check.cmake b/Tests/RunCMake/PackageInfo/Minimal-check.cmake
new file mode 100644
index 0000000..9608ed8
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/Minimal-check.cmake
@@ -0,0 +1,18 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/Minimal-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983")
+
+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")
diff --git a/Tests/RunCMake/PackageInfo/Minimal.cmake b/Tests/RunCMake/PackageInfo/Minimal.cmake
new file mode 100644
index 0000000..6c060b9
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/Minimal.cmake
@@ -0,0 +1,3 @@
+add_library(foo INTERFACE)
+install(TARGETS foo EXPORT foo DESTINATION .)
+install(PACKAGE_INFO foo DESTINATION cps EXPORT foo)
diff --git a/Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget-result.txt b/Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget-stderr.txt b/Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget-stderr.txt
new file mode 100644
index 0000000..c68d518
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget-stderr.txt
@@ -0,0 +1 @@
+CMake Error: install\(PACKAGE_INFO "dog" \.\.\.\) includes target "canine" which requires target "mammal" that is not in any export set.
diff --git a/Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget.cmake b/Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget.cmake
new file mode 100644
index 0000000..a835582
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/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 .)
+install(PACKAGE_INFO dog EXPORT dog)
diff --git a/Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget-result.txt b/Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget-stderr.txt b/Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget-stderr.txt
new file mode 100644
index 0000000..0a74e18
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/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.
diff --git a/Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget.cmake b/Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget.cmake
new file mode 100644
index 0000000..3e3d21d
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/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 .)
+
+install(EXPORT foo DESTINATION .)
+install(EXPORT bar DESTINATION . NAMESPACE bar_)
+
+install(TARGETS test EXPORT test DESTINATION .)
+install(PACKAGE_INFO test EXPORT test)
diff --git a/Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget-result.txt b/Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget-stderr.txt b/Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget-stderr.txt
new file mode 100644
index 0000000..cc4e824
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/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.
diff --git a/Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget.cmake b/Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget.cmake
new file mode 100644
index 0000000..8addd64
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/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 .)
+install(PACKAGE_INFO foo EXPORT foo)
diff --git a/Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget-result.txt b/Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget-stderr.txt b/Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget-stderr.txt
new file mode 100644
index 0000000..0a6872e
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/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.
diff --git a/Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget.cmake b/Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget.cmake
new file mode 100644
index 0000000..7a4f9c0
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/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 .)
+install(PACKAGE_INFO foo EXPORT foo)
diff --git a/Tests/RunCMake/PackageInfo/Requirements-check.cmake b/Tests/RunCMake/PackageInfo/Requirements-check.cmake
new file mode 100644
index 0000000..59a212f
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/Requirements-check.cmake
@@ -0,0 +1,20 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/Requirements-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983")
+
+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_null("${content}" "requires" "foo")
+expect_null("${content}" "requires" "test")
+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)
diff --git a/Tests/RunCMake/PackageInfo/Requirements.cmake b/Tests/RunCMake/PackageInfo/Requirements.cmake
new file mode 100644
index 0000000..a4e947c
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/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 .)
+install(PACKAGE_INFO foo DESTINATION cps EXPORT foo)
+
+install(TARGETS libc libd EXPORT bar DESTINATION .)
+install(PACKAGE_INFO bar DESTINATION cps EXPORT bar)
diff --git a/Tests/RunCMake/PackageInfo/RunCMakeTest.cmake b/Tests/RunCMake/PackageInfo/RunCMakeTest.cmake
new file mode 100644
index 0000000..54a32d5
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/RunCMakeTest.cmake
@@ -0,0 +1,32 @@
+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(BadArgs5)
+run_cmake(BadDefaultTarget)
+run_cmake(ReferencesNonExportedTarget)
+run_cmake(ReferencesWronglyExportedTarget)
+run_cmake(ReferencesWronglyImportedTarget)
+run_cmake(ReferencesWronglyNamespacedTarget)
+
+# Test functionality
+run_cmake(Appendix)
+run_cmake(InterfaceProperties)
+run_cmake(Metadata)
+run_cmake(Minimal)
+run_cmake(LowerCaseFile)
+run_cmake(Requirements)
+run_cmake(TargetTypes)
diff --git a/Tests/RunCMake/PackageInfo/TargetTypes-check.cmake b/Tests/RunCMake/PackageInfo/TargetTypes-check.cmake
new file mode 100644
index 0000000..34ca5ab
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/TargetTypes-check.cmake
@@ -0,0 +1,11 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/TargetTypes-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983")
+
+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")
diff --git a/Tests/RunCMake/PackageInfo/TargetTypes.cmake b/Tests/RunCMake/PackageInfo/TargetTypes.cmake
new file mode 100644
index 0000000..755c3a3
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/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 .
+  )
+
+install(PACKAGE_INFO foo DESTINATION cps EXPORT foo)
diff --git a/Tests/RunCMake/PackageInfo/broken-config.cmake b/Tests/RunCMake/PackageInfo/broken-config.cmake
new file mode 100644
index 0000000..09e40df
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/broken-config.cmake
@@ -0,0 +1 @@
+add_library(wrong::lib INTERFACE IMPORTED)
diff --git a/Tests/RunCMake/PackageInfo/foo.cxx b/Tests/RunCMake/PackageInfo/foo.cxx
new file mode 100644
index 0000000..3695dc9
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/foo.cxx
@@ -0,0 +1,3 @@
+void foo()
+{
+}
diff --git a/Tests/RunCMake/PackageInfo/test-config.cmake b/Tests/RunCMake/PackageInfo/test-config.cmake
new file mode 100644
index 0000000..3c46ea6
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/test-config.cmake
@@ -0,0 +1 @@
+add_library(test::liba INTERFACE IMPORTED)
diff --git a/Tests/RunCMake/PackageInfo/test.cxx b/Tests/RunCMake/PackageInfo/test.cxx
new file mode 100644
index 0000000..f8b643a
--- /dev/null
+++ b/Tests/RunCMake/PackageInfo/test.cxx
@@ -0,0 +1,4 @@
+int main()
+{
+  return 0;
+}