[cmake-developers] Setting include directories via target_link_libraries() ?

Brad King brad.king at kitware.com
Thu Dec 13 13:06:38 EST 2012


On 12/12/2012 05:32 PM, David Cole wrote:
> I strongly agree with Alex here.
> 
> Mysteriously changing the target_link_libraries implementation to
> automatically do a bunch of stuff BY DEFAULT that it didn't used
> to do violates the principle of least surprise big time.

Yes.  Also I think some of the complexity, as discussed in related
threads during the last couple of days, comes from trying to figure
out when to propagate linking (-l), includes (-I), definitions (-D),
and any other part of the usage requirements transitively.

The main distinction between the two approaches is:

* target_link_libraries: Specify link (-l) rules explicitly, get
  other usage requirements (-I/-D) implicitly.

* target_use_interfaces: Specify interfaces explicitly, get all
  usage requirements implicitly (-l/-I/-D).

I think what David and Alex are saying is that the latter is much
clearer.  After your (Steve's) dive into the former approach so
far we've seen that implementation is difficult, especially when
considering compatibility.

Having thought more about how to implement either approach I think
that the latter approach is not only clearer but cleaner too.  I
really like the name "target_use_interfaces" because it reads as
"use the interfaces defined by the following".  Any of the normal
target types can define interface properties.  The proposed
INTERFACE_LIBRARY would be distinct only in that it does not do
anything other than define an interface.

I think the key here is to make the "interface" a first-class
concept that subsumes all usage requirements (-l/-I/-D/etc.).
Transitive closure can be handled at the interface usage level
and the results used to populate the interface components.

I propose the following new command:

 target_use_interfaces(tgt [<PUBLIC|PRIVATE|INTERFACE> tgts...]...)

Forcing use of the keywords handles Daniel's concern about clear
argument separation.  The command will populate target properties:

 USE_INTERFACES           = to build tgt (set by PUBLIC/PRIVATE)
 INTERFACE_USE_INTERFACES = to use tgt   (set by PUBLIC/INTERFACE)

To generate the build rules for a target read its USE_INTERFACES,
follow the INTERFACE_USE_INTERFACES dependency links of those, and
compute the transitive interface closure.  Order it by a simple
topological sort that starts with the original USE_INTERFACES and
adds any dependencies in topological order.  This is how library
ordering already works for linking, but it will be simpler for
interfaces because all dependencies are explicit and cycles are
not allowed.

Note that this says nothing about what is *in* the interfaces.  It
only declares relationships.  We don't even need to distinguish
separate behavior for STATIC v. SHARED libraries.  The distinction
can be made under the hood in populating the interface components.
I'll defer my thoughts on details of defining and exporting each
part of the interface for a future message after we've discussed
the overall approach.

Now, given the interface closure computed as above to build a
target, we simply take (non-transitively) the usage requirements
from each interface and append them to the corresponding build
rules of the target:

  impl.Libraries += iface.Libraries
  impl.Includes  += iface.Includes
  impl.Defines   += iface.Defines
  ...

This can all be done inside C++ structures rather than with
properties and generator expressions because it is only done during
generation.  The final impl.Libraries will still be processed to
compute a final transitive link closure, but that won't add anything
new to the other usage requirements anyway.

So, the final build rules (-l/-I/-D/etc.) for a target will start
with whatever was specified explicitly via target_link_libraries,
include_directories, add_definitions, etc., which take precedence.
Then we append anything from the used interfaces.  The project can
choose whether to use the old approach or the new approach or a
mixture of the two.

-Brad



More information about the cmake-developers mailing list