5

I use CMake to build a project that consists of multiple nested static libraries .A similar but simple structure is shown in the figure below:

TestProject:
|-CMakeLists.txt
|-Main.cpp
|-level2
|    | - level2.cpp
|    | - level2.h
|    | - CMakeLists.txt
|    | - level1
|    |     |-level1.cpp
|    |     |-level1.h
|    |     |-CMakeLists.txt

Now, I use CMake to build static libraries for each level separately. According to my test, the static library of each layer only contains the .cpp and .h files of that layer. However, I want to combine it with the library referenced by the previous layer when the static library of each layer is generated. For example, I build the static lib of level 1 first.Then, in the CMakeLists.txt of level 2, I create the static library of level 2 depend on the static library of level 1 ( target_link_libraries(${PROJECT_NAME} LEVEL1) ), and then, I wanted to merge the libraries of level 2 and level 1 together to a new static lib file named as level1_2.lib.

Here is my CMakeLists.txt in Level1:

cmake_minimum_required(VERSION 3.5)

#projcet name
project(LEVEL1 LANGUAGES CXX)

add_library( ${PROJECT_NAME} add.cpp)

# Add the include directories of user-written sources.
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR})

And this is the CMakelists.txt of level2.

cmake_minimum_required(VERSION 3.5)

project(LEVEL2 LANGUAGES CXX)

add_subdirectory(level1)

add_library( ${PROJECT_NAME} addplus.cpp)
target_link_libraries(${PROJECT_NAME} LEVEL1)
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR})

find_program(MSVC_LIB_TOOL lib.exe)
set(LIBNAME "level1_2.lib")
add_custom_command(
    TARGET examplelib POST_BUILD
    COMMAND ${MSVC_LIB_TOOL} /OUT:${LIBNAME} $<TARGET_FILE:LEVEL2> $<TARGET_FILE:LEVEL1>
    DEPENDS LEVEL1 LEVEL2
    COMMENT "Combining libs..."
    )
add_custom_target(combinedLib
    ALL
    DEPENDS ${LIBNAME}
    )

I use the add_custom_command and add_custom_target method trying to generate the mixed lib, referenced to several website below:

CMake linking libraries into one single library

CMake Project Structure: How do I properly merge libraries together and include them in multiple executables

But they can't really solve my needs.Only level1.lib and level2.lib is generated.

Any help would be appreciated.

UPDATE in 08-21 =======================================================

Thanks for everyone's reply.Now I used object library(reference from Alex's answer), and get the merged static library.Here's my new code:

#CMakeLists.txt in level1

cmake_minimum_required(VERSION 3.5)

#projcet name
project(LEVEL1 LANGUAGES CXX)


# Generate lib
add_library( LEVEL1obj OBJECT add.cpp)


# Add the include directories of user-written sources.
target_include_directories(LEVEL1obj PUBLIC ${PROJECT_SOURCE_DIR})

add_library(${PROJECT_NAME})

target_link_libraries(${PROJECT_NAME} PUBLIC LEVEL1obj)

And this is the CMakelists.txt of level2.

#CMakeLists.txt in level2
cmake_minimum_required(VERSION 3.5)

#projcet name
project(LEVEL2 LANGUAGES CXX)


add_subdirectory(level1)

add_library( LEVEL2obj OBJECT addplus.cpp addplus.h)

# Add the include directories of user-written sources.
target_include_directories(LEVEL2obj PUBLIC ${PROJECT_SOURCE_DIR})

target_link_libraries(LEVEL2obj LEVEL1obj)


add_library( ${PROJECT_NAME})

target_link_libraries(${PROJECT_NAME} PUBLIC LEVEL1obj LEVEL2obj)

This is the top level CMakeLists.txt in test project

cmake_minimum_required(VERSION 3.5)

project(TestCppLib)

file(GLOB SRC "${PROJECT_SOURCE_DIR}/*.cpp")

add_subdirectory(level2)

add_executable(${PROJECT_NAME} ${SRC})

target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR})

target_link_libraries(${PROJECT_NAME} PRIVATE LEVEL2)

The slightly different from Alex's answer is : because my level2 library depends on the level1 library, I added a library dependency to it by target_link_libraries(LEVEL2obj LEVEL1obj). The slightly different answer from Alex's is that because my level2 library depends on the level1 library generation, I added a library dependency to it through the following code. At least for now, it works well.

YuhaoQiu
  • 63
  • 5
  • Why do you use [build event](https://cmake.org/cmake/help/latest/command/add_custom_command.html#build-events) variant of the `add_custom_command`? (And use `exampleLib` target which is never created, which should give a configuration error.) Instead, use common [generating](https://cmake.org/cmake/help/latest/command/add_custom_command.html#generating-files) variant of that command: `add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME} COMMAND ...)`. – Tsyvarev Aug 20 '21 at 15:31
  • Use object libraries instead, if you want this behavior. – Alex Reinking Aug 20 '21 at 16:13
  • 1
    Do you need an actual static library or do you want to use them all together in cmake? `I wanted to merge the libraries of level 2 and level 1 together to a new static lib` But what for? Just link library `level1` with library `level2`, and the dependencies are transitive. Or do you want to specifically distribute `level1_2.lib` to the clients? If so, I would suggest instead making one library from the start in cmake. – KamilCuk Aug 20 '21 at 16:38
  • `file(GLOB SRC "${PROJECT_SOURCE_DIR}/*.cpp")` -- please do not glob without `CONFIGURE_DEPENDS` (put it after `SRC`) – Alex Reinking Aug 21 '21 at 13:03
  • Also please remember to **always** call `target_link_libraries` with a visibility specifier (one of PRIVATE, PUBLIC, or INTERFACE). There is not an implied default; instead, forgetting to put one triggers legacy behavior with strange edge cases that can bite you later as your code evolves. – Alex Reinking Aug 21 '21 at 13:05
  • `because my level2 library depends on the level1 library generation` - depending on the type of dependency, consider using `add_dependencies` instead of `target_link_libraries`. – Alex Reinking Aug 21 '21 at 13:07
  • 1
    Also, object libraries don't work right prior to version 3.12. You NEED to test your code with the ACTUAL CMake version you declare as your minimum. I really doubt you're running 3.5. Run `cmake --version` and put that in your `cmake_minimum_required` – Alex Reinking Aug 21 '21 at 13:13

1 Answers1

6

Here is a minimal example using object libraries to manage sharing object files between static (or shared!) libraries and how to link to them.

In level1/CMakeLists.txt:

add_library(level1_obj OBJECT level1.cpp level1.h)
target_include_directories(level1_obj PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>")

add_library(level1)
target_link_libraries(level1 PUBLIC level1_obj)

In level2/CMakeLists.txt

add_subdirectory(level1)

add_library(level2_obj OBJECT level2.cpp level2.h)
target_include_directories(level2_obj PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>")

add_library(level2)
target_link_libraries(level2 PUBLIC level1_obj level2_obj)

Notice that level2 is linked to level1_obj, not level1.

In main/CMakeLists.txt:

cmake_minimum_required(VERSION 3.21)
project(TestProject)

add_subdirectory(level2)

add_executable(app Main.cpp)
target_link_libraries(app PRIVATE level2)

One bug to be aware of is that Xcode does not like libraries without any real source files. If that's a concern, you can add an empty source file to your static library targets.

Please be sure to read the documentation on object libraries:

  1. https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#object-libraries
  2. https://cmake.org/cmake/help/latest/command/target_link_libraries.html#linking-object-libraries
  3. https://cmake.org/cmake/help/latest/command/add_library.html#object-libraries

Also worth noting is that unless you have some need to distribute your level2 library independently from level1, it would be better to keep their objects separate and require linking to both. Normal target linking in CMake handles this automatically via the transitive linking mechanism.

Alex Reinking
  • 16,724
  • 5
  • 52
  • 86
  • Really? we should go change all targets from `STATIC` to `OBJECT` (or add new object-targets)? **No, adding `$` to sources is in most cases more than enough.** – Top-Master Aug 20 '21 at 18:14
  • That's the old way of doing things that doesn't track interface properties – Alex Reinking Aug 20 '21 at 18:16
  • This is no old or new matter, your way is just the more right one (to be fair), which may be required in some cases to install headers or set additional linker flags. – Top-Master Aug 20 '21 at 18:21
  • I'll grant you that `$` is an acceptable workaround when you aren't in control of the target (e.g. it's from a third party build you got via FetchContent). I also understand there's a new generator expression in development that improves this use case by enabling linking to a library only for its interface properties and not for the actual link line. – Alex Reinking Aug 20 '21 at 18:22
  • Thank you for your continued help. But now I have another question: If the level1 static library depends on another external static library (.lib file), how can I merge it with the level1 static library? Can I generate an OBJECT LIBRARY for this lib file? – YuhaoQiu Aug 21 '21 at 13:37
  • "I have another question"... so [ask another StackOverflow question](https://stackoverflow.com/questions/ask) :) – Alex Reinking Aug 21 '21 at 15:20