[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