[cmake-developers] Setting include directories via target_link_libraries() ?
Stephen Kelly
steveire at gmail.com
Sat Dec 29 05:22:00 EST 2012
Stephen Kelly wrote:
> I'd like to defer the details of the
> porcelain API for now and focus instead on the plumbing API and
> implementation.
I have several quirks with the new command proposal, The new command can be
emulated with my branch and this macro:
include(CMakeParseArguments)
macro(target_use_interfaces target)
cmake_parse_arguments(TUI "" "" "INTERFACE;PUBLIC;PRIVATE" ${ARGN})
if(TUI_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "Unknown keywords given to target_use_interfaces():
\"${TUI_UNPARSED_ARGUMENTS}\"")
endif()
foreach(lib ${TUI_INTERFACE} ${TUI_PRIVATE} ${TUI_PUBLIC})
if (NOT TARGET ${lib})
message(FATAL_ERROR "This macro only accepts targets")
endif()
endforeach()
if (TUI_INTERFACE)
target_link_libraries(${target}
INTERFACE_LINK_LIBRARIES ${TUI_INTERFACE})
endif()
if (TUI_PRIVATE)
target_link_libraries(${target} LINK_PRIVATE ${TUI_PRIVATE})
endif()
if (TUI_PUBLIC)
target_link_libraries(${target} LINK_PUBLIC ${TUI_PUBLIC})
endif()
endmacro()
The quirks:
1) Restrictiveness
Only targets are allowed, which means you likely can't use ${Foo_LIBRARIES}
at all anymore, even for porting, unless you know exactly what it contains.
Even if you do know exactly what it contains, that doesn't necessarily help.
Consider that Bar maybe doesn't provide 'rich' imported targets yet. Then
Foo will have this:
set(Foo_LIBRARIES Foo::importedlib ${Bar_LIBRARIES})
Or worse, FooConfig.cmake will create IMPORTED targets for Bar (Bad idea,
see 5 below)
This is solvable by letting target_use_interfaces also accept library names
and library paths, but then the new command is less 'exact' and more
redundant.
2) Inconvenience
Assuming target_use_interfaces only allows targets, you'll have a lot of
this:
tll(foo lib1)
target_use_interfaces(foo lib2)
tll(foo lib3)
instead of just
tll(foo lib1 lib2 lib3)
3) Incompleteness
link-libraries, INTERFACE_POSITION_INDEPENDENT_CODE,
INTERFACE_WIN32_EXECUTABLE, INTERFACE_STD_CXX_11,
INTERFACE_CXX_RUNTIME_LIBCXX, INTERFACE_CXX_RUNTIME_LIBSTDCXX
will all handled by tll in both proposals (as they depend on the
LINK_LIBRARIES, which is populated by tll), but defines and includes alone
will be the INTERFACE properties which do not get populated with tll, and
need the new command instead.
4) Redundancy into the future
Some Bar packages will not create IMPORTED targets with appropriate
INTERFACE properties.
tll will not 'go away'. The extent to which a project can achieve a 'fully
ported' state depends on its upstreams. The new command may drive some (but
not most) people to read the help entry for it for a couple of releases.
Depending on how fast their cmake dependency moves, how fast they move their
upstream dependency versions and how fast their upstream dependencies move
their cmake version, the new command could be very old news by the time they
get to use it.
And after that, there will always be two commands for very similar things.
Not every good idea is adopted (particularly not in a reasonable time), no
matter how good the idea is. Consider the amount and rate of adoption of
Config files to date.
5) Greater incentive to make future changes accidentally harder.
People will create their own IMPORTED targets to 'port fully' to the new
command and push themselves over the line (both in their own CMakeLists
files, and in their own Config files).
The same thing could happen even if tll does includes and defines too, but a
greater incentive is there if there's a new command and a 'port fully to the
new command' goal.
That means that if upstream introduces IMPORTED targets where they didn't
before there will be breakage.
6) API non-argument
One of the suggested benefits of a new command is a more restrictive
signature to be more typo safe.
http://thread.gmane.org/gmane.comp.programming.tools.cmake.devel/5526/focus=5569
If that is really such a problem for tll that it needs a solution (I don't
believe it is) we can just deprecate
tll(foo lib1 lib2)
with a policy and require people to use
tll(foo LINK_INTERFACE_LIBRARIES lib1 lib2)
tll(foo LINK_PRIVATE lib1 lib2)
tll(foo LINK_PUBLIC lib1 lib2)
instead if the policy is NEW. (and also
tll(foo INTERFACE_LINK_LIBRARIES lib1 lib2)
later, when that exists of course - The deprecation policy can be
implemented immediately for the next CMake release).
7) Complexity non-argument
The implementation complexity argument leveled against using tll to set
includes and defines is just not true. The patch to add it is very simple
('Add includes and compile definitions with target_link_libraries') where it
touches the tll command implementation.
Implementing a new command would probably be more complex as tll may have to
be refactored for code sharing.
8) Non-discoverability
No one will use/benefit from/discover the new feature (with regard to
includes and defines) without first explicitly using the new command. No one
will discover it accidentally. Maybe we can add a note about it to the tll
documentation, but if people are going to read that, then it is just as easy
for us to write 'tll also populates includes and defines from its
dependencies'.
9) No benefits, only burden
The stated benefits of the new command have been easier backward
compatibility (I disagree), more readable (not true, see 2), having a goal
of a 'fully ported' state (not achieveable, see 4), less complexity (not
true, see 7), easier debugging (not true, as previously shown -
CMAKE_DEBUG_TARGET_PROPERTIES is the answer instead), discoverability (I
don't think so, see 4, 8).
The only thing a new command provides is a porting and maintenance burden,
command-similarity-confusion and less discoverability.
Thanks,
Steve.
More information about the cmake-developers
mailing list