[cmake-developers] Generator expressisons in target properties

Stephen Kelly steveire at gmail.com
Tue Nov 6 12:00:07 EST 2012


Stephen Kelly wrote:

> * Port cmGeneratorExpression API to cmTarget
> * Add a way to determine at runtime whether linking-related information is
> being requested at configure-time or generate-time. A property on the
> Makefile might work.
> * Add a const char *config where appropriate to cmTarget API to pass to
> the generator expressions.
> * Return to the depends issue and everything else.
> 

I've done parts of everything in the wip-target-interface branch in my 
clone. 

I'll want to get everything working with the relevant INTERFACE target 
properties and the new INTERFACE_LIBRARY type before submitting any of it, 
to make sure that we can get it all into one release with confidence.

Using the branch, I'm able to use Qt5Widgets just by using it with 
target_link_libraries without specifying the include directories, compile 
definitions, -fPIC flag or Qt5::WinMain in my CMakeLists.txt. Even if the 
implementation changes, this will remain my goal: 

https://codereview.qt-project.org/#patch,all_unified,38876,2


1) Policy

The include directories and compile definitions are set by the 
target_link_libraries command. It might make sense to add a policy for that 
feature, as otherwise the order of include directories would change, eg:

 add_executable(foo ...)
 target_link_libraries(foo bar bat)
 include_directories(${bat_INCLUDE_DIRS} ${bar_INCLUDE_DIRS}) 



2) Transitivity

The topic branch also introduces the use of generator-expression-based 
transitive behavior for the include directories and compile definitions. For 
linking, the transitive behavior is not based on generator expressions, but 
relies on the existing cmComputeLinkDepends. The link order and the include 
order should not necessarily be the same, so I think it's ok for the 
includes and libraries to be processed in a different way. The transitive 
includes still need to be specified as I described before.

I would like target_link_libraries to be the 'universal' use function. 
Unfortunately at the moment it is not possible to use it with an imported 
target, so in Qt I couldn't do this:

 target_link_libraries(Qt5::Gui LINK_INTERFACE_LIBRARIES Qt5::WinMain)

but I had to use set_property instead. 

This doesn't matter in the above case in Qt as Qt5::WinMain has no public 
includes or compile definition requirements. For another library in a 
similar situation though, there might be a need to add include directories 
or compile definitions. Currently that requires multiple set_property calls, 
which is not convenient, makes tll() non-universal (We can't document 
'always just use it'), and the user might not call set_property as many 
times as they should (ie, currently for both include directories and compile 
definitions, but that might be expanded in the future, which could be 
encapsulated in tll()).

I propose that 

 target_link_libraries(foo LINK_INTERFACE_LIBRARIES bar)

should be allowed for an imported foo, but 

 target_link_libraries(foo bar)

remains not-allowed.

As I introduced the INTERFACE_LINK_LIBRARIES property, I also introduced a 
backward-compatibility feature to also read the old IMPORTED*_<CONFIG> 
properties. I don't know enough about cmComputeLinkDepends to know if that's 
ok.


3) INTERFACE_LIBRARY depends

Also on the subject of link depends, the INTERFACE library type might have a 
funny behavior. 

I asked before for more information about why direct depends are separated 
from transitive ones. The reason seems to be related to ordering of the 
depends when invoking the linker, which is relevant for static libraries 
(but not shared libraries, right?).

So, say I have this:

 add_library(iface INTERFACE)
 set_property(TARGET iface PROPERTY INTERFACE_LINK_LIBRARIES foo bar)

 add_executable(test_exe ...)

And I do this:

 target_link_libraries(test_exe directdep1 iface directdep2)

It is not really equivalent to this:

 target_link_libraries(test_exe directdep1 foo bar directdep2)

But maybe it should be, or maybe I would want it to be? Maybe we can make it 
possible to populate the non-INTERFACE LINK_LIBRARIES property on libraries 
of type INTERFACE_LIBRARY to use the contents as direct depends of test_exe?


4) GenEx context and memoization

Finally in my topic, I created a patch to remove a lot of memoization in 
cmake and pass a contextual target from cmComputeLinkDepends. This is 
necessary because the generator expression transitive feature are not used 
for linking. I need it so that I can 

 target_link_libraries(Qt5::Gui 
   LINK_INTERFACE_LIBRARIES   
     $<$<TARGET_PROPERTY:WIN32_EXECUTABLE>:Qt5::WinMain>
 )

such that the $<TARGET_PROPERTY:WIN32_EXECUTABLE> is evaluated in the 
context of the caller, not in the context of Qt5::Gui.

I'm thinking of simply removing the memoization from cmTarget. The mappings 
need to be cleared multiple times during configure-time, and now they will 
depend not only on the config (which most of the memoizations use as the 
key), but also the target, which would make the memoization more complex to 
achieve, and/or make the code more complex to forego memoization when it is 
really not wanted.

Any thoughts on that?


5) Boost as a use-case and cicular depends

Finally, I've been looking a little bit at boost. 

They are in the process of modularizing and assessing CMake, and they're 
interested in these features too (they're a good fit), and they linked to a 
previous thread on the subject of usage requirements too on their wiki page

https://svn.boost.org/trac/boost/wiki/CMakeModularizationStatus

I am not very familiar with boost, but I started looking into it a few days 
ago as a way to feature-proove this work that I'm doing.

Because many boost libraries are header-only, my idea was that boost could 
generate config and target files with content such as (pseudo dependencies):

 add_library(boost::core INTERFACE IMPORTED)
 set_property(TARGET boost::core 
              INTERFACE_INCLUDE_DIRECTORIES
              ${CMAKE_INSTALL_PREFIX}/boost/core # Or whatever
 )

 add_library(boost::bind INTERFACE IMPORTED)
 set_property(TARGET boost::bind 
              INTERFACE_INCLUDE_DIRECTORIES
              ${CMAKE_INSTALL_PREFIX}/boost/bind
              $<TARGET_PROPERTY:boost::core,INTERFACE_INCLUDE_DIRECTORIES>
 )

 (Or:
 target_link_libraries(boost::bind INTERFACE boost::core)
 )

 add_library(boost::msm INTERFACE IMPORTED)
 set_property(TARGET boost::msm 
              INTERFACE_INCLUDE_DIRECTORIES
              ${CMAKE_INSTALL_PREFIX}/boost/msm
              $<TARGET_PROPERTY:boost::bind,INTERFACE_INCLUDE_DIRECTORIES>
 )


So that it is fully aware of all of its [transitive] dependencies (and any 
includes and compile defintions requirements) and I would use it like this:

 add_executable(foo_exe ...)
 target_link_libraries(foo_exe boost::mpl)


The problem is that boost dependencies are circular. I didn't look into it 
much, but at least boost::config depends on boost::core, and boost::core 
depends on boost::config, boost::integer, etc.

One way to solve that would be to do something like this to make it non-
circular by using more internal-implementation-detail INTERFACE libraries:

 # Not documented. Note that they do not know their dependencies.
 add_library(boost::private::core_iface INTERFACE IMPORTED)
 set_property(TARGET boost::private::core_iface
              INTERFACE_INCLUDE_DIRECTORIES
              ${CMAKE_INSTALL_PREFIX}/boost/core # Or whatever
 )
 add_library(boost::private::config_iface INTERFACE IMPORTED)
 set_property(TARGET boost::private::config_iface
              INTERFACE_INCLUDE_DIRECTORIES
              ${CMAKE_INSTALL_PREFIX}/boost/config
 )
 add_library(boost::private::integer_iface INTERFACE IMPORTED)
 set_property(TARGET boost::private::integer_iface
              INTERFACE_INCLUDE_DIRECTORIES
              ${CMAKE_INSTALL_PREFIX}/boost/integer
 )

 # Documented. Depends on the iface targets to break the circular dependence
 add_library(boost::core INTERFACE IMPORTED)
 set_property(TARGET boost::core 
     INTERFACE_INCLUDE_DIRECTORIES
     $<TARGET_PROPERTY:boost::private::config_iface,INTERFACE_INCLUDE_DIRECTORIES>
     $<TARGET_PROPERTY:boost::private::integer_iface,INTERFACE_INCLUDE_DIRECTORIES>
 )

 add_library(boost::config INTERFACE IMPORTED)
 set_property(TARGET boost::config
     INTERFACE_INCLUDE_DIRECTORIES
     $<TARGET_PROPERTY:boost::private::core_iface,INTERFACE_INCLUDE_DIRECTORIES>
 )


I don't know if boost::core and boost::config really are public API, but 
lets assume that they are for the purpose of the discussion, as a comparable 
situation will arise with other boost libraries anyway.

I'm not sure if there's any good alternative to the _iface targets for 
cycle-avoidance, but if it's good enough, and can be implemented, I'd say it 
would be feature-prooven.


Thanks,

Steve.






More information about the cmake-developers mailing list