I am working moving a code base to cmake where depending on which executable is getting built different implementations of a particular library are needed. In some cases this is because the resultant executables have different requirements/capabilities, but in many cases it is so we can pull in implementations that can be controlled by unit tests that are not used in production.
Since libraries need to depend on interfaces rather than implementations, there are some cases where cmake can't build the dependency tree correctly causing the linking step to fail (an issue described here).
For instance consider the following contrived example. There exists 2 libraries a & b, where b depends on a, and a has two implementations. I have defined the targets as follows in CMakeLists.txt
:
add_library(lib-a-intf INTERFACE)
target_include_directories(lib-a-intf INTERFACE lib-a)
add_library(lib-a-impl-a impl-a/impl-a.cpp)
target_link_libraries(lib-a-impl-a PUBLIC lib-a-intf)
add_library(lib-a-impl-b impl-b/impl-b.cpp)
target_link_libraries(lib-a-impl-b PUBLIC lib-a-intf)
add_library(lib-b lib-b.cpp)
target_link_libraries(lib-b PRIVATE lib-a-intf)
target_include_directories(lib-b PUBLIC lib-b)
And then the executable pulls in the version it needs based on its requirements
add_executable(main main.cpp)
target_link_libraries(main lib-a-impl-b lib-b)
Since cmake can't set up the link order correctly, this fails to link do to an undefined reference for functions defined in library a.
I know of a few workarounds, but they are not optimal. Defining the library a implementations as object libraries works, but doesn't scale well since those implementations will get recompiled for every executable. Using the linker --whole-archive
flag also works, but also doesn't scale well since it leads to larger than necessary executables since uncalled symbols in library a won't be garbage collected.
Is there a way in cmake to indicate that a library implements an interface library such that the link order is correct? Alternatively, is there a way to change how the libraries are defined to work around this issue?