Commit d34d28e6 authored by Kyle Edwards's avatar Kyle Edwards

Genex: Add TARGET_RUNTIME_DLLS genex

Co-Authored-by: Brad King's avatarBrad King <brad.king@kitware.com>
parent e742cf2c
......@@ -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
......
runtime-dll-deps
----------------
* A new :genex:`TARGET_RUNTIME_DLLS` generator expression was added.
......@@ -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.
......
......@@ -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;
......
......@@ -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"
......@@ -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 },
......
......@@ -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
{
......
......@@ -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;
......
......@@ -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)
......
cmake_minimum_required(VERSION 3.19)
project(${RunCMake_TEST} NONE)
include(${RunCMake_TEST}.cmake)
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)
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")
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\)
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})
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\)
enable_language(C)
add_library(lib1 SHARED lib1.c)
target_link_libraries(lib1 PRIVATE $<TARGET_RUNTIME_DLLS:lib1>)
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\)
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>)
enable_language(C)
add_library(lib1 SHARED lib1.c)
add_library(lib2 SHARED lib2.c)
target_link_libraries(lib1 PRIVATE $<TARGET_RUNTIME_DLLS:lib2>)
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})
#ifdef _WIN32
__declspec(dllimport)
#endif
extern void lib2(void);
#ifdef _WIN32
__declspec(dllexport)
#endif
void lib1(void)
{
lib2();
}
#ifdef _WIN32
__declspec(dllexport)
#endif
void lib2(void)
{
}
#ifdef _WIN32
__declspec(dllexport)
#endif
void lib3(void)
{
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment