Home > OS >  CMake nested OBJECT library dependencies
CMake nested OBJECT library dependencies

Time:02-10

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's LINK_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 && :
  • Related