Przeglądaj źródła

Merge topic 'install-with-runtime-dependencies'

8d898cb3e1 FileAPI: Add integration for runtime dependency installers
72f2448e82 Help: Add documentation for runtime dependency installation
0c3c6acaff Tests: Add tests for new options
4910132d8c install: Add RUNTIME_DEPENDENCY_SET mode
bc8a4a06a4 install(IMPORTED_RUNTIME_ARTIFACTS): Add RUNTIME_DEPENDENCY_SET option
3e7d3c252a install(TARGETS): Add RUNTIME_DEPENDENCY_SET argument
ed3633d88c install(TARGETS): Add RUNTIME_DEPENDENCIES option
f2617cf8e6 Source: Add cmInstallRuntimeDependencySet
...

Acked-by: Kitware Robot <[email protected]>
Merge-request: !6186
Brad King 4 lat temu
rodzic
commit
acb25d50d9
80 zmienionych plików z 2585 dodań i 244 usunięć
  1. 108 0
      Help/command/install.rst
  2. 24 0
      Help/manual/cmake-file-api.7.rst
  3. 3 2
      Help/manual/cmake-generator-expressions.7.rst
  4. 9 0
      Help/release/dev/install-runtime-dependencies.rst
  5. 6 0
      Source/CMakeLists.txt
  6. 14 8
      Source/cmBinUtilsMacOSMachOLinker.cxx
  7. 2 0
      Source/cmBinUtilsMacOSMachOLinker.h
  8. 26 2
      Source/cmFileAPICodemodel.cxx
  9. 131 80
      Source/cmFileCommand.cxx
  10. 24 0
      Source/cmGlobalGenerator.cxx
  11. 11 0
      Source/cmGlobalGenerator.h
  12. 359 2
      Source/cmInstallCommand.cxx
  13. 32 19
      Source/cmInstallGenerator.cxx
  14. 2 1
      Source/cmInstallGenerator.h
  15. 206 0
      Source/cmInstallGetRuntimeDependenciesGenerator.cxx
  16. 56 0
      Source/cmInstallGetRuntimeDependenciesGenerator.h
  17. 95 0
      Source/cmInstallRuntimeDependencySet.cxx
  18. 163 0
      Source/cmInstallRuntimeDependencySet.h
  19. 276 0
      Source/cmInstallRuntimeDependencySetGenerator.cxx
  20. 74 0
      Source/cmInstallRuntimeDependencySetGenerator.h
  21. 28 11
      Source/cmRuntimeDependencyArchive.cxx
  22. 15 11
      Source/cmRuntimeDependencyArchive.h
  23. 140 70
      Source/cmSystemTools.cxx
  24. 5 1
      Source/cmSystemTools.h
  25. 1 0
      Tests/CMakeLists.txt
  26. 22 14
      Tests/ExportImport/CMakeLists.txt
  27. 2 0
      Tests/ExportImport/Export/CMakeLists.txt
  28. 42 0
      Tests/ExportImport/Export/install-RUNTIME_DEPENDENCY_SET/CMakeLists.txt
  29. 12 0
      Tests/ExportImport/Export/install-RUNTIME_DEPENDENCY_SET/deplib.c
  30. 6 0
      Tests/ExportImport/Import/CMakeLists.txt
  31. 20 0
      Tests/ExportImport/Import/check_installed.cmake
  32. 1 20
      Tests/ExportImport/Import/install-IMPORTED_RUNTIME_ARTIFACTS/check_installed.cmake
  33. 74 0
      Tests/ExportImport/Import/install-RUNTIME_DEPENDENCIES/CMakeLists.txt
  34. 11 0
      Tests/ExportImport/Import/install-RUNTIME_DEPENDENCIES/check_installed.cmake
  35. 12 0
      Tests/ExportImport/Import/install-RUNTIME_DEPENDENCIES/lib.c
  36. 31 0
      Tests/ExportImport/Import/install-RUNTIME_DEPENDENCIES/main.c
  37. 12 0
      Tests/ExportImport/Import/install-RUNTIME_DEPENDENCIES/mod.c
  38. 12 0
      Tests/ExportImport/Import/install-RUNTIME_DEPENDENCIES/sublib1.c
  39. 12 0
      Tests/ExportImport/Import/install-RUNTIME_DEPENDENCIES/sublib2.c
  40. 33 0
      Tests/ExportImport/Import/install-RUNTIME_DEPENDENCY_SET/CMakeLists.txt
  41. 11 0
      Tests/ExportImport/Import/install-RUNTIME_DEPENDENCY_SET/check_installed.cmake
  42. 15 0
      Tests/ExportImport/Import/install-RUNTIME_DEPENDENCY_SET/main.c
  43. 16 0
      Tests/RunCMake/FileAPI/codemodel-v2-check.py
  44. 161 2
      Tests/RunCMake/FileAPI/codemodel-v2-data/directories/cxx.json
  45. 17 0
      Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe.json
  46. 15 0
      Tests/RunCMake/FileAPI/cxx/CMakeLists.txt
  47. 1 0
      Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/RunCMakeTest.cmake
  48. 35 0
      Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/macos-rpath.cmake
  49. 37 0
      Tests/RunCMake/file-RPATH/Common.cmake
  50. 1 0
      Tests/RunCMake/install/IMPORTED_RUNTIME_ARTIFACTS-RUNTIME_DEPENDENCY_SET-unsupported-result.txt
  51. 6 0
      Tests/RunCMake/install/IMPORTED_RUNTIME_ARTIFACTS-RUNTIME_DEPENDENCY_SET-unsupported-stderr.txt
  52. 2 0
      Tests/RunCMake/install/IMPORTED_RUNTIME_ARTIFACTS-RUNTIME_DEPENDENCY_SET-unsupported.cmake
  53. 1 0
      Tests/RunCMake/install/RUNTIME_DEPENDENCY_SET-unsupported-result.txt
  54. 5 0
      Tests/RunCMake/install/RUNTIME_DEPENDENCY_SET-unsupported-stderr.txt
  55. 1 0
      Tests/RunCMake/install/RUNTIME_DEPENDENCY_SET-unsupported.cmake
  56. 21 1
      Tests/RunCMake/install/RunCMakeTest.cmake
  57. 39 0
      Tests/RunCMake/install/RuntimeDependencies-COMPONENTS.cmake
  58. 1 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-cross-result.txt
  59. 4 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-cross-stderr.txt
  60. 4 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-cross.cmake
  61. 1 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-empty-all-check.cmake
  62. 9 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-empty.cmake
  63. 1 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-no-framework-result.txt
  64. 4 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-no-framework-stderr.txt
  65. 4 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-no-framework.cmake
  66. 1 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-two-bundle-result.txt
  67. 4 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-two-bundle-stderr.txt
  68. 10 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-two-bundle.cmake
  69. 1 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-nodep-all-check.cmake
  70. 9 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-nodep.cmake
  71. 1 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-unsupported-result.txt
  72. 5 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-unsupported-stderr.txt
  73. 4 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-unsupported.cmake
  74. 1 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCY_SET-RUNTIME_DEPENDENCIES-conflict-result.txt
  75. 5 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCY_SET-RUNTIME_DEPENDENCIES-conflict-stderr.txt
  76. 7 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCY_SET-RUNTIME_DEPENDENCIES-conflict.cmake
  77. 1 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCY_SET-unsupported-result.txt
  78. 5 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCY_SET-unsupported-stderr.txt
  79. 4 0
      Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCY_SET-unsupported.cmake
  80. 3 0
      bootstrap

+ 108 - 0
Help/command/install.rst

@@ -15,6 +15,7 @@ Synopsis
   install(`SCRIPT`_ <file> [...])
   install(`CODE`_ <code> [...])
   install(`EXPORT`_ <export-name> [...])
+  install(`RUNTIME_DEPENDENCY_SET`_ <set-name> [...])
 
 Introduction
 ^^^^^^^^^^^^
@@ -125,6 +126,7 @@ Installing Targets
 .. code-block:: cmake
 
   install(TARGETS targets... [EXPORT <export-name>]
+          [RUNTIME_DEPENDENCIES args...|RUNTIME_DEPENDENCY_SET <set-name>]
           [[ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE|
             PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE]
            [DESTINATION <dir>]
@@ -339,6 +341,34 @@ top level:
   relative path is specified, it is treated as relative to the
   ``$<INSTALL_PREFIX>``.
 
+``RUNTIME_DEPENDENCY_SET``
+  .. versionadded:: 3.21
+
+  This option causes all runtime dependencies of installed executable, shared
+  library, and module targets to be added to the specified runtime dependency
+  set. This set can then be installed later on with an
+  `install(RUNTIME_DEPENDENCY_SET)`_ command.
+
+  This argument and the ``RUNTIME_DEPENDENCIES`` argument are mutually
+  exclusive.
+
+``RUNTIME_DEPENDENCIES``
+  .. versionadded:: 3.21
+
+  This option causes all runtime dependencies of installed executable, shared
+  library, and module targets to be installed along with the targets
+  themselves. The ``RUNTIME``, ``LIBRARY``, ``FRAMEWORK``, and generic
+  arguments are used to determine the properties (``DESTINATION``,
+  ``COMPONENT``, etc.) of the installation of these dependencies.
+
+  ``RUNTIME_DEPENDENCIES`` is semantically equivalent to calling
+  ``install(TARGETS ... RUNTIME_DEPENDENCY_SET)`` and then
+  `install(RUNTIME_DEPENDENCY_SET)`_ with a randomly generated name. It accepts
+  all of the same options as `install(RUNTIME_DEPENDENCY_SET)`_.
+
+  This argument and the ``RUNTIME_DEPENDENCY_SET`` argument are mutually
+  exclusive.
+
 One or more groups of properties may be specified in a single call to
 the ``TARGETS`` form of this command.  A target may be installed more than
 once to different locations.  Consider hypothetical targets ``myExe``,
@@ -394,6 +424,7 @@ Installing Imported Runtime Artifacts
 .. code-block:: cmake
 
   install(IMPORTED_RUNTIME_ARTIFACTS targets...
+          [RUNTIME_DEPENDENCY_SET <set-name>]
           [[LIBRARY|RUNTIME|FRAMEWORK|BUNDLE]
            [DESTINATION <dir>]
            [PERMISSIONS permissions...]
@@ -415,6 +446,15 @@ not installed. In the case of :prop_tgt:`FRAMEWORK` libraries,
 :prop_tgt:`MACOSX_BUNDLE` executables, and :prop_tgt:`BUNDLE` CFBundles, the
 entire directory is installed.
 
+``IMPORTED_RUNTIME_ARTIFACTS`` accepts the following additional arguments:
+
+``RUNTIME_DEPENDENCY_SET``
+
+  This option causes all runtime dependencies of installed executable, shared
+  library, and module targets to be added to the specified runtime dependency
+  set. This set can then be installed later on with an
+  `install(RUNTIME_DEPENDENCY_SET)`_ command.
+
 Installing Files
 ^^^^^^^^^^^^^^^^
 
@@ -773,6 +813,74 @@ executable from the installation tree using the imported target name
   :command:`install_files`, and :command:`install_programs` commands
   is not defined.
 
+Installing Runtime Dependencies
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. _`install(RUNTIME_DEPENDENCY_SET)`:
+.. _RUNTIME_DEPENDENCY_SET:
+
+.. versionadded:: 3.21
+
+.. code-block:: cmake
+
+  install(RUNTIME_DEPENDENCY_SET <set-name>
+          [[LIBRARY|RUNTIME|FRAMEWORK]
+           [DESTINATION <dir>]
+           [PERMISSIONS permissions...]
+           [CONFIGURATIONS [Debug|Release|...]]
+           [COMPONENT <component>]
+           [NAMELINK_COMPONENT <component>]
+           [OPTIONAL] [EXCLUDE_FROM_ALL]
+          ] [...]
+          [PRE_INCLUDE_REGEXES regexes...]
+          [PRE_EXCLUDE_REGEXES regexes...]
+          [POST_INCLUDE_REGEXES regexes...]
+          [POST_EXCLUDE_REGEXES regexes...]
+          [DIRECTORIES directories...]
+          )
+
+Installs a runtime dependency set created by one or more
+`install(TARGETS)`_ or `install(IMPORTED_RUNTIME_ARTIFACTS)`_ commands. The
+dependencies of targets belonging to a runtime dependency set are installed in
+the ``RUNTIME`` destination and component on DLL platforms, and in the
+``LIBRARY`` destination and component on non-DLL platforms. macOS frameworks
+are installed in the ``FRAMEWORK`` destination and component. The generated
+install script calls :command:`file(GET_RUNTIME_DEPENDENCIES)` on the
+build-tree files to calculate the runtime dependencies, with the build-tree
+executable files as the ``EXECUTABLES`` argument, the build-tree shared
+libraries as the ``LIBRARIES`` argument, and the build-tree modules as the
+``MODULES`` argument. If one of the executables is a :prop_tgt:`MACOSX_BUNDLE`
+executable on a macOS platform, that executable is passed as the
+``BUNDLE_EXECUTABLE`` argument. If ``RUNTIME_DEPENDENCY_SET`` is specified on
+a macOS platform, at most one :prop_tgt:`MACOSX_BUNDLE` executable may be in
+the runtime dependency set. The :prop_tgt:`MACOSX_BUNDLE` property has no
+effect on non-macOS platforms. Targets built within the build tree will never
+be installed as runtime dependencies, nor will their own dependencies, unless
+the targets themselves are installed with `install(TARGETS)`_.
+
+This argument accepts the following sub-arguments:
+
+``DIRECTORIES <directories>``
+  List of directories to be passed as the ``DIRECTORIES`` argument of
+  :command:`file(GET_RUNTIME_DEPENDENCIES)`. This argument supports
+  :manual:`generator expressions <cmake-generator-expressions(7)>`. If a
+  ``DIRECTORIES`` argument evaluates to an empty string, it is not passed to
+  :command:`file(GET_RUNTIME_DEPENDENCIES)`.
+
+``PRE_INCLUDE_REGEXES <regexes>``, ``PRE_EXCLUDE_REGEXES <regexes>``, ``POST_INCLUDE_REGEXES <regexes>``, ``POST_EXCLUDE_REGEXES <regexes>``
+  List of regular expressions to be passed as their respective arguments to
+  :command:`file(GET_RUNTIME_DEPENDENCIES)`. These arguments support
+  :manual:`generator expressions <cmake-generator-expressions(7)>`. If an
+  argument evaluates to an empty string, it is not passed to
+  :command:`file(GET_RUNTIME_DEPENDENCIES)`.
+
+``POST_INCLUDE_FILES <files>``, ``POST_EXCLUDE_FILES <files>``
+  List of files to be passed as their respective arguments to
+  :command:`file(GET_RUNTIME_DEPENDENCIES)`. These arguments support
+  :manual:`generator expressions <cmake-generator-expressions(7)>`. If an
+  argument evaluates to an empty string, it is not passed to
+  :command:`file(GET_RUNTIME_DEPENDENCIES)`.
+
 Generated Installation Script
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 

+ 24 - 0
Help/manual/cmake-file-api.7.rst

@@ -752,6 +752,12 @@ with members:
       The ``destination`` member is populated. The ``isOptional`` member may
       exist. This type has no additional members.
 
+    ``runtimeDependencySet``
+      An :command:`install(RUNTIME_DEPENDENCY_SET)` call or an
+      :command:`install(TARGETS)` call with ``RUNTIME_DEPENDENCIES``. The
+      ``destination`` member is populated. This type has additional members
+      ``runtimeDependencySetName`` and ``runtimeDependencySetType``.
+
   ``isExcludeFromAll``
     Optional member that is present with boolean value ``true`` when
     :command:`install` is called with the ``EXCLUDE_FROM_ALL`` option.
@@ -811,6 +817,24 @@ with members:
       An unsigned integer 0-based index into the main "codemodel"
       object's ``targets`` array for the target.
 
+  ``runtimeDependencySetName``
+    Optional member that is present when ``type`` is ``runtimeDependencySet``
+    and the installer was created by an
+    :command:`install(RUNTIME_DEPENDENCY_SET)` call. The value is a string
+    specifying the name of the runtime dependency set that was installed.
+
+  ``runtimeDependencySetType``
+    Optional member that is present when ``type`` is ``runtimeDependencySet``.
+    The value is a string with one of the following values:
+
+    ``library``
+      Indicates that this installer installs dependencies that are not macOS
+      frameworks.
+
+    ``framework``
+      Indicates that this installer installs dependencies that are macOS
+      frameworks.
+
   ``scriptFile``
     Optional member that is present when ``type`` is ``script``.
     The value is a string specifying the path to the script file on disk,

+ 3 - 2
Help/manual/cmake-generator-expressions.7.rst

@@ -966,8 +966,9 @@ which is just the string ``tgt``.
 .. genex:: $<INSTALL_PREFIX>
 
   Content of the install prefix when the target is exported via
-  :command:`install(EXPORT)`, or when evaluated in
-  :prop_tgt:`INSTALL_NAME_DIR`, and empty otherwise.
+  :command:`install(EXPORT)`, or when evaluated in the
+  :prop_tgt:`INSTALL_NAME_DIR` property or the ``INSTALL_NAME_DIR`` argument of
+  :command:`install(RUNTIME_DEPENDENCY_SET)`, and empty otherwise.
 
 Output-Related Expressions
 --------------------------

+ 9 - 0
Help/release/dev/install-runtime-dependencies.rst

@@ -0,0 +1,9 @@
+install-runtime-dependencies
+----------------------------
+
+* The :command:`install(TARGETS)` command gained new ``RUNTIME_DEPENDENCIES``
+  and ``RUNTIME_DEPENDENCY_SET`` arguments, which can be used to install
+  runtime dependencies using :command:`file(GET_RUNTIME_DEPENDENCIES)`.
+* The :command:`install` command gained a new ``RUNTIME_DEPENDENCY_SET`` mode,
+  which can be used to install runtime dependencies using
+  :command:`file(GET_RUNTIME_DEPENDENCIES)`.

+ 6 - 0
Source/CMakeLists.txt

@@ -345,6 +345,8 @@ set(SRCS
   cmGraphVizWriter.h
   cmInstallGenerator.h
   cmInstallGenerator.cxx
+  cmInstallGetRuntimeDependenciesGenerator.h
+  cmInstallGetRuntimeDependenciesGenerator.cxx
   cmInstallExportGenerator.cxx
   cmInstalledFile.h
   cmInstalledFile.cxx
@@ -352,6 +354,10 @@ set(SRCS
   cmInstallFilesGenerator.cxx
   cmInstallImportedRuntimeArtifactsGenerator.h
   cmInstallImportedRuntimeArtifactsGenerator.cxx
+  cmInstallRuntimeDependencySet.h
+  cmInstallRuntimeDependencySet.cxx
+  cmInstallRuntimeDependencySetGenerator.h
+  cmInstallRuntimeDependencySetGenerator.cxx
   cmInstallScriptGenerator.h
   cmInstallScriptGenerator.cxx
   cmInstallSubdirectoryGenerator.h

+ 14 - 8
Source/cmBinUtilsMacOSMachOLinker.cxx

@@ -65,18 +65,18 @@ bool cmBinUtilsMacOSMachOLinker::ScanDependencies(
   if (!executableFile.empty()) {
     executablePath = cmSystemTools::GetFilenamePath(executableFile);
   }
-  return this->ScanDependencies(file, executablePath);
-}
-
-bool cmBinUtilsMacOSMachOLinker::ScanDependencies(
-  std::string const& file, std::string const& executablePath)
-{
   std::vector<std::string> libs;
   std::vector<std::string> rpaths;
   if (!this->Tool->GetFileInfo(file, libs, rpaths)) {
     return false;
   }
+  return this->ScanDependencies(file, libs, rpaths, executablePath);
+}
 
+bool cmBinUtilsMacOSMachOLinker::ScanDependencies(
+  std::string const& file, std::vector<std::string> const& libs,
+  std::vector<std::string> const& rpaths, std::string const& executablePath)
+{
   std::string loaderPath = cmSystemTools::GetFilenamePath(file);
   return this->GetFileDependencies(libs, executablePath, loaderPath, rpaths);
 }
@@ -98,8 +98,14 @@ bool cmBinUtilsMacOSMachOLinker::GetFileDependencies(
             !IsMissingSystemDylib(path)) {
           auto filename = cmSystemTools::GetFilenameName(path);
           bool unique;
-          this->Archive->AddResolvedPath(filename, path, unique);
-          if (unique && !this->ScanDependencies(path, executablePath)) {
+          std::vector<std::string> libs;
+          std::vector<std::string> depRpaths;
+          if (!this->Tool->GetFileInfo(path, libs, depRpaths)) {
+            return false;
+          }
+          this->Archive->AddResolvedPath(filename, path, unique, depRpaths);
+          if (unique &&
+              !this->ScanDependencies(path, libs, depRpaths, executablePath)) {
             return false;
           }
         }

+ 2 - 0
Source/cmBinUtilsMacOSMachOLinker.h

@@ -27,6 +27,8 @@ private:
   std::unique_ptr<cmBinUtilsMacOSMachOGetRuntimeDependenciesTool> Tool;
 
   bool ScanDependencies(std::string const& file,
+                        std::vector<std::string> const& libs,
+                        std::vector<std::string> const& rpaths,
                         std::string const& executablePath);
 
   bool GetFileDependencies(std::vector<std::string> const& names,

+ 26 - 2
Source/cmFileAPICodemodel.cxx

@@ -15,6 +15,7 @@
 #include <utility>
 #include <vector>
 
+#include <cm/string_view>
 #include <cmext/algorithm>
 
 #include <cm3p/json/value.h>
@@ -29,7 +30,10 @@
 #include "cmInstallExportGenerator.h"
 #include "cmInstallFilesGenerator.h"
 #include "cmInstallGenerator.h"
+#include "cmInstallGetRuntimeDependenciesGenerator.h"
 #include "cmInstallImportedRuntimeArtifactsGenerator.h"
+#include "cmInstallRuntimeDependencySet.h"
+#include "cmInstallRuntimeDependencySetGenerator.h"
 #include "cmInstallScriptGenerator.h"
 #include "cmInstallSubdirectoryGenerator.h"
 #include "cmInstallTargetGenerator.h"
@@ -877,8 +881,10 @@ Json::Value DirectoryObject::DumpInstaller(cmInstallGenerator* gen)
   assert(gen);
   Json::Value installer = Json::objectValue;
 
-  // Exclude subdirectory installers.  They are implementation details.
-  if (dynamic_cast<cmInstallSubdirectoryGenerator*>(gen)) {
+  // Exclude subdirectory installers and file(GET_RUNTIME_DEPENDENCIES)
+  // installers. They are implementation details.
+  if (dynamic_cast<cmInstallSubdirectoryGenerator*>(gen) ||
+      dynamic_cast<cmInstallGetRuntimeDependenciesGenerator*>(gen)) {
     return installer;
   }
 
@@ -1020,6 +1026,24 @@ Json::Value DirectoryObject::DumpInstaller(cmInstallGenerator* gen)
     if (installImportedRuntimeArtifacts->GetOptional()) {
       installer["isOptional"] = true;
     }
+  } else if (auto* installRuntimeDependencySet =
+               dynamic_cast<cmInstallRuntimeDependencySetGenerator*>(gen)) {
+    installer["type"] = "runtimeDependencySet";
+    installer["destination"] =
+      installRuntimeDependencySet->GetDestination(this->Config);
+    std::string name(
+      installRuntimeDependencySet->GetRuntimeDependencySet()->GetName());
+    if (!name.empty()) {
+      installer["runtimeDependencySetName"] = name;
+    }
+    switch (installRuntimeDependencySet->GetDependencyType()) {
+      case cmInstallRuntimeDependencySetGenerator::DependencyType::Framework:
+        installer["runtimeDependencySetType"] = "framework";
+        break;
+      case cmInstallRuntimeDependencySetGenerator::DependencyType::Library:
+        installer["runtimeDependencySetType"] = "library";
+        break;
+    }
   }
 
   // Add fields common to all install generators.

+ 131 - 80
Source/cmFileCommand.cxx

@@ -990,50 +990,42 @@ bool HandleRPathChangeCommand(std::vector<std::string> const& args,
 {
   // Evaluate arguments.
   std::string file;
-  const char* oldRPath = nullptr;
-  const char* newRPath = nullptr;
+  std::string oldRPath;
+  std::string newRPath;
   bool removeEnvironmentRPath = false;
-  enum Doing
-  {
-    DoingNone,
-    DoingFile,
-    DoingOld,
-    DoingNew
-  };
-  Doing doing = DoingNone;
-  for (unsigned int i = 1; i < args.size(); ++i) {
-    if (args[i] == "OLD_RPATH") {
-      doing = DoingOld;
-    } else if (args[i] == "NEW_RPATH") {
-      doing = DoingNew;
-    } else if (args[i] == "FILE") {
-      doing = DoingFile;
-    } else if (args[i] == "INSTALL_REMOVE_ENVIRONMENT_RPATH") {
-      removeEnvironmentRPath = true;
-    } else if (doing == DoingFile) {
-      file = args[i];
-      doing = DoingNone;
-    } else if (doing == DoingOld) {
-      oldRPath = args[i].c_str();
-      doing = DoingNone;
-    } else if (doing == DoingNew) {
-      newRPath = args[i].c_str();
-      doing = DoingNone;
-    } else {
-      status.SetError(
-        cmStrCat("RPATH_CHANGE given unknown argument ", args[i]));
-      return false;
-    }
+  cmArgumentParser<void> parser;
+  std::vector<std::string> unknownArgs;
+  std::vector<std::string> missingArgs;
+  std::vector<std::string> parsedArgs;
+  parser.Bind("FILE"_s, file)
+    .Bind("OLD_RPATH"_s, oldRPath)
+    .Bind("NEW_RPATH"_s, newRPath)
+    .Bind("INSTALL_REMOVE_ENVIRONMENT_RPATH"_s, removeEnvironmentRPath);
+  parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs,
+               &parsedArgs);
+  if (!unknownArgs.empty()) {
+    status.SetError(
+      cmStrCat("RPATH_CHANGE given unknown argument ", unknownArgs.front()));
+    return false;
+  }
+  if (!missingArgs.empty()) {
+    status.SetError(cmStrCat("RPATH_CHANGE \"", missingArgs.front(),
+                             "\" argument not given value."));
+    return false;
   }
   if (file.empty()) {
     status.SetError("RPATH_CHANGE not given FILE option.");
     return false;
   }
-  if (!oldRPath) {
+  if (oldRPath.empty() &&
+      std::find(parsedArgs.begin(), parsedArgs.end(), "OLD_RPATH") ==
+        parsedArgs.end()) {
     status.SetError("RPATH_CHANGE not given OLD_RPATH option.");
     return false;
   }
-  if (!newRPath) {
+  if (newRPath.empty() &&
+      std::find(parsedArgs.begin(), parsedArgs.end(), "NEW_RPATH") ==
+        parsedArgs.end()) {
     status.SetError("RPATH_CHANGE not given NEW_RPATH option.");
     return false;
   }
@@ -1065,28 +1057,85 @@ bool HandleRPathChangeCommand(std::vector<std::string> const& args,
   return success;
 }
 
+bool HandleRPathSetCommand(std::vector<std::string> const& args,
+                           cmExecutionStatus& status)
+{
+  // Evaluate arguments.
+  std::string file;
+  std::string newRPath;
+  cmArgumentParser<void> parser;
+  std::vector<std::string> unknownArgs;
+  std::vector<std::string> missingArgs;
+  std::vector<std::string> parsedArgs;
+  parser.Bind("FILE"_s, file).Bind("NEW_RPATH"_s, newRPath);
+  parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs,
+               &parsedArgs);
+  if (!unknownArgs.empty()) {
+    status.SetError(cmStrCat("RPATH_SET given unrecognized argument \"",
+                             unknownArgs.front(), "\"."));
+    return false;
+  }
+  if (!missingArgs.empty()) {
+    status.SetError(cmStrCat("RPATH_SET \"", missingArgs.front(),
+                             "\" argument not given value."));
+    return false;
+  }
+  if (file.empty()) {
+    status.SetError("RPATH_SET not given FILE option.");
+    return false;
+  }
+  if (newRPath.empty() &&
+      std::find(parsedArgs.begin(), parsedArgs.end(), "NEW_RPATH") ==
+        parsedArgs.end()) {
+    status.SetError("RPATH_SET not given NEW_RPATH option.");
+    return false;
+  }
+  if (!cmSystemTools::FileExists(file, true)) {
+    status.SetError(
+      cmStrCat("RPATH_SET given FILE \"", file, "\" that does not exist."));
+    return false;
+  }
+  bool success = true;
+  cmFileTimes const ft(file);
+  std::string emsg;
+  bool changed;
+
+  if (!cmSystemTools::SetRPath(file, newRPath, &emsg, &changed)) {
+    status.SetError(cmStrCat("RPATH_SET could not write new RPATH:\n  ",
+                             newRPath, "\nto the file:\n  ", file, "\n",
+                             emsg));
+    success = false;
+  }
+  if (success) {
+    if (changed) {
+      std::string message =
+        cmStrCat("Set runtime path of \"", file, "\" to \"", newRPath, '"');
+      status.GetMakefile().DisplayStatus(message, -1);
+    }
+    ft.Store(file);
+  }
+  return success;
+}
+
 bool HandleRPathRemoveCommand(std::vector<std::string> const& args,
                               cmExecutionStatus& status)
 {
   // Evaluate arguments.
   std::string file;
-  enum Doing
-  {
-    DoingNone,
-    DoingFile
-  };
-  Doing doing = DoingNone;
-  for (unsigned int i = 1; i < args.size(); ++i) {
-    if (args[i] == "FILE") {
-      doing = DoingFile;
-    } else if (doing == DoingFile) {
-      file = args[i];
-      doing = DoingNone;
-    } else {
-      status.SetError(
-        cmStrCat("RPATH_REMOVE given unknown argument ", args[i]));
-      return false;
-    }
+  cmArgumentParser<void> parser;
+  std::vector<std::string> unknownArgs;
+  std::vector<std::string> missingArgs;
+  parser.Bind("FILE"_s, file);
+  parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs);
+  if (!unknownArgs.empty()) {
+    status.SetError(
+      cmStrCat("RPATH_REMOVE given unknown argument ", unknownArgs.front()));
+    return false;
+  }
+  if (!missingArgs.empty()) {
+    status.SetError(cmStrCat("RPATH_REMOVE \"", missingArgs.front(),
+                             "\" argument not given value."));
+    return false;
   }
   if (file.empty()) {
     status.SetError("RPATH_REMOVE not given FILE option.");
@@ -1123,36 +1172,31 @@ bool HandleRPathCheckCommand(std::vector<std::string> const& args,
 {
   // Evaluate arguments.
   std::string file;
-  const char* rpath = nullptr;
-  enum Doing
-  {
-    DoingNone,
-    DoingFile,
-    DoingRPath
-  };
-  Doing doing = DoingNone;
-  for (unsigned int i = 1; i < args.size(); ++i) {
-    if (args[i] == "RPATH") {
-      doing = DoingRPath;
-    } else if (args[i] == "FILE") {
-      doing = DoingFile;
-    } else if (doing == DoingFile) {
-      file = args[i];
-      doing = DoingNone;
-    } else if (doing == DoingRPath) {
-      rpath = args[i].c_str();
-      doing = DoingNone;
-    } else {
-      status.SetError(
-        cmStrCat("RPATH_CHECK given unknown argument ", args[i]));
-      return false;
-    }
+  std::string rpath;
+  cmArgumentParser<void> parser;
+  std::vector<std::string> unknownArgs;
+  std::vector<std::string> missingArgs;
+  std::vector<std::string> parsedArgs;
+  parser.Bind("FILE"_s, file).Bind("RPATH"_s, rpath);
+  parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs,
+               &parsedArgs);
+  if (!unknownArgs.empty()) {
+    status.SetError(
+      cmStrCat("RPATH_CHECK given unknown argument ", unknownArgs.front()));
+    return false;
+  }
+  if (!missingArgs.empty()) {
+    status.SetError(cmStrCat("RPATH_CHECK \"", missingArgs.front(),
+                             "\" argument not given value."));
+    return false;
   }
   if (file.empty()) {
     status.SetError("RPATH_CHECK not given FILE option.");
     return false;
   }
-  if (!rpath) {
+  if (rpath.empty() &&
+      std::find(parsedArgs.begin(), parsedArgs.end(), "RPATH") ==
+        parsedArgs.end()) {
     status.SetError("RPATH_CHECK not given RPATH option.");
     return false;
   }
@@ -3000,11 +3044,10 @@ bool HandleCreateLinkCommand(std::vector<std::string> const& args,
 bool HandleGetRuntimeDependenciesCommand(std::vector<std::string> const& args,
                                          cmExecutionStatus& status)
 {
-  static const std::set<std::string> supportedPlatforms = { "Windows", "Linux",
-                                                            "Darwin" };
   std::string platform =
     status.GetMakefile().GetSafeDefinition("CMAKE_HOST_SYSTEM_NAME");
-  if (!supportedPlatforms.count(platform)) {
+  if (!cmRuntimeDependencyArchive::PlatformSupportsRuntimeDependencies(
+        platform)) {
     status.SetError(
       cmStrCat("GET_RUNTIME_DEPENDENCIES is not supported on system \"",
                platform, "\""));
@@ -3032,6 +3075,7 @@ bool HandleGetRuntimeDependenciesCommand(std::vector<std::string> const& args,
     std::string ResolvedDependenciesVar;
     std::string UnresolvedDependenciesVar;
     std::string ConflictingDependenciesPrefix;
+    std::string RPathPrefix;
     std::string BundleExecutable;
     std::vector<std::string> Executables;
     std::vector<std::string> Libraries;
@@ -3053,6 +3097,7 @@ bool HandleGetRuntimeDependenciesCommand(std::vector<std::string> const& args,
             &Arguments::UnresolvedDependenciesVar)
       .Bind("CONFLICTING_DEPENDENCIES_PREFIX"_s,
             &Arguments::ConflictingDependenciesPrefix)
+      .Bind("RPATH_PREFIX"_s, &Arguments::RPathPrefix)
       .Bind("BUNDLE_EXECUTABLE"_s, &Arguments::BundleExecutable)
       .Bind("EXECUTABLES"_s, &Arguments::Executables)
       .Bind("LIBRARIES"_s, &Arguments::Libraries)
@@ -3135,6 +3180,11 @@ bool HandleGetRuntimeDependenciesCommand(std::vector<std::string> const& args,
 
     if (unique) {
       deps.push_back(firstPath);
+      if (!parsedArgs.RPathPrefix.empty()) {
+        status.GetMakefile().AddDefinition(
+          parsedArgs.RPathPrefix + "_" + firstPath,
+          cmJoin(archive.GetRPaths().at(firstPath), ";"));
+      }
     } else if (!parsedArgs.ConflictingDependenciesPrefix.empty()) {
       conflictingDeps.push_back(val.first);
       std::vector<std::string> paths;
@@ -3741,6 +3791,7 @@ bool cmFileCommand(std::vector<std::string> const& args,
     { "DIFFERENT"_s, HandleDifferentCommand },
     { "RPATH_CHANGE"_s, HandleRPathChangeCommand },
     { "CHRPATH"_s, HandleRPathChangeCommand },
+    { "RPATH_SET"_s, HandleRPathSetCommand },
     { "RPATH_CHECK"_s, HandleRPathCheckCommand },
     { "RPATH_REMOVE"_s, HandleRPathRemoveCommand },
     { "READ_ELF"_s, HandleReadElfCommand },

+ 24 - 0
Source/cmGlobalGenerator.cxx

@@ -36,6 +36,7 @@
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
 #include "cmInstallGenerator.h"
+#include "cmInstallRuntimeDependencySet.h"
 #include "cmLinkLineComputer.h"
 #include "cmListFileCache.h"
 #include "cmLocalGenerator.h"
@@ -3332,3 +3333,26 @@ bool cmGlobalGenerator::GenerateCPackPropertiesFile()
 
   return true;
 }
+
+cmInstallRuntimeDependencySet*
+cmGlobalGenerator::CreateAnonymousRuntimeDependencySet()
+{
+  auto set = cm::make_unique<cmInstallRuntimeDependencySet>();
+  auto* retval = set.get();
+  this->RuntimeDependencySets.push_back(std::move(set));
+  return retval;
+}
+
+cmInstallRuntimeDependencySet* cmGlobalGenerator::GetNamedRuntimeDependencySet(
+  const std::string& name)
+{
+  auto it = this->RuntimeDependencySetsByName.find(name);
+  if (it == this->RuntimeDependencySetsByName.end()) {
+    auto set = cm::make_unique<cmInstallRuntimeDependencySet>(name);
+    it =
+      this->RuntimeDependencySetsByName.insert(std::make_pair(name, set.get()))
+        .first;
+    this->RuntimeDependencySets.push_back(std::move(set));
+  }
+  return it->second;
+}

+ 11 - 0
Source/cmGlobalGenerator.h

@@ -42,6 +42,7 @@ class cmDirectoryId;
 class cmExportBuildFileGenerator;
 class cmExternalMakefileProjectGenerator;
 class cmGeneratorTarget;
+class cmInstallRuntimeDependencySet;
 class cmLinkLineComputer;
 class cmLocalGenerator;
 class cmMakefile;
@@ -528,6 +529,11 @@ public:
 
   std::string NewDeferId();
 
+  cmInstallRuntimeDependencySet* CreateAnonymousRuntimeDependencySet();
+
+  cmInstallRuntimeDependencySet* GetNamedRuntimeDependencySet(
+    const std::string& name);
+
 protected:
   // for a project collect all its targets by following depend
   // information, and also collect all the targets
@@ -747,6 +753,11 @@ private:
 
   std::unordered_set<std::string> GeneratedFiles;
 
+  std::vector<std::unique_ptr<cmInstallRuntimeDependencySet>>
+    RuntimeDependencySets;
+  std::map<std::string, cmInstallRuntimeDependencySet*>
+    RuntimeDependencySetsByName;
+
 #if !defined(CMAKE_BOOTSTRAP)
   // Pool of file locks
   cmFileLockPool FileLockPool;

+ 359 - 2
Source/cmInstallCommand.cxx

@@ -2,8 +2,10 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmInstallCommand.h"
 
+#include <algorithm>
 #include <cassert>
 #include <cstddef>
+#include <iterator>
 #include <set>
 #include <sstream>
 #include <utility>
@@ -23,7 +25,10 @@
 #include "cmInstallExportGenerator.h"
 #include "cmInstallFilesGenerator.h"
 #include "cmInstallGenerator.h"
+#include "cmInstallGetRuntimeDependenciesGenerator.h"
 #include "cmInstallImportedRuntimeArtifactsGenerator.h"
+#include "cmInstallRuntimeDependencySet.h"
+#include "cmInstallRuntimeDependencySetGenerator.h"
 #include "cmInstallScriptGenerator.h"
 #include "cmInstallTargetGenerator.h"
 #include "cmListFileCache.h"
@@ -31,6 +36,7 @@
 #include "cmMessageType.h"
 #include "cmPolicies.h"
 #include "cmProperty.h"
+#include "cmRuntimeDependencyArchive.h"
 #include "cmStateTypes.h"
 #include "cmStringAlgorithms.h"
 #include "cmSubcommandTable.h"
@@ -40,6 +46,29 @@
 
 namespace {
 
+struct RuntimeDependenciesArgs
+{
+  std::vector<std::string> Directories;
+  std::vector<std::string> PreIncludeRegexes;
+  std::vector<std::string> PreExcludeRegexes;
+  std::vector<std::string> PostIncludeRegexes;
+  std::vector<std::string> PostExcludeRegexes;
+  std::vector<std::string> PostIncludeFiles;
+  std::vector<std::string> PostExcludeFiles;
+};
+
+auto const RuntimeDependenciesArgHelper =
+  cmArgumentParser<RuntimeDependenciesArgs>{}
+    .Bind("DIRECTORIES"_s, &RuntimeDependenciesArgs::Directories)
+    .Bind("PRE_INCLUDE_REGEXES"_s, &RuntimeDependenciesArgs::PreIncludeRegexes)
+    .Bind("PRE_EXCLUDE_REGEXES"_s, &RuntimeDependenciesArgs::PreExcludeRegexes)
+    .Bind("POST_INCLUDE_REGEXES"_s,
+          &RuntimeDependenciesArgs::PostIncludeRegexes)
+    .Bind("POST_EXCLUDE_REGEXES"_s,
+          &RuntimeDependenciesArgs::PostExcludeRegexes)
+    .Bind("POST_INCLUDE_FILES"_s, &RuntimeDependenciesArgs::PostIncludeFiles)
+    .Bind("POST_EXCLUDE_FILES"_s, &RuntimeDependenciesArgs::PostExcludeFiles);
+
 class Helper
 {
 public:
@@ -147,12 +176,106 @@ std::unique_ptr<cmInstallFilesGenerator> CreateInstallFilesGenerator(
                                      args.GetDestination());
 }
 
+void AddInstallRuntimeDependenciesGenerator(
+  Helper& helper, cmInstallRuntimeDependencySet* runtimeDependencySet,
+  const cmInstallCommandArguments& runtimeArgs,
+  const cmInstallCommandArguments& libraryArgs,
+  const cmInstallCommandArguments& frameworkArgs,
+  RuntimeDependenciesArgs runtimeDependenciesArgs, bool& installsRuntime,
+  bool& installsLibrary, bool& installsFramework)
+{
+  bool dllPlatform =
+    !helper.Makefile->GetSafeDefinition("CMAKE_IMPORT_LIBRARY_SUFFIX").empty();
+  bool apple =
+    helper.Makefile->GetSafeDefinition("CMAKE_HOST_SYSTEM_NAME") == "Darwin";
+  auto const& runtimeDependenciesArgsRef =
+    dllPlatform ? runtimeArgs : libraryArgs;
+  std::vector<std::string> configurations =
+    runtimeDependenciesArgsRef.GetConfigurations();
+  if (apple) {
+    std::copy(frameworkArgs.GetConfigurations().begin(),
+              frameworkArgs.GetConfigurations().end(),
+              std::back_inserter(configurations));
+  }
+
+  // Create file(GET_RUNTIME_DEPENDENCIES) generator.
+  auto getRuntimeDependenciesGenerator =
+    cm::make_unique<cmInstallGetRuntimeDependenciesGenerator>(
+      runtimeDependencySet, std::move(runtimeDependenciesArgs.Directories),
+      std::move(runtimeDependenciesArgs.PreIncludeRegexes),
+      std::move(runtimeDependenciesArgs.PreExcludeRegexes),
+      std::move(runtimeDependenciesArgs.PostIncludeRegexes),
+      std::move(runtimeDependenciesArgs.PostExcludeRegexes),
+      std::move(runtimeDependenciesArgs.PostIncludeFiles),
+      std::move(runtimeDependenciesArgs.PostExcludeFiles),
+      runtimeDependenciesArgsRef.GetComponent(),
+      apple ? frameworkArgs.GetComponent() : "", true, "_CMAKE_DEPS",
+      "_CMAKE_RPATH", configurations,
+      cmInstallGenerator::SelectMessageLevel(helper.Makefile),
+      runtimeDependenciesArgsRef.GetExcludeFromAll() &&
+        (apple ? frameworkArgs.GetExcludeFromAll() : true),
+      helper.Makefile->GetBacktrace());
+  helper.Makefile->AddInstallGenerator(
+    std::move(getRuntimeDependenciesGenerator));
+
+  // Create the library dependencies generator.
+  auto libraryRuntimeDependenciesGenerator =
+    cm::make_unique<cmInstallRuntimeDependencySetGenerator>(
+      cmInstallRuntimeDependencySetGenerator::DependencyType::Library,
+      runtimeDependencySet, std::vector<std::string>{}, true, std::string{},
+      true, "_CMAKE_DEPS", "_CMAKE_RPATH", "_CMAKE_TMP",
+      dllPlatform ? helper.GetRuntimeDestination(&runtimeArgs)
+                  : helper.GetLibraryDestination(&libraryArgs),
+      runtimeDependenciesArgsRef.GetConfigurations(),
+      runtimeDependenciesArgsRef.GetComponent(),
+      runtimeDependenciesArgsRef.GetPermissions(),
+      cmInstallGenerator::SelectMessageLevel(helper.Makefile),
+      runtimeDependenciesArgsRef.GetExcludeFromAll(),
+      helper.Makefile->GetBacktrace());
+  helper.Makefile->AddInstallGenerator(
+    std::move(libraryRuntimeDependenciesGenerator));
+  if (dllPlatform) {
+    installsRuntime = true;
+  } else {
+    installsLibrary = true;
+  }
+
+  if (apple) {
+    // Create the framework dependencies generator.
+    auto frameworkRuntimeDependenciesGenerator =
+      cm::make_unique<cmInstallRuntimeDependencySetGenerator>(
+        cmInstallRuntimeDependencySetGenerator::DependencyType::Framework,
+        runtimeDependencySet, std::vector<std::string>{}, true, std::string{},
+        true, "_CMAKE_DEPS", "_CMAKE_RPATH", "_CMAKE_TMP",
+        frameworkArgs.GetDestination(), frameworkArgs.GetConfigurations(),
+        frameworkArgs.GetComponent(), frameworkArgs.GetPermissions(),
+        cmInstallGenerator::SelectMessageLevel(helper.Makefile),
+        frameworkArgs.GetExcludeFromAll(), helper.Makefile->GetBacktrace());
+    helper.Makefile->AddInstallGenerator(
+      std::move(frameworkRuntimeDependenciesGenerator));
+    installsFramework = true;
+  }
+}
+
 std::set<std::string> const allowedTypes{
   "BIN",         "SBIN",       "LIB",      "INCLUDE", "SYSCONF",
   "SHAREDSTATE", "LOCALSTATE", "RUNSTATE", "DATA",    "INFO",
   "LOCALE",      "MAN",        "DOC",
 };
 
+template <typename T>
+bool AddBundleExecutable(Helper& helper,
+                         cmInstallRuntimeDependencySet* runtimeDependencySet,
+                         T&& bundleExecutable)
+{
+  if (!runtimeDependencySet->AddBundleExecutable(bundleExecutable)) {
+    helper.SetError(
+      "A runtime dependency set may only have one bundle executable.");
+    return false;
+  }
+  return true;
+}
+
 bool HandleScriptMode(std::vector<std::string> const& args,
                       cmExecutionStatus& status)
 {
@@ -289,13 +412,25 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
   // These generic args also contain the targets and the export stuff
   std::vector<std::string> targetList;
   std::string exports;
+  std::vector<std::string> runtimeDependenciesArgVector;
+  std::string runtimeDependencySetArg;
   std::vector<std::string> unknownArgs;
+  std::vector<std::string> parsedArgs;
   cmInstallCommandArguments genericArgs(helper.DefaultComponentName);
   genericArgs.Bind("TARGETS"_s, targetList);
   genericArgs.Bind("EXPORT"_s, exports);
-  genericArgs.Parse(genericArgVector, &unknownArgs);
+  genericArgs.Bind("RUNTIME_DEPENDENCIES"_s, runtimeDependenciesArgVector);
+  genericArgs.Bind("RUNTIME_DEPENDENCY_SET"_s, runtimeDependencySetArg);
+  genericArgs.Parse(genericArgVector, &unknownArgs, nullptr, &parsedArgs);
   bool success = genericArgs.Finalize();
 
+  bool withRuntimeDependencies =
+    std::find(parsedArgs.begin(), parsedArgs.end(), "RUNTIME_DEPENDENCIES") !=
+    parsedArgs.end();
+  RuntimeDependenciesArgs runtimeDependenciesArgs =
+    RuntimeDependenciesArgHelper.Parse(runtimeDependenciesArgVector,
+                                       &unknownArgs);
+
   cmInstallCommandArguments archiveArgs(helper.DefaultComponentName);
   cmInstallCommandArguments libraryArgs(helper.DefaultComponentName);
   cmInstallCommandArguments runtimeArgs(helper.DefaultComponentName);
@@ -402,6 +537,49 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
     return false;
   }
 
+  cmInstallRuntimeDependencySet* runtimeDependencySet = nullptr;
+  if (withRuntimeDependencies) {
+    if (!runtimeDependencySetArg.empty()) {
+      status.SetError("TARGETS cannot have both RUNTIME_DEPENDENCIES and "
+                      "RUNTIME_DEPENDENCY_SET.");
+      return false;
+    }
+    auto system = helper.Makefile->GetSafeDefinition("CMAKE_HOST_SYSTEM_NAME");
+    if (!cmRuntimeDependencyArchive::PlatformSupportsRuntimeDependencies(
+          system)) {
+      status.SetError(
+        cmStrCat("TARGETS RUNTIME_DEPENDENCIES is not supported on system \"",
+                 system, '"'));
+      return false;
+    }
+    if (helper.Makefile->IsOn("CMAKE_CROSSCOMPILING")) {
+      status.SetError("TARGETS RUNTIME_DEPENDENCIES is not supported "
+                      "when cross-compiling.");
+      return false;
+    }
+    if (helper.Makefile->GetSafeDefinition("CMAKE_HOST_SYSTEM_NAME") ==
+          "Darwin" &&
+        frameworkArgs.GetDestination().empty()) {
+      status.SetError(
+        "TARGETS RUNTIME_DEPENDENCIES given no FRAMEWORK DESTINATION");
+      return false;
+    }
+    runtimeDependencySet = helper.Makefile->GetGlobalGenerator()
+                             ->CreateAnonymousRuntimeDependencySet();
+  } else if (!runtimeDependencySetArg.empty()) {
+    auto system = helper.Makefile->GetSafeDefinition("CMAKE_HOST_SYSTEM_NAME");
+    if (!cmRuntimeDependencyArchive::PlatformSupportsRuntimeDependencies(
+          system)) {
+      status.SetError(cmStrCat(
+        "TARGETS RUNTIME_DEPENDENCY_SET is not supported on system \"", system,
+        '"'));
+      return false;
+    }
+    runtimeDependencySet =
+      helper.Makefile->GetGlobalGenerator()->GetNamedRuntimeDependencySet(
+        runtimeDependencySetArg);
+  }
+
   // Select the mode for installing symlinks to versioned shared libraries.
   cmInstallTargetGenerator::NamelinkModeType namelinkMode =
     cmInstallTargetGenerator::NamelinkModeNone;
@@ -546,6 +724,9 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
               target, runtimeArgs, false, helper.Makefile->GetBacktrace(),
               helper.GetRuntimeDestination(nullptr));
           }
+          if (runtimeDependencySet && runtimeGenerator) {
+            runtimeDependencySet->AddLibrary(runtimeGenerator.get());
+          }
         } else {
           // This is a non-DLL platform.
           // If it is marked with FRAMEWORK property use the FRAMEWORK set of
@@ -591,6 +772,9 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
             namelinkOnly =
               (namelinkMode == cmInstallTargetGenerator::NamelinkModeOnly);
           }
+          if (runtimeDependencySet && libraryGenerator) {
+            runtimeDependencySet->AddLibrary(libraryGenerator.get());
+          }
         }
       } break;
       case cmStateEnums::STATIC_LIBRARY: {
@@ -633,6 +817,9 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
           libraryGenerator->SetNamelinkMode(namelinkMode);
           namelinkOnly =
             (namelinkMode == cmInstallTargetGenerator::NamelinkModeOnly);
+          if (runtimeDependencySet) {
+            runtimeDependencySet->AddModule(libraryGenerator.get());
+          }
         } else {
           status.SetError(
             cmStrCat("TARGETS given no LIBRARY DESTINATION for module "
@@ -685,6 +872,12 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
                                      target.GetName(), "\"."));
             return false;
           }
+          if (runtimeDependencySet) {
+            if (!AddBundleExecutable(helper, runtimeDependencySet,
+                                     bundleGenerator.get())) {
+              return false;
+            }
+          }
         } else {
           // Executables use the RUNTIME properties.
           if (!runtimeArgs.GetDestination().empty()) {
@@ -693,6 +886,9 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
           runtimeGenerator = CreateInstallTargetGenerator(
             target, runtimeArgs, false, helper.Makefile->GetBacktrace(),
             helper.GetRuntimeDestination(&runtimeArgs));
+          if (runtimeDependencySet) {
+            runtimeDependencySet->AddExecutable(runtimeGenerator.get());
+          }
         }
 
         // On DLL platforms an executable may also have an import
@@ -821,6 +1017,13 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
     helper.Makefile->AddInstallGenerator(std::move(resourceGenerator));
   }
 
+  if (withRuntimeDependencies && !runtimeDependencySet->Empty()) {
+    AddInstallRuntimeDependenciesGenerator(
+      helper, runtimeDependencySet, runtimeArgs, libraryArgs, frameworkArgs,
+      std::move(runtimeDependenciesArgs), installsRuntime, installsLibrary,
+      installsFramework);
+  }
+
   // Tell the global generator about any installation component names
   // specified
   if (installsArchive) {
@@ -895,9 +1098,11 @@ bool HandleImportedRuntimeArtifactsMode(std::vector<std::string> const& args,
   // now parse the generic args (i.e. the ones not specialized on LIBRARY,
   // RUNTIME etc. (see above)
   std::vector<std::string> targetList;
+  std::string runtimeDependencySetArg;
   std::vector<std::string> unknownArgs;
   cmInstallCommandArguments genericArgs(helper.DefaultComponentName);
-  genericArgs.Bind("IMPORTED_RUNTIME_ARTIFACTS"_s, targetList);
+  genericArgs.Bind("IMPORTED_RUNTIME_ARTIFACTS"_s, targetList)
+    .Bind("RUNTIME_DEPENDENCY_SET"_s, runtimeDependencySetArg);
   genericArgs.Parse(genericArgVector, &unknownArgs);
   bool success = genericArgs.Finalize();
 
@@ -936,6 +1141,22 @@ bool HandleImportedRuntimeArtifactsMode(std::vector<std::string> const& args,
     return false;
   }
 
+  cmInstallRuntimeDependencySet* runtimeDependencySet = nullptr;
+  if (!runtimeDependencySetArg.empty()) {
+    auto system = helper.Makefile->GetSafeDefinition("CMAKE_HOST_SYSTEM_NAME");
+    if (!cmRuntimeDependencyArchive::PlatformSupportsRuntimeDependencies(
+          system)) {
+      status.SetError(
+        cmStrCat("IMPORTED_RUNTIME_ARTIFACTS RUNTIME_DEPENDENCY_SET is not "
+                 "supported on system \"",
+                 system, '"'));
+      return false;
+    }
+    runtimeDependencySet =
+      helper.Makefile->GetGlobalGenerator()->GetNamedRuntimeDependencySet(
+        runtimeDependencySetArg);
+  }
+
   // Check if there is something to do.
   if (targetList.empty()) {
     return true;
@@ -1014,6 +1235,9 @@ bool HandleImportedRuntimeArtifactsMode(std::vector<std::string> const& args,
         if (target.IsDLLPlatform()) {
           runtimeGenerator = createInstallGenerator(
             target, runtimeArgs, helper.GetRuntimeDestination(&runtimeArgs));
+          if (runtimeDependencySet) {
+            runtimeDependencySet->AddLibrary(runtimeGenerator.get());
+          }
         } else if (target.IsFrameworkOnApple()) {
           if (frameworkArgs.GetDestination().empty()) {
             status.SetError(cmStrCat("IMPORTED_RUNTIME_ARTIFACTS given no "
@@ -1024,14 +1248,23 @@ bool HandleImportedRuntimeArtifactsMode(std::vector<std::string> const& args,
           }
           frameworkGenerator = createInstallGenerator(
             target, frameworkArgs, frameworkArgs.GetDestination());
+          if (runtimeDependencySet) {
+            runtimeDependencySet->AddLibrary(frameworkGenerator.get());
+          }
         } else {
           libraryGenerator = createInstallGenerator(
             target, libraryArgs, helper.GetLibraryDestination(&libraryArgs));
+          if (runtimeDependencySet) {
+            runtimeDependencySet->AddLibrary(libraryGenerator.get());
+          }
         }
         break;
       case cmStateEnums::MODULE_LIBRARY:
         libraryGenerator = createInstallGenerator(
           target, libraryArgs, helper.GetLibraryDestination(&libraryArgs));
+        if (runtimeDependencySet) {
+          runtimeDependencySet->AddModule(libraryGenerator.get());
+        }
         break;
       case cmStateEnums::EXECUTABLE:
         if (target.IsAppBundleOnApple()) {
@@ -1044,9 +1277,18 @@ bool HandleImportedRuntimeArtifactsMode(std::vector<std::string> const& args,
           }
           bundleGenerator = createInstallGenerator(
             target, bundleArgs, bundleArgs.GetDestination());
+          if (runtimeDependencySet) {
+            if (!AddBundleExecutable(helper, runtimeDependencySet,
+                                     bundleGenerator.get())) {
+              return false;
+            }
+          }
         } else {
           runtimeGenerator = createInstallGenerator(
             target, runtimeArgs, helper.GetRuntimeDestination(&runtimeArgs));
+          if (runtimeDependencySet) {
+            runtimeDependencySet->AddExecutable(runtimeGenerator.get());
+          }
         }
         break;
       default:
@@ -1700,6 +1942,120 @@ bool HandleExportMode(std::vector<std::string> const& args,
   return true;
 }
 
+bool HandleRuntimeDependencySetMode(std::vector<std::string> const& args,
+                                    cmExecutionStatus& status)
+{
+  Helper helper(status);
+
+  auto system = helper.Makefile->GetSafeDefinition("CMAKE_HOST_SYSTEM_NAME");
+  if (!cmRuntimeDependencyArchive::PlatformSupportsRuntimeDependencies(
+        system)) {
+    status.SetError(cmStrCat(
+      "RUNTIME_DEPENDENCY_SET is not supported on system \"", system, '"'));
+    return false;
+  }
+
+  // This is the RUNTIME_DEPENDENCY_SET mode.
+  cmInstallRuntimeDependencySet* runtimeDependencySet;
+
+  struct ArgVectors
+  {
+    std::vector<std::string> Library;
+    std::vector<std::string> Runtime;
+    std::vector<std::string> Framework;
+  };
+
+  static auto const argHelper = cmArgumentParser<ArgVectors>{}
+                                  .Bind("LIBRARY"_s, &ArgVectors::Library)
+                                  .Bind("RUNTIME"_s, &ArgVectors::Runtime)
+                                  .Bind("FRAMEWORK"_s, &ArgVectors::Framework);
+
+  std::vector<std::string> genericArgVector;
+  ArgVectors const argVectors = argHelper.Parse(args, &genericArgVector);
+
+  // now parse the generic args (i.e. the ones not specialized on LIBRARY,
+  // RUNTIME, FRAMEWORK etc. (see above)
+  // These generic args also contain the runtime dependency set
+  std::string runtimeDependencySetArg;
+  std::vector<std::string> runtimeDependencyArgVector;
+  std::vector<std::string> parsedArgs;
+  cmInstallCommandArguments genericArgs(helper.DefaultComponentName);
+  genericArgs.Bind("RUNTIME_DEPENDENCY_SET"_s, runtimeDependencySetArg);
+  genericArgs.Parse(genericArgVector, &runtimeDependencyArgVector, nullptr,
+                    &parsedArgs);
+  bool success = genericArgs.Finalize();
+
+  cmInstallCommandArguments libraryArgs(helper.DefaultComponentName);
+  cmInstallCommandArguments runtimeArgs(helper.DefaultComponentName);
+  cmInstallCommandArguments frameworkArgs(helper.DefaultComponentName);
+
+  // Now also parse the file(GET_RUNTIME_DEPENDENCY) args
+  std::vector<std::string> unknownArgs;
+  auto runtimeDependencyArgs = RuntimeDependenciesArgHelper.Parse(
+    runtimeDependencyArgVector, &unknownArgs);
+
+  // now parse the args for specific parts of the target (e.g. LIBRARY,
+  // RUNTIME, FRAMEWORK etc.
+  libraryArgs.Parse(argVectors.Library, &unknownArgs);
+  runtimeArgs.Parse(argVectors.Runtime, &unknownArgs);
+  frameworkArgs.Parse(argVectors.Framework, &unknownArgs);
+
+  libraryArgs.SetGenericArguments(&genericArgs);
+  runtimeArgs.SetGenericArguments(&genericArgs);
+  frameworkArgs.SetGenericArguments(&genericArgs);
+
+  success = success && libraryArgs.Finalize();
+  success = success && runtimeArgs.Finalize();
+  success = success && frameworkArgs.Finalize();
+
+  if (!success) {
+    return false;
+  }
+
+  if (!unknownArgs.empty()) {
+    helper.SetError(
+      cmStrCat("RUNTIME_DEPENDENCY_SET given unknown argument \"",
+               unknownArgs.front(), "\"."));
+    return false;
+  }
+
+  if (runtimeDependencySetArg.empty()) {
+    helper.SetError(
+      "RUNTIME_DEPENDENCY_SET not given a runtime dependency set.");
+    return false;
+  }
+
+  runtimeDependencySet =
+    helper.Makefile->GetGlobalGenerator()->GetNamedRuntimeDependencySet(
+      runtimeDependencySetArg);
+
+  bool installsRuntime = false;
+  bool installsLibrary = false;
+  bool installsFramework = false;
+
+  AddInstallRuntimeDependenciesGenerator(
+    helper, runtimeDependencySet, runtimeArgs, libraryArgs, frameworkArgs,
+    std::move(runtimeDependencyArgs), installsRuntime, installsLibrary,
+    installsFramework);
+
+  // Tell the global generator about any installation component names
+  // specified
+  if (installsLibrary) {
+    helper.Makefile->GetGlobalGenerator()->AddInstallComponent(
+      libraryArgs.GetComponent());
+  }
+  if (installsRuntime) {
+    helper.Makefile->GetGlobalGenerator()->AddInstallComponent(
+      runtimeArgs.GetComponent());
+  }
+  if (installsFramework) {
+    helper.Makefile->GetGlobalGenerator()->AddInstallComponent(
+      frameworkArgs.GetComponent());
+  }
+
+  return true;
+}
+
 bool Helper::MakeFilesFullPath(const char* modeName,
                                const std::vector<std::string>& relFiles,
                                std::vector<std::string>& absFiles)
@@ -1934,6 +2290,7 @@ bool cmInstallCommand(std::vector<std::string> const& args,
     { "DIRECTORY"_s, HandleDirectoryMode },
     { "EXPORT"_s, HandleExportMode },
     { "EXPORT_ANDROID_MK"_s, HandleExportAndroidMKMode },
+    { "RUNTIME_DEPENDENCY_SET"_s, HandleRuntimeDependencySetMode },
   };
 
   return subcommand(args[0], args, status);

+ 32 - 19
Source/cmInstallGenerator.cxx

@@ -43,7 +43,8 @@ void cmInstallGenerator::AddInstallRule(
   std::vector<std::string> const& files, bool optional /* = false */,
   const char* permissions_file /* = 0 */,
   const char* permissions_dir /* = 0 */, const char* rename /* = 0 */,
-  const char* literal_args /* = 0 */, Indent indent)
+  const char* literal_args /* = 0 */, Indent indent,
+  const char* files_var /* = 0 */)
 {
   // Use the FILE command to install the file.
   std::string stype;
@@ -70,37 +71,46 @@ void cmInstallGenerator::AddInstallRule(
       stype = "FILE";
       break;
   }
-  os << indent;
   if (cmSystemTools::FileIsFullPath(dest)) {
-    os << "list(APPEND CMAKE_ABSOLUTE_DESTINATION_FILES\n";
-    os << indent << " \"";
-    bool firstIteration = true;
-    for (std::string const& file : files) {
-      if (!firstIteration) {
-        os << ";";
+    if (!files.empty()) {
+      os << indent << "list(APPEND CMAKE_ABSOLUTE_DESTINATION_FILES\n";
+      os << indent << " \"";
+      bool firstIteration = true;
+      for (std::string const& file : files) {
+        if (!firstIteration) {
+          os << ";";
+        }
+        os << dest << "/";
+        if (rename && *rename) {
+          os << rename;
+        } else {
+          os << cmSystemTools::GetFilenameName(file);
+        }
+        firstIteration = false;
       }
-      os << dest << "/";
-      if (rename && *rename) {
-        os << rename;
-      } else {
-        os << cmSystemTools::GetFilenameName(file);
-      }
-      firstIteration = false;
+      os << "\")\n";
+    }
+    if (files_var) {
+      os << indent << "foreach(_f IN LISTS " << files_var << ")\n";
+      os << indent.Next() << "get_filename_component(_fn \"${_f}\" NAME)\n";
+      os << indent.Next() << "list(APPEND CMAKE_ABSOLUTE_DESTINATION_FILES \""
+         << dest << "/${_fn}\")\n";
+      os << indent << "endforeach()\n";
     }
-    os << "\")\n";
     os << indent << "if(CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION)\n";
-    os << indent << indent << "message(WARNING \"ABSOLUTE path INSTALL "
+    os << indent.Next() << "message(WARNING \"ABSOLUTE path INSTALL "
        << "DESTINATION : ${CMAKE_ABSOLUTE_DESTINATION_FILES}\")\n";
     os << indent << "endif()\n";
 
     os << indent << "if(CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION)\n";
-    os << indent << indent << "message(FATAL_ERROR \"ABSOLUTE path INSTALL "
+    os << indent.Next() << "message(FATAL_ERROR \"ABSOLUTE path INSTALL "
        << "DESTINATION forbidden (by caller): "
        << "${CMAKE_ABSOLUTE_DESTINATION_FILES}\")\n";
     os << indent << "endif()\n";
   }
   std::string absDest = ConvertToAbsoluteDestination(dest);
-  os << "file(INSTALL DESTINATION \"" << absDest << "\" TYPE " << stype;
+  os << indent << "file(INSTALL DESTINATION \"" << absDest << "\" TYPE "
+     << stype;
   if (optional) {
     os << " OPTIONAL";
   }
@@ -133,6 +143,9 @@ void cmInstallGenerator::AddInstallRule(
     for (std::string const& f : files) {
       os << "\n" << indent << "  \"" << f << "\"";
     }
+    if (files_var) {
+      os << " ${" << files_var << "}";
+    }
     os << "\n" << indent << " ";
     if (!(literal_args && *literal_args)) {
       os << " ";

+ 2 - 1
Source/cmInstallGenerator.h

@@ -50,7 +50,8 @@ public:
     std::vector<std::string> const& files, bool optional = false,
     const char* permissions_file = nullptr,
     const char* permissions_dir = nullptr, const char* rename = nullptr,
-    const char* literal_args = nullptr, Indent indent = Indent());
+    const char* literal_args = nullptr, Indent indent = Indent(),
+    const char* files_var = nullptr);
 
   /** Get the install destination as it should appear in the
       installation script.  */

+ 206 - 0
Source/cmInstallGetRuntimeDependenciesGenerator.cxx

@@ -0,0 +1,206 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmInstallGetRuntimeDependenciesGenerator.h"
+
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <cm/optional>
+#include <cm/string_view>
+#include <cmext/string_view>
+
+#include "cmGeneratorExpression.h"
+#include "cmInstallRuntimeDependencySet.h"
+#include "cmLocalGenerator.h"
+#include "cmMakefile.h"
+#include "cmOutputConverter.h"
+#include "cmStringAlgorithms.h"
+
+namespace {
+template <typename T, typename F>
+void WriteMultiArgument(std::ostream& os, const cm::string_view& keyword,
+                        const std::vector<T>& list,
+                        cmScriptGeneratorIndent indent, F transform)
+{
+  bool first = true;
+  for (auto const& item : list) {
+    cm::optional<std::string> result = transform(item);
+    if (result) {
+      if (first) {
+        os << indent << "  " << keyword << "\n";
+        first = false;
+      }
+      os << indent << "    " << *result << "\n";
+    }
+  }
+}
+
+void WriteFilesArgument(
+  std::ostream& os, const cm::string_view& keyword,
+  const std::vector<std::unique_ptr<cmInstallRuntimeDependencySet::Item>>&
+    items,
+  const std::string& config, cmScriptGeneratorIndent indent)
+{
+  WriteMultiArgument(
+    os, keyword, items, indent,
+    [config](const std::unique_ptr<cmInstallRuntimeDependencySet::Item>& i)
+      -> std::string { return cmStrCat('"', i->GetItemPath(config), '"'); });
+}
+
+void WriteGenexEvaluatorArgument(std::ostream& os,
+                                 const cm::string_view& keyword,
+                                 const std::vector<std::string>& genexes,
+                                 const std::string& config,
+                                 cmLocalGenerator* lg,
+                                 cmScriptGeneratorIndent indent)
+{
+  WriteMultiArgument(
+    os, keyword, genexes, indent,
+    [config, lg](const std::string& genex) -> cm::optional<std::string> {
+      std::string result = cmGeneratorExpression::Evaluate(genex, lg, config);
+      if (result.empty()) {
+        return cm::nullopt;
+      }
+      return cmOutputConverter::EscapeForCMake(result);
+    });
+}
+}
+
+cmInstallGetRuntimeDependenciesGenerator::
+  cmInstallGetRuntimeDependenciesGenerator(
+    cmInstallRuntimeDependencySet* runtimeDependencySet,
+    std::vector<std::string> directories,
+    std::vector<std::string> preIncludeRegexes,
+    std::vector<std::string> preExcludeRegexes,
+    std::vector<std::string> postIncludeRegexes,
+    std::vector<std::string> postExcludeRegexes,
+    std::vector<std::string> postIncludeFiles,
+    std::vector<std::string> postExcludeFiles, std::string libraryComponent,
+    std::string frameworkComponent, bool noInstallRPath, const char* depsVar,
+    const char* rpathPrefix, std::vector<std::string> const& configurations,
+    MessageLevel message, bool exclude_from_all, cmListFileBacktrace backtrace)
+  : cmInstallGenerator("", configurations, "", message, exclude_from_all,
+                       false, std::move(backtrace))
+  , RuntimeDependencySet(runtimeDependencySet)
+  , Directories(std::move(directories))
+  , PreIncludeRegexes(std::move(preIncludeRegexes))
+  , PreExcludeRegexes(std::move(preExcludeRegexes))
+  , PostIncludeRegexes(std::move(postIncludeRegexes))
+  , PostExcludeRegexes(std::move(postExcludeRegexes))
+  , PostIncludeFiles(std::move(postIncludeFiles))
+  , PostExcludeFiles(std::move(postExcludeFiles))
+  , LibraryComponent(std::move(libraryComponent))
+  , FrameworkComponent(std::move(frameworkComponent))
+  , NoInstallRPath(noInstallRPath)
+  , DepsVar(depsVar)
+  , RPathPrefix(rpathPrefix)
+{
+  this->ActionsPerConfig = true;
+}
+
+bool cmInstallGetRuntimeDependenciesGenerator::Compute(cmLocalGenerator* lg)
+{
+  this->LocalGenerator = lg;
+  return true;
+}
+
+void cmInstallGetRuntimeDependenciesGenerator::GenerateScript(std::ostream& os)
+{
+  // Track indentation.
+  Indent indent;
+
+  // Begin this block of installation.
+  os << indent << "if(";
+  if (this->FrameworkComponent.empty() ||
+      this->FrameworkComponent == this->LibraryComponent) {
+    os << this->CreateComponentTest(this->LibraryComponent,
+                                    this->ExcludeFromAll);
+  } else {
+    os << this->CreateComponentTest(this->LibraryComponent, true) << " OR "
+       << this->CreateComponentTest(this->FrameworkComponent,
+                                    this->ExcludeFromAll);
+  }
+  os << ")\n";
+
+  // Generate the script possibly with per-configuration code.
+  this->GenerateScriptConfigs(os, indent.Next());
+
+  // End this block of installation.
+  os << indent << "endif()\n\n";
+}
+
+void cmInstallGetRuntimeDependenciesGenerator::GenerateScriptForConfig(
+  std::ostream& os, const std::string& config, Indent indent)
+{
+  std::string installNameTool =
+    this->LocalGenerator->GetMakefile()->GetSafeDefinition(
+      "CMAKE_INSTALL_NAME_TOOL");
+
+  os << indent << "file(GET_RUNTIME_DEPENDENCIES\n"
+     << indent << "  RESOLVED_DEPENDENCIES_VAR " << this->DepsVar << '\n';
+  WriteFilesArgument(os, "EXECUTABLES"_s,
+                     this->RuntimeDependencySet->GetExecutables(), config,
+                     indent);
+  WriteFilesArgument(os, "LIBRARIES"_s,
+                     this->RuntimeDependencySet->GetLibraries(), config,
+                     indent);
+  WriteFilesArgument(os, "MODULES"_s, this->RuntimeDependencySet->GetModules(),
+                     config, indent);
+  if (this->RuntimeDependencySet->GetBundleExecutable()) {
+    os << indent << "  BUNDLE_EXECUTABLE \""
+       << this->RuntimeDependencySet->GetBundleExecutable()->GetItemPath(
+            config)
+       << "\"\n";
+  }
+  WriteGenexEvaluatorArgument(os, "DIRECTORIES"_s, this->Directories, config,
+                              this->LocalGenerator, indent);
+  WriteGenexEvaluatorArgument(os, "PRE_INCLUDE_REGEXES"_s,
+                              this->PreIncludeRegexes, config,
+                              this->LocalGenerator, indent);
+  WriteGenexEvaluatorArgument(os, "PRE_EXCLUDE_REGEXES"_s,
+                              this->PreExcludeRegexes, config,
+                              this->LocalGenerator, indent);
+  WriteGenexEvaluatorArgument(os, "POST_INCLUDE_REGEXES"_s,
+                              this->PostIncludeRegexes, config,
+                              this->LocalGenerator, indent);
+  WriteGenexEvaluatorArgument(os, "POST_EXCLUDE_REGEXES"_s,
+                              this->PostExcludeRegexes, config,
+                              this->LocalGenerator, indent);
+  WriteGenexEvaluatorArgument(os, "POST_INCLUDE_FILES"_s,
+                              this->PostIncludeFiles, config,
+                              this->LocalGenerator, indent);
+  WriteGenexEvaluatorArgument(os, "POST_EXCLUDE_FILES"_s,
+                              this->PostExcludeFiles, config,
+                              this->LocalGenerator, indent);
+
+  std::set<std::string> postExcludeFiles;
+  auto const addPostExclude =
+    [config, &postExcludeFiles, this](
+      const std::vector<std::unique_ptr<cmInstallRuntimeDependencySet::Item>>&
+        tgts) {
+      for (auto const& item : tgts) {
+        item->AddPostExcludeFiles(config, postExcludeFiles,
+                                  this->RuntimeDependencySet);
+      }
+    };
+  addPostExclude(this->RuntimeDependencySet->GetExecutables());
+  addPostExclude(this->RuntimeDependencySet->GetLibraries());
+  addPostExclude(this->RuntimeDependencySet->GetModules());
+  bool first = true;
+  for (auto const& file : postExcludeFiles) {
+    if (first) {
+      os << indent << "  POST_EXCLUDE_FILES_STRICT\n";
+      first = false;
+    }
+    os << indent << "    \"" << file << "\"\n";
+  }
+
+  if (!installNameTool.empty() && !this->NoInstallRPath) {
+    os << indent << "  RPATH_PREFIX " << this->RPathPrefix << '\n';
+  }
+  os << indent << "  )\n";
+}

+ 56 - 0
Source/cmInstallGetRuntimeDependenciesGenerator.h

@@ -0,0 +1,56 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <iosfwd>
+#include <string>
+#include <vector>
+
+#include "cmInstallGenerator.h"
+#include "cmListFileCache.h"
+#include "cmScriptGenerator.h"
+
+class cmLocalGenerator;
+class cmInstallRuntimeDependencySet;
+
+class cmInstallGetRuntimeDependenciesGenerator : public cmInstallGenerator
+{
+public:
+  cmInstallGetRuntimeDependenciesGenerator(
+    cmInstallRuntimeDependencySet* runtimeDependencySet,
+    std::vector<std::string> directories,
+    std::vector<std::string> preIncludeRegexes,
+    std::vector<std::string> preExcludeRegexes,
+    std::vector<std::string> postIncludeRegexes,
+    std::vector<std::string> postExcludeRegexes,
+    std::vector<std::string> postIncludeFiles,
+    std::vector<std::string> postExcludeFiles, std::string libraryComponent,
+    std::string frameworkComponent, bool noInstallRPath, const char* depsVar,
+    const char* rpathPrefix, std::vector<std::string> const& configurations,
+    MessageLevel message, bool exclude_from_all,
+    cmListFileBacktrace backtrace);
+
+  bool Compute(cmLocalGenerator* lg) override;
+
+protected:
+  void GenerateScript(std::ostream& os) override;
+
+  void GenerateScriptForConfig(std::ostream& os, const std::string& config,
+                               Indent indent) override;
+
+private:
+  cmInstallRuntimeDependencySet* RuntimeDependencySet;
+  std::vector<std::string> Directories;
+  std::vector<std::string> PreIncludeRegexes;
+  std::vector<std::string> PreExcludeRegexes;
+  std::vector<std::string> PostIncludeRegexes;
+  std::vector<std::string> PostExcludeRegexes;
+  std::vector<std::string> PostIncludeFiles;
+  std::vector<std::string> PostExcludeFiles;
+  std::string LibraryComponent;
+  std::string FrameworkComponent;
+  bool NoInstallRPath;
+  const char* DepsVar;
+  const char* RPathPrefix;
+  cmLocalGenerator* LocalGenerator = nullptr;
+};

+ 95 - 0
Source/cmInstallRuntimeDependencySet.cxx

@@ -0,0 +1,95 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmInstallRuntimeDependencySet.h"
+
+#include <set>
+#include <string>
+#include <utility>
+
+#include "cmGeneratorTarget.h"
+#include "cmGlobalGenerator.h"
+#include "cmInstallImportedRuntimeArtifactsGenerator.h"
+#include "cmInstallTargetGenerator.h"
+#include "cmStateTypes.h"
+#include "cmTargetDepend.h"
+
+cmInstallRuntimeDependencySet::cmInstallRuntimeDependencySet(std::string name)
+  : Name(std::move(name))
+{
+}
+
+void cmInstallRuntimeDependencySet::AddExecutable(
+  std::unique_ptr<Item> executable)
+{
+  this->Executables.push_back(std::move(executable));
+}
+
+void cmInstallRuntimeDependencySet::AddLibrary(std::unique_ptr<Item> library)
+{
+  this->Libraries.push_back(std::move(library));
+}
+
+void cmInstallRuntimeDependencySet::AddModule(std::unique_ptr<Item> module)
+{
+  this->Modules.push_back(std::move(module));
+}
+
+bool cmInstallRuntimeDependencySet::AddBundleExecutable(
+  std::unique_ptr<Item> bundleExecutable)
+{
+  if (this->BundleExecutable) {
+    return false;
+  }
+  this->BundleExecutable = bundleExecutable.get();
+  this->AddExecutable(std::move(bundleExecutable));
+  return true;
+}
+
+std::string cmInstallRuntimeDependencySet::TargetItem::GetItemPath(
+  const std::string& config) const
+{
+  return this->Target->GetTarget()->GetFullPath(config);
+}
+
+namespace {
+const std::set<const cmGeneratorTarget*>& GetTargetDependsClosure(
+  std::map<const cmGeneratorTarget*, std::set<const cmGeneratorTarget*>>&
+    targetDepends,
+  const cmGeneratorTarget* tgt)
+{
+  auto it = targetDepends.insert({ tgt, {} });
+  auto& retval = it.first->second;
+  if (it.second) {
+    auto const& deps = tgt->GetGlobalGenerator()->GetTargetDirectDepends(tgt);
+    for (auto const& dep : deps) {
+      if (!dep.IsCross() && dep.IsLink()) {
+        auto type = dep->GetType();
+        if (type == cmStateEnums::EXECUTABLE ||
+            type == cmStateEnums::SHARED_LIBRARY ||
+            type == cmStateEnums::MODULE_LIBRARY) {
+          retval.insert(dep);
+        }
+        auto const& depDeps = GetTargetDependsClosure(targetDepends, dep);
+        retval.insert(depDeps.begin(), depDeps.end());
+      }
+    }
+  }
+  return retval;
+}
+}
+
+void cmInstallRuntimeDependencySet::TargetItem::AddPostExcludeFiles(
+  const std::string& config, std::set<std::string>& files,
+  cmInstallRuntimeDependencySet* set) const
+{
+  for (auto const* dep : GetTargetDependsClosure(set->TargetDepends,
+                                                 this->Target->GetTarget())) {
+    files.insert(dep->GetFullPath(config));
+  }
+}
+
+std::string cmInstallRuntimeDependencySet::ImportedTargetItem::GetItemPath(
+  const std::string& config) const
+{
+  return this->Target->GetTarget()->GetFullPath(config);
+}

+ 163 - 0
Source/cmInstallRuntimeDependencySet.h

@@ -0,0 +1,163 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <cm/memory>
+#include <cm/string_view>
+#include <cmext/string_view>
+
+class cmGeneratorTarget;
+class cmInstallImportedRuntimeArtifactsGenerator;
+class cmInstallTargetGenerator;
+
+class cmInstallRuntimeDependencySet
+{
+public:
+  cmInstallRuntimeDependencySet(std::string name = "");
+
+  cmInstallRuntimeDependencySet(const cmInstallRuntimeDependencySet&) = delete;
+  cmInstallRuntimeDependencySet& operator=(
+    const cmInstallRuntimeDependencySet&) = delete;
+
+  cm::string_view GetName() const { return this->Name; }
+
+  cm::string_view GetDisplayName() const
+  {
+    if (this->Name.empty()) {
+      return "<anonymous>"_s;
+    }
+    return this->Name;
+  }
+
+  class Item
+  {
+  public:
+    virtual ~Item() = default;
+
+    virtual std::string GetItemPath(const std::string& config) const = 0;
+
+    virtual void AddPostExcludeFiles(
+      const std::string& /*config*/, std::set<std::string>& /*files*/,
+      cmInstallRuntimeDependencySet* /*set*/) const
+    {
+    }
+  };
+
+  class TargetItem : public Item
+  {
+  public:
+    TargetItem(cmInstallTargetGenerator* target)
+      : Target(target)
+    {
+    }
+
+    std::string GetItemPath(const std::string& config) const override;
+
+    void AddPostExcludeFiles(
+      const std::string& config, std::set<std::string>& files,
+      cmInstallRuntimeDependencySet* set) const override;
+
+  private:
+    cmInstallTargetGenerator* Target;
+  };
+
+  class ImportedTargetItem : public Item
+  {
+  public:
+    ImportedTargetItem(cmInstallImportedRuntimeArtifactsGenerator* target)
+      : Target(target)
+    {
+    }
+
+    std::string GetItemPath(const std::string& config) const override;
+
+  private:
+    cmInstallImportedRuntimeArtifactsGenerator* Target;
+  };
+
+  void AddExecutable(std::unique_ptr<Item> executable);
+  void AddLibrary(std::unique_ptr<Item> library);
+  void AddModule(std::unique_ptr<Item> module);
+  bool AddBundleExecutable(std::unique_ptr<Item> bundleExecutable);
+
+  void AddExecutable(cmInstallTargetGenerator* executable)
+  {
+    this->AddExecutable(cm::make_unique<TargetItem>(executable));
+  }
+
+  void AddLibrary(cmInstallTargetGenerator* library)
+  {
+    this->AddLibrary(cm::make_unique<TargetItem>(library));
+  }
+
+  void AddModule(cmInstallTargetGenerator* module)
+  {
+    this->AddModule(cm::make_unique<TargetItem>(module));
+  }
+
+  bool AddBundleExecutable(cmInstallTargetGenerator* bundleExecutable)
+  {
+    return this->AddBundleExecutable(
+      cm::make_unique<TargetItem>(bundleExecutable));
+  }
+
+  void AddExecutable(cmInstallImportedRuntimeArtifactsGenerator* executable)
+  {
+    this->AddExecutable(cm::make_unique<ImportedTargetItem>(executable));
+  }
+
+  void AddLibrary(cmInstallImportedRuntimeArtifactsGenerator* library)
+  {
+    this->AddLibrary(cm::make_unique<ImportedTargetItem>(library));
+  }
+
+  void AddModule(cmInstallImportedRuntimeArtifactsGenerator* module)
+  {
+    this->AddModule(cm::make_unique<ImportedTargetItem>(module));
+  }
+
+  bool AddBundleExecutable(
+    cmInstallImportedRuntimeArtifactsGenerator* bundleExecutable)
+  {
+    return this->AddBundleExecutable(
+      cm::make_unique<ImportedTargetItem>(bundleExecutable));
+  }
+
+  const std::vector<std::unique_ptr<Item>>& GetExecutables() const
+  {
+    return this->Executables;
+  }
+
+  const std::vector<std::unique_ptr<Item>>& GetLibraries() const
+  {
+    return this->Libraries;
+  }
+
+  const std::vector<std::unique_ptr<Item>>& GetModules() const
+  {
+    return this->Modules;
+  }
+
+  Item* GetBundleExecutable() const { return this->BundleExecutable; }
+
+  bool Empty() const
+  {
+    return this->Executables.empty() && this->Libraries.empty() &&
+      this->Modules.empty();
+  }
+
+private:
+  std::string Name;
+  std::vector<std::unique_ptr<Item>> Executables;
+  std::vector<std::unique_ptr<Item>> Libraries;
+  std::vector<std::unique_ptr<Item>> Modules;
+  Item* BundleExecutable = nullptr;
+
+  std::map<const cmGeneratorTarget*, std::set<const cmGeneratorTarget*>>
+    TargetDepends;
+};

+ 276 - 0
Source/cmInstallRuntimeDependencySetGenerator.cxx

@@ -0,0 +1,276 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmInstallRuntimeDependencySetGenerator.h"
+
+#include <ostream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "cmGeneratorExpression.h"
+#include "cmInstallGenerator.h"
+#include "cmInstallType.h"
+#include "cmListFileCache.h"
+#include "cmLocalGenerator.h"
+#include "cmMakefile.h"
+#include "cmMessageType.h"
+#include "cmOutputConverter.h"
+#include "cmScriptGenerator.h"
+#include "cmStringAlgorithms.h"
+#include "cmake.h"
+
+cmInstallRuntimeDependencySetGenerator::cmInstallRuntimeDependencySetGenerator(
+  DependencyType type, cmInstallRuntimeDependencySet* dependencySet,
+  std::vector<std::string> installRPaths, bool noInstallRPath,
+  std::string installNameDir, bool noInstallName, const char* depsVar,
+  const char* rpathPrefix, const char* tmpVarPrefix, std::string destination,
+  std::vector<std::string> const& configurations, std::string component,
+  std::string permissions, MessageLevel message, bool exclude_from_all,
+  cmListFileBacktrace backtrace)
+  : cmInstallGenerator(std::move(destination), configurations,
+                       std::move(component), message, exclude_from_all, false,
+                       std::move(backtrace))
+  , Type(type)
+  , DependencySet(dependencySet)
+  , InstallRPaths(std::move(installRPaths))
+  , NoInstallRPath(noInstallRPath)
+  , InstallNameDir(std::move(installNameDir))
+  , NoInstallName(noInstallName)
+  , Permissions(std::move(permissions))
+  , DepsVar(depsVar)
+  , RPathPrefix(rpathPrefix)
+  , TmpVarPrefix(tmpVarPrefix)
+{
+  this->ActionsPerConfig = true;
+}
+
+bool cmInstallRuntimeDependencySetGenerator::Compute(cmLocalGenerator* lg)
+{
+  this->LocalGenerator = lg;
+  return true;
+}
+
+void cmInstallRuntimeDependencySetGenerator::GenerateScriptForConfig(
+  std::ostream& os, const std::string& config, Indent indent)
+{
+  if (!this->LocalGenerator->GetMakefile()
+         ->GetSafeDefinition("CMAKE_INSTALL_NAME_TOOL")
+         .empty() &&
+      !this->NoInstallName) {
+    std::string installNameDir = "@rpath/";
+    if (!this->InstallNameDir.empty()) {
+      installNameDir = this->InstallNameDir;
+      cmGeneratorExpression::ReplaceInstallPrefix(installNameDir,
+                                                  "${CMAKE_INSTALL_PREFIX}");
+      installNameDir = cmGeneratorExpression::Evaluate(
+        installNameDir, this->LocalGenerator, config);
+      if (installNameDir.empty()) {
+        this->LocalGenerator->GetMakefile()->GetCMakeInstance()->IssueMessage(
+          MessageType::FATAL_ERROR,
+          "INSTALL_NAME_DIR argument must not evaluate to an "
+          "empty string",
+          this->Backtrace);
+        return;
+      }
+      if (installNameDir.back() != '/') {
+        installNameDir += '/';
+      }
+    }
+    os << indent << "set(" << this->TmpVarPrefix << "_install_name_dir \""
+       << installNameDir << "\")\n";
+  }
+
+  os << indent << "foreach(" << this->TmpVarPrefix << "_dep IN LISTS "
+     << this->DepsVar << ")\n";
+
+  if (!this->LocalGenerator->GetMakefile()
+         ->GetSafeDefinition("CMAKE_INSTALL_NAME_TOOL")
+         .empty()) {
+    std::vector<std::string> evaluatedRPaths;
+    for (auto const& rpath : this->InstallRPaths) {
+      std::string result =
+        cmGeneratorExpression::Evaluate(rpath, this->LocalGenerator, config);
+      if (!result.empty()) {
+        evaluatedRPaths.push_back(std::move(result));
+      }
+    }
+
+    switch (this->Type) {
+      case DependencyType::Library:
+        this->GenerateAppleLibraryScript(os, config, evaluatedRPaths,
+                                         indent.Next());
+        break;
+      case DependencyType::Framework:
+        this->GenerateAppleFrameworkScript(os, config, evaluatedRPaths,
+                                           indent.Next());
+        break;
+    }
+  } else {
+    std::string depVar = cmStrCat(this->TmpVarPrefix, "_dep");
+
+    this->AddInstallRule(
+      os, this->GetDestination(config), cmInstallType_SHARED_LIBRARY, {},
+      false, this->Permissions.c_str(), nullptr, nullptr,
+      " FOLLOW_SYMLINK_CHAIN", indent.Next(), depVar.c_str());
+
+    if (this->LocalGenerator->GetMakefile()->GetSafeDefinition(
+          "CMAKE_SYSTEM_NAME") == "Linux" &&
+        !this->NoInstallRPath) {
+      std::string evaluatedRPath;
+      for (auto const& rpath : this->InstallRPaths) {
+        std::string result =
+          cmGeneratorExpression::Evaluate(rpath, this->LocalGenerator, config);
+        if (!result.empty()) {
+          if (evaluatedRPath.empty()) {
+            evaluatedRPath = std::move(result);
+          } else {
+            evaluatedRPath += ':';
+            evaluatedRPath += result;
+          }
+        }
+      }
+
+      os << indent.Next() << "get_filename_component(" << this->TmpVarPrefix
+         << "_dep_name \"${" << this->TmpVarPrefix << "_dep}\" NAME)\n";
+      if (evaluatedRPath.empty()) {
+        os << indent.Next() << "file(RPATH_REMOVE FILE \""
+           << GetDestDirPath(
+                ConvertToAbsoluteDestination(this->GetDestination(config)))
+           << "/${" << this->TmpVarPrefix << "_dep_name}\")\n";
+      } else {
+        os << indent.Next() << "file(RPATH_SET FILE \""
+           << GetDestDirPath(
+                ConvertToAbsoluteDestination(this->GetDestination(config)))
+           << "/${" << this->TmpVarPrefix << "_dep_name}\" NEW_RPATH "
+           << cmOutputConverter::EscapeForCMake(evaluatedRPath) << ")\n";
+      }
+    }
+  }
+
+  os << indent << "endforeach()\n";
+}
+
+void cmInstallRuntimeDependencySetGenerator::GenerateAppleLibraryScript(
+  std::ostream& os, const std::string& config,
+  const std::vector<std::string>& evaluatedRPaths, Indent indent)
+{
+  os << indent << "if(NOT " << this->TmpVarPrefix
+     << "_dep MATCHES \"\\\\.framework/\")\n";
+
+  auto depName = cmStrCat(this->TmpVarPrefix, "_dep");
+  this->AddInstallRule(
+    os, this->GetDestination(config), cmInstallType_SHARED_LIBRARY, {}, false,
+    this->Permissions.c_str(), nullptr, nullptr, " FOLLOW_SYMLINK_CHAIN",
+    indent.Next(), depName.c_str());
+
+  os << indent.Next() << "get_filename_component(" << this->TmpVarPrefix
+     << "_dep_name \"${" << this->TmpVarPrefix << "_dep}\" NAME)\n";
+  auto depNameVar = cmStrCat("${", this->TmpVarPrefix, "_dep_name}");
+  this->GenerateInstallNameFixup(os, config, evaluatedRPaths,
+                                 cmStrCat("${", this->TmpVarPrefix, "_dep}"),
+                                 depNameVar, indent.Next());
+
+  os << indent << "endif()\n";
+}
+
+void cmInstallRuntimeDependencySetGenerator::GenerateAppleFrameworkScript(
+  std::ostream& os, const std::string& config,
+  const std::vector<std::string>& evaluatedRPaths, Indent indent)
+{
+  os << indent << "if(" << this->TmpVarPrefix
+     << "_dep MATCHES \"^(.*/)?([^/]*\\\\.framework)/(.*)$\")\n"
+     << indent.Next() << "set(" << this->TmpVarPrefix
+     << "_dir \"${CMAKE_MATCH_1}\")\n"
+     << indent.Next() << "set(" << this->TmpVarPrefix
+     << "_name \"${CMAKE_MATCH_2}\")\n"
+     << indent.Next() << "set(" << this->TmpVarPrefix
+     << "_file \"${CMAKE_MATCH_3}\")\n"
+     << indent.Next() << "set(" << this->TmpVarPrefix << "_path \"${"
+     << this->TmpVarPrefix << "_dir}${" << this->TmpVarPrefix << "_name}\")\n";
+
+  auto depName = cmStrCat(this->TmpVarPrefix, "_path");
+  this->AddInstallRule(
+    os, this->GetDestination(config), cmInstallType_DIRECTORY, {}, false,
+    this->Permissions.c_str(), nullptr, nullptr, " USE_SOURCE_PERMISSIONS",
+    indent.Next(), depName.c_str());
+
+  auto depNameVar = cmStrCat("${", this->TmpVarPrefix, "_name}/${",
+                             this->TmpVarPrefix, "_file}");
+  this->GenerateInstallNameFixup(os, config, evaluatedRPaths,
+                                 cmStrCat("${", this->TmpVarPrefix, "_dep}"),
+                                 depNameVar, indent.Next());
+
+  os << indent << "endif()\n";
+}
+
+void cmInstallRuntimeDependencySetGenerator::GenerateInstallNameFixup(
+  std::ostream& os, const std::string& config,
+  const std::vector<std::string>& evaluatedRPaths, const std::string& filename,
+  const std::string& depName, Indent indent)
+{
+  if (!(this->NoInstallRPath && this->NoInstallName)) {
+    auto indent2 = indent;
+    if (evaluatedRPaths.empty() && this->NoInstallName) {
+      indent2 = indent2.Next();
+      os << indent << "if(" << this->RPathPrefix << "_" << filename << ")\n";
+    }
+    os << indent2 << "set(" << this->TmpVarPrefix << "_rpath_args)\n";
+    if (!this->NoInstallRPath) {
+      os << indent2 << "foreach(" << this->TmpVarPrefix << "_rpath IN LISTS "
+         << this->RPathPrefix << '_' << filename << ")\n"
+         << indent2.Next() << "list(APPEND " << this->TmpVarPrefix
+         << "_rpath_args -delete_rpath \"${" << this->TmpVarPrefix
+         << "_rpath}\")\n"
+         << indent2 << "endforeach()\n";
+    }
+    os << indent2 << "execute_process(COMMAND \""
+       << this->LocalGenerator->GetMakefile()->GetSafeDefinition(
+            "CMAKE_INSTALL_NAME_TOOL")
+       << "\" ${" << this->TmpVarPrefix << "_rpath_args}\n";
+    if (!this->NoInstallRPath) {
+      for (auto const& rpath : evaluatedRPaths) {
+        os << indent2 << "  -add_rpath "
+           << cmOutputConverter::EscapeForCMake(rpath) << "\n";
+      }
+    }
+    if (!this->NoInstallName) {
+      os << indent2 << "  -id \"${" << this->TmpVarPrefix
+         << "_install_name_dir}" << depName << "\"\n";
+    }
+    os << indent2 << "  \""
+       << GetDestDirPath(
+            ConvertToAbsoluteDestination(this->GetDestination(config)))
+       << "/" << depName << "\")\n";
+    if (evaluatedRPaths.empty() && this->NoInstallName) {
+      os << indent << "endif()\n";
+    }
+  }
+}
+
+void cmInstallRuntimeDependencySetGenerator::GenerateStripFixup(
+  std::ostream& os, const std::string& config, const std::string& depName,
+  Indent indent)
+{
+  std::string strip =
+    this->LocalGenerator->GetMakefile()->GetSafeDefinition("CMAKE_STRIP");
+  if (!strip.empty()) {
+    os << indent << "if(CMAKE_INSTALL_DO_STRIP)\n"
+       << indent.Next() << "execute_process(COMMAND \"" << strip << "\" ";
+    if (this->LocalGenerator->GetMakefile()->GetSafeDefinition(
+          "CMAKE_HOST_SYSTEM_NAME") == "Darwin") {
+      os << "-x ";
+    }
+    os << "\""
+       << GetDestDirPath(
+            ConvertToAbsoluteDestination(this->GetDestination(config)))
+       << "/" << depName << "\")\n"
+       << indent << "endif()\n";
+  }
+}
+
+std::string cmInstallRuntimeDependencySetGenerator::GetDestination(
+  std::string const& config) const
+{
+  return cmGeneratorExpression::Evaluate(this->Destination,
+                                         this->LocalGenerator, config);
+}

+ 74 - 0
Source/cmInstallRuntimeDependencySetGenerator.h

@@ -0,0 +1,74 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <iosfwd>
+#include <string>
+#include <vector>
+
+#include "cmInstallGenerator.h"
+#include "cmListFileCache.h"
+#include "cmScriptGenerator.h"
+
+class cmInstallRuntimeDependencySet;
+class cmLocalGenerator;
+
+class cmInstallRuntimeDependencySetGenerator : public cmInstallGenerator
+{
+public:
+  enum class DependencyType
+  {
+    Library,
+    Framework,
+  };
+
+  cmInstallRuntimeDependencySetGenerator(
+    DependencyType type, cmInstallRuntimeDependencySet* dependencySet,
+    std::vector<std::string> installRPaths, bool noInstallRPath,
+    std::string installNameDir, bool noInstallName, const char* depsVar,
+    const char* rpathPrefix, const char* tmpVarPrefix, std::string destination,
+    std::vector<std::string> const& configurations, std::string component,
+    std::string permissions, MessageLevel message, bool exclude_from_all,
+    cmListFileBacktrace backtrace);
+
+  bool Compute(cmLocalGenerator* lg) override;
+
+  DependencyType GetDependencyType() const { return this->Type; }
+
+  cmInstallRuntimeDependencySet* GetRuntimeDependencySet() const
+  {
+    return this->DependencySet;
+  }
+
+  std::string GetDestination(std::string const& config) const;
+
+protected:
+  void GenerateScriptForConfig(std::ostream& os, const std::string& config,
+                               Indent indent) override;
+
+private:
+  DependencyType Type;
+  cmInstallRuntimeDependencySet* DependencySet;
+  std::vector<std::string> InstallRPaths;
+  bool NoInstallRPath;
+  std::string InstallNameDir;
+  bool NoInstallName;
+  std::string Permissions;
+  const char* DepsVar;
+  const char* RPathPrefix;
+  const char* TmpVarPrefix;
+  cmLocalGenerator* LocalGenerator = nullptr;
+
+  void GenerateAppleLibraryScript(
+    std::ostream& os, const std::string& config,
+    const std::vector<std::string>& evaluatedRPaths, Indent indent);
+  void GenerateAppleFrameworkScript(
+    std::ostream& os, const std::string& config,
+    const std::vector<std::string>& evaluatedRPaths, Indent indent);
+  void GenerateInstallNameFixup(
+    std::ostream& os, const std::string& config,
+    const std::vector<std::string>& evaluatedRPaths,
+    const std::string& filename, const std::string& depName, Indent indent);
+  void GenerateStripFixup(std::ostream& os, const std::string& config,
+                          const std::string& depName, Indent indent);
+};

+ 28 - 11
Source/cmRuntimeDependencyArchive.cxx

@@ -198,25 +198,26 @@ void cmRuntimeDependencyArchive::SetError(const std::string& e)
   this->Status.SetError(e);
 }
 
-std::string cmRuntimeDependencyArchive::GetBundleExecutable()
+const std::string& cmRuntimeDependencyArchive::GetBundleExecutable() const
 {
   return this->BundleExecutable;
 }
 
 const std::vector<std::string>&
-cmRuntimeDependencyArchive::GetSearchDirectories()
+cmRuntimeDependencyArchive::GetSearchDirectories() const
 {
   return this->SearchDirectories;
 }
 
-std::string cmRuntimeDependencyArchive::GetGetRuntimeDependenciesTool()
+const std::string& cmRuntimeDependencyArchive::GetGetRuntimeDependenciesTool()
+  const
 {
   return this->GetMakefile()->GetSafeDefinition(
     "CMAKE_GET_RUNTIME_DEPENDENCIES_TOOL");
 }
 
 bool cmRuntimeDependencyArchive::GetGetRuntimeDependenciesCommand(
-  const std::string& search, std::vector<std::string>& command)
+  const std::string& search, std::vector<std::string>& command) const
 {
   // First see if it was supplied by the user
   std::string toolCommand = this->GetMakefile()->GetSafeDefinition(
@@ -309,7 +310,7 @@ bool cmRuntimeDependencyArchive::GetGetRuntimeDependenciesCommand(
   return false;
 }
 
-bool cmRuntimeDependencyArchive::IsPreExcluded(const std::string& name)
+bool cmRuntimeDependencyArchive::IsPreExcluded(const std::string& name) const
 {
   cmsys::RegularExpressionMatch match;
   auto const regexMatch =
@@ -326,7 +327,7 @@ bool cmRuntimeDependencyArchive::IsPreExcluded(const std::string& name)
     regexSearch(this->PreExcludeRegexes);
 }
 
-bool cmRuntimeDependencyArchive::IsPostExcluded(const std::string& name)
+bool cmRuntimeDependencyArchive::IsPostExcluded(const std::string& name) const
 {
   cmsys::RegularExpressionMatch match;
   auto const regexMatch =
@@ -353,9 +354,9 @@ bool cmRuntimeDependencyArchive::IsPostExcluded(const std::string& name)
       fileSearch(this->PostExcludeFiles)));
 }
 
-void cmRuntimeDependencyArchive::AddResolvedPath(const std::string& name,
-                                                 const std::string& path,
-                                                 bool& unique)
+void cmRuntimeDependencyArchive::AddResolvedPath(
+  const std::string& name, const std::string& path, bool& unique,
+  std::vector<std::string> rpaths)
 {
   auto it = this->ResolvedPaths.emplace(name, std::set<std::string>{}).first;
   unique = true;
@@ -366,6 +367,7 @@ void cmRuntimeDependencyArchive::AddResolvedPath(const std::string& name,
     }
   }
   it->second.insert(path);
+  this->RPaths[path] = std::move(rpaths);
 }
 
 void cmRuntimeDependencyArchive::AddUnresolvedPath(const std::string& name)
@@ -373,18 +375,33 @@ void cmRuntimeDependencyArchive::AddUnresolvedPath(const std::string& name)
   this->UnresolvedPaths.insert(name);
 }
 
-cmMakefile* cmRuntimeDependencyArchive::GetMakefile()
+cmMakefile* cmRuntimeDependencyArchive::GetMakefile() const
 {
   return &this->Status.GetMakefile();
 }
 
 const std::map<std::string, std::set<std::string>>&
-cmRuntimeDependencyArchive::GetResolvedPaths()
+cmRuntimeDependencyArchive::GetResolvedPaths() const
 {
   return this->ResolvedPaths;
 }
 
 const std::set<std::string>& cmRuntimeDependencyArchive::GetUnresolvedPaths()
+  const
 {
   return this->UnresolvedPaths;
 }
+
+const std::map<std::string, std::vector<std::string>>&
+cmRuntimeDependencyArchive::GetRPaths() const
+{
+  return this->RPaths;
+}
+
+bool cmRuntimeDependencyArchive::PlatformSupportsRuntimeDependencies(
+  const std::string& platform)
+{
+  static const std::set<std::string> supportedPlatforms = { "Windows", "Linux",
+                                                            "Darwin" };
+  return supportedPlatforms.count(platform);
+}

+ 15 - 11
Source/cmRuntimeDependencyArchive.h

@@ -36,21 +36,24 @@ public:
 
   void SetError(const std::string& e);
 
-  std::string GetBundleExecutable();
-  const std::vector<std::string>& GetSearchDirectories();
-  std::string GetGetRuntimeDependenciesTool();
-  bool GetGetRuntimeDependenciesCommand(const std::string& search,
-                                        std::vector<std::string>& command);
-  bool IsPreExcluded(const std::string& name);
-  bool IsPostExcluded(const std::string& name);
+  const std::string& GetBundleExecutable() const;
+  const std::vector<std::string>& GetSearchDirectories() const;
+  const std::string& GetGetRuntimeDependenciesTool() const;
+  bool GetGetRuntimeDependenciesCommand(
+    const std::string& search, std::vector<std::string>& command) const;
+  bool IsPreExcluded(const std::string& name) const;
+  bool IsPostExcluded(const std::string& name) const;
 
   void AddResolvedPath(const std::string& name, const std::string& path,
-                       bool& unique);
+                       bool& unique, std::vector<std::string> rpaths = {});
   void AddUnresolvedPath(const std::string& name);
 
-  cmMakefile* GetMakefile();
-  const std::map<std::string, std::set<std::string>>& GetResolvedPaths();
-  const std::set<std::string>& GetUnresolvedPaths();
+  cmMakefile* GetMakefile() const;
+  const std::map<std::string, std::set<std::string>>& GetResolvedPaths() const;
+  const std::set<std::string>& GetUnresolvedPaths() const;
+  const std::map<std::string, std::vector<std::string>>& GetRPaths() const;
+
+  static bool PlatformSupportsRuntimeDependencies(const std::string& platform);
 
 private:
   cmExecutionStatus& Status;
@@ -70,4 +73,5 @@ private:
   std::vector<std::string> PostExcludeFilesStrict;
   std::map<std::string, std::set<std::string>> ResolvedPaths;
   std::set<std::string> UnresolvedPaths;
+  std::map<std::string, std::vector<std::string>> RPaths;
 };

+ 140 - 70
Source/cmSystemTools.cxx

@@ -14,6 +14,7 @@
 
 #include "cmSystemTools.h"
 
+#include <cm/optional>
 #include <cmext/algorithm>
 
 #include <cm3p/uv.h>
@@ -65,6 +66,7 @@
 #include <cstdlib>
 #include <cstring>
 #include <ctime>
+#include <functional>
 #include <iostream>
 #include <sstream>
 #include <utility>
@@ -2524,6 +2526,7 @@ std::string::size_type cmSystemToolsFindRPath(cm::string_view const& have,
 #endif
 
 #if defined(CMake_USE_ELF_PARSER)
+namespace {
 struct cmSystemToolsRPathInfo
 {
   unsigned long Position;
@@ -2531,15 +2534,15 @@ struct cmSystemToolsRPathInfo
   std::string Name;
   std::string Value;
 };
-#endif
+
+using EmptyCallback = std::function<bool(std::string*, const cmELF&)>;
+using AdjustCallback = std::function<bool(
+  cm::optional<std::string>&, const std::string&, const char*, std::string*)>;
 
 // FIXME: Dispatch if multiple formats are supported.
-#if defined(CMake_USE_ELF_PARSER)
-bool cmSystemTools::ChangeRPath(std::string const& file,
-                                std::string const& oldRPath,
-                                std::string const& newRPath,
-                                bool removeEnvironmentRPath, std::string* emsg,
-                                bool* changed)
+bool AdjustRPath(std::string const& file, const EmptyCallback& emptyCallback,
+                 const AdjustCallback& adjustCallback, std::string* emsg,
+                 bool* changed)
 {
   if (changed) {
     *changed = false;
@@ -2566,17 +2569,7 @@ bool cmSystemTools::ChangeRPath(std::string const& file,
       ++se_count;
     }
     if (se_count == 0) {
-      if (newRPath.empty()) {
-        // The new rpath is empty and there is no rpath anyway so it is
-        // okay.
-        return true;
-      }
-      if (emsg) {
-        *emsg =
-          cmStrCat("No valid ELF RPATH or RUNPATH entry exists in the file; ",
-                   elf.GetErrorMessage());
-      }
-      return false;
+      return emptyCallback(emsg, elf);
     }
 
     for (int i = 0; i < se_count; ++i) {
@@ -2586,68 +2579,38 @@ bool cmSystemTools::ChangeRPath(std::string const& file,
         continue;
       }
 
-      // Make sure the current rpath contains the old rpath.
-      std::string::size_type pos =
-        cmSystemToolsFindRPath(se[i]->Value, oldRPath);
-      if (pos == std::string::npos) {
-        // If it contains the new rpath instead then it is okay.
-        if (cmSystemToolsFindRPath(se[i]->Value, newRPath) !=
-            std::string::npos) {
-          remove_rpath = false;
-          continue;
-        }
-        if (emsg) {
-          std::ostringstream e;
-          /* clang-format off */
-        e << "The current " << se_name[i] << " is:\n"
-          << "  " << se[i]->Value << "\n"
-          << "which does not contain:\n"
-          << "  " << oldRPath << "\n"
-          << "as was expected.";
-          /* clang-format on */
-          *emsg = e.str();
-        }
-        return false;
-      }
-
       // Store information about the entry in the file.
       rp[rp_count].Position = se[i]->Position;
       rp[rp_count].Size = se[i]->Size;
       rp[rp_count].Name = se_name[i];
 
-      std::string::size_type prefix_len = pos;
-
-      // If oldRPath was at the end of the file's RPath, and newRPath is empty,
-      // we should remove the unnecessary ':' at the end.
-      if (newRPath.empty() && pos > 0 && se[i]->Value[pos - 1] == ':' &&
-          pos + oldRPath.length() == se[i]->Value.length()) {
-        prefix_len--;
-      }
-
-      // Construct the new value which preserves the part of the path
-      // not being changed.
-      if (!removeEnvironmentRPath) {
-        rp[rp_count].Value = se[i]->Value.substr(0, prefix_len);
+      // Adjust the rpath.
+      cm::optional<std::string> outRPath;
+      if (!adjustCallback(outRPath, se[i]->Value, se_name[i], emsg)) {
+        return false;
       }
-      rp[rp_count].Value += newRPath;
-      rp[rp_count].Value += se[i]->Value.substr(pos + oldRPath.length());
 
-      if (!rp[rp_count].Value.empty()) {
-        remove_rpath = false;
-      }
+      if (outRPath) {
+        if (!outRPath->empty()) {
+          remove_rpath = false;
+        }
 
-      // Make sure there is enough room to store the new rpath and at
-      // least one null terminator.
-      if (rp[rp_count].Size < rp[rp_count].Value.length() + 1) {
-        if (emsg) {
-          *emsg = cmStrCat("The replacement path is too long for the ",
-                           se_name[i], " entry.");
+        // Make sure there is enough room to store the new rpath and at
+        // least one null terminator.
+        if (rp[rp_count].Size < outRPath->length() + 1) {
+          if (emsg) {
+            *emsg = cmStrCat("The replacement path is too long for the ",
+                             se_name[i], " entry.");
+          }
+          return false;
         }
-        return false;
-      }
 
-      // This entry is ready for update.
-      ++rp_count;
+        // This entry is ready for update.
+        rp[rp_count].Value = std::move(*outRPath);
+        ++rp_count;
+      } else {
+        remove_rpath = false;
+      }
     }
   }
 
@@ -2706,6 +2669,99 @@ bool cmSystemTools::ChangeRPath(std::string const& file,
   }
   return true;
 }
+
+std::function<bool(std::string*, const cmELF&)> MakeEmptyCallback(
+  const std::string& newRPath)
+{
+  return [newRPath](std::string* emsg, const cmELF& elf) -> bool {
+    if (newRPath.empty()) {
+      // The new rpath is empty and there is no rpath anyway so it is
+      // okay.
+      return true;
+    }
+    if (emsg) {
+      *emsg =
+        cmStrCat("No valid ELF RPATH or RUNPATH entry exists in the file; ",
+                 elf.GetErrorMessage());
+    }
+    return false;
+  };
+};
+}
+
+bool cmSystemTools::ChangeRPath(std::string const& file,
+                                std::string const& oldRPath,
+                                std::string const& newRPath,
+                                bool removeEnvironmentRPath, std::string* emsg,
+                                bool* changed)
+{
+  auto adjustCallback = [oldRPath, newRPath, removeEnvironmentRPath](
+                          cm::optional<std::string>& outRPath,
+                          const std::string& inRPath, const char* se_name,
+                          std::string* emsg2) -> bool {
+    // Make sure the current rpath contains the old rpath.
+    std::string::size_type pos = cmSystemToolsFindRPath(inRPath, oldRPath);
+    if (pos == std::string::npos) {
+      // If it contains the new rpath instead then it is okay.
+      if (cmSystemToolsFindRPath(inRPath, newRPath) != std::string::npos) {
+        return true;
+      }
+      if (emsg2) {
+        std::ostringstream e;
+        /* clang-format off */
+        e << "The current " << se_name << " is:\n"
+          << "  " << inRPath << "\n"
+          << "which does not contain:\n"
+          << "  " << oldRPath << "\n"
+          << "as was expected.";
+        /* clang-format on */
+        *emsg2 = e.str();
+      }
+      return false;
+    }
+
+    std::string::size_type prefix_len = pos;
+
+    // If oldRPath was at the end of the file's RPath, and newRPath is empty,
+    // we should remove the unnecessary ':' at the end.
+    if (newRPath.empty() && pos > 0 && inRPath[pos - 1] == ':' &&
+        pos + oldRPath.length() == inRPath.length()) {
+      prefix_len--;
+    }
+
+    // Construct the new value which preserves the part of the path
+    // not being changed.
+    outRPath.emplace();
+    if (!removeEnvironmentRPath) {
+      *outRPath += inRPath.substr(0, prefix_len);
+    }
+    *outRPath += newRPath;
+    *outRPath += inRPath.substr(pos + oldRPath.length());
+
+    return true;
+  };
+
+  return AdjustRPath(file, MakeEmptyCallback(newRPath), adjustCallback, emsg,
+                     changed);
+}
+
+bool cmSystemTools::SetRPath(std::string const& file,
+                             std::string const& newRPath, std::string* emsg,
+                             bool* changed)
+{
+  auto adjustCallback = [newRPath](cm::optional<std::string>& outRPath,
+                                   const std::string& inRPath,
+                                   const char* /*se_name*/, std::string *
+                                   /*emsg*/) -> bool {
+    if (inRPath != newRPath) {
+      outRPath = newRPath;
+    }
+    return true;
+  };
+
+  return AdjustRPath(file, MakeEmptyCallback(newRPath), adjustCallback, emsg,
+                     changed);
+}
 #elif defined(CMake_USE_XCOFF_PARSER)
 bool cmSystemTools::ChangeRPath(std::string const& file,
                                 std::string const& oldRPath,
@@ -2775,6 +2831,13 @@ bool cmSystemTools::ChangeRPath(std::string const& file,
   }
   return true;
 }
+
+bool cmSystemTools::SetRPath(std::string const& /*file*/,
+                             std::string const& /*newRPath*/,
+                             std::string* /*emsg*/, bool* /*changed*/)
+{
+  return false;
+}
 #else
 bool cmSystemTools::ChangeRPath(std::string const& /*file*/,
                                 std::string const& /*oldRPath*/,
@@ -2784,6 +2847,13 @@ bool cmSystemTools::ChangeRPath(std::string const& /*file*/,
 {
   return false;
 }
+
+bool cmSystemTools::SetRPath(std::string const& /*file*/,
+                             std::string const& /*newRPath*/,
+                             std::string* /*emsg*/, bool* /*changed*/)
+{
+  return false;
+}
 #endif
 
 bool cmSystemTools::VersionCompare(cmSystemTools::CompareOp op,

+ 5 - 1
Source/cmSystemTools.h

@@ -456,13 +456,17 @@ public:
   static bool GuessLibraryInstallName(std::string const& fullPath,
                                       std::string& soname);
 
-  /** Try to set the RPATH in an ELF binary.  */
+  /** Try to change the RPATH in an ELF binary.  */
   static bool ChangeRPath(std::string const& file, std::string const& oldRPath,
                           std::string const& newRPath,
                           bool removeEnvironmentRPath,
                           std::string* emsg = nullptr,
                           bool* changed = nullptr);
 
+  /** Try to set the RPATH in an ELF binary.  */
+  static bool SetRPath(std::string const& file, std::string const& newRPath,
+                       std::string* emsg = nullptr, bool* changed = nullptr);
+
   /** Try to remove the RPATH from an ELF binary.  */
   static bool RemoveRPath(std::string const& file, std::string* emsg = nullptr,
                           bool* removed = nullptr);

+ 1 - 0
Tests/CMakeLists.txt

@@ -478,6 +478,7 @@ if(BUILD_TESTING)
   ADD_TEST_MACRO(Preprocess Preprocess)
   set(ExportImport_BUILD_OPTIONS -DCMake_TEST_NESTED_MAKE_PROGRAM:FILEPATH=${CMake_TEST_EXPLICIT_MAKE_PROGRAM}
                                  -DCMake_TEST_CUDA:BOOL=${CMake_TEST_CUDA}
+                                 -DCMake_INSTALL_NAME_TOOL_BUG:BOOL=${CMake_INSTALL_NAME_TOOL_BUG}
                                  )
   ADD_TEST_MACRO(ExportImport ExportImport)
   set_property(TEST ExportImport APPEND

+ 22 - 14
Tests/ExportImport/CMakeLists.txt

@@ -77,21 +77,29 @@ set_property(
   PROPERTY SYMBOLIC 1
   )
 
-# Install the imported targets.
-add_custom_command(
-  OUTPUT ${ExportImport_BINARY_DIR}/ImportInstall
-  COMMAND ${CMAKE_COMMAND} -E rm -rf ${ExportImport_BINARY_DIR}/Import/install
-  COMMAND ${CMAKE_COMMAND}
-    --install ${ExportImport_BINARY_DIR}/Import
-    --prefix ${ExportImport_BINARY_DIR}/Import/install
-    ${NESTED_CONFIG_INSTALL_TYPE}
-  )
-add_custom_target(ImportInstallTarget ALL DEPENDS ${ExportImport_BINARY_DIR}/ImportInstall)
+# Run the install tests.
+set(install_deps)
+set(rdep_tests)
+if(CMAKE_SYSTEM_NAME MATCHES "^(Linux|Windows|Darwin)$" AND NOT CMake_INSTALL_NAME_TOOL_BUG)
+  set(rdep_tests RUNTIME_DEPENDENCIES RUNTIME_DEPENDENCY_SET)
+endif()
+foreach(name IMPORTED_RUNTIME_ARTIFACTS ${rdep_tests})
+  add_custom_command(
+    OUTPUT ${ExportImport_BINARY_DIR}/ImportInstall-${name}
+    COMMAND ${CMAKE_COMMAND} -E rm -rf ${ExportImport_BINARY_DIR}/Import/install-${name}/install
+    COMMAND ${CMAKE_COMMAND}
+      --install ${ExportImport_BINARY_DIR}/Import/install-${name}
+      --prefix ${ExportImport_BINARY_DIR}/Import/install-${name}/install
+      ${NESTED_CONFIG_INSTALL_TYPE}
+    )
+  list(APPEND install_deps ${ExportImport_BINARY_DIR}/ImportInstall-${name})
+  set_property(
+    SOURCE ${ExportImport_BINARY_DIR}/ImportInstall-${name}
+    PROPERTY SYMBOLIC 1
+    )
+endforeach()
+add_custom_target(ImportInstallTarget ALL DEPENDS ${install_deps})
 add_dependencies(ImportInstallTarget ImportTarget)
-set_property(
-  SOURCE ${ExportImport_BINARY_DIR}/ImportInstall
-  PROPERTY SYMBOLIC 1
-  )
 
 add_executable(ExportImport main.c)
 add_dependencies(ExportImport ImportTarget)

+ 2 - 0
Tests/ExportImport/Export/CMakeLists.txt

@@ -690,3 +690,5 @@ if(NOT XCODE)
   install(TARGETS testLibFromGeneratedSource EXPORT testLibFromGeneratedSource_Export)
   install(EXPORT testLibFromGeneratedSource_Export DESTINATION lib)
 endif()
+
+add_subdirectory(install-RUNTIME_DEPENDENCY_SET)

+ 42 - 0
Tests/ExportImport/Export/install-RUNTIME_DEPENDENCY_SET/CMakeLists.txt

@@ -0,0 +1,42 @@
+cmake_minimum_required(VERSION 3.20)
+
+set(CMAKE_SKIP_RPATH OFF)
+
+foreach(i 1 2 3 4 5 6 7 8 9 10 11 12)
+  file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/dep${i}.c"
+"#ifdef _WIN32
+__declspec(dllexport)
+#endif
+  void dep${i}(void)
+{
+}
+")
+  add_library(dep${i} SHARED "${CMAKE_CURRENT_BINARY_DIR}/dep${i}.c")
+endforeach()
+
+set_target_properties(dep9 PROPERTIES
+  FRAMEWORK TRUE
+  )
+set_target_properties(dep2 PROPERTIES
+  VERSION 1.2.3
+  SOVERSION 1
+  )
+
+add_library(deplib SHARED deplib.c)
+target_link_libraries(deplib PRIVATE dep1)
+if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+  set_target_properties(deplib PROPERTIES
+    INSTALL_RPATH "@loader_path/"
+    )
+endif()
+
+install(TARGETS dep1 dep2 dep3 dep4 dep5 dep6 dep7 dep8 dep9 dep10 dep11 dep12 deplib EXPORT install-RUNTIME_DEPENDENCY_SET
+  RUNTIME DESTINATION install-RUNTIME_DEPENDENCY_SET/bin
+  LIBRARY DESTINATION install-RUNTIME_DEPENDENCY_SET/lib
+  ARCHIVE DESTINATION install-RUNTIME_DEPENDENCY_SET/lib
+  FRAMEWORK DESTINATION install-RUNTIME_DEPENDENCY_SET/frameworks
+  )
+install(EXPORT install-RUNTIME_DEPENDENCY_SET
+  FILE targets.cmake
+  DESTINATION install-RUNTIME_DEPENDENCY_SET
+  )

+ 12 - 0
Tests/ExportImport/Export/install-RUNTIME_DEPENDENCY_SET/deplib.c

@@ -0,0 +1,12 @@
+#ifdef _WIN32
+__declspec(dllimport)
+#endif
+  extern void dep1(void);
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+  void deplib(void)
+{
+  dep1();
+}

+ 6 - 0
Tests/ExportImport/Import/CMakeLists.txt

@@ -29,3 +29,9 @@ add_subdirectory(version_range)
 
 # Test install(IMPORTED_RUNTIME_ARTIFACTS)
 add_subdirectory(install-IMPORTED_RUNTIME_ARTIFACTS)
+
+# Test install(RUNTIME_DEPENDENCIES) and install(RUNTIME_DEPENDENCY_SET)
+if(CMAKE_SYSTEM_NAME MATCHES "^(Linux|Windows|Darwin)$")
+  add_subdirectory(install-RUNTIME_DEPENDENCIES)
+  add_subdirectory(install-RUNTIME_DEPENDENCY_SET)
+endif()

+ 20 - 0
Tests/ExportImport/Import/check_installed.cmake

@@ -0,0 +1,20 @@
+cmake_minimum_required(VERSION 3.20)
+
+function(check_installed expect)
+  file(GLOB_RECURSE actual
+    LIST_DIRECTORIES TRUE
+    RELATIVE ${CMAKE_INSTALL_PREFIX}
+    ${CMAKE_INSTALL_PREFIX}/*
+    )
+  if(actual)
+    list(SORT actual)
+  endif()
+  if(NOT "${actual}" MATCHES "${expect}")
+    message(FATAL_ERROR "Installed files:
+  ${actual}
+do not match what we expected:
+  ${expect}
+in directory:
+  ${CMAKE_INSTALL_PREFIX}")
+  endif()
+endfunction()

+ 1 - 20
Tests/ExportImport/Import/install-IMPORTED_RUNTIME_ARTIFACTS/check_installed.cmake

@@ -1,23 +1,4 @@
-cmake_minimum_required(VERSION 3.20)
-
-function(check_installed expect)
-  file(GLOB_RECURSE actual
-    LIST_DIRECTORIES TRUE
-    RELATIVE ${CMAKE_INSTALL_PREFIX}
-    ${CMAKE_INSTALL_PREFIX}/*
-    )
-  if(actual)
-    list(SORT actual)
-  endif()
-  if(NOT "${actual}" MATCHES "${expect}")
-    message(FATAL_ERROR "Installed files:
-  ${actual}
-do not match what we expected:
-  ${expect}
-in directory:
-  ${CMAKE_INSTALL_PREFIX}")
-  endif()
-endfunction()
+include("${CMAKE_CURRENT_LIST_DIR}/../check_installed.cmake")
 
 if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
   set(_dirs [[aaa;aaa/executables;aaa/executables/testExe1-4;aaa/executables/testExe3;aaa/libraries;aaa/libraries/libtestExe2lib\.so;aaa/libraries/libtestLib3lib(-d|-r)?\.so\.1\.2;aaa/libraries/libtestLib3lib(-d|-r)?\.so\.3;aaa/libraries/libtestLib4\.so;aaa/libraries/libtestMod1\.so;aaa/libraries/libtestMod2\.so]])

+ 74 - 0
Tests/ExportImport/Import/install-RUNTIME_DEPENDENCIES/CMakeLists.txt

@@ -0,0 +1,74 @@
+set(CMAKE_SKIP_RPATH OFF)
+
+# Import targets from the install tree.
+include(${Import_BINARY_DIR}/../Root/install-RUNTIME_DEPENDENCY_SET/targets.cmake)
+
+add_executable(exe1 main.c)
+add_executable(exe2 main.c)
+
+if(NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+  set_target_properties(exe1 exe2 PROPERTIES
+    # Multiple MACOSX_BUNDLE executables are allowed on non-macOS platforms.
+    MACOSX_BUNDLE TRUE
+    )
+endif()
+
+add_library(sublib1 SHARED sublib1.c)
+target_link_libraries(sublib1 PRIVATE dep6)
+
+add_library(sublib2 SHARED sublib2.c)
+target_link_libraries(sublib2 PRIVATE dep7)
+
+foreach(i exe1 exe2)
+  target_link_libraries(${i} PRIVATE
+    dep1
+    dep2
+    dep3
+    dep4
+    dep5
+    dep10
+    dep11
+    dep12
+    sublib1
+    sublib2
+    )
+endforeach()
+
+add_library(lib SHARED lib.c)
+target_link_libraries(lib PRIVATE dep8)
+
+add_library(mod MODULE mod.c)
+target_link_libraries(mod PRIVATE dep9)
+if(NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+  set_target_properties(mod PROPERTIES
+    SKIP_BUILD_RPATH TRUE
+    )
+endif()
+
+set(_framework_args)
+if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+  set(_framework_args FRAMEWORK DESTINATION subdir/frameworks)
+endif()
+install(TARGETS exe1 exe2 lib mod sublib1
+  RUNTIME_DEPENDENCIES
+    PRE_INCLUDE_REGEXES "$<1:dep([2-9]|1[012])>"
+    PRE_EXCLUDE_REGEXES "$<1:.*>"
+    POST_INCLUDE_REGEXES "$<1:(bin|lib)/(lib)?dep3>"
+    POST_EXCLUDE_REGEXES "$<1:(bin|lib)/(lib)?dep[34]>"
+    POST_INCLUDE_FILES "$<TARGET_FILE:dep10>" "$<TARGET_FILE:dep11>"
+    POST_EXCLUDE_FILES "$<TARGET_FILE:dep11>" "$<TARGET_FILE:dep12>"
+    DIRECTORIES "$<TARGET_FILE_DIR:dep9>"
+  RUNTIME DESTINATION "$<1:subdir/bin>"
+  LIBRARY DESTINATION "$<1:subdir/lib>"
+  ${_framework_args}
+  )
+
+install(TARGETS lib
+  RUNTIME_DEPENDENCIES
+    PRE_INCLUDE_REGEXES dep8
+    PRE_EXCLUDE_REGEXES ".*"
+    DIRECTORIES "$<TARGET_FILE_DIR:dep8>"
+  ${_framework_args}
+  )
+
+install(SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/check_installed.cmake")

+ 11 - 0
Tests/ExportImport/Import/install-RUNTIME_DEPENDENCIES/check_installed.cmake

@@ -0,0 +1,11 @@
+include("${CMAKE_CURRENT_LIST_DIR}/../check_installed.cmake")
+
+if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
+  check_installed([[^lib;lib/libdep8\.so;lib/liblib\.so;subdir;subdir/bin;subdir/bin/exe1;subdir/bin/exe2;subdir/lib;subdir/lib/libdep10\.so;subdir/lib/libdep11\.so;subdir/lib/libdep2\.so\.1;subdir/lib/libdep2\.so\.1\.2\.3;subdir/lib/libdep3\.so;subdir/lib/libdep5\.so;subdir/lib/libdep6\.so;subdir/lib/libdep8\.so;subdir/lib/libdep9\.so;subdir/lib/liblib\.so;subdir/lib/libmod\.so;subdir/lib/libsublib1\.so$]])
+elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
+  set(_msvc_check [[bin;bin/dep8\.dll;bin/lib\.dll;lib;lib/lib\.lib;subdir;subdir/bin;subdir/bin/dep10\.dll;subdir/bin/dep11\.dll;subdir/bin/dep2\.dll;subdir/bin/dep3\.dll;subdir/bin/dep5\.dll;subdir/bin/dep6\.dll;subdir/bin/dep8\.dll;subdir/bin/dep9\.dll;subdir/bin/exe1\.exe;subdir/bin/exe2\.exe;subdir/bin/lib\.dll;subdir/bin/sublib1\.dll;subdir/lib;subdir/lib/mod\.dll]])
+  set(_mingw_check [[bin;bin/libdep8\.dll;bin/liblib\.dll;lib;lib/liblib\.dll\.a;lib/liblib\.lib;subdir;subdir/bin;subdir/bin/exe1\.exe;subdir/bin/exe2\.exe;subdir/bin/libdep10\.dll;subdir/bin/libdep11\.dll;subdir/bin/libdep2\.dll;subdir/bin/libdep3\.dll;subdir/bin/libdep5\.dll;subdir/bin/libdep6\.dll;subdir/bin/libdep8\.dll;subdir/bin/libdep9\.dll;subdir/bin/liblib\.dll;subdir/bin/libsublib1\.dll;subdir/lib;subdir/lib/libmod\.dll]])
+  check_installed("^(${_msvc_check}|${_mingw_check})$")
+elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
+  check_installed([[^lib;lib/libdep8\.dylib;lib/liblib\.dylib;subdir;subdir/bin;subdir/bin/exe1;subdir/bin/exe2;subdir/frameworks;subdir/frameworks/dep9\.framework;subdir/frameworks/dep9\.framework/Resources;subdir/frameworks/dep9\.framework/Versions;subdir/frameworks/dep9\.framework/Versions/A;subdir/frameworks/dep9\.framework/Versions/A/Resources;subdir/frameworks/dep9\.framework/Versions/A/Resources/Info\.plist(;subdir/frameworks/dep9.framework/Versions/A/_CodeSignature;subdir/frameworks/dep9.framework/Versions/A/_CodeSignature/CodeResources)?;subdir/frameworks/dep9\.framework/Versions/A/dep9;subdir/frameworks/dep9\.framework/Versions/Current;subdir/frameworks/dep9\.framework/dep9;subdir/lib;subdir/lib/libdep10\.dylib;subdir/lib/libdep11\.dylib;subdir/lib/libdep2\.1\.2\.3\.dylib;subdir/lib/libdep2\.1\.dylib;subdir/lib/libdep3\.dylib;subdir/lib/libdep5\.dylib;subdir/lib/libdep6\.dylib;subdir/lib/libdep8\.dylib;subdir/lib/liblib\.dylib;subdir/lib/libmod\.so;subdir/lib/libsublib1\.dylib$]])
+endif()

+ 12 - 0
Tests/ExportImport/Import/install-RUNTIME_DEPENDENCIES/lib.c

@@ -0,0 +1,12 @@
+#ifdef _WIN32
+__declspec(dllimport)
+#endif
+  extern void dep8(void);
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+  void lib(void)
+{
+  dep8();
+}

+ 31 - 0
Tests/ExportImport/Import/install-RUNTIME_DEPENDENCIES/main.c

@@ -0,0 +1,31 @@
+#ifdef _WIN32
+#  define DLLIMPORT __declspec(dllimport)
+#else
+#  define DLLIMPORT
+#endif
+
+DLLIMPORT extern void dep1(void);
+DLLIMPORT extern void dep2(void);
+DLLIMPORT extern void dep3(void);
+DLLIMPORT extern void dep4(void);
+DLLIMPORT extern void dep5(void);
+DLLIMPORT extern void dep10(void);
+DLLIMPORT extern void dep11(void);
+DLLIMPORT extern void dep12(void);
+DLLIMPORT extern void sublib1(void);
+DLLIMPORT extern void sublib2(void);
+
+int main(void)
+{
+  dep1();
+  dep2();
+  dep3();
+  dep4();
+  dep5();
+  dep10();
+  dep11();
+  dep12();
+  sublib1();
+  sublib2();
+  return 0;
+}

+ 12 - 0
Tests/ExportImport/Import/install-RUNTIME_DEPENDENCIES/mod.c

@@ -0,0 +1,12 @@
+#ifdef _WIN32
+__declspec(dllimport)
+#endif
+  extern void dep9(void);
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+  void mod(void)
+{
+  dep9();
+}

+ 12 - 0
Tests/ExportImport/Import/install-RUNTIME_DEPENDENCIES/sublib1.c

@@ -0,0 +1,12 @@
+#ifdef _WIN32
+__declspec(dllimport)
+#endif
+  extern void dep6(void);
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+  void sublib1(void)
+{
+  dep6();
+}

+ 12 - 0
Tests/ExportImport/Import/install-RUNTIME_DEPENDENCIES/sublib2.c

@@ -0,0 +1,12 @@
+#ifdef _WIN32
+__declspec(dllimport)
+#endif
+  extern void dep7(void);
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+  void sublib2(void)
+{
+  dep7();
+}

+ 33 - 0
Tests/ExportImport/Import/install-RUNTIME_DEPENDENCY_SET/CMakeLists.txt

@@ -0,0 +1,33 @@
+set(CMAKE_SKIP_RPATH OFF)
+
+# Import targets from the install tree.
+include(${Import_BINARY_DIR}/../Root/install-RUNTIME_DEPENDENCY_SET/targets.cmake)
+
+add_executable(exe main.c)
+target_link_libraries(exe PRIVATE dep3 dep4)
+
+install(TARGETS exe RUNTIME_DEPENDENCY_SET myset)
+install(IMPORTED_RUNTIME_ARTIFACTS deplib RUNTIME_DEPENDENCY_SET myset)
+
+install(RUNTIME_DEPENDENCY_SET myset
+  PRE_INCLUDE_REGEXES "dep[134]"
+  PRE_EXCLUDE_REGEXES ".*"
+  POST_INCLUDE_REGEXES "dep[13]"
+  POST_EXCLUDE_REGEXES "dep[34]"
+  DIRECTORIES "$<TARGET_FILE_DIR:dep1>"
+  )
+install(RUNTIME_DEPENDENCY_SET myset
+  PRE_INCLUDE_REGEXES "dep[134]"
+  PRE_EXCLUDE_REGEXES ".*"
+  DIRECTORIES "$<TARGET_FILE_DIR:dep1>"
+  RUNTIME DESTINATION yyy/bin
+  LIBRARY DESTINATION yyy/lib
+  )
+install(RUNTIME_DEPENDENCY_SET myset
+  PRE_INCLUDE_REGEXES "dep[134]"
+  PRE_EXCLUDE_REGEXES ".*"
+  DIRECTORIES "$<TARGET_FILE_DIR:dep1>"
+  DESTINATION zzz
+  )
+
+install(SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/check_installed.cmake")

+ 11 - 0
Tests/ExportImport/Import/install-RUNTIME_DEPENDENCY_SET/check_installed.cmake

@@ -0,0 +1,11 @@
+include("${CMAKE_CURRENT_LIST_DIR}/../check_installed.cmake")
+
+if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
+  check_installed([[^bin;bin/exe;lib;lib/libdep1\.so;lib/libdep3\.so;lib/libdeplib\.so;yyy;yyy/lib;yyy/lib/libdep1\.so;yyy/lib/libdep3\.so;yyy/lib/libdep4\.so;zzz;zzz/libdep1\.so;zzz/libdep3\.so;zzz/libdep4\.so$]])
+elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
+  set(_msvc_check [[bin;bin/dep1\.dll;bin/dep3\.dll;bin/deplib\.dll;bin/exe\.exe;yyy;yyy/bin;yyy/bin/dep1\.dll;yyy/bin/dep3\.dll;yyy/bin/dep4\.dll;zzz;zzz/dep1\.dll;zzz/dep3\.dll;zzz/dep4\.dll]])
+  set(_mingw_check [[bin;bin/exe\.exe;bin/libdep1\.dll;bin/libdep3\.dll;bin/libdeplib\.dll;yyy;yyy/bin;yyy/bin/libdep1\.dll;yyy/bin/libdep3\.dll;yyy/bin/libdep4\.dll;zzz;zzz/libdep1\.dll;zzz/libdep3\.dll;zzz/libdep4\.dll]])
+  check_installed("^(${_msvc_check}|${_mingw_check})$")
+elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
+  check_installed([[^bin;bin/exe;lib;lib/libdep1\.dylib;lib/libdep3\.dylib;lib/libdeplib\.dylib;yyy;yyy/lib;yyy/lib/libdep1\.dylib;yyy/lib/libdep3\.dylib;yyy/lib/libdep4\.dylib;zzz;zzz/libdep1\.dylib;zzz/libdep3\.dylib;zzz/libdep4\.dylib$]])
+endif()

+ 15 - 0
Tests/ExportImport/Import/install-RUNTIME_DEPENDENCY_SET/main.c

@@ -0,0 +1,15 @@
+#ifdef _WIN32
+__declspec(dllimport)
+#endif
+  extern void dep3(void);
+#ifdef _WIN32
+__declspec(dllimport)
+#endif
+  extern void dep4(void);
+
+int main(void)
+{
+  dep3();
+  dep4();
+  return 0;
+}

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

@@ -180,6 +180,14 @@ def check_directory(c):
                 expected_keys.append("scriptFile")
                 assert is_string(a["scriptFile"], e["scriptFile"])
 
+            if e.get("runtimeDependencySetName", None) is not None:
+                expected_keys.append("runtimeDependencySetName")
+                assert is_string(a["runtimeDependencySetName"], e["runtimeDependencySetName"])
+
+            if e.get("runtimeDependencySetType", None) is not None:
+                expected_keys.append("runtimeDependencySetType")
+                assert is_string(a["runtimeDependencySetType"], e["runtimeDependencySetType"])
+
             if e["backtrace"] is not None:
                 expected_keys.append("backtrace")
                 check_backtrace(d, a["backtrace"], e["backtrace"])
@@ -650,6 +658,14 @@ def gen_check_directories(c, g):
                     if "pathsNamelink" in i:
                         i["paths"] = i["pathsNamelink"]
 
+    if sys.platform not in ("win32", "darwin") and "linux" not in sys.platform:
+        for e in expected:
+            e["installers"] = list(filter(lambda i: i["type"] != "runtimeDependencySet", e["installers"]))
+
+    if sys.platform != "darwin":
+        for e in expected:
+            e["installers"] = list(filter(lambda i: i.get("runtimeDependencySetType", None) != "framework", e["installers"]))
+
     return expected
 
 def check_directories(c, g):

+ 161 - 2
Tests/RunCMake/FileAPI/codemodel-v2-data/directories/cxx.json

@@ -17,6 +17,165 @@
     ],
     "projectName": "Cxx",
     "minimumCMakeVersion": "3.12",
-    "hasInstallRule": null,
-    "installers": []
+    "hasInstallRule": true,
+    "installers": [
+        {
+            "component": "Unspecified",
+            "type": "target",
+            "destination": "lib",
+            "paths": [
+                "^cxx/((Debug|Release|MinSizeRel|RelWithDebInfo)/)?cxx_exe(\\.exe)?$"
+            ],
+            "isExcludeFromAll": null,
+            "isForAllComponents": null,
+            "isOptional": null,
+            "targetId": "^cxx_exe::@a56b12a3f5c0529fb296$",
+            "targetIndex": "cxx_exe",
+            "targetIsImportLibrary": null,
+            "targetInstallNamelink": null,
+            "exportName": null,
+            "exportTargets": null,
+            "scriptFile": null,
+            "backtrace": [
+                {
+                    "file": "^cxx/CMakeLists\\.txt$",
+                    "line": 38,
+                    "command": "install",
+                    "hasParent": true
+                },
+                {
+                    "file": "^cxx/CMakeLists\\.txt$",
+                    "line": null,
+                    "command": null,
+                    "hasParent": false
+                }
+            ]
+        },
+        {
+            "component": "Unspecified",
+            "type": "runtimeDependencySet",
+            "destination": "lib",
+            "paths": null,
+            "isExcludeFromAll": null,
+            "isForAllComponents": null,
+            "isOptional": null,
+            "targetId": null,
+            "targetIndex": null,
+            "targetIsImportLibrary": null,
+            "targetInstallNamelink": null,
+            "exportName": null,
+            "exportTargets": null,
+            "scriptFile": null,
+            "runtimeDependencySetType": "library",
+            "backtrace": [
+                {
+                    "file": "^cxx/CMakeLists\\.txt$",
+                    "line": 38,
+                    "command": "install",
+                    "hasParent": true
+                },
+                {
+                    "file": "^cxx/CMakeLists\\.txt$",
+                    "line": null,
+                    "command": null,
+                    "hasParent": false
+                }
+            ]
+        },
+        {
+            "component": "Unspecified",
+            "type": "runtimeDependencySet",
+            "destination": "fw",
+            "paths": null,
+            "isExcludeFromAll": null,
+            "isForAllComponents": null,
+            "isOptional": null,
+            "targetId": null,
+            "targetIndex": null,
+            "targetIsImportLibrary": null,
+            "targetInstallNamelink": null,
+            "exportName": null,
+            "exportTargets": null,
+            "scriptFile": null,
+            "runtimeDependencySetType": "framework",
+            "backtrace": [
+                {
+                    "file": "^cxx/CMakeLists\\.txt$",
+                    "line": 38,
+                    "command": "install",
+                    "hasParent": true
+                },
+                {
+                    "file": "^cxx/CMakeLists\\.txt$",
+                    "line": null,
+                    "command": null,
+                    "hasParent": false
+                }
+            ]
+        },
+        {
+            "component": "Unspecified",
+            "type": "runtimeDependencySet",
+            "destination": "lib",
+            "paths": null,
+            "isExcludeFromAll": null,
+            "isForAllComponents": null,
+            "isOptional": null,
+            "targetId": null,
+            "targetIndex": null,
+            "targetIsImportLibrary": null,
+            "targetInstallNamelink": null,
+            "exportName": null,
+            "exportTargets": null,
+            "scriptFile": null,
+            "runtimeDependencySetType": "library",
+            "runtimeDependencySetName": "deps",
+            "backtrace": [
+                {
+                    "file": "^cxx/CMakeLists\\.txt$",
+                    "line": 43,
+                    "command": "install",
+                    "hasParent": true
+                },
+                {
+                    "file": "^cxx/CMakeLists\\.txt$",
+                    "line": null,
+                    "command": null,
+                    "hasParent": false
+                }
+            ]
+        },
+        {
+            "component": "Unspecified",
+            "type": "runtimeDependencySet",
+            "destination": "fw",
+            "paths": null,
+            "isExcludeFromAll": null,
+            "isForAllComponents": null,
+            "isOptional": null,
+            "targetId": null,
+            "targetIndex": null,
+            "targetIsImportLibrary": null,
+            "targetInstallNamelink": null,
+            "exportName": null,
+            "exportTargets": null,
+            "scriptFile": null,
+            "runtimeDependencySetType": "framework",
+            "runtimeDependencySetName": "deps",
+            "backtrace": [
+                {
+                    "file": "^cxx/CMakeLists\\.txt$",
+                    "line": 43,
+                    "command": "install",
+                    "hasParent": true
+                },
+                {
+                    "file": "^cxx/CMakeLists\\.txt$",
+                    "line": null,
+                    "command": null,
+                    "hasParent": false
+                }
+            ]
+        }
+    ]
 }

+ 17 - 0
Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe.json

@@ -114,6 +114,23 @@
     "install": {
         "prefix": "^(/usr/local|[A-Za-z]:.*/codemodel-v2)$",
         "destinations": [
+            {
+                "path": "lib",
+                "backtrace": [
+                    {
+                        "file": "^cxx/CMakeLists\\.txt$",
+                        "line": 38,
+                        "command": "install",
+                        "hasParent": true
+                    },
+                    {
+                        "file": "^cxx/CMakeLists\\.txt$",
+                        "line": null,
+                        "command": null,
+                        "hasParent": false
+                    }
+                ]
+            },
             {
                 "path": "bin",
                 "backtrace": [

+ 15 - 0
Tests/RunCMake/FileAPI/cxx/CMakeLists.txt

@@ -30,3 +30,18 @@ if(CMAKE_CXX_STANDARD_DEFAULT AND DEFINED CMAKE_CXX11_STANDARD_COMPILE_OPTION)
   target_compile_features(cxx_standard_compile_feature_exe PRIVATE cxx_decltype)
   file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/cxx_std_11.txt" "")
 endif()
+
+set(_rdeps)
+if(CMAKE_SYSTEM_NAME MATCHES "^(Linux|Windows|Darwin)$")
+  set(_rdeps RUNTIME_DEPENDENCIES)
+endif()
+install(TARGETS cxx_exe ${_rdeps}
+  DESTINATION lib
+  FRAMEWORK DESTINATION fw
+  )
+if(_rdeps)
+  install(RUNTIME_DEPENDENCY_SET deps
+    DESTINATION lib
+    FRAMEWORK DESTINATION fw
+    )
+endif()

+ 1 - 0
Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/RunCMakeTest.cmake

@@ -36,6 +36,7 @@ endfunction()
 if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
   if(NOT CMake_INSTALL_NAME_TOOL_BUG)
     run_install_test(macos)
+    run_install_test(macos-rpath)
     run_install_test(macos-unresolved)
     run_install_test(macos-conflict)
     run_install_test(macos-notfile)

+ 35 - 0
Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/macos-rpath.cmake

@@ -0,0 +1,35 @@
+enable_language(C)
+
+file(WRITE "${CMAKE_BINARY_DIR}/toplib.c" "extern void sublib1(void);\nextern void sublib2(void);\nvoid toplib(void)\n{\n  sublib1();\n  sublib2();\n}\n")
+add_library(toplib SHARED "${CMAKE_BINARY_DIR}/toplib.c")
+file(WRITE "${CMAKE_BINARY_DIR}/sublib1.c" "extern void sublib2(void);\nvoid sublib1(void)\n{\n  sublib2();\n}\n")
+add_library(sublib1 SHARED "${CMAKE_BINARY_DIR}/sublib1.c")
+file(WRITE "${CMAKE_BINARY_DIR}/sublib2.c" "void sublib2(void)\n{\n}\n")
+add_library(sublib2 SHARED "${CMAKE_BINARY_DIR}/sublib2.c")
+target_link_libraries(toplib PRIVATE sublib1 sublib2)
+target_link_libraries(sublib1 PRIVATE sublib2)
+set_property(TARGET toplib PROPERTY INSTALL_RPATH "@loader_path/d1;@loader_path/d2")
+set_property(TARGET sublib1 PROPERTY INSTALL_RPATH "@loader_path/;@loader_path/../d2")
+install(TARGETS toplib DESTINATION lib)
+install(TARGETS sublib1 DESTINATION lib/d1)
+install(TARGETS sublib2 DESTINATION lib/d2)
+
+install(CODE [[
+  file(GET_RUNTIME_DEPENDENCIES
+    LIBRARIES
+      "${CMAKE_INSTALL_PREFIX}/lib/$<TARGET_FILE_NAME:toplib>"
+    RPATH_PREFIX _rpaths
+    )
+
+  set(_expected_rpath "(^|;)@loader_path/;@loader_path/\\.\\./d2$")
+  set(_actual_rpath "${_rpaths_${CMAKE_INSTALL_PREFIX}/lib/d1/$<TARGET_FILE_NAME:sublib1>}")
+  if(NOT _actual_rpath MATCHES "${_expected_rpath}")
+    message(FATAL_ERROR "Expected rpath:\n  ${_expected_rpath}\nActual rpath:\n  ${_actual_rpath}")
+  endif()
+
+  # Since RPATH_PREFIX is an undocumented option for install(), we don't really need the rpath
+  # for the top files anyway.
+  if(DEFINED "_rpaths_${CMAKE_INSTALL_PREFIX}/lib/$<TARGET_FILE_NAME:toplib>")
+    message(FATAL_ERROR "rpath for top library should not be defined")
+  endif()
+  ]])

+ 37 - 0
Tests/RunCMake/file-RPATH/Common.cmake

@@ -62,3 +62,40 @@ foreach(f ${files})
     message(FATAL_ERROR "RPATH_CHECK did not remove ${f}")
   endif()
 endforeach()
+
+# TODO Implement RPATH_SET in XCOFF.
+if(format STREQUAL "ELF")
+  foreach(f ${names})
+    file(COPY ${in}/${f} DESTINATION ${out} NO_SOURCE_PERMISSIONS)
+  endforeach()
+
+  foreach(f ${files})
+    # Set the RPATH.
+    file(RPATH_SET FILE "${f}"
+      NEW_RPATH "/new/rpath")
+    set(rpath)
+    file(STRINGS "${f}" rpath REGEX "/new/rpath" LIMIT_COUNT 1)
+    if(NOT rpath)
+      message(FATAL_ERROR "RPATH not set in ${f}")
+    endif()
+    file(STRINGS "${f}" rpath REGEX "/rpath/sample" LIMIT_COUNT 1)
+    if(rpath)
+      message(FATAL_EROR "RPATH not removed in ${f}")
+    endif()
+
+    # Remove the RPATH.
+    file(RPATH_SET FILE "${f}"
+      NEW_RPATH "")
+    set(rpath)
+    file(STRINGS "${f}" rpath REGEX "/new/rpath" LIMIT_COUNT 1)
+    if(rpath)
+      message(FATAL_ERROR "RPATH not removed from ${f}")
+    endif()
+
+    # Check again...this should remove the file.
+    file(RPATH_CHECK FILE "${f}" RPATH "/new/rpath")
+    if(EXISTS "${f}")
+      message(FATAL_ERROR "RPATH_CHECK did not remove ${f}")
+    endif()
+  endforeach()
+endif()

+ 1 - 0
Tests/RunCMake/install/IMPORTED_RUNTIME_ARTIFACTS-RUNTIME_DEPENDENCY_SET-unsupported-result.txt

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

+ 6 - 0
Tests/RunCMake/install/IMPORTED_RUNTIME_ARTIFACTS-RUNTIME_DEPENDENCY_SET-unsupported-stderr.txt

@@ -0,0 +1,6 @@
+^CMake Error at IMPORTED_RUNTIME_ARTIFACTS-RUNTIME_DEPENDENCY_SET-unsupported\.cmake:[0-9]+ \(install\):
+  install IMPORTED_RUNTIME_ARTIFACTS RUNTIME_DEPENDENCY_SET is not supported
+  on system "[^
+]*"
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 2 - 0
Tests/RunCMake/install/IMPORTED_RUNTIME_ARTIFACTS-RUNTIME_DEPENDENCY_SET-unsupported.cmake

@@ -0,0 +1,2 @@
+add_executable(exe IMPORTED)
+install(IMPORTED_RUNTIME_ARTIFACTS exe RUNTIME_DEPENDENCY_SET deps)

+ 1 - 0
Tests/RunCMake/install/RUNTIME_DEPENDENCY_SET-unsupported-result.txt

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

+ 5 - 0
Tests/RunCMake/install/RUNTIME_DEPENDENCY_SET-unsupported-stderr.txt

@@ -0,0 +1,5 @@
+^CMake Error at RUNTIME_DEPENDENCY_SET-unsupported\.cmake:[0-9]+ \(install\):
+  install RUNTIME_DEPENDENCY_SET is not supported on system "[^
+]*"
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 1 - 0
Tests/RunCMake/install/RUNTIME_DEPENDENCY_SET-unsupported.cmake

@@ -0,0 +1 @@
+install(RUNTIME_DEPENDENCY_SET deps)

+ 21 - 1
Tests/RunCMake/install/RunCMakeTest.cmake

@@ -44,7 +44,7 @@ function(check_installed expect)
 do not match what we expected:
   ${expect}
 in directory:
-  ${CMAKE_INSTALL_PREFIX}" PARENT_SCOPE)
+  ${CMAKE_INSTALL_PREFIX}\n" PARENT_SCOPE)
   endif()
 endfunction()
 
@@ -174,6 +174,26 @@ run_install_test(FILES-PERMISSIONS)
 run_install_test(TARGETS-RPATH)
 run_install_test(InstallRequiredSystemLibraries)
 
+if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+  run_cmake(TARGETS-RUNTIME_DEPENDENCIES-macos-two-bundle)
+  run_cmake(TARGETS-RUNTIME_DEPENDENCIES-macos-no-framework)
+endif()
+
+if(CMAKE_SYSTEM_NAME MATCHES "^(Linux|Darwin|Windows)$")
+  run_install_test(TARGETS-RUNTIME_DEPENDENCIES-nodep)
+  run_install_test(TARGETS-RUNTIME_DEPENDENCIES-empty)
+  set(RunCMake_TEST_OPTIONS "-DCMAKE_SYSTEM_NAME:STRING=${CMAKE_SYSTEM_NAME}")
+  run_cmake(TARGETS-RUNTIME_DEPENDENCIES-cross)
+  unset(RunCMake_TEST_OPTIONS)
+  run_cmake(TARGETS-RUNTIME_DEPENDENCY_SET-RUNTIME_DEPENDENCIES-conflict)
+  run_cmake(RuntimeDependencies-COMPONENTS)
+else()
+  run_cmake(TARGETS-RUNTIME_DEPENDENCIES-unsupported)
+  run_cmake(TARGETS-RUNTIME_DEPENDENCY_SET-unsupported)
+  run_cmake(IMPORTED_RUNTIME_ARTIFACTS-RUNTIME_DEPENDENCY_SET-unsupported)
+  run_cmake(RUNTIME_DEPENDENCY_SET-unsupported)
+endif()
+
 set(run_install_test_components 1)
 run_install_test(FILES-EXCLUDE_FROM_ALL)
 run_install_test(TARGETS-EXCLUDE_FROM_ALL)

+ 39 - 0
Tests/RunCMake/install/RuntimeDependencies-COMPONENTS.cmake

@@ -0,0 +1,39 @@
+enable_language(C)
+
+function(check_components value)
+  get_cmake_property(comp COMPONENTS)
+  if(NOT comp STREQUAL value)
+    message(FATAL_ERROR "Expected value of COMPONENTS:\n  ${value}\nActual value of COMPONENTS:\n  ${comp}")
+  endif()
+endfunction()
+
+if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+  add_library(tgt MODULE obj1.c)
+else()
+  add_executable(tgt main.c)
+endif()
+
+install(TARGETS tgt
+  RUNTIME_DEPENDENCIES
+  RUNTIME DESTINATION bin COMPONENT bin1
+  LIBRARY DESTINATION lib COMPONENT lib1
+  FRAMEWORK DESTINATION fw COMPONENT fw1
+  )
+if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+  check_components("bin1;fw1;lib1")
+else()
+  check_components("bin1;lib1")
+endif()
+
+install(RUNTIME_DEPENDENCY_SET deps
+  RUNTIME DESTINATION bin COMPONENT bin2
+  LIBRARY DESTINATION lib COMPONENT lib2
+  FRAMEWORK DESTINATION fw COMPONENT fw2
+  )
+if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+  check_components("bin1;fw1;fw2;lib1;lib2")
+elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+  check_components("bin1;bin2;lib1")
+elseif()
+  check_components("bin1;lib1;lib2")
+endif()

+ 1 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-cross-result.txt

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

+ 4 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-cross-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at TARGETS-RUNTIME_DEPENDENCIES-cross\.cmake:[0-9]+ \(install\):
+  install TARGETS RUNTIME_DEPENDENCIES is not supported when cross-compiling\.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$

+ 4 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-cross.cmake

@@ -0,0 +1,4 @@
+enable_language(C)
+
+add_executable(exe main.c)
+install(TARGETS exe RUNTIME_DEPENDENCIES)

+ 1 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-empty-all-check.cmake

@@ -0,0 +1 @@
+check_installed([[^static;static/(liblib\.a|lib\.lib)$]])

+ 9 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-empty.cmake

@@ -0,0 +1,9 @@
+enable_language(C)
+
+add_library(lib STATIC obj1.c)
+
+install(TARGETS lib RUNTIME_DEPENDENCIES
+  LIBRARY DESTINATION lib
+  ARCHIVE DESTINATION static
+  FRAMEWORK DESTINATION fw
+  )

+ 1 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-no-framework-result.txt

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

+ 4 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-no-framework-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at TARGETS-RUNTIME_DEPENDENCIES-macos-no-framework\.cmake:[0-9]+ \(install\):
+  install TARGETS RUNTIME_DEPENDENCIES given no FRAMEWORK DESTINATION
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 4 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-no-framework.cmake

@@ -0,0 +1,4 @@
+enable_language(C)
+
+add_executable(exe main.c)
+install(TARGETS exe RUNTIME_DEPENDENCIES)

+ 1 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-two-bundle-result.txt

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

+ 4 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-two-bundle-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at TARGETS-RUNTIME_DEPENDENCIES-macos-two-bundle\.cmake:[0-9]+ \(install\):
+  install A runtime dependency set may only have one bundle executable\.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$

+ 10 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-two-bundle.cmake

@@ -0,0 +1,10 @@
+enable_language(C)
+
+add_executable(exe1 MACOSX_BUNDLE main.c)
+add_executable(exe2 MACOSX_BUNDLE main.c)
+
+install(TARGETS exe1 exe2
+  RUNTIME_DEPENDENCIES
+  BUNDLE DESTINATION bundles
+  FRAMEWORK DESTINATION fw
+  )

+ 1 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-nodep-all-check.cmake

@@ -0,0 +1 @@
+check_installed([[^bin;bin/exe(\.exe)?$]])

+ 9 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-nodep.cmake

@@ -0,0 +1,9 @@
+enable_language(C)
+
+add_executable(exe main.c)
+
+install(TARGETS exe
+  RUNTIME_DEPENDENCIES
+    PRE_EXCLUDE_REGEXES ".*"
+  FRAMEWORK DESTINATION fw
+  )

+ 1 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-unsupported-result.txt

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

+ 5 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-unsupported-stderr.txt

@@ -0,0 +1,5 @@
+^CMake Error at TARGETS-RUNTIME_DEPENDENCIES-unsupported\.cmake:[0-9]+ \(install\):
+  install TARGETS RUNTIME_DEPENDENCIES is not supported on system "[^
+]*"
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 4 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-unsupported.cmake

@@ -0,0 +1,4 @@
+enable_language(C)
+
+add_executable(exe main.c)
+install(TARGETS exe RUNTIME_DEPENDENCIES)

+ 1 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCY_SET-RUNTIME_DEPENDENCIES-conflict-result.txt

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

+ 5 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCY_SET-RUNTIME_DEPENDENCIES-conflict-stderr.txt

@@ -0,0 +1,5 @@
+^CMake Error at TARGETS-RUNTIME_DEPENDENCY_SET-RUNTIME_DEPENDENCIES-conflict\.cmake:[0-9]+ \(install\):
+  install TARGETS cannot have both RUNTIME_DEPENDENCIES and
+  RUNTIME_DEPENDENCY_SET\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 7 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCY_SET-RUNTIME_DEPENDENCIES-conflict.cmake

@@ -0,0 +1,7 @@
+enable_language(C)
+
+add_executable(exe main.c)
+install(TARGETS exe
+  RUNTIME_DEPENDENCY_SET deps
+  RUNTIME_DEPENDENCIES
+  )

+ 1 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCY_SET-unsupported-result.txt

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

+ 5 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCY_SET-unsupported-stderr.txt

@@ -0,0 +1,5 @@
+^CMake Error at TARGETS-RUNTIME_DEPENDENCY_SET-unsupported\.cmake:[0-9]+ \(install\):
+  install TARGETS RUNTIME_DEPENDENCY_SET is not supported on system "[^
+]*"
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 4 - 0
Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCY_SET-unsupported.cmake

@@ -0,0 +1,4 @@
+enable_language(C)
+
+add_executable(exe main.c)
+install(TARGETS exe RUNTIME_DEPENDENCY_SET deps)

+ 3 - 0
bootstrap

@@ -387,7 +387,10 @@ CMAKE_CXX_SOURCES="\
   cmInstallFilesCommand \
   cmInstallFilesGenerator \
   cmInstallGenerator \
+  cmInstallGetRuntimeDependenciesGenerator \
   cmInstallImportedRuntimeArtifactsGenerator \
+  cmInstallRuntimeDependencySet \
+  cmInstallRuntimeDependencySetGenerator \
   cmInstallScriptGenerator \
   cmInstallSubdirectoryGenerator \
   cmInstallTargetGenerator \