GenEx: Duplicate transitive target INTERFACE property in other target context lead to incorrect results
Description
The evaluation of a generator expression for transitive interface properties $<TARGET_PROEPRTY:target,INTERFACE_*>
that occurs multiple times in the evaluation of a single generator expression but in the context of a different target leads to empty results after its' first evaluation.
Error Cause
The cmGeneratorTarget
class has an extra evaluation for interface properties cmGeneratorTarget::EvaluateInterfaceProperty
which returns an empty string for already encountered references (see cmGeneratorExpressionDAGChecker::ALREADY_SEEN
). This seems to be a bug.
Possible Fix (?)
Re-evalute the same generator expression multiple times, or cache the result of previous evaluated expressions. I would not prefer the second options, as this seems to introduce overhead for large expression that do not share common expressions. Either by caching too many unused results or by precomputing shared references of interest.
Reproduce
To reproduce the inconsistent behavior, I made a small project file that will only succeed in building if the generator expression with and without root target context will result in the same output.
CMakeLists.txt
cmake_minimum_required(VERSION 3.26)
project(Bug LANGUAGES NONE)
add_library(source INTERFACE)
target_sources(source INTERFACE foo.c bar.cpp)
set(sourceSources $<TARGET_PROPERTY:source,INTERFACE_SOURCES>)
set(cSources $<LIST:FILTER,${sourceSources},INCLUDE,.*\\.c$>)
set(cppSources $<LIST:FILTER,${sourceSources},EXCLUDE,.*\\.c$>)
set(allSources $<LIST:APPEND,${cSources},${cppSources}>)
add_library(sink INTERFACE)
target_sources(sink INTERFACE ${allSources})
set(targetSources $<TARGET_PROPERTY:sink,INTERFACE_SOURCES>)
file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/output_sink.txt CONTENT $<JOIN:${targetSources},\n>)
file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/output_all.txt CONTENT $<JOIN:${allSources},\n>)
set(eqCheck $<STREQUAL:$<JOIN:$<LIST:SORT,${allSources}>,>,$<JOIN:$<LIST:SORT,${targetSources}>,>>)
add_custom_target(check ALL COMMAND ${CMAKE_COMMAND} -E $<IF:${eqCheck},true,false>)
Explanation
The generator expression LIST:APPEND
creates a DAG with a shared sink TARGET_PROPERTY:INTERFACE_SOURCES
. One of the intermediate expressions evaluates the C source, and the other returns the CPP source. This expression works as expected unless the generator expression is assessed in the context of another target. In that case, the second evaluation (filtering the CPP file) of TARGET_PROPERTY:INTERFACE_SOURCES
returns an empty string; thus, only the C file is received.
graph TD;
LIST:APPEND-->FILTER:INCLUDE,.*\\.c$;
LIST:APPEND-->FILTER:EXCLUDE,.*\\.c$;
FILTER:INCLUDE,.*\\.c$ -->TARGET_PROPERTY:INTERFACE_SOURCES;
FILTER:EXCLUDE,.*\\.c$ -->TARGET_PROPERTY:INTERFACE_SOURCES;
After configuring the project, the binary directory will contain two files. The first output_all.txt
contains both source files, and the second output_sink.txt
contains only the C file.
Here, I used an intermediate target sink
with target_sources
as the context to trigger that behavior, but other target contexts also suffice. For example: add_custom_target(check ALL COMMAND ${CMAKE_COMMAND} -E echo $<JOIN:${allSources},\n>)
will only print the C file.