How are circular generator expressions evaluated?

Consider the following:

cmake_minimum_required(VERSION 3.21)
project(test)

add_executable(foo foo.cpp)
target_compile_definitions(foo
  PRIVATE "FOO$<TARGET_PROPERTY:bar,COMPILE_DEFINITIONS>") 

add_executable(bar bar.cpp)
target_compile_definitions(bar
  PRIVATE "BAR$<TARGET_PROPERTY:foo,COMPILE_DEFINITIONS>")

This puts -DFOOBAR on foo.cpp's command line and -DBARFOO on bar.cpp's. How does that happen? Why isn’t this an error?

Hmm. That is interesting. I would expect this to be an error as well. I see BadSelfReference tests, but it seems that cycles are not detected in general. This would likely need a policy to resolve.

Cc: @brad.king

1 Like

The implementation avoids repeatedly following the same target multiple times, which is needed even in non-cyclical DAGs. On repeat encounter, evaluation produces an empty string.

I’m not sure what “repeatedly following the same target multiple times” means. If I write instead:

add_executable(foo foo.cpp)
target_compile_definitions(foo
  PRIVATE "FOO$<TARGET_PROPERTY:bar,COMPILE_DEFINITIONS>") 
set_property(TARGET foo PROPERTY COMPILE_DEFINITIONS_2 "BAZ")

add_executable(bar bar.cpp)
target_compile_definitions(bar
  PRIVATE "BAR$<TARGET_PROPERTY:foo,COMPILE_DEFINITIONS_2>")

Then foo gets -DFOOBARBAZ and bar gets -DBARBAZ. It looks like we went foobarfoo just fine.

I meant that the same property on the same target is not repeated.

That makes more sense, thanks.

I don’t believe this is documented anywhere. I think it would be helpful to add some text on the genex evaluation strategy to the docs. For another instance, the fact that all sub-genex are always evaluated prevents using $<IF:...> to check if a string names a target before using it to access a target property (and therefore avoid a fatal error).