From ed3633d88cc5faa6fd7eb68fdd774d6d1f9cfdc9 Mon Sep 17 00:00:00 2001
From: Kyle Edwards <kyle.edwards@kitware.com>
Date: Wed, 14 Apr 2021 10:43:30 -0400
Subject: [PATCH] install(TARGETS): Add RUNTIME_DEPENDENCIES option

---
 Source/CMakeLists.txt                         |   4 +
 Source/cmFileCommand.cxx                      |   5 +-
 Source/cmInstallCommand.cxx                   | 186 +++++++++++-
 Source/cmInstallGenerator.cxx                 |  51 ++--
 Source/cmInstallGenerator.h                   |   3 +-
 ...InstallGetRuntimeDependenciesGenerator.cxx | 206 +++++++++++++
 ...cmInstallGetRuntimeDependenciesGenerator.h |  56 ++++
 ...cmInstallRuntimeDependencySetGenerator.cxx | 276 ++++++++++++++++++
 .../cmInstallRuntimeDependencySetGenerator.h  |  74 +++++
 Source/cmRuntimeDependencyArchive.cxx         |   8 +
 Source/cmRuntimeDependencyArchive.h           |   2 +
 Tests/RunCMake/install/RunCMakeTest.cmake     |  17 +-
 ...GETS-RUNTIME_DEPENDENCIES-cross-result.txt |   1 +
 ...GETS-RUNTIME_DEPENDENCIES-cross-stderr.txt |   4 +
 .../TARGETS-RUNTIME_DEPENDENCIES-cross.cmake  |   4 +
 ...RUNTIME_DEPENDENCIES-empty-all-check.cmake |   1 +
 .../TARGETS-RUNTIME_DEPENDENCIES-empty.cmake  |   9 +
 ...DEPENDENCIES-macos-no-framework-result.txt |   1 +
 ...DEPENDENCIES-macos-no-framework-stderr.txt |   4 +
 ...TIME_DEPENDENCIES-macos-no-framework.cmake |   4 +
 ...E_DEPENDENCIES-macos-two-bundle-result.txt |   1 +
 ...E_DEPENDENCIES-macos-two-bundle-stderr.txt |   4 +
 ...UNTIME_DEPENDENCIES-macos-two-bundle.cmake |  10 +
 ...RUNTIME_DEPENDENCIES-nodep-all-check.cmake |   1 +
 .../TARGETS-RUNTIME_DEPENDENCIES-nodep.cmake  |   9 +
 ...UNTIME_DEPENDENCIES-unsupported-result.txt |   1 +
 ...UNTIME_DEPENDENCIES-unsupported-stderr.txt |   5 +
 ...ETS-RUNTIME_DEPENDENCIES-unsupported.cmake |   4 +
 bootstrap                                     |   2 +
 29 files changed, 928 insertions(+), 25 deletions(-)
 create mode 100644 Source/cmInstallGetRuntimeDependenciesGenerator.cxx
 create mode 100644 Source/cmInstallGetRuntimeDependenciesGenerator.h
 create mode 100644 Source/cmInstallRuntimeDependencySetGenerator.cxx
 create mode 100644 Source/cmInstallRuntimeDependencySetGenerator.h
 create mode 100644 Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-cross-result.txt
 create mode 100644 Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-cross-stderr.txt
 create mode 100644 Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-cross.cmake
 create mode 100644 Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-empty-all-check.cmake
 create mode 100644 Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-empty.cmake
 create mode 100644 Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-no-framework-result.txt
 create mode 100644 Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-no-framework-stderr.txt
 create mode 100644 Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-no-framework.cmake
 create mode 100644 Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-two-bundle-result.txt
 create mode 100644 Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-two-bundle-stderr.txt
 create mode 100644 Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-two-bundle.cmake
 create mode 100644 Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-nodep-all-check.cmake
 create mode 100644 Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-nodep.cmake
 create mode 100644 Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-unsupported-result.txt
 create mode 100644 Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-unsupported-stderr.txt
 create mode 100644 Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-unsupported.cmake

diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 5bcf2233b2..9a18184fd3 100644
--- a/Source/CMakeLists.txt
+++ b/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
@@ -354,6 +356,8 @@ set(SRCS
   cmInstallImportedRuntimeArtifactsGenerator.cxx
   cmInstallRuntimeDependencySet.h
   cmInstallRuntimeDependencySet.cxx
+  cmInstallRuntimeDependencySetGenerator.h
+  cmInstallRuntimeDependencySetGenerator.cxx
   cmInstallScriptGenerator.h
   cmInstallScriptGenerator.cxx
   cmInstallSubdirectoryGenerator.h
diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx
index 82546b915e..0ad59c7b5d 100644
--- a/Source/cmFileCommand.cxx
+++ b/Source/cmFileCommand.cxx
@@ -3044,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, "\""));
diff --git a/Source/cmInstallCommand.cxx b/Source/cmInstallCommand.cxx
index c9bb467b36..c36778d861 100644
--- a/Source/cmInstallCommand.cxx
+++ b/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,23 @@ 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::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.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 +535,32 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
     return false;
   }
 
+  cmInstallRuntimeDependencySet* runtimeDependencySet = nullptr;
+  if (withRuntimeDependencies) {
+    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();
+  }
+
   // Select the mode for installing symlinks to versioned shared libraries.
   cmInstallTargetGenerator::NamelinkModeType namelinkMode =
     cmInstallTargetGenerator::NamelinkModeNone;
@@ -546,6 +705,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 +753,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 +798,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 +853,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 +867,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 +998,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) {
diff --git a/Source/cmInstallGenerator.cxx b/Source/cmInstallGenerator.cxx
index 9f0d119e4c..4cfeb477e7 100644
--- a/Source/cmInstallGenerator.cxx
+++ b/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 << " ";
diff --git a/Source/cmInstallGenerator.h b/Source/cmInstallGenerator.h
index 97acb88e83..d342c99261 100644
--- a/Source/cmInstallGenerator.h
+++ b/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.  */
diff --git a/Source/cmInstallGetRuntimeDependenciesGenerator.cxx b/Source/cmInstallGetRuntimeDependenciesGenerator.cxx
new file mode 100644
index 0000000000..4d585cec55
--- /dev/null
+++ b/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";
+}
diff --git a/Source/cmInstallGetRuntimeDependenciesGenerator.h b/Source/cmInstallGetRuntimeDependenciesGenerator.h
new file mode 100644
index 0000000000..19f6cc6db8
--- /dev/null
+++ b/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;
+};
diff --git a/Source/cmInstallRuntimeDependencySetGenerator.cxx b/Source/cmInstallRuntimeDependencySetGenerator.cxx
new file mode 100644
index 0000000000..44f03e1af9
--- /dev/null
+++ b/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);
+}
diff --git a/Source/cmInstallRuntimeDependencySetGenerator.h b/Source/cmInstallRuntimeDependencySetGenerator.h
new file mode 100644
index 0000000000..8e98b57624
--- /dev/null
+++ b/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);
+};
diff --git a/Source/cmRuntimeDependencyArchive.cxx b/Source/cmRuntimeDependencyArchive.cxx
index 06246d1df5..26f255da01 100644
--- a/Source/cmRuntimeDependencyArchive.cxx
+++ b/Source/cmRuntimeDependencyArchive.cxx
@@ -397,3 +397,11 @@ 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);
+}
diff --git a/Source/cmRuntimeDependencyArchive.h b/Source/cmRuntimeDependencyArchive.h
index 20a38e203d..b170815b4b 100644
--- a/Source/cmRuntimeDependencyArchive.h
+++ b/Source/cmRuntimeDependencyArchive.h
@@ -53,6 +53,8 @@ public:
   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;
   std::unique_ptr<cmBinUtilsLinker> Linker;
diff --git a/Tests/RunCMake/install/RunCMakeTest.cmake b/Tests/RunCMake/install/RunCMakeTest.cmake
index 94887a0dbe..0f6ec708bd 100644
--- a/Tests/RunCMake/install/RunCMakeTest.cmake
+++ b/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,21 @@ 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)
+else()
+  run_cmake(TARGETS-RUNTIME_DEPENDENCIES-unsupported)
+endif()
+
 set(run_install_test_components 1)
 run_install_test(FILES-EXCLUDE_FROM_ALL)
 run_install_test(TARGETS-EXCLUDE_FROM_ALL)
diff --git a/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-cross-result.txt b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-cross-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-cross-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-cross-stderr.txt b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-cross-stderr.txt
new file mode 100644
index 0000000000..b7bbb55cec
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-cross.cmake b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-cross.cmake
new file mode 100644
index 0000000000..36b2eb7b4d
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-empty-all-check.cmake b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-empty-all-check.cmake
new file mode 100644
index 0000000000..dafc2a48b4
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-empty-all-check.cmake
@@ -0,0 +1 @@
+check_installed([[^static;static/(liblib\.a|lib\.lib)$]])
diff --git a/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-empty.cmake b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-empty.cmake
new file mode 100644
index 0000000000..e46e1d9b2a
--- /dev/null
+++ b/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
+  )
diff --git a/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-no-framework-result.txt b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-no-framework-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-no-framework-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-no-framework-stderr.txt b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-no-framework-stderr.txt
new file mode 100644
index 0000000000..6ab14f6c1b
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-no-framework.cmake b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-no-framework.cmake
new file mode 100644
index 0000000000..36b2eb7b4d
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-two-bundle-result.txt b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-two-bundle-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-two-bundle-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-two-bundle-stderr.txt b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-two-bundle-stderr.txt
new file mode 100644
index 0000000000..c7b49c73b9
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-two-bundle.cmake b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-macos-two-bundle.cmake
new file mode 100644
index 0000000000..a848a24c3a
--- /dev/null
+++ b/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
+  )
diff --git a/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-nodep-all-check.cmake b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-nodep-all-check.cmake
new file mode 100644
index 0000000000..9f455b1ba4
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-nodep-all-check.cmake
@@ -0,0 +1 @@
+check_installed([[^bin;bin/exe(\.exe)?$]])
diff --git a/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-nodep.cmake b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-nodep.cmake
new file mode 100644
index 0000000000..02466561d6
--- /dev/null
+++ b/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
+  )
diff --git a/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-unsupported-result.txt b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-unsupported-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-unsupported-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-unsupported-stderr.txt b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-unsupported-stderr.txt
new file mode 100644
index 0000000000..c098a4c4e2
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-unsupported.cmake b/Tests/RunCMake/install/TARGETS-RUNTIME_DEPENDENCIES-unsupported.cmake
new file mode 100644
index 0000000000..36b2eb7b4d
--- /dev/null
+++ b/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)
diff --git a/bootstrap b/bootstrap
index 720fa76b67..f8d2f693c2 100755
--- a/bootstrap
+++ b/bootstrap
@@ -387,8 +387,10 @@ CMAKE_CXX_SOURCES="\
   cmInstallFilesCommand \
   cmInstallFilesGenerator \
   cmInstallGenerator \
+  cmInstallGetRuntimeDependenciesGenerator \
   cmInstallImportedRuntimeArtifactsGenerator \
   cmInstallRuntimeDependencySet \
+  cmInstallRuntimeDependencySetGenerator \
   cmInstallScriptGenerator \
   cmInstallSubdirectoryGenerator \
   cmInstallTargetGenerator \
-- 
GitLab