I created a project with some nested library dependencies using the OBJECT
library type. The motivation for this is to avoid link order issues with static libraries.
I have a simple directory structure as follows:
├── bar
│ ├── bar.c
│ └── bar.h
├── foo
│ ├── foo.c
│ └── foo.h
├── main.c
└── CMakeLists.txt
foo
depends on bar
, and main
depends on foo
. For example,
bar.c
#include "bar.h"
#include <stdio.h>
void bar(void) {
printf("woot woot\n");
}
foo.c
#include "foo.h"
#include "bar.h"
void foo(void) {
bar();
}
main.c
#include "foo.h"
int main() {
foo();
return 0;
}
The CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(myproj)
enable_language(C ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
# build bar
add_library(bar OBJECT ${PROJECT_SOURCE_DIR}/bar/bar.c)
target_include_directories(bar PUBLIC ${PROJECT_SOURCE_DIR}/bar)
# build foo, depends on bar
add_library(foo OBJECT ${PROJECT_SOURCE_DIR}/foo/foo.c)
target_include_directories(foo PUBLIC ${PROJECT_SOURCE_DIR}/foo)
target_link_libraries(foo PUBLIC bar)
# build executable, depends on foo
add_executable(myexe ${PROJECT_SOURCE_DIR}/main.c)
target_link_libraries(myexe PUBLIC foo)
My expectation is that myexe
would inherit the bar
dependency from foo
when it links to it. However, this is not the case it seems. When the code is compiled, main.o
only links to foo.o
, and thus there is an undefined reference to bar
. However, if I change the library types from OBJECT
to STATIC
everything works fine. Why does this happen?
CodePudding user response:
Object libraries cannot be chained this way. You must link directly (not transitively) to an object library to acquire its object files. As it says in the documentation,
Object Libraries may "link" to other object libraries to get usage requirements, but since they do not have a link step nothing is done with their object files. [...] In other words, when Object Libraries appear in a target's
INTERFACE_LINK_LIBRARIES
property they will be treated as Interface Libraries, but when they appear in a target'sLINK_LIBRARIES
property their object files will be included in the link too.
I agree it defies all reason, though.
However, you can manually propagate the object files via target_link_libraries(INTERFACE)
and the $<TARGET_OBJECTS>
generator expression as of 3.21 (doing so was buggy in earlier versions, and the even-hacker target_sources
may be used in earlier versions, too).
Do note that this is a bit of a hack, since there are some cases that will break. Notably, if a library target links to such a target publicly, then the object files will propagate again. So... be careful.
Still, here's a corrected version of your example build:
cmake_minimum_required(VERSION 3.21)
project(myproj LANGUAGES C ASM)
set(CMAKE_C_STANDARD 11 CACHE STRING "The C standard to use")
option(CMAKE_C_STANDARD_REQUIRED "Enforce strict C standard selection" ON)
option(CMAKE_C_EXTENSIONS "Allow compiler-specific C extensions" OFF)
# build bar
add_library(bar OBJECT bar/bar.c)
target_include_directories(
bar PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/bar>"
)
# build foo, depends on bar
add_library(foo OBJECT foo/foo.c)
target_include_directories(
foo PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/foo>"
)
target_link_libraries(foo PUBLIC bar "$<TARGET_OBJECTS:bar>")
# build executable, depends on foo
add_executable(myexe main.c)
target_link_libraries(myexe PRIVATE foo)
At the command line:
$ cmake -G Ninja -S . -B build
...
$ cmake --build build -- -nv
[1/4] /usr/bin/cc -I/path/to/bar -std=c11 -MD -MT CMakeFiles/bar.dir/bar/bar.c.o -MF CMakeFiles/bar.dir/bar/bar.c.o.d -o CMakeFiles/bar.dir/bar/bar.c.o -c /path/to/bar/bar.c
[2/4] /usr/bin/cc -I/path/to/foo -I/path/to/bar -std=c11 -MD -MT CMakeFiles/foo.dir/foo/foo.c.o -MF CMakeFiles/foo.dir/foo/foo.c.o.d -o CMakeFiles/foo.dir/foo/foo.c.o -c /path/to/foo/foo.c
[3/4] /usr/bin/cc -I/path/to/foo -I/path/to/bar -std=c11 -MD -MT CMakeFiles/myexe.dir/main.c.o -MF CMakeFiles/myexe.dir/main.c.o.d -o CMakeFiles/myexe.dir/main.c.o -c /path/to/main.c
[4/4] : && /usr/bin/cc CMakeFiles/foo.dir/foo/foo.c.o CMakeFiles/myexe.dir/main.c.o -o myexe CMakeFiles/bar.dir/./bar/bar.c.o && :