Re-export private compile definitions as interface definitions (cmake 3.12)

Given the following CMakeLists, I would expect the ‘genexdebug’ target to print out “foo bar” when run, but it only prints “foo”. Can anyone tell me why? What’s the best way to check for specific private definitions, and to reexport them as interface definitions if they exist?

Thanks for your help!

cmake_minimum_required(VERSION 3.12)
project(test)

add_library(foo INTERFACE)
target_compile_definitions(foo INTERFACE foo)

add_library(bar INTERFACE)
target_compile_definitions(bar INTERFACE bar)

set(source ${CMAKE_CURRENT_BINARY_DIR}/main.cpp)
file(TOUCH ${source})

add_library(combined ${source})
target_link_libraries(combined PRIVATE foo bar)

set(combined_defs $<TARGET_PROPERTY:combined,COMPILE_DEFINITIONS>)

foreach(def IN ITEMS foo bar)
    target_compile_definitions(combined INTERFACE $<$<IN_LIST:${def},${combined_defs}>:${def}>)
endforeach()

add_custom_target(genexdebug COMMAND ${CMAKE_COMMAND} -E echo $<TARGET_PROPERTY:combined,INTERFACE_COMPILE_DEFINITIONS>)

This seems to be a bug in CMake.

It took me quite some time to debug, but this is what is happening:

  • $<TARGET_PROPERTY:combined,INTERFACE_COMPILE_DEFINITIONS> is parsed and evaluated to
    $<$<IN_LIST:foo,$<TARGET_PROPERTY:combined,COMPILE_DEFINITIONS>>:foo>;$<$<IN_LIST:bar,$<TARGET_PROPERTY:combined,COMPILE_DEFINITIONS>>:bar>.

  • The first occurrence of $<TARGET_PROPERTY:combined,COMPILE_DEFINITIONS> is parsed and evaluated to "foo;bar" as expected. So we get $<$<IN_LIST:foo,foo;bar>:foo> which is evaluated to "foo", all good.

  • The second occurrence of $<TARGET_PROPERTY:combined,COMPILE_DEFINITIONS> is parsed and evaluated to "", not good. What is happening?

    • CMake starts by looking at the COMPILE_DEFINITIONS property of the combined target itself (no problem here).
    • Then CMake looks at the INTERFACE_COMPILE_DEFINITIONS property of the targets that the combined target links to (foo and bar targets). When evaluating INTERFACE_COMPILE_DEFINITIONS on both foo and bar targets, cmGeneratorTarget::EvaluateInterfaceProperty reaches the cmGeneratorExpressionDAGChecker::ALREADY_SEEN case and returns an empty string.

I don’t know how to fix this bug, but to workaround it you’ll need to find a way to call target_compile_definitions(combined INTERFACE ...) with a generator expression that contains $<TARGET_PROPERTY:combined,COMPILE_DEFINITIONS> only once.

Wow, thanks for the detailed response! I wasn’t expecting a cmake bug…

The 'single evaluation of $<TARGET_PROPERTY:combined,COMPILE_DEFINITIONS>’ is very awkward. I think in cmake 3.15+ the FILTER generator expression would work. However, I can’t think of a way of achieving anything similiar in 3.12, as the list-processing generator expressions are more limited in that version. I’m going to sleep on it, but in the meantime, if anyone has any idea how this might be achieved in 3.12, I’d be incredibly grateful!

The result can be done without using generator expressions:

cmake_minimum_required(VERSION 3.12)
project(test)

add_library(foo INTERFACE)
target_compile_definitions(foo INTERFACE foo)

add_library(bar INTERFACE)
target_compile_definitions(bar INTERFACE bar)

set(source ${CMAKE_CURRENT_BINARY_DIR}/main.cpp)
file(TOUCH ${source})

add_library(combined ${source})
target_link_libraries(combined PRIVATE foo bar)

get_property (deps TARGET combined PROPERTY LINK_LIBRARIES)
foreach (dep IN LISTS deps)
  get_property (defs TARGET ${dep} PROPERTY INTERFACE_COMPILE_DEFINITIONS)
  list (FILTER defs INCLUDE REGEX "^(foo|bar)$")
  target_compile_definitions (combined INTERFACE ${defs})
endforeach()

add_custom_target(genexdebug COMMAND ${CMAKE_COMMAND} -E echo "\"$<TARGET_PROPERTY:combined,INTERFACE_COMPILE_DEFINITIONS>\"")

Thanks for the reply! Unfortunately I don’t think this will work for my use-case, as in my project combined is created/configured by a factory function for a custom target type, and users may link extra libraries to it at any point after this factory function has returned. That is, at the point where combined is created, we don’t know the full set of libraries that it will link against, and I was using generator expressions so that the full set of transitive compile definitions would be available.

In this case, apply my example snippet as the end of the main CMakeLists.txt., after all CMakeLists.txt had been processed.