KidneyChris
KidneyChris

Reputation: 857

Static libraries linked against other static libraries with CMake - one works, one doesn't. Why?

Background I have a project that uses other smaller projects. These projects themselves are made of other projects. A lot of this is legacy or has other managerial-fiat reasons for being arranged as is, so rolling everything into a single project is not an option. Some libraries are pre-compiled on remote shares.

I have 2 main subprojects that are giving me a headache:

As far as I can tell, the two projects are arranged similarly with their linkage instructions. Looking on SO, I discover that linking static libraries against static libraries shouldn't work, but then I'm confused as to how Project Foo is compiled successfully.

gcc and g++ 4.9.2 are the compilers (extern "C" problems of having some parts in C and some in C++ have already been checked for)


Question

I have misunderstood something about either or both how CMake add_subdirectory works, or about how the linker works. Can someone please explain how Project Foo works successfully, and Project Bar (doesn't) works as expected?

Update I looked closer at foo_lib.a and foo_runtime.

I should have determined that something was off to start with, because foo_runtime is nearly 100MB in size, and foo_lib is only 10KB.

nm reveals that foo_lib.a references a few dozen symbols, most of which are undefined. foo_runtime meanwhile references everything.

Equally confusing is that foo_subproject_1.a is similarly mostly undefined. Again, this is what I expect to see; but I don't understand how foo_runtime can be built from this?

I'm still unclear as to why some_library -> subproject -> foo_runtime is successful, but some_library -> subproject -> foo_lib -> bar isn't. At this stage of my investigations, I am expecting both commands to fail.


Project Foo is arranged (using CMake) thusly:

cmake_minimum_required(VERSION 2.6)
project(foo)

set(FOO_SRCS
    # source and headers for main foo project
)

# Project Foo libraries are subdirectories within this project
add_subdirectory(subs/foo_subproject_1)
add_subdirectory(subs/foo_subproject_2)

# Runtime executable
add_executable(foo_runtime main.c ${FOO_SRCS})
target_link_libraries(foo_runtime foo_subproject_1 foo_subproject_2)

# Library version (static library)
add_library(foo_lib STATIC ${FOO_SRCS})
target_link_libraries(foo_lib foo_subproject_1 foo_subproject_2)

Project Foo's subdirectories loosely have the following architecture:

cmake_minimum_required(VERSION 2.6)
project(foo_subproject_<n>)

set(FOO_SUBPROJECT_<N>_SRCS
    # source and headers for subproject
)

# foo_subproject's remote libraries are all static
add_library(some_lib STATIC IMPORTED)
set_target_properties(some_lib PROPERTIES IMPORTED_LOCATION /path/to/libsome_lib.a)

add_library(some_other_lib STATIC IMPORTED)
set_target_properties(some_other_lib PROPERTIES IMPORTED_LOCATION /path/to/libsome_other_lib.a)

include_directories(/paths/to/libs/include/)

# Static library for foo_subproject_N, links against static libs above
add_library(foo_subproject_<N> STATIC ${FOO_SUBPROJECT_<N>_SRCS})
target_link_libraries(foo_subproject_<N> some_library some_other_library)

Project Bar is arranged thusly:

cmake_minimum_required(VERSION 2.6)
project(bar)

set(BAR_SRCS
    # source and headers for main bar project
)

# Project Bar libraries are remote from Bar's perspective
add_library(foo_lib STATIC IMPORTED)
set_target_properties(foo_lib PROPERTIES IMPORTED_LOCATION /path/to/foo/libfoo_lib.a)

include_directories(/path/to/foo/include/)

# Runtime executable
add_executable(bar main.c ${BAR_SRCS} foo_lib)

Project Bar fails to link (compiles ok) with multiple errors of the form:

bar_frobulator.cpp:123: undefined reference to 'foo_subproject_1_init_frobulation'

where foo_subproject_1_init_frobulation lives in foo_subproject_1

Upvotes: 9

Views: 10244

Answers (2)

KidneyChris
KidneyChris

Reputation: 857

Tsyvarev's answer described well-enough what I was actually doing (rather than what I thought I was doing) to get me to look up the right things to answer the root question that I had ("Why does foo_runtime work but bar_runtime doesn't, when both link against static libraries linked against static libraries?")

From the CMake documentation for target_link_libraries:

Library dependencies are transitive by default with this signature. When this target is linked into another target then the libraries linked to this target will appear on the link line for the other target too.

target_link_libraries does not cause linking in the static libraries foo_subproject_1 and foo_subproject_2 (static libraries don't invoke the linker). What is does is makes the list of required libraries available for anything that tries to link with foo_subproject_1 or foo_subproject_2

So, effectively, my foo_runtime and foo_lib target_link_libraries command is, as far as CMake is concerned:

target_link_libraries(foo_runtime foo_subproject_1 some_library some_other_library foo_subproject_2 some_library some_other_library)
target_link_libraries(foo_lib foo_subproject_1 some_library some_other_library foo_subproject_2 some_library some_other_library)

foo_lib, being static, doesn't invoke the linker. foo_runtime, being an executable, does.

bar is a totally different project, so doesn't get to take advantage of the transitive dependencies (and linking fails, because I'm missing all the symbols from the lower-level libraries).

This behaviour of target_link_libraries wasn't expected, as such the behaviour of the project as a whole was somewhat misleading (since it looked like I was bundling a bunch of libraries at the subproject level, and then bundling those at the upper level. In reality, I had just told the upper level what all the libraries it needed were).

To summerise:

Q "Why does foo_runtime work but bar_runtime doesn't, when both link against static libraries linked against static libraries?"

A "foo_runtime is not linking against static libraries linked against static libraries. foo_runtime is linked against more static libraries than you originally thought it was linking against.

bar_runtime doesn't work because your foo_lib is basically empty"

Upvotes: 2

Tsyvarev
Tsyvarev

Reputation: 65870

Can someone please explain how Project Foo works successfully, and Project Bar (doesn't) works as expected?

In short: Creating STATIC library doesn't involve linking step!

Details

In the Foo project you have an executable foo_runtime, which "works" because it is linked with proper libraries (e.g. with library foo_subproject_1 which defines foo_subproject_1_init_frobulation symbol).

An executable bar from Bar project doesn't perform that linking, so it fails. The line

target_link_libraries(bar foo_lib)

links with foo_lib, but this library doesn't defines the needed symbol foo_subproject_1_init_frobulation.

Note, that the line

target_link_libraries(foo_lib foo_subproject_1 foo_subproject_2)

in the Foo project doesn't perform actual linking: in general, building a static library doesn't involve linking step.

Given line just propagates include directories (and other compile-features) from foo_subproject_* libraries to the foo_lib one.

How to make it work

Because static library foo_lib doesn't track its dependency, you need to link bar with a library, which knows that. E.g., make foo_lib shared, or combine foo_subproject_* libraries into archive library, as suggested by the referenced question How to combine several C/C++ libraries into one?.

Alternatively, you may build Foo subproject within Bar one and, instead of creation of IMPORTED foo_lib target, use "normal" foo_lib target, created within Foo project. In that case, line

target_link_libraries(bar foo_lib)

would mean for CMake to (actually) link bar with foo_subproject_* libraries, because those libraries are "linked" (in CMake sense) into foo_lib. Again, the last "linking" has a meaning only for CMake: the file foo_lib.a doesn't aware about needing of foo_subproject_* libraries.

Upvotes: 10

Related Questions