【问题标题】:CMake linking static libraries in different subdirectories into one single static libraryCMake将不同子目录中的静态库链接到一个静态库中
【发布时间】:2021-10-22 03:32:21
【问题描述】:

我使用CMake构建了一个包含多个嵌套静态库的项目。类似但简单的结构如下图所示:

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

现在,我使用 CMake 分别为每个级别构建静态库。根据我的测试,每一层的静态库只包含该层的.cpp和.h文件。但是,我想在生成每一层的静态库时,将它与上一层引用的库结合起来。比如我先构建了1级的静态库。然后,在2级的CMakeLists.txt中,我创建了2级的静态库,依赖于1级的静态库( target_link_libraries(${PROJECT_NAME} LEVEL1) ) ,然后,我想将level 2和level 1的库合并到一个名为level1_2.lib的新静态lib文件中。

这是我在 Level1 中的 CMakeLists.txt:

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})

这是level2的CMakelists.txt。

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}
    )

我使用 add_custom_command 和 add_custom_target 方法尝试生成混合库,参考以下几个网站:

CMake linking libraries into one single library

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

但是它们并不能真正解决我的需求。只生成了level1.lib和level2.lib。

任何帮助将不胜感激。

08-21 更新 ======================================= ================

感谢大家的回复。现在我使用了对象库(参考Alex的回答),并得到了合并的静态库。这是我的新代码:

#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)

这是level2的CMakelists.txt。

#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)

这是测试项目中的顶级 CMakeLists.txt

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)

与Alex的回答稍有不同的是:因为我的level2库依赖于level1库,所以我通过target_link_libraries(LEVEL2obj LEVEL1obj)给它添加了一个库依赖。与 Alex 的回答略有不同的是,因为我的 level2 库依赖于 level1 库的生成,所以我通过以下代码为其添加了库依赖项。至少目前,它运作良好。

【问题讨论】:

  • 为什么使用build eventadd_custom_command 变体? (并使用从未创建的exampleLib 目标,这应该会产生配置错误。)相反,使用该命令的常见generating 变体:add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME} COMMAND ...)
  • 如果您想要这种行为,请改用对象库。
  • 你需要一个实际的静态库还是想在 cmake 中一起使用它们? I wanted to merge the libraries of level 2 and level 1 together to a new static lib 但是为了什么?只需将库level1 与库level2 链接,并且依赖关系是可传递的。或者您想专门向客户分发level1_2.lib?如果是这样,我建议从一开始就在 cmake 中创建一个库。
  • file(GLOB SRC "${PROJECT_SOURCE_DIR}/*.cpp") -- 请不要在没有 CONFIGURE_DEPENDS 的情况下使用 glob(放在 SRC 之后)
  • 此外,对象库在 3.12 版本之前无法正常工作。您需要使用您声明为最低版本的 ACTUAL CMake 版本来测试您的代码。我真的怀疑你正在运行3.5。运行cmake --version 并将其放入您的cmake_minimum_required

标签: c++ cmake static-libraries static-linking


【解决方案1】:

这是一个使用对象库管理静态(或共享!)库之间共享对象文件以及如何链接到它们的最小示例。

在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)

在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)

请注意,level2 链接到 level1_obj,而不是 level1。

在 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)

需要注意的一个错误是 Xcode 不喜欢没有任何真实源文件的库。如果这是一个问题,您可以将一个空源文件添加到您的静态库目标。

请务必阅读有关对象库的文档:

  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

另外值得注意的是,除非您需要独立于 level1 分发您的 level2 库,否则最好将它们的对象分开并需要链接到两者。 CMake 中的普通目标链接通过传递链接机制自动处理此问题。

【讨论】:

  • 真的吗?我们应该将所有目标从STATIC 更改为OBJECT(或添加新的对象目标)? 不,在大多数情况下,将$&lt;TARGET_OBJECTS:MyOtherTarget&gt; 添加到来源就足够了。
  • 这是不跟踪界面属性的旧方法
  • 这不是旧事或新事,您的方式只是更正确的方式(公平地说),在某些情况下可能需要安装标头或设置额外的链接器标志。
  • 当您无法控制目标时(例如,它来自您通过 FetchContent 获得的第三方构建),我会承认 $&lt;TARGET_OBJECTS&gt; 是一种可接受的解决方法。我还了解到,开发中存在一个新的生成器表达式,它通过仅针对其接口属性而不是针对实际链接行启用链接到库来改进此用例。
  • 感谢您一直以来的帮助。但是现在我有另一个问题:如果level1静态库依赖于另一个外部静态库(.lib文件),如何与level1静态库合并?我可以为这个 lib 文件生成一个 OBJECT LIBRARY 吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多