mirror of
https://github.com/Kitware/CMake.git
synced 2025-10-18 17:31:57 +08:00
Genex: Add TARGET_RUNTIME_DLLS genex
Co-Authored-by: Brad King <brad.king@kitware.com>
This commit is contained in:
@@ -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
|
||||
|
4
Help/release/dev/runtime-dll-deps.rst
Normal file
4
Help/release/dev/runtime-dll-deps.rst
Normal file
@@ -0,0 +1,4 @@
|
||||
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)
|
||||
|
3
Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/CMakeLists.txt
Normal file
3
Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
cmake_minimum_required(VERSION 3.19)
|
||||
project(${RunCMake_TEST} NONE)
|
||||
include(${RunCMake_TEST}.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)
|
@@ -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")
|
@@ -0,0 +1 @@
|
||||
1
|
@@ -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\)
|
@@ -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})
|
@@ -0,0 +1 @@
|
||||
1
|
@@ -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\)
|
@@ -0,0 +1,4 @@
|
||||
enable_language(C)
|
||||
|
||||
add_library(lib1 SHARED lib1.c)
|
||||
target_link_libraries(lib1 PRIVATE $<TARGET_RUNTIME_DLLS:lib1>)
|
@@ -0,0 +1 @@
|
||||
1
|
@@ -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\)
|
@@ -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>)
|
@@ -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>)
|
@@ -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})
|
12
Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/lib1.c
Normal file
12
Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/lib1.c
Normal file
@@ -0,0 +1,12 @@
|
||||
#ifdef _WIN32
|
||||
__declspec(dllimport)
|
||||
#endif
|
||||
extern void lib2(void);
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
void lib1(void)
|
||||
{
|
||||
lib2();
|
||||
}
|
6
Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/lib2.c
Normal file
6
Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/lib2.c
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
void lib2(void)
|
||||
{
|
||||
}
|
6
Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/lib3.c
Normal file
6
Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/lib3.c
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
void lib3(void)
|
||||
{
|
||||
}
|
4
Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/main.c
Normal file
4
Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/main.c
Normal file
@@ -0,0 +1,4 @@
|
||||
int main(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
3
Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/static.c
Normal file
3
Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/static.c
Normal file
@@ -0,0 +1,3 @@
|
||||
void static_func(void)
|
||||
{
|
||||
}
|
Reference in New Issue
Block a user