GenEx: Evaluate LINK_LIBRARIES target properties transitively

The `LINK_LIBRARIES` and `INTERFACE_LINK_LIBRARIES` target properties
establish the graph of link dependencies used to propagate usage
requirements transitively.  Therefore the `$<TARGET_PROPERTY:...>`
generator expression should evaluate them transitively as it does for
other transitive properties.  Add policy CMP0189 for compatibility.

Fixes: #26709
Issue: #12435
This commit is contained in:
Brad King 2025-02-24 11:43:47 -05:00
parent 63788c7568
commit b3da9c6d60
14 changed files with 218 additions and 15 deletions

View File

@ -1919,7 +1919,10 @@ The expressions have special evaluation rules for some properties:
:prop_tgt:`INTERFACE_LINK_LIBRARIES` *including* entries guarded by the
:genex:`LINK_ONLY` generator expression. See policy :policy:`CMP0166`.
Evaluation of :prop_tgt:`LINK_LIBRARIES` itself is not transitive.
.. versionchanged:: 4.1
Evaluation of :prop_tgt:`LINK_LIBRARIES` itself is now transitive.
See policy :policy:`CMP0189`.
:ref:`Target Usage Requirement Properties <Target Usage Requirements>`
These evaluate as a :ref:`semicolon-separated list <CMake Language Lists>`
@ -1936,7 +1939,10 @@ The expressions have special evaluation rules for some properties:
*including* entries guarded by the :genex:`LINK_ONLY` generator expression.
See policy :policy:`CMP0166`.
Evaluation of :prop_tgt:`INTERFACE_LINK_LIBRARIES` itself is not transitive.
.. versionchanged:: 4.1
Evaluation of :prop_tgt:`INTERFACE_LINK_LIBRARIES` itself is now
transitive. See policy :policy:`CMP0189`.
:ref:`Custom Transitive Properties`
.. versionadded:: 3.30

View File

@ -98,6 +98,7 @@ Policies Introduced by CMake 4.1
.. toctree::
:maxdepth: 1
CMP0189: TARGET_PROPERTY evaluates LINK_LIBRARIES properties transitively. </policy/CMP0189>
CMP0188: The FindGCCXML module is removed. </policy/CMP0188>
CMP0187: Include source file without an extension after the same name with an extension. </policy/CMP0187>
CMP0186: Regular expressions match ^ at most once in repeated searches. </policy/CMP0186>

30
Help/policy/CMP0189.rst Normal file
View File

@ -0,0 +1,30 @@
CMP0189
-------
.. versionadded:: 4.1
:genex:`TARGET_PROPERTY` evaluates ``LINK_LIBRARIES`` properties transitively.
The :prop_tgt:`LINK_LIBRARIES` and :prop_tgt:`INTERFACE_LINK_LIBRARIES`
target properties record link dependencies through which the
:genex:`TARGET_PROPERTY` generator expression evaluates transitive properties.
However, in CMake 4.0 and below, the properties themselves were not evaluated
transitively. CMake 4.1 and above prefer to evaluate the
:prop_tgt:`LINK_LIBRARIES` and :prop_tgt:`INTERFACE_LINK_LIBRARIES`
target properties transitively because they are among the
:ref:`build specification <Target Build Specification>` and
:ref:`usage requirement <Target Usage Requirements>` properties, respectively.
This policy provides compatibility for projects that have not been updated to
expect the new behavior.
The ``OLD`` behavior of this policy is for :genex:`TARGET_PROPERTY` to not
evaluate :prop_tgt:`LINK_LIBRARIES` and :prop_tgt:`INTERFACE_LINK_LIBRARIES`
transitively. The ``NEW`` behavior is for :genex:`TARGET_PROPERTY` to
evaluate :prop_tgt:`LINK_LIBRARIES` and :prop_tgt:`INTERFACE_LINK_LIBRARIES`
transitively.
.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.1
.. |WARNS_OR_DOES_NOT_WARN| replace:: does *not* warn
.. include:: STANDARD_ADVICE.txt
.. include:: DEPRECATED.txt

View File

@ -0,0 +1,6 @@
genex-transitive-link-libraries
-------------------------------
* The :genex:`TARGET_PROPERTY` generator expression now evaluates the
:prop_tgt:`LINK_LIBRARIES` and :prop_tgt:`INTERFACE_LINK_LIBRARIES`
target properties transitively. See policy :policy:`CMP0189`.

View File

@ -45,6 +45,7 @@ std::map<cm::string_view, TransitiveProperty> const
{ "INTERFACE_INCLUDE_DIRECTORIES"_s, UseTo::Compile } },
{ "LINK_DEPENDS"_s, { "INTERFACE_LINK_DEPENDS"_s, UseTo::Link } },
{ "LINK_DIRECTORIES"_s, { "INTERFACE_LINK_DIRECTORIES"_s, UseTo::Link } },
{ "LINK_LIBRARIES"_s, { "INTERFACE_LINK_LIBRARIES"_s, UseTo::Link } },
{ "LINK_OPTIONS"_s, { "INTERFACE_LINK_OPTIONS"_s, UseTo::Link } },
{ "PRECOMPILE_HEADERS"_s,
{ "INTERFACE_PRECOMPILE_HEADERS"_s, UseTo::Compile } },
@ -196,6 +197,13 @@ cmGeneratorTarget::IsTransitiveProperty(
prop = prop.substr(kINTERFACE_.length());
}
auto i = BuiltinTransitiveProperties.find(prop);
if (i != BuiltinTransitiveProperties.end() &&
// Look up CMP0189 in the context where evaluation occurs,
// not where the target was created.
lg->GetPolicyStatus(cmPolicies::CMP0189) != cmPolicies::NEW &&
prop == "LINK_LIBRARIES"_s) {
i = BuiltinTransitiveProperties.end();
}
if (i != BuiltinTransitiveProperties.end()) {
result = i->second;
if (result->Usage != cmGeneratorTarget::UseTo::Compile) {

View File

@ -563,7 +563,10 @@ class cmMakefile;
"Include source file without an extension after the same name with " \
"an extension.", \
4, 1, 0, WARN) \
SELECT(POLICY, CMP0188, "The FindGCCXML module is removed.", 4, 1, 0, WARN)
SELECT(POLICY, CMP0188, "The FindGCCXML module is removed.", 4, 1, 0, WARN) \
SELECT(POLICY, CMP0189, \
"TARGET_PROPERTY evaluates LINK_LIBRARIES properties transitively.", \
4, 1, 0, WARN)
#define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
#define CM_FOR_EACH_POLICY_ID(POLICY) \

View File

@ -0,0 +1,57 @@
cmake_policy(SET CMP0189 NEW)
set(out "${CMAKE_CURRENT_BINARY_DIR}/out-$<CONFIG>.txt")
file(GENERATE OUTPUT "${out}" CONTENT "# file(GENERATE) produced:
${in_LINK_LIBRARIES}
")
add_custom_target(check-CMP0189-NEW ALL VERBATIM
COMMAND ${CMAKE_COMMAND} -Dconfig=$<CONFIG> -Dout=${out} -P${CMAKE_CURRENT_SOURCE_DIR}/check.cmake
COMMAND check-args
"$<TARGET_PROPERTY:iface1,LINK_LIBRARIES>" ""
"$<TARGET_PROPERTY:iface1,INTERFACE_LINK_LIBRARIES>" ""
"$<TARGET_PROPERTY:iface2,LINK_LIBRARIES>" ""
"$<TARGET_PROPERTY:iface2,INTERFACE_LINK_LIBRARIES>" "iface1"
"$<TARGET_PROPERTY:static1,LINK_LIBRARIES>" "iface2;iface1"
"$<TARGET_PROPERTY:static1,INTERFACE_LINK_LIBRARIES>" "iface2;iface1"
"$<TARGET_PROPERTY:CustomTransitiveProperties,LINK_LIBRARIES>" "static1;object1;iface2;iface1;iface2"
"$<TARGET_PROPERTY:CustomTransitiveProperties,INTERFACE_LINK_LIBRARIES>" ""
COMMAND check-args
"$<TARGET_PROPERTY:iface10,LINK_LIBRARIES>" ""
"$<TARGET_PROPERTY:iface10,INTERFACE_LINK_LIBRARIES>" ""
"$<TARGET_PROPERTY:iface11,LINK_LIBRARIES>" ""
"$<TARGET_PROPERTY:iface11,INTERFACE_LINK_LIBRARIES>" "iface10"
"$<TARGET_PROPERTY:static10,LINK_LIBRARIES>" "iface11;iface10"
# _/ \__
# / \
# "static10[iface11];iface11[iface10]"
"$<TARGET_PROPERTY:static10,INTERFACE_LINK_LIBRARIES>" "iface11;iface10"
"$<TARGET_PROPERTY:static11,LINK_LIBRARIES>" "static10;iface11;iface11;iface10"
# __/ __/ \__ \__________
# / / \ \
# "static11[static10;iface11];static10[iface11;iface11[iface10]]"
"$<TARGET_PROPERTY:static11,INTERFACE_LINK_LIBRARIES>" "static10;iface11;iface11;iface10"
"$<TARGET_PROPERTY:main10,LINK_LIBRARIES>" "static11;static10;static10;iface11;iface11;iface10"
# _______/ _______/ | | \______ \______________
# / / | | \ \
# "main10[static11;static10];static11[static10;iface11;static10[iface11;iface11[iface10]]]"
"$<TARGET_PROPERTY:main10,INTERFACE_LINK_LIBRARIES>" ""
COMMAND check-args
"$<TARGET_PROPERTY:iface20,LINK_LIBRARIES>" ""
"$<TARGET_PROPERTY:iface20,INTERFACE_LINK_LIBRARIES>" ""
"$<TARGET_PROPERTY:iface21,LINK_LIBRARIES>" ""
"$<TARGET_PROPERTY:iface21,INTERFACE_LINK_LIBRARIES>" "iface20"
"$<TARGET_PROPERTY:static20,LINK_LIBRARIES>" "iface21;iface20"
# _/ \__
# / \
# "static20[iface21];iface21[iface20]"
"$<TARGET_PROPERTY:static20,INTERFACE_LINK_LIBRARIES>" "iface21;iface20"
"$<TARGET_PROPERTY:static21,LINK_LIBRARIES>" "static20;iface21;iface21;iface20"
# __/ __/ \__ \__________
# / / \ \
# "static21[static20;iface21];static20[iface21;iface21[iface20]]"
"$<TARGET_PROPERTY:static21,INTERFACE_LINK_LIBRARIES>" "static20;iface21;iface21;iface20"
"$<TARGET_PROPERTY:main20,LINK_LIBRARIES>" "static21;static20;static20;iface21;iface21;iface20"
# _______/ _______/ | | \______ \______________
# / / | | \ \
# "main20[static21;static20];static21[static20;iface21;static20[iface21;iface21[iface20]]]"
"$<TARGET_PROPERTY:main20,INTERFACE_LINK_LIBRARIES>" ""
)

View File

@ -0,0 +1,32 @@
set(expect [[
# file\(GENERATE\) produced:
iface1 LINK_LIBRARIES: ''
iface1 INTERFACE_LINK_LIBRARIES: ''
iface2 LINK_LIBRARIES: ''
iface2 INTERFACE_LINK_LIBRARIES: 'iface1'
static1 LINK_LIBRARIES: 'iface2;iface1'
static1 INTERFACE_LINK_LIBRARIES: 'iface2;iface1'
main LINK_LIBRARIES: 'static1;object1;iface2;iface1;iface2'
main INTERFACE_LINK_LIBRARIES: ''
iface10 LINK_LIBRARIES: ''
iface10 INTERFACE_LINK_LIBRARIES: ''
iface11 LINK_LIBRARIES: ''
iface11 INTERFACE_LINK_LIBRARIES: 'iface10'
static10 LINK_LIBRARIES: 'iface11;iface10'
static10 INTERFACE_LINK_LIBRARIES: 'iface11;iface10'
static11 LINK_LIBRARIES: 'static10;iface11;iface11;iface10'
static11 INTERFACE_LINK_LIBRARIES: 'static10;iface11;iface11;iface10'
main10 LINK_LIBRARIES: 'static11;static10;static10;iface11;iface11;iface10'
main10 INTERFACE_LINK_LIBRARIES: ''
iface20 LINK_LIBRARIES: ''
iface20 INTERFACE_LINK_LIBRARIES: ''
iface21 LINK_LIBRARIES: ''
iface21 INTERFACE_LINK_LIBRARIES: 'iface20'
static20 LINK_LIBRARIES: 'iface21;iface20'
static20 INTERFACE_LINK_LIBRARIES: 'iface21;iface20'
static21 LINK_LIBRARIES: 'static20;iface21;iface21;iface20'
static21 INTERFACE_LINK_LIBRARIES: 'static20;iface21;iface21;iface20'
main20 LINK_LIBRARIES: 'static21;static20;static20;iface21;iface21;iface20'
main20 INTERFACE_LINK_LIBRARIES: ''
]])
include(${CMAKE_CURRENT_LIST_DIR}/../check-common.cmake)

View File

@ -102,6 +102,17 @@ target_link_libraries(static11 PRIVATE static10 iface11)
add_executable(main10 main10.c)
target_link_libraries(main10 PRIVATE static11 static10)
# Test CMP0189 OLD and NEW behavior.
add_library(iface20 INTERFACE)
add_library(iface21 INTERFACE)
target_link_libraries(iface21 INTERFACE iface20)
add_library(static20 STATIC static20.c)
target_link_libraries(static20 PRIVATE iface21)
add_library(static21 STATIC static21.c)
target_link_libraries(static21 PRIVATE static20 iface21)
add_executable(main20 main20.c)
target_link_libraries(main20 PRIVATE static21 static20)
# Test TRANSITIVE_*_PROPERTY evaluation outside of usage requirements.
add_executable(check-args check-args.c)
set(out "${CMAKE_CURRENT_BINARY_DIR}/out-$<CONFIG>.txt")
@ -158,6 +169,16 @@ static11 LINK_LIBRARIES: '$<TARGET_PROPERTY:static11,LINK_LIBRARIES>'
static11 INTERFACE_LINK_LIBRARIES: '$<TARGET_PROPERTY:static11,INTERFACE_LINK_LIBRARIES>'
main10 LINK_LIBRARIES: '$<TARGET_PROPERTY:main10,LINK_LIBRARIES>'
main10 INTERFACE_LINK_LIBRARIES: '$<TARGET_PROPERTY:main10,INTERFACE_LINK_LIBRARIES>'
iface20 LINK_LIBRARIES: '$<TARGET_PROPERTY:iface20,LINK_LIBRARIES>'
iface20 INTERFACE_LINK_LIBRARIES: '$<TARGET_PROPERTY:iface20,INTERFACE_LINK_LIBRARIES>'
iface21 LINK_LIBRARIES: '$<TARGET_PROPERTY:iface21,LINK_LIBRARIES>'
iface21 INTERFACE_LINK_LIBRARIES: '$<TARGET_PROPERTY:iface21,INTERFACE_LINK_LIBRARIES>'
static20 LINK_LIBRARIES: '$<TARGET_PROPERTY:static20,LINK_LIBRARIES>'
static20 INTERFACE_LINK_LIBRARIES: '$<TARGET_PROPERTY:static20,INTERFACE_LINK_LIBRARIES>'
static21 LINK_LIBRARIES: '$<TARGET_PROPERTY:static21,LINK_LIBRARIES>'
static21 INTERFACE_LINK_LIBRARIES: '$<TARGET_PROPERTY:static21,INTERFACE_LINK_LIBRARIES>'
main20 LINK_LIBRARIES: '$<TARGET_PROPERTY:main20,LINK_LIBRARIES>'
main20 INTERFACE_LINK_LIBRARIES: '$<TARGET_PROPERTY:main20,INTERFACE_LINK_LIBRARIES>'
]====])
file(GENERATE OUTPUT "${out}" CONTENT "# file(GENERATE) produced:
${in_CUSTOM}
@ -210,4 +231,17 @@ add_custom_target(check ALL VERBATIM
# / / | | \ \
# "main10[static11;static10];static11[static10;iface11;static10[iface11;iface11[iface10]]]"
"$<TARGET_PROPERTY:main10,INTERFACE_LINK_LIBRARIES>" ""
COMMAND check-args
"$<TARGET_PROPERTY:iface20,LINK_LIBRARIES>" ""
"$<TARGET_PROPERTY:iface20,INTERFACE_LINK_LIBRARIES>" ""
"$<TARGET_PROPERTY:iface21,LINK_LIBRARIES>" ""
"$<TARGET_PROPERTY:iface21,INTERFACE_LINK_LIBRARIES>" "iface20"
"$<TARGET_PROPERTY:static20,LINK_LIBRARIES>" "iface21"
"$<TARGET_PROPERTY:static20,INTERFACE_LINK_LIBRARIES>" "$<LINK_ONLY:iface21$<ANGLE-R>"
"$<TARGET_PROPERTY:static21,LINK_LIBRARIES>" "static20;iface21"
"$<TARGET_PROPERTY:static21,INTERFACE_LINK_LIBRARIES>" "$<LINK_ONLY:static20$<ANGLE-R>;$<LINK_ONLY:iface21$<ANGLE-R>"
"$<TARGET_PROPERTY:main20,LINK_LIBRARIES>" "static21;static20"
"$<TARGET_PROPERTY:main20,INTERFACE_LINK_LIBRARIES>" ""
)
add_subdirectory(CMP0189)

View File

@ -0,0 +1,12 @@
string(REGEX REPLACE "\r\n" "\n" expect "${expect}")
string(REGEX REPLACE "\n+$" "" expect "${expect}")
file(READ "${out}" actual)
string(REGEX REPLACE "\r\n" "\n" actual "${actual}")
string(REGEX REPLACE "\n+$" "" actual "${actual}")
if(NOT actual MATCHES "^${expect}$")
string(REPLACE "\n" "\n expect> " expect " expect> ${expect}")
string(REPLACE "\n" "\n actual> " actual " actual> ${actual}")
message(FATAL_ERROR "Expected file(GENERATE) output:\n${expect}\ndoes not match actual output:\n${actual}")
endif()

View File

@ -51,16 +51,15 @@ static11 LINK_LIBRARIES: 'static10;iface11;iface11;iface10'
static11 INTERFACE_LINK_LIBRARIES: 'static10;iface11;iface11;iface10'
main10 LINK_LIBRARIES: 'static11;static10;static10;iface11;iface11;iface10'
main10 INTERFACE_LINK_LIBRARIES: ''
iface20 LINK_LIBRARIES: ''
iface20 INTERFACE_LINK_LIBRARIES: ''
iface21 LINK_LIBRARIES: ''
iface21 INTERFACE_LINK_LIBRARIES: 'iface20'
static20 LINK_LIBRARIES: 'iface21'
static20 INTERFACE_LINK_LIBRARIES: '\$<LINK_ONLY:iface21>'
static21 LINK_LIBRARIES: 'static20;iface21'
static21 INTERFACE_LINK_LIBRARIES: '\$<LINK_ONLY:static20>;\$<LINK_ONLY:iface21>'
main20 LINK_LIBRARIES: 'static21;static20'
main20 INTERFACE_LINK_LIBRARIES: ''
]])
string(REGEX REPLACE "\r\n" "\n" expect "${expect}")
string(REGEX REPLACE "\n+$" "" expect "${expect}")
file(READ "${out}" actual)
string(REGEX REPLACE "\r\n" "\n" actual "${actual}")
string(REGEX REPLACE "\n+$" "" actual "${actual}")
if(NOT actual MATCHES "^${expect}$")
string(REPLACE "\n" "\n expect> " expect " expect> ${expect}")
string(REPLACE "\n" "\n actual> " actual " actual> ${actual}")
message(FATAL_ERROR "Expected file(GENERATE) output:\n${expect}\ndoes not match actual output:\n${actual}")
endif()
include(${CMAKE_CURRENT_LIST_DIR}/check-common.cmake)

View File

@ -0,0 +1,7 @@
extern int static20(void);
extern int static21(void);
int main(void)
{
return static20() + static21();
}

View File

@ -0,0 +1,4 @@
int static20(void)
{
return 0;
}

View File

@ -0,0 +1,4 @@
int static21(void)
{
return 0;
}