Link errors for executables which have transitive dependencies involving interface libraries
Setup
Consider the following situation:
- There is an interface specification (e.g. set of C language header files):
H
- There are two (static) libraries
L1
andL2
, each of them implementing this interfaceH
- There is a (static) library
U
which uses theH
interface but does not care whetherL1
orL2
is used to implement that interface - There is another (static) library
M
which usesU
- Finally, there are two executables
E1
andE2
which both useM
and additionallyL1
orL2
, respectively.
The goal is that E1
pulls in M
which in turn pulls in U
and that the unresolved symbols (from the H
interface) are resolved using L1
. (Similarly for E2
.)
The following diagram illustrates the intended setup. (Ideally, U
would have been placed between L1
and L2
; I did not succeed in convincing mermaid to use this order.)
classDiagram
class H {
<<INTERFACE>>
Set of C language header files
}
class L1 {
<<STATIC library>>
Implements the H interface
}
class U {
<<STATIC library>>
}
class L2 {
<<STATIC library>>
Implements the H interface
}
class M {
<<STATIC library>>
}
class E1 {
<<Executable>>
}
class E2 {
<<Executable>>
}
L1 ..|> H : target_link_libraries(L1 PRIVATE H)
L2 ..|> H : target_link_libraries(L2 PRIVATE H)
U --> H: target_link_libraries(U PRIVATE H)
M o-- U: target_link_libraries(M PRIVATE U)
E1 o-- M : target_link_libraries(E1 PRIVATE M)
E1 o-- L1 : target_link_libraries(E1 PRIVATE L1)
E2 o-- M : target_link_libraries(E2 PRIVATE M)
E2 o-- L2 : target_link_libraries(E2 PRIVATE L2)
As illustrated above, involved CMake scripts
- define an
INTERFACE
libraryH
- define "normal" libraries
L1
andL2
and specifytarget_link_libraries(L1 PRIVATE H)
andtarget_link_libraries(L2 PRIVATE H)
- define a "normal" library
U
and specifytarget_link_libraries(U PRIVATE H)
- define a "normal" library
M
and specifytarget_link_libraries(M PRIVATE U)
- define executables
E1
andE2
and specifytarget_link_libraries(E1 PRIVATE M L1)
andtarget_link_libraries(E2 PRIVATE M L2)
The Problem
Unfortunately, the resulting sequence of libraries linked with the executables is
M
- one of
L1
andL2
-
U
(with unresolved symbols from theH
interface)
This is no wonder as U
does not depend on L1
/L2
. Nevertheless, U
is linked too late resulting in unresolved symbols.
Suggested Solution
The issue could probably be solved if CMake were using depth first search when generating the link sequence; in this particular case, the sequence of libraries would then be
M
-
U
(with unresolved symbols from theH
interface) - one of
L1
andL2
(providing the missing symbols from step 2)
As this would constitute a significant change in the behavior of CMake generators, it could be guarded by a policy.
Alternative
An alternative could be to add a meachanism by which a library (L1
or L2
in the above example) states that it is a realization of an INTERFACE
library. CMake could then notice that U
is using an INTERFACE
library H
, that there is a realization of that interface in the target_link_libraries()
of the final executable and thus move that realization to a place after U
in the link sequence.