How can I combine INTERFACE libraries with shared libraries?

Hello.
I have a situation like this.

add_library(lib INTERFACE)
target_sources(lib INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/lib.cpp")
target_include_directories(lib INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}")

Next, I want to create a shared library, which links to this library, so that all things from lib.cpp get compiled into it:

add_library(lib_shared SHARED)
target_link_libraries(lib_shared PUBLIC lib)

And finally, I link it to my main executable:

add_executable(main "main.cpp")
target_link_libraries(main PRIVATE lib_shared)

However, when I link to it, I don’t get a shared library linked into “main”, I don’t get a dependency on lib_shared.so. Instead, I get lib.cpp being compiled as a part of main target (due to “lib” target being INTERFACE), so I don’t get a dynamic linking to shared lib produced by lib_shared target.

The only way I’ve found to prevent this is to do this:

target_link_libraries(lib_shared PRIVATE lib)

This way, “main” links to “lib_shared” dynamically. However, I lose the include directories which were declared by “target_include_directories” in “lib” target and I have to manually declare them once again, so in the end I have to do this:

add_library(lib_shared)
target_link_libraries(lib_shared PUBLIC lib)

add_executable(main "main.cpp")
target_include_directories(main PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/lib")
target_link_libraries(main PUBLIC lib_shared)

Is there a way to avoid this? How can shared libraries link to interface libraries but still be “true” shared libraries when other targets link to said shared libraries?

It’s really unusual to be adding sources to consumers. I’m assuming the above description is a simplification of your real situation. What’s the underlying scenario that is leading to the need to add sources to consumers of lib? Perhaps there’s an alternative way of accomplishing what you are trying to achieve.

Responding to your questions more directly, I wonder if the shared library is being linked, but then optimised away because main.cpp doesn’t end up needing any symbols from it? Have you checked the linker command lines to confirm your observation?

What’s the underlying scenario that is leading to the need to add sources to consumers of lib?

I’m implementing CMake build for Dear ImGui - it has a lot of different rendering and platform backends. You can see the concrete example here: GitHub - eliasdaler/imgui at cmake

So, I define targets imgui_opengl2, imgui_opengl3, imgui_sdl and imgui_glfw. All of them link to imgui target (which is an interface target, as I don’t want to have libimgui.a or libimgui.so as it’s not to be used without any backends).

The user is meant to use them as following:

target_link_libraries(some_target PRIVATE imgui_opengl2 imgui_sdl)

However, I don’t want to produce static or shared libraries for imgui_opengl2, imgui_opengl3 and so on. Instead, I want the user to be able to do this:

add_library(imgui_impl_sdl_gl2)
target_link_libraries(imgui_impl_sdl_gl2 PUBLIC imgui_opengl2 imgui_sdl)
target_link_libraries(some_target PRIVATE imgui_impl_sdl_gl2)

This will produce libimgui_impl_sdl_gl2.so in case of static linking instead of producing libimgui_sdl.so and libimgui_opengl2.so which make little sense to use separately (and imgui will link to both of them statically causing them to have the same symbols and MSVC complains about them)

So, my solution was to make all imgui_*** libraries as INTERFACE libraries so that the user can either directly incorporate their sources into the some_target or make a shared or static library and then link to it.

Responding to your questions more directly, I wonder if the shared library is being linked, but then optimised away because main.cpp doesn’t end up needing any symbols from it? Have you checked the linker command lines to confirm your observation?

I checked - main.cpp calls the function from lib.cpp. When the PRIVATE linking is done to the INTERFACE library, main gets a dependency from lib_shared and gets linked to it dynamically. Otherwise, the function inside the lib.cpp gets compiled into main binary and now dynamic linking is performed.

The main issue with adding sources to consumers is that the source file is communicated to the entire dependency tree and to all nodes that “see” the source file interface. This is really asking for ODR issues (especially if targets compile the source with different flags).

This split between PUBLIC usage and PRIVATE symbols more readily matches an OBJECT library. If that works, I would recommend that solution instead (though installation probably gets a little tricky).

Unfortunately OBJECT libraries don’t work for me, as my INTERFACE targets are quite complex and link to other targets + have target_include_directories set on them

Why though? This seems to be at the heart of the problem for you. Why not let these be ordinary libraries?

The problem is that on Windows I’d have to copy imgui.dll, imgui_opengl2.dll and imgui_sdl.dll to executable’s directory, while in the case of INTERFACE case I’d be able to make a big DLL: imgui_sdl_opengl2.dll and only copy it.

Or maybe it’s somehow possible to make imgui_sdl_opengl2 target which will “join” all these small libraries into a bigger one without using INTERFACE?

That’s just going to be a fact of life for any shared library-using project on Windows. file(GET_RUNTIME_DEPENDENCIES) is what CMake provides for the general use, but you can also look at providing tools to help install the DLLs as needed.

There are flags to “repack” libraries, but CMake doesn’t provide an interface for it (and I think it is mostly about static libraries rather than shared).