cmake target_link_libraries INTERFACE works incorrect

CMAKE 3.25.1, linux, GCC 4.6.4

I have the following CMakeLists.txt:

cmake_minimum_required(VERSION 3.12)
project(all)

add_library(lib1 STATIC lib1.cpp lib1.h)
add_library(lib1_wrapper INTERFACE)
target_link_libraries(lib1_wrapper INTERFACE lib1 ${CMAKE_DL_LIBS})

add_library(lib3 STATIC lib3.cpp lib3.h)
add_library(lib3_wrapper INTERFACE)
target_link_libraries(lib3_wrapper INTERFACE lib3 ${CMAKE_DL_LIBS})

add_executable(app app.cpp)
target_link_libraries(app PRIVATE lib1_wrapper lib3_wrapper)

I expect to see the following linker command in the link.txt:

/usr/bin/g++ CMakeFiles/app.dir/app.cpp.o -o app  liblib1.a -ldl liblib3.a -ldl

But the last -ldl is missing and I see this:

/usr/bin/g++ CMakeFiles/app.dir/app.cpp.o -o app  liblib1.a -ldl liblib3.a

It will be compilation error if lib1 doesn’t really use libdl functions and lib3 does.

The question: is this a bug or I’m doing something wrong?

The problem is that lib1 doesn’t really use libdl. Only lib3 uses it. And I get the following error:

/usr/bin/ld: liblib3.a(lib3.cpp.o): in function `test_lib3()':
lib3.cpp:(.text+0xa): undefined reference to `dlclose'
collect2: ld returned 1 exit status

Once again, why we don’t have -ldl after lib3.a in the linker arguments?

Flags tend to be deduplicated, so this isn’t surprising. However, if lib3 itself needs -ldl, why only add it via the _wrapper target? Does it work if you add it directly to the target(s) that need it?

Good question. It really works but … This is only demo. In the real project I have access only to wrappers. And each *_wrapper refers to several IMPORTED libs beside it. Some libs in _wrapper need -ldl and I don’t know how to add -ldl correctly to the end of the final list of libraries. Even if I add -ldl here:

add_executable(app app.cpp)
target_link_libraries(app PRIVATE lib1_wrapper lib3_wrapper -ldl)

it is useless… because the final list will be:

/usr/bin/g++ CMakeFiles/app.dir/app.cpp.o -o app  -ldl liblib1.a liblib3.a

Flags tend to be deduplicated
Probably so. But correct deduplication should be:

/usr/bin/g++ CMakeFiles/app.dir/app.cpp.o -o app  liblib1.a liblib3.a -ldl

Optimization is important but the order of libs is more important IMHO

CMake does offer some control over static library cycles, but fine-grained control over the link line is not something CMake provides because IDEs make their own command lines from a list of properties CMake sets up in the IDE project files.

This expectation is incorrect. In your example, you specify this:

target_link_libraries(lib3_wrapper INTERFACE lib3 ${CMAKE_DL_LIBS})

This says only that something that links to lib3_wrapper needs to also link to lib3 and ${CMAKE_DL_LIBS}. Importantly, it does not say that lib3_wrapper itself uses or requires either of those two libraries. If you want it to say that it does use/require them, that should be PUBLIC rather than INTERFACE.

1 Like

Scott, I can’t do

target_link_libraries(lib3_wrapper PUBLIC lib3 ${CMAKE_DL_LIBS})

cmake gives error on that: INTERFACE library can only be used with the INTERFACE keyword of target_link_libraries

so, there is no was to provide the correct libs order in this case?

Ah, sorry, I misread. But your expectation is still incorrect. The constraints you have specified are the following:

  • lib1 depends on nothing. Anything that links to lib1 doesn’t get any usage requirements at all. This implies lib1 does not depend on anything outside itself.
  • Similarly, lib3 depends on nothing. Anything that links to lib3 also doesn’t get any usage requirements at all. This implies lib3 does not depend on anything outside itself. Note in particular this means you are saying that lib3 does not depend on the dl library.
  • lib1_wrapper adds lib1 and ${CMAKE_DL_LIBS} as usage requirements for anything that links to lib1_wrapper, but note that this doesn’t imply any ordering of those two libraries.
  • Similarly lib3_wrapper adds lib3 and ${CMAKE_DL_LIBS} as usage requirements for anything that links to lib3_wrapper, but again this doesn’t imply any ordering of those two libraries.

So when CMake constructs the dependency graph for app, it adds lib1 and dl as dependencies of lib1_wrapper, then when it adds the dependencies for lib3_wrapper, it can see that dl has already been added and doesn’t need to be added a second time.

I suspect the actual constraints you have are that lib1 and lib3 should both link directly to ${CMAKE_DL_LIBS}. If lib1 and lib3 depend on the dl library, you must specify that constraint on lib1 and lib3, not on the wrappers.

Craig, thanks for detailed explanation.

But in the real life lib1 and lib3 are imported libs. I can do:

target_link_libraries(lib1 INTERFACE ${CMAKE_DL_LIBS})

but…

lib1 will be unwrapped to something like /some/absolute/path/lib1.a and again there is a chance that it will be

-ldl /some/absolute/path/lib1.a

but not

/some/absolute/path/lib1.a -ldl

It’s important to include these details in examples when asking questions (especially when something doesn’t match your expectations; those details could be very important, as it is here). We cannot know what you’ve simplified out based on any assumptions you’ve had on how things work. There are a few things to try:

  • fix the project that exports the imported libraries (if made by another project)
  • if made by a FindX.cmake, add ${CMAKE_DL_LIBS} to the target’s INTERFACE_LINK_LIBRARIES property.
1 Like

Thanks Ben, Craig

It’s really something wrong with my project. In the test project I can add -ldl to `INTERFACE_LINK_LIBRARIES of the imported libraries and everything is ok.

Thanks for feedback!