[cmake-developers] Generator Expression self-references now allowed?

Stephen Kelly steveire at gmail.com
Fri Feb 22 11:32:34 EST 2013


Brad King wrote:

> Can't you instead simply have the usage requirements skip appending
> a target's own requirements to itself?  That way the resulting
> generator expression would still not have a self reference.

Yes, but I only realized that after replying to the rest of the mail :).

As it has some useful information, I've left the rest of the reply intact.



> Steve,
> 
> Can you explain the need for 953def1e17f3bbba0aa42037ae15ced011d8fd2a:
> 
>     Fix DAG checker finding cycling dependencies.
> 
>     Before this patch, the following is reported falsely as a
>     self-reference:
> 
>      target_link_libraries(empty2 LINK_PUBLIC empty3)
>      target_link_libraries(empty3 LINK_PUBLIC empty2)
> 
>      add_custom_target(...
>         -DINCLUDES=$<TARGET_PROPERTY:empty2,INTERFACE_INCLUDE_DIRECTORIES>
>      )

Before the patch, this was not allowed:

 add_library(foo ...)
 add_library(bat ...)

 set_property(TARGET foo PROPERTY 
   INCLUDE_DIRECTORIES 
     $<TARGET_PROPERTY:bat,INTERFACE_INCLUDE_DIRECTORIES>
 ) 
 set_property(TARGET bat PROPERTY 
   INTERFACE_INCLUDE_DIRECTORIES 
     $<TARGET_PROPERTY:bat,INTERFACE_INCLUDE_DIRECTORIES>
 ) 

Note that the INTERFACE_INCLUDE_DIRECTORIES of bat depends on its own 
INTERFACE_INCLUDE_DIRECTORIES.

The generator expressions allow cycles involving another node, but disallow 
direct self-loops. So this is allowed:

 add_library(foo ...)
 add_library(bat ...)
 add_library(zar ...)

 set_property(TARGET foo PROPERTY 
   INCLUDE_DIRECTORIES 
     $<TARGET_PROPERTY:bat,INTERFACE_INCLUDE_DIRECTORIES>
 ) 
 set_property(TARGET bat PROPERTY 
   INTERFACE_INCLUDE_DIRECTORIES 
     $<TARGET_PROPERTY:zar,INTERFACE_INCLUDE_DIRECTORIES>
 ) 
 set_property(TARGET zar PROPERTY 
   INTERFACE_INCLUDE_DIRECTORIES 
     $<TARGET_PROPERTY:bat,INTERFACE_INCLUDE_DIRECTORIES>
 ) 

The foo INCLUDE_DIRECTORIES will include the interface includes from zar and 
bat, and there is no error. The INCLUDE_DIRECTORIES of foo are determined by 
cmTarget::GetIncludeDirectories, which creates a DAGChecker recording that 
the graph starts with target=foo,property=INCLUDE_DIRECTORIES. 

Similarly, the INCLUDE_DIRECTORIES of bat are determined by creating a 
DagChecker with target=bat,property=INCLUDE_DIRECTORIES, following to zar, 
and then to target=bat,property=INTERFACE_INCLUDE_DIRECTORIES and and 
aborting when target=zar,property=INTERFACE_INCLUDE_DIRECTORIES is 
encountered again. Because there is still another dagchecker parent (the one 
created in cmTarget::GetIncludeDirectories), this is correctly determined to 
be a cycle by cmGeneratorExpressionDAGChecker::checkGraph.

(target -- property -- parentDagChecker)

 bar -- INTERFACE_INCLUDE_DIRECTORIES -- 0x7fff5b38efd0
 zat -- INTERFACE_INCLUDE_DIRECTORIES -- 0x7fff5b38f750
 bar -- INTERFACE_INCLUDE_DIRECTORIES -- 0x7fff5b38edd0
 foo -- INCLUDE_DIRECTORIES -- 0x0    # null parent, abort 

In the case of evaluating a generator expression which reads the 
INCLUDE_DIRECTORIES of bar, such as in a add_custom_target invocation, 
things work similarly, but the 'top' dag checker is created in 
TargetProprtyNode. 

 add_custom_command(... 
   $<TARGET_PROPERTY:bar,INCLUDE_DIRECTORIES> 
 ...)

cmTarget::GetIncludeDirectories is not called.

The case where the INTERFACE includes are used in a genex is not the same 
though:

 add_custom_command(... 
   $<TARGET_PROPERTY:bar,INTERFACE_INCLUDE_DIRECTORIES> 
 ...)

Here the top DagChecker is created with 
target=bar,property=INTERFACE_INCLUDE_DIRECTORIES, and again the zat 
dependency is followed, which reaches 
target=bar,property=INTERFACE_INCLUDE_DIRECTORIES again. From that point, 
the DagCheckers are traversed up:

(target -- property -- parentDagChecker)

 bar -- INTERFACE_INCLUDE_DIRECTORIES -- 0x7fff5b38efd0
 zat -- INTERFACE_INCLUDE_DIRECTORIES -- 0x7fff5b38f750
 bar -- INTERFACE_INCLUDE_DIRECTORIES -- 0x0

Previously, the code assumed incorrectly that there would always be a 
INCLUDE_DIRECTORIES node as the parent at the top, because it assumed that 
INCLUDE_DIRECTORIES would only be read through GetIncludeDirectories. So the 
parent check was reasonable at the time. Logically though, it is not 
correct.

Meanwhile, there was another bug that the link interface libraries were not 
queried for includes and defines of bar in cases like 

 add_custom_command(... 
   $<TARGET_PROPERTY:bar,INTERFACE_INCLUDE_DIRECTORIES> 
 ...)

because the code which reads the link interface is only entered if the 
parent DagChecker is non-null. That is fixed in 
a10539b91bcd832c51bc8549e000d530d67de4cb. 

However, as the fix processes the link interface of targets, and as that 
forms a graph from which we get includes, and as the LinkInterfaceLoop test 
has a self-reference which is represented in the graph of DagCheckers, the 
DAG check() correctly now finds the self-reference.

So, if the self-reference is allowed in the LINK_INTERFACE_LIBRARIES of a 
target, it must also be allowed in the INTERFACE_INCLUDE_DIRECTORIES of a 
target.

Hopefully the above is clear. I've updated the branch on my clone with some 
logging output.

> ?  The LinkInterfaceLoop test was added here:
> 
>  http://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=8e756d2b
> 
> to cover a case that was accidentally allowed but that should not be used.
> I'd rather not drop this great safety check for backward-compatibility
> with bad code.

Perhaps a policy can be added for that?



Thanks,

Steve.





More information about the cmake-developers mailing list