summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Help/manual/cmake-generator-expressions.7.rst23
-rw-r--r--Help/release/dev/runtime-dll-deps.rst4
-rw-r--r--Source/cmComputeLinkInformation.cxx19
-rw-r--r--Source/cmComputeLinkInformation.h6
-rw-r--r--Source/cmGeneratorExpressionNode.cxx55
-rw-r--r--Source/cmGeneratorTarget.cxx14
-rw-r--r--Source/cmGeneratorTarget.h5
-rw-r--r--Tests/RunCMake/CMakeLists.txt1
-rw-r--r--Tests/RunCMake/File_Generate/OutputNameMatchesObjects-stderr.txt4
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/CMakeLists.txt3
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/RunCMakeTest.cmake7
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-check.cmake15
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-static-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-static-stderr.txt9
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-static.cmake9
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle1-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle1-stderr.txt5
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle1.cmake4
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle2-result.txt1
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle2-stderr.txt5
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle2.cmake6
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries.cmake5
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS.cmake37
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/lib1.c12
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/lib2.c6
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/lib3.c6
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/main.c4
-rw-r--r--Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/static.c3
-rw-r--r--Tests/RunCMake/ObjectLibrary/BadSourceExpression3-stderr.txt4
-rw-r--r--Tests/RunCMake/TargetObjects/NotObjlibTarget-stderr.txt4
30 files changed, 270 insertions, 8 deletions
diff --git a/Help/manual/cmake-generator-expressions.7.rst b/Help/manual/cmake-generator-expressions.7.rst
index ca4ea3e0aa..e7828168de 100644
--- a/Help/manual/cmake-generator-expressions.7.rst
+++ b/Help/manual/cmake-generator-expressions.7.rst
@@ -938,6 +938,29 @@ which is just the string ``tgt``.
:ref:`Target Usage Requirements` this is the consuming target rather
than the target specifying the requirement.
+.. genex:: $<TARGET_RUNTIME_DLLS:tgt>
+
+ List of DLLs that the target depends on at runtime. This is determined by
+ the locations of all the ``SHARED`` and ``MODULE`` targets in the target's
+ transitive dependencies. Using this generator expression on targets other
+ than executables, ``SHARED`` libraries, and ``MODULE`` libraries is an error.
+ On non-DLL platforms, it evaluates to an empty string.
+
+ This generator expression can be used to copy all of the DLLs that a target
+ depends on into its output directory in a ``POST_BUILD`` custom command. For
+ example:
+
+ .. code-block:: cmake
+
+ find_package(foo REQUIRED)
+
+ add_executable(exe main.c)
+ target_link_libraries(exe PRIVATE foo::foo foo::bar)
+ add_custom_command(TARGET exe POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_RUNTIME_DLLS:exe> $<TARGET_FILE_DIR:exe>
+ COMMAND_EXPAND_LISTS
+ )
+
.. genex:: $<INSTALL_PREFIX>
Content of the install prefix when the target is exported via
diff --git a/Help/release/dev/runtime-dll-deps.rst b/Help/release/dev/runtime-dll-deps.rst
new file mode 100644
index 0000000000..831410f3bc
--- /dev/null
+++ b/Help/release/dev/runtime-dll-deps.rst
@@ -0,0 +1,4 @@
+runtime-dll-deps
+----------------
+
+* A new :genex:`TARGET_RUNTIME_DLLS` generator expression was added.
diff --git a/Source/cmComputeLinkInformation.cxx b/Source/cmComputeLinkInformation.cxx
index 6225a4a9ed..5473316daf 100644
--- a/Source/cmComputeLinkInformation.cxx
+++ b/Source/cmComputeLinkInformation.cxx
@@ -701,6 +701,10 @@ void cmComputeLinkInformation::AddItem(BT<std::string> const& item,
this->AddTargetItem(lib, tgt);
this->AddLibraryRuntimeInfo(lib.Value, tgt);
+ if (tgt && tgt->GetType() == cmStateEnums::SHARED_LIBRARY &&
+ this->Target->IsDLLPlatform()) {
+ this->AddRuntimeDLL(tgt);
+ }
}
} else {
// This is not a CMake target. Use the name given.
@@ -728,6 +732,13 @@ void cmComputeLinkInformation::AddItem(BT<std::string> const& item,
void cmComputeLinkInformation::AddSharedDepItem(BT<std::string> const& item,
const cmGeneratorTarget* tgt)
{
+ // Record dependencies on DLLs.
+ if (tgt && tgt->GetType() == cmStateEnums::SHARED_LIBRARY &&
+ this->Target->IsDLLPlatform() &&
+ this->SharedDependencyMode != SharedDepModeLink) {
+ this->AddRuntimeDLL(tgt);
+ }
+
// If dropping shared library dependencies, ignore them.
if (this->SharedDependencyMode == SharedDepModeNone) {
return;
@@ -799,6 +810,14 @@ void cmComputeLinkInformation::AddSharedDepItem(BT<std::string> const& item,
}
}
+void cmComputeLinkInformation::AddRuntimeDLL(cmGeneratorTarget const* tgt)
+{
+ if (std::find(this->RuntimeDLLs.begin(), this->RuntimeDLLs.end(), tgt) ==
+ this->RuntimeDLLs.end()) {
+ this->RuntimeDLLs.emplace_back(tgt);
+ }
+}
+
void cmComputeLinkInformation::ComputeLinkTypeInfo()
{
// Check whether archives may actually be shared libraries.
diff --git a/Source/cmComputeLinkInformation.h b/Source/cmComputeLinkInformation.h
index 9fec702065..4acb99f742 100644
--- a/Source/cmComputeLinkInformation.h
+++ b/Source/cmComputeLinkInformation.h
@@ -64,6 +64,10 @@ public:
std::string GetRPathString(bool for_install) const;
std::string GetChrpathString() const;
std::set<cmGeneratorTarget const*> const& GetSharedLibrariesLinked() const;
+ std::vector<cmGeneratorTarget const*> const& GetRuntimeDLLs() const
+ {
+ return this->RuntimeDLLs;
+ }
std::string const& GetLibLinkFileFlag() const
{
@@ -81,6 +85,7 @@ private:
void AddItem(BT<std::string> const& item, const cmGeneratorTarget* tgt);
void AddSharedDepItem(BT<std::string> const& item,
cmGeneratorTarget const* tgt);
+ void AddRuntimeDLL(cmGeneratorTarget const* tgt);
// Output information.
ItemVector Items;
@@ -89,6 +94,7 @@ private:
std::vector<std::string> FrameworkPaths;
std::vector<std::string> RuntimeSearchPath;
std::set<cmGeneratorTarget const*> SharedLibrariesLinked;
+ std::vector<cmGeneratorTarget const*> RuntimeDLLs;
// Context information.
cmGeneratorTarget const* const Target;
diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx
index e40316e1af..71251705a8 100644
--- a/Source/cmGeneratorExpressionNode.cxx
+++ b/Source/cmGeneratorExpressionNode.cxx
@@ -14,6 +14,7 @@
#include <utility>
#include <cm/iterator>
+#include <cm/optional>
#include <cm/string_view>
#include <cm/vector>
#include <cmext/algorithm>
@@ -23,6 +24,7 @@
#include "cmsys/String.h"
#include "cmAlgorithms.h"
+#include "cmComputeLinkInformation.h"
#include "cmGeneratorExpression.h"
#include "cmGeneratorExpressionContext.h"
#include "cmGeneratorExpressionDAGChecker.h"
@@ -1627,8 +1629,8 @@ static const struct TargetObjectsNode : public cmGeneratorExpressionNode
type != cmStateEnums::OBJECT_LIBRARY) {
std::ostringstream e;
e << "Objects of target \"" << tgtName
- << "\" referenced but is not an allowed library types (EXECUTABLE, "
- << "STATIC, SHARED, MODULE, OBJECT).";
+ << "\" referenced but is not one of the allowed target types "
+ << "(EXECUTABLE, STATIC, SHARED, MODULE, OBJECT).";
reportError(context, content->GetOriginalExpression(), e.str());
return std::string();
}
@@ -1687,6 +1689,54 @@ static const struct TargetObjectsNode : public cmGeneratorExpressionNode
}
} targetObjectsNode;
+static const struct TargetRuntimeDllsNode : public cmGeneratorExpressionNode
+{
+ TargetRuntimeDllsNode() {} // NOLINT(modernize-use-equals-default)
+
+ std::string Evaluate(
+ const std::vector<std::string>& parameters,
+ cmGeneratorExpressionContext* context,
+ const GeneratorExpressionContent* content,
+ cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
+ {
+ std::string tgtName = parameters.front();
+ cmGeneratorTarget* gt = context->LG->FindGeneratorTargetToUse(tgtName);
+ if (!gt) {
+ std::ostringstream e;
+ e << "Objects of target \"" << tgtName
+ << "\" referenced but no such target exists.";
+ reportError(context, content->GetOriginalExpression(), e.str());
+ return std::string();
+ }
+ cmStateEnums::TargetType type = gt->GetType();
+ if (type != cmStateEnums::EXECUTABLE &&
+ type != cmStateEnums::SHARED_LIBRARY &&
+ type != cmStateEnums::MODULE_LIBRARY) {
+ std::ostringstream e;
+ e << "Objects of target \"" << tgtName
+ << "\" referenced but is not one of the allowed target types "
+ << "(EXECUTABLE, SHARED, MODULE).";
+ reportError(context, content->GetOriginalExpression(), e.str());
+ return std::string();
+ }
+
+ if (auto* cli = gt->GetLinkInformation(context->Config)) {
+ std::vector<std::string> dllPaths;
+ auto const& dlls = cli->GetRuntimeDLLs();
+
+ for (auto const& dll : dlls) {
+ if (auto loc = dll->MaybeGetLocation(context->Config)) {
+ dllPaths.emplace_back(*loc);
+ }
+ }
+
+ return cmJoin(dllPaths, ";");
+ }
+
+ return "";
+ }
+} targetRuntimeDllsNode;
+
static const struct CompileFeaturesNode : public cmGeneratorExpressionNode
{
CompileFeaturesNode() {} // NOLINT(modernize-use-equals-default)
@@ -2603,6 +2653,7 @@ const cmGeneratorExpressionNode* cmGeneratorExpressionNode::GetNode(
{ "TARGET_EXISTS", &targetExistsNode },
{ "TARGET_NAME_IF_EXISTS", &targetNameIfExistsNode },
{ "TARGET_GENEX_EVAL", &targetGenexEvalNode },
+ { "TARGET_RUNTIME_DLLS", &targetRuntimeDllsNode },
{ "GENEX_EVAL", &genexEvalNode },
{ "BUILD_INTERFACE", &buildInterfaceNode },
{ "INSTALL_INTERFACE", &installInterfaceNode },
diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx
index d7e9952a2f..d3c9959614 100644
--- a/Source/cmGeneratorTarget.cxx
+++ b/Source/cmGeneratorTarget.cxx
@@ -1062,6 +1062,20 @@ const std::string& cmGeneratorTarget::GetLocation(
return location;
}
+cm::optional<std::string> cmGeneratorTarget::MaybeGetLocation(
+ std::string const& config) const
+{
+ cm::optional<std::string> location;
+ if (cmGeneratorTarget::ImportInfo const* imp = this->GetImportInfo(config)) {
+ if (!imp->Location.empty()) {
+ location = imp->Location;
+ }
+ } else {
+ location = this->GetFullPath(config, cmStateEnums::RuntimeBinaryArtifact);
+ }
+ return location;
+}
+
std::vector<cmCustomCommand> const& cmGeneratorTarget::GetPreBuildCommands()
const
{
diff --git a/Source/cmGeneratorTarget.h b/Source/cmGeneratorTarget.h
index 8fe70abfc1..2935e0bce0 100644
--- a/Source/cmGeneratorTarget.h
+++ b/Source/cmGeneratorTarget.h
@@ -14,6 +14,8 @@
#include <utility>
#include <vector>
+#include <cm/optional>
+
#include "cmLinkItem.h"
#include "cmListFileCache.h"
#include "cmPolicies.h"
@@ -50,6 +52,9 @@ public:
bool CanCompileSources() const;
const std::string& GetLocation(const std::string& config) const;
+ /** Get the full path to the target's main artifact, if known. */
+ cm::optional<std::string> MaybeGetLocation(std::string const& config) const;
+
std::vector<cmCustomCommand> const& GetPreBuildCommands() const;
std::vector<cmCustomCommand> const& GetPreLinkCommands() const;
std::vector<cmCustomCommand> const& GetPostBuildCommands() const;
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index c842574109..fed084119d 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -258,6 +258,7 @@ add_RunCMake_test(GenEx-HOST_LINK)
add_RunCMake_test(GenEx-DEVICE_LINK)
add_RunCMake_test(GenEx-TARGET_FILE -DLINKER_SUPPORTS_PDB=${LINKER_SUPPORTS_PDB})
add_RunCMake_test(GenEx-GENEX_EVAL)
+add_RunCMake_test(GenEx-TARGET_RUNTIME_DLLS)
add_RunCMake_test(GeneratorExpression)
add_RunCMake_test(GeneratorInstance)
add_RunCMake_test(GeneratorPlatform)
diff --git a/Tests/RunCMake/File_Generate/OutputNameMatchesObjects-stderr.txt b/Tests/RunCMake/File_Generate/OutputNameMatchesObjects-stderr.txt
index 4c2e35fcc7..4d7370c472 100644
--- a/Tests/RunCMake/File_Generate/OutputNameMatchesObjects-stderr.txt
+++ b/Tests/RunCMake/File_Generate/OutputNameMatchesObjects-stderr.txt
@@ -3,7 +3,7 @@ CMake Error at OutputNameMatchesObjects.cmake:[0-9]+ \(file\):
\$<TARGET_OBJECTS:foo>
- Objects of target "foo" referenced but is not an allowed library types
- \(EXECUTABLE, STATIC, SHARED, MODULE, OBJECT\).
+ Objects of target "foo" referenced but is not one of the allowed target
+ types \(EXECUTABLE, STATIC, SHARED, MODULE, OBJECT\).
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/CMakeLists.txt b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/CMakeLists.txt
new file mode 100644
index 0000000000..ab1a20c7eb
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.19)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/RunCMakeTest.cmake b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/RunCMakeTest.cmake
new file mode 100644
index 0000000000..edc495c94c
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/RunCMakeTest.cmake
@@ -0,0 +1,7 @@
+include(RunCMake)
+
+run_cmake(TARGET_RUNTIME_DLLS)
+run_cmake(TARGET_RUNTIME_DLLS-static)
+run_cmake(TARGET_RUNTIME_DLLS-target_link_libraries)
+run_cmake(TARGET_RUNTIME_DLLS-target_link_libraries-cycle1)
+run_cmake(TARGET_RUNTIME_DLLS-target_link_libraries-cycle2)
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-check.cmake b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-check.cmake
new file mode 100644
index 0000000000..e19598e144
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-check.cmake
@@ -0,0 +1,15 @@
+function(check_genex expected actual)
+ if(NOT expected STREQUAL actual)
+ string(APPEND RunCMake_TEST_FAILED "Expected DLLs:\n")
+ foreach(dll IN LISTS expected)
+ string(APPEND RunCMake_TEST_FAILED " ${dll}\n")
+ endforeach()
+ string(APPEND RunCMake_TEST_FAILED "Actual DLLs:\n")
+ foreach(dll IN LISTS actual)
+ string(APPEND RunCMake_TEST_FAILED " ${dll}\n")
+ endforeach()
+ endif()
+ set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
+endfunction()
+
+include("${RunCMake_TEST_BINARY_DIR}/dlls.cmake")
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-static-result.txt b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-static-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-static-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-static-stderr.txt b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-static-stderr.txt
new file mode 100644
index 0000000000..7ce588a742
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-static-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at TARGET_RUNTIME_DLLS-static\.cmake:[0-9]+ \(file\):
+ Error evaluating generator expression:
+
+ \$<TARGET_RUNTIME_DLLS:static>
+
+ Objects of target "static" referenced but is not one of the allowed target
+ types \(EXECUTABLE, SHARED, MODULE\)\.
+Call Stack \(most recent call first\):
+ CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-static.cmake b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-static.cmake
new file mode 100644
index 0000000000..dc900ddf19
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-static.cmake
@@ -0,0 +1,9 @@
+enable_language(C)
+
+add_library(static STATIC static.c)
+set(condition)
+get_property(multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(multi_config)
+ set(condition CONDITION "$<CONFIG:Debug>")
+endif()
+file(GENERATE OUTPUT "${CMAKE_BINARY_DIR}/dlls.txt" CONTENT "$<TARGET_RUNTIME_DLLS:static>" ${condition})
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle1-result.txt b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle1-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle1-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle1-stderr.txt b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle1-stderr.txt
new file mode 100644
index 0000000000..8cfcf7e41a
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle1-stderr.txt
@@ -0,0 +1,5 @@
+CMake Error at TARGET_RUNTIME_DLLS-target_link_libraries-cycle1\.cmake:[0-9]+ \(add_library\):
+ The SOURCES of "lib1" use a generator expression that depends on the
+ SOURCES themselves\.
+Call Stack \(most recent call first\):
+ CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle1.cmake b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle1.cmake
new file mode 100644
index 0000000000..f19e9e6a73
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle1.cmake
@@ -0,0 +1,4 @@
+enable_language(C)
+
+add_library(lib1 SHARED lib1.c)
+target_link_libraries(lib1 PRIVATE $<TARGET_RUNTIME_DLLS:lib1>)
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle2-result.txt b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle2-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle2-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle2-stderr.txt b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle2-stderr.txt
new file mode 100644
index 0000000000..bacbf63b7d
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle2-stderr.txt
@@ -0,0 +1,5 @@
+CMake Error at TARGET_RUNTIME_DLLS-target_link_libraries-cycle2\.cmake:[0-9]+ \(add_library\):
+ The SOURCES of "(lib1|lib2)" use a generator expression that depends on the
+ SOURCES themselves\.
+Call Stack \(most recent call first\):
+ CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle2.cmake b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle2.cmake
new file mode 100644
index 0000000000..7d035bd876
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries-cycle2.cmake
@@ -0,0 +1,6 @@
+enable_language(C)
+
+add_library(lib1 SHARED lib1.c)
+add_library(lib2 SHARED lib2.c)
+target_link_libraries(lib1 PRIVATE $<TARGET_RUNTIME_DLLS:lib2>)
+target_link_libraries(lib2 PRIVATE $<TARGET_RUNTIME_DLLS:lib1>)
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries.cmake b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries.cmake
new file mode 100644
index 0000000000..f44dbf4b18
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS-target_link_libraries.cmake
@@ -0,0 +1,5 @@
+enable_language(C)
+
+add_library(lib1 SHARED lib1.c)
+add_library(lib2 SHARED lib2.c)
+target_link_libraries(lib1 PRIVATE $<TARGET_RUNTIME_DLLS:lib2>)
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS.cmake b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS.cmake
new file mode 100644
index 0000000000..806f0b60f2
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/TARGET_RUNTIME_DLLS.cmake
@@ -0,0 +1,37 @@
+enable_language(C)
+
+add_executable(exe main.c)
+add_library(lib1 SHARED lib1.c)
+add_library(lib2 SHARED lib2.c)
+add_library(lib3 SHARED lib3.c)
+add_library(static STATIC static.c)
+add_library(imported SHARED IMPORTED)
+set_property(TARGET imported PROPERTY IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/imported.dll")
+set_property(TARGET imported PROPERTY IMPORTED_IMPLIB "${CMAKE_SOURCE_DIR}/imported.lib")
+add_library(imported2 SHARED IMPORTED)
+if(NOT WIN32 AND NOT CYGWIN)
+ set_property(TARGET imported2 PROPERTY IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/imported2.dll")
+endif()
+set_property(TARGET imported2 PROPERTY IMPORTED_IMPLIB "${CMAKE_SOURCE_DIR}/imported2.lib")
+
+target_link_libraries(exe PRIVATE lib1 static imported imported2)
+target_link_libraries(lib1 PRIVATE lib2)
+target_link_libraries(lib1 INTERFACE lib3)
+
+set(expected_dlls "")
+if(WIN32 OR CYGWIN)
+ set(expected_dlls
+ "$<TARGET_FILE:lib1>"
+ "$<TARGET_FILE:imported>"
+ "$<TARGET_FILE:lib3>"
+ "$<TARGET_FILE:lib2>"
+ )
+endif()
+
+set(content "check_genex(\"${expected_dlls}\" \"$<TARGET_RUNTIME_DLLS:exe>\")\n")
+set(condition)
+get_property(multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(multi_config)
+ set(condition CONDITION "$<CONFIG:Debug>")
+endif()
+file(GENERATE OUTPUT "${CMAKE_BINARY_DIR}/dlls.cmake" CONTENT "${content}" ${condition})
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/lib1.c b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/lib1.c
new file mode 100644
index 0000000000..524b5b22fa
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/lib1.c
@@ -0,0 +1,12 @@
+#ifdef _WIN32
+__declspec(dllimport)
+#endif
+ extern void lib2(void);
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+ void lib1(void)
+{
+ lib2();
+}
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/lib2.c b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/lib2.c
new file mode 100644
index 0000000000..e145117477
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/lib2.c
@@ -0,0 +1,6 @@
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+ void lib2(void)
+{
+}
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/lib3.c b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/lib3.c
new file mode 100644
index 0000000000..5392f7a3c8
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/lib3.c
@@ -0,0 +1,6 @@
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+ void lib3(void)
+{
+}
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/main.c b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/main.c
new file mode 100644
index 0000000000..8488f4e58f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/main.c
@@ -0,0 +1,4 @@
+int main(void)
+{
+ return 0;
+}
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/static.c b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/static.c
new file mode 100644
index 0000000000..7f5dab50a7
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/static.c
@@ -0,0 +1,3 @@
+void static_func(void)
+{
+}
diff --git a/Tests/RunCMake/ObjectLibrary/BadSourceExpression3-stderr.txt b/Tests/RunCMake/ObjectLibrary/BadSourceExpression3-stderr.txt
index 4dbd861442..6a7c17167b 100644
--- a/Tests/RunCMake/ObjectLibrary/BadSourceExpression3-stderr.txt
+++ b/Tests/RunCMake/ObjectLibrary/BadSourceExpression3-stderr.txt
@@ -3,7 +3,7 @@ CMake Error at BadSourceExpression3.cmake:2 \(add_library\):
\$<TARGET_OBJECTS:NotObjLib>
- Objects of target "NotObjLib" referenced but is not an allowed library
- types \(EXECUTABLE, STATIC, SHARED, MODULE, OBJECT\).
+ Objects of target "NotObjLib" referenced but is not one of the allowed
+ target types \(EXECUTABLE, STATIC, SHARED, MODULE, OBJECT\).
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/TargetObjects/NotObjlibTarget-stderr.txt b/Tests/RunCMake/TargetObjects/NotObjlibTarget-stderr.txt
index 77c4afd081..9145a56e64 100644
--- a/Tests/RunCMake/TargetObjects/NotObjlibTarget-stderr.txt
+++ b/Tests/RunCMake/TargetObjects/NotObjlibTarget-stderr.txt
@@ -3,7 +3,7 @@ CMake Error at NotObjlibTarget.cmake:[0-9]+ \(file\):
\$<TARGET_OBJECTS:IFaceLib>
- Objects of target "IFaceLib" referenced but is not an allowed library types
- \(EXECUTABLE, STATIC, SHARED, MODULE, OBJECT\).
+ Objects of target "IFaceLib" referenced but is not one of the allowed
+ target types \(EXECUTABLE, STATIC, SHARED, MODULE, OBJECT\).
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)