CMake: how create a single shared library from all static libraries of subprojects?

OK, I figured it out: this is much more painful than it should be. Until very recently, people at Kitware didn’t understand why anyone would ever want to create a DLL from static libs. Their argument is that there should always be source files in the main (e.g. top_project in my case) directory because it is effectively a project of its own. I see things differently & I need to break top_project into smaller subprojects which should not exist independently (i.e. there is no point in creating a full-blown project for them & add them using ExternalProject_Add). Besides, when I ship my shared library (for use, e.g. with a Java Native Interface), I don’t want to ship dozens of shared libraries because that would amount to exposing the internal layout of my project. Anyway, having – I think – made a case for creating a shared library from static libraries, I’ll proceed to the technical details.

In the CMakeLists.txt of subproject1 and subproject2, you should create your target using the OBJECT library feature (introduced in CMake 2.8.8):

add_library(${PROJECT_NAME} OBJECT ${SRC})

where SRC designates the list of source files (note that these should be set explicitly in the CMakeLists.txt file as it allows make to re-launch CMake when a modification of CMakeLists.txt is detected, e.g. when adding or removing a file)

In the top_project, add the subprojects using:

add_subdirectory(subproject1)
add_subdirectory(subproject2)

In order to see the symbols from the static library, use:

set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--export-all-symbols")

You can then create the shared library using:

add_library(${PROJECT_NAME} SHARED $<TARGET_OBJECTS:subproject1>
                                   $<TARGET_OBJECTS:subproject2>)

I’ve found that any “normal” library (i.e. not object) needs to be added in a separate add_library command, otherwise it is simply ignored.

For executables, you can use:

add_executable(name_of_executable $<TARGET_OBJECTS:subproject1>
                  $<TARGET_OBJECTS:subproject2>)
set(LINK_FLAGS ${LINK_FLAGS} "-Wl,-whole-archive")
target_link_libraries(name_of_executable ${PROJECT_NAME}

I repeat that this only works as of version 2.8.8 of CMake. Just as well CMake manages the dependencies extremely well & is cross-platform because it’s not much less painful than plain old Makefiles & certainly less flexible.

Leave a Comment