Transitive Object Libraries in CMake

510 Views Asked by At

Question: Is it possible to join multiple object libraries into a single larger object library?

Consider the following simple project. libA.cpp is compiled into an object library LibA.Obj, and libB.cpp is compiled into an object library LibB.Obj. I want to have an object library, LibAB.Obj, that consists of both LibA.Obj and LibB.Obj, and I want to link against it to produce a single shared library.

MyProject
├── CMakeLists.txt
└── src
    ├── libA.cpp
    └── libB.cpp

If I want to link two libraries, A and B, into a single unit, I can do that by declaring them as object libraries:

cmake_minimum_required(VERSION 3.21)

project(ObjectLibExample)

add_library(LibA.Obj OBJECT src/libA.cpp)
add_library(LibB.Obj OBJECT src/libB.cpp)

add_library(LibAB SHARED $<TARGET_OBJECTS:LibA.Obj> $<TARGET_OBJECTS:LibB.Obj>)

This works as expected, and everything builds.

For reasons I will soon explain, I would like to merge multiple object libraries together into a single unit:

cmake_minimum_required(VERSION 3.21)

project(ObjectLibExample)

add_library(LibA.Obj OBJECT src/libA.cpp)
add_library(LibB.Obj OBJECT src/libB.cpp)

# Using LibAB.Obj as a source should be equivilant to using both LibA.Obj and LibB.Obj
add_library(LibAB.Obj OBJECT $<TARGET_OBJECTS:LibA.Obj> $<TARGET_OBJECTS:LibB.Obj>)

add_library(LibAB SHARED $<TARGET_OBJECTS:LibAB.Obj>)

Here, LibAB.Obj should be an object library collecting the sources of both LibA.Obj and LibB.Obj.

For some reason, CMake acts as though LibAB.Obj provides no sources, and the build fails.

  • Why does this happen?
  • Is this a bug?
  • What workarounds are there?

enter image description here

Motivation / Background

I am currently working on re-organizing an existing project to have a modular structure. It will have modules A, B, and C, each of which will have submodules.

You can imagine the directory structure looking like this:

MyProject
├── A
│   ├── A1
│   ├── A2
│   └── A3
├── B
│   ├── B1
│   ├── B2
│   └── B3
└── C
    ├── C1
    ├── C2
    └── C3

Each submodule represents a library that forms a part of a greater whole, and the build system should link everything together into a single library at the end.

The reason for this structure is twofold:

  • each submodule forms a cohesive unit. It has it's own tests, benchmarks, and example code
  • submodules should provide a clean API for their functionality, even if the internals are kind of hairy.

For example, MyProject/A/A1 looks like this:

MyProject/A/A1
├── bench
├── CMakeLists.txt
├── examples
├── src
└── test

My initial plan was to have each module contain an object library linking all of it's respective submodules, with all modules being statically linked together at root to produce a single library, MyProject.so.

Each submodule would also produce a library specific to that submodule (eg, MyProject/A/A1 would produce MyProject.A.A1.so), against which tests, benchmark code, and examples would be linked. This would allow faster iteration, without needing to re-build the entire project.

Unfortunately, this initial approach failed, for the reasons discussed above.

1

There are 1 best solutions below

1
fabian On

According to the documentation of add_library(OBJECT):

An object library compiles source files but does not archive or link their object files into a library.

[...]

Object libraries may contain only sources that compile, header files, and other files that would not affect linking of a normal library (e.g. .txt).

You haven't provided any sources that would compile, so a fatal error indicating that the object library isn't used in the intended way is expected.

A workaround possible starting cmake 3.12 would be to create an INTERFACE library and linking the object libraries with INTERFACE visibility.

add_library(LibAB INTERFACE)
target_link_libraries(LibAB.Obj INTERFACE LibA.Obj LibB.Obj)
add_library(LibAB SHARED)
target_link_libraries(LibAB PRIVATE LibAB.Obj)

This does prevent you from using $<TARGET_OBJECTS:LibAB.Obj> though.