Link only directly dependent SHARED libraries

I would like;

  • Properties for compilation shall be propagated transitively. (eg. target_include_directories(PUBLIC/INTERFACE)
  • Libraries shall be specified as add_library(SHARED).
  • The linker shall use directly specified library targets.
add_library(A SHARED)
target_compile_definitions(A PUBLIC FOO)

add_library(B SHARED)
target_compile_definitions(B PUBLIC BAR)
target_link_libraries(B PUBLIC A)

add_library(C SHARED)
target_compile_definitions(C PUBLIC HOGE)
target_link_libraries(C PUBLIC B)

add_executable(X)
target_link_libraries(X PRIVATE A C)

I will see each definition below in compilation;

  • A: -DFOO
  • B: -DFOO -DBAR
  • C: -DFOO -DBAR -DHOGE
  • X: Same as C

This is expected and desirable to my use.
OTOH, I will see libraries in each linker command line below;

  • A: (none)
  • B: A
  • C: B A
  • X: C B A

I would like to enforce link libraries as directly depends by target_link_libraries.

  • A: (none)
  • B: A
  • C: B (excludes A)
  • X: C A (excludes B)

To enforce direct depends, I could specify PRIVATE for each target_link_libraries. But it prevents transitive target_compile_definitions, like;

  • A: -DFOO
  • B: -DFOO -DBAR
  • C: -DBAR -DHOGE (A’s -DFOO is invisible)
  • X: -DFOO -DHOGE (B’s -DBAR is invisible)

Can I make compatible “target_compile_definitions is propagated transitively but linking shall use direct depends only”?

You can achieve this by splitting requirements in two libraries:

add_library(A_INTERFACE INTERFACE)
target_compile_definitions(A_INTERFACE INTERFACE FOO)

add_library(A SHARED)
target_link_libraries(A PUBLIC A_INTERFACE)

add_library(B_INTERFACE INTERFACE)
target_compile_definitions(B_INTERFACE INTERFACE BAR)
target_link_libraries(B_INTERFACE INTERFACE A_INTERFACE)

add_library(B SHARED)
target_link_libraries(B PUBLIC B_INTERFACE)
target_link_libraries(B PRIVATE A)

add_library(C_INTERFACE INTERFACE)
target_compile_definitions(C_INTERFACE INTERFACE HOGE)
target_link_libraries(C_INTERFACE INTERFACE B_INTERFACE)

add_library(C SHARED)
target_link_libraries(C PUBLIC C_INTERFACE)
target_link_libraries(C PRIVATE B)

add_executable(X)
target_link_libraries(X PRIVATE A C)

The upcoming COMPILE_ONLY generator expression for CMake 3.27 should simplify this.
You would have something like:

add_library(A SHARED)
target_compile_definitions(A PUBLIC FOO)

add_library(B SHARED)
target_compile_definitions(B PUBLIC BAR)
target_link_libraries(B PUBLIC $<COMPILE_ONLY:A> PRIVATE A)

add_library(C SHARED)
target_compile_definitions(C PUBLIC HOGE)
target_link_libraries(C PUBLIC $<COMPILE_ONLY:B> PRIVATE B)

add_executable(X)
target_link_libraries(X PRIVATE A C)


@marc.chevrier Thank you for the suggestion.

This assumes every library should have its interface library. I think it would be intrusive a bit. Even if the genex to prune undefined libs for each target_link_libraries would be introduced, I also think the latter solution would be less intrusive.

@robert.maynard Thank you to let me know.

Although I haven’t tried master, I expect it would work.
I have confirmed $<COMPILE_ONLY> could be emulated with like target_link_libraries(B INTERFACE $<IF:$<BOOL:$<LINK_ONLY:1>>,,A>)

As consistency to CMAKE_LINK_WHAT_YOU_USE, I think introducing an option might be introduced like CMAKE_LINK_ONLY_DIRECT_USE.