Alecto
Alecto

Reputation: 10750

Transitive Object Libraries in CMake

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.

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:

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.

Upvotes: 3

Views: 640

Answers (2)

Frederik
Frederik

Reputation: 727

What worked for me was to link the transitive object libraries normally, and then recursively add their target objects using this function:

function(add_target_objects TARGET_NAME LIBRARY_NAME)
    target_sources(${TARGET_NAME} PRIVATE $<TARGET_OBJECTS:${LIBRARY_NAME}>)

    # recurse into library dependencies
    get_target_property(libs ${LIBRARY_NAME} LINK_LIBRARIES)
    if(libs)
        foreach(lib ${libs})
            add_target_objects(${TARGET_NAME} ${lib})
        endforeach()
    endif()
endfunction()


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

add_library(LibAB.Obj OBJECT)
target_link_libraries(LibAB.Obj PRIVATE LibA.Obj LibB.Obj)

add_library(LibAB SHARED)
add_target_objects(LibAB LibAB.Obj)

Upvotes: 0

fabian
fabian

Reputation: 82491

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.

Upvotes: 0

Related Questions