CMake:如何从子项目的所有静态库中创建一个共享库?

Posted

技术标签:

【中文标题】CMake:如何从子项目的所有静态库中创建一个共享库?【英文标题】:CMake: how create a single shared library from all static libraries of subprojects? 【发布时间】:2012-07-10 20:36:28 【问题描述】:

我有以下布局:

top_project
    + subproject1
    + subproject2

subproject1subproject2 中的每一个都创建一个静态库。我想将这些静态库链接到 top_project 级别的单个共享库中。

目前我收集到的信息是:

要么使用-fPic 编译(除Windows 之外的所有设备都需要),以创建位置无关代码,允许将静态库链接到单个共享库或解压缩所有静态库(例如使用ar)并重新- 将它们链接到共享库(我认为这是一个不雅且不可移植的解决方案) 所有源文件都必须明确地提供给add_library 命令:出于某种我无法理解的原因,简单地编写add_library($PROJECT_NAME SHARED subproject1 subproject2) 不能按预期工作(它实际上创建了一个空库并且没有正确注册依赖项) CMake 中有一个 OBJECT 库功能,但我认为它的目的并不是真正做我想做的事。

有什么想法吗?

【问题讨论】:

我使用的是 cmake 3.4.+,我只是将静态库添加到共享库中,它们被编译为单个文件:) 我在 android 上测试了这个:) 有人能提示如何在 MSVC 下执行此操作吗?我使用的是 qmake 而不是 cmake,但我可以自己处理这些步骤,如果我能弄清楚的话...... 【参考方案1】:

好吧,我想通了:这比它应该的要痛苦得多。直到最近,Kitware 的人们才明白为什么有人会想要从静态库创建 DLL。他们的论点是主目录(例如top_project 在我的例子中)目录中应该总是有源文件,因为它实际上是一个自己的项目。我对事物的看法不同,我需要将top_project 分解为不应独立存在的较小的子项目(即,为它们创建一个成熟的项目并使用ExternalProject_Add 添加它们是没有意义的)。此外,当我发布我的共享库(供使用,例如使用 Java 原生接口)时,我不想发布数十个共享库,因为这等于暴露了我项目的内部布局。无论如何,我认为已经为从静态库创建共享库做了一个案例,我将继续讨论技术细节。

subproject1subproject2 的 CMakeLists.txt 中,您应该使用 OBJECT 库功能(在 CMake 2.8.8 中引入)创建目标:

add_library($PROJECT_NAME OBJECT $SRC)

SRC 指定源文件列表(请注意,这些应在 CMakeLists.txt 文件中明确设置,因为它允许 make 在检测到 CMakeLists.txt 修改时重新启动 CMake,例如添加或删除文件)

top_project 中,使用以下命令添加子项目:

add_subdirectory(subproject1)
add_subdirectory(subproject2)

要查看静态库中的符号,请使用:

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

然后您可以使用以下方法创建共享库:

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

我发现任何“普通”库(即不是对象)都需要添加到单独的 add_library 命令中,否则会被忽略。

对于可执行文件,您可以使用:

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

我再说一遍,这仅适用于 CMake 2.8.8 版。同样,CMake 管理依赖关系非常好并且是跨平台的,因为它并不比普通的旧 Makefile 少多少痛苦,而且肯定不那么灵活。

【讨论】:

呸,令人讨厌的是 Ubuntu 12.04 卡在 CMake 2.8.7 上,是否有旧版本的替代方案?定义库时是否只需要引用所有源文件? 我通过使用 -fPIC 编译我的静态库来解决我的问题,我的共享库确实链接正确,但我不知道它是否真的有效,因为我还没有尝试使用它。 这个解决方案让我免于大量打字。【参考方案2】:

我的解决方案是简单地将/WHOLEARCHIVE-all_load--whole-archive 添加到链接器标志中,这样当你的主库被链接时,所有的子库都包括在内,包括它们的所有符号(默认行为是只包含主库使用的子库的符号。例如:

源文件

$ echo "void Func1()  " > source1.cpp
$ echo "void Func2()  " > source2.cpp
$ echo "void Func3()  " > source3.cpp
$ echo "void Func4()  " > source4.cpp

天真的 CMakeLists.txt

cmake_minimum_required(VERSION 3.7)

# The 'sub' libraries, e.g. from an `add_subdirectory()` call.
add_library(sublib_a STATIC source1.cpp source2.cpp)
add_library(sublib_b STATIC source3.cpp source4.cpp)

# The main library that contains all of the sub libraries.
add_library(mainlib SHARED)

target_link_libraries(mainlib sublib_a sublib_b)

运行它(在 OSX 上):

$ make VERBOSE=1
...
[100%] Linking CXX shared library libmainlib.dylib
/usr/local/Cellar/cmake/3.7.1/bin/cmake -E cmake_link_script CMakeFiles/mainlib.dir/link.txt --verbose=1
/Library/Developer/CommandLineTools/usr/bin/c++   -dynamiclib -Wl,-headerpad_max_install_names  -o libmainlib.dylib -install_name @rpath/libmainlib.dylib  libsublib_a.a libsublib_b.a 
[100%] Built target mainlib

$ nm libmainlib.dylib | grep Func
$

正确的 CMakeLists.txt

附加这个:

# By default, symbols provided by the sublibs that are not used by mainlib (which is all of them in this case)
# are not used. This changes that.
if (WIN32)
    set_target_properties(mainlib PROPERTIES
        LINK_FLAGS "/WHOLEARCHIVE"
    )
elseif (APPLE)
    set_target_properties(mainlib PROPERTIES
        LINK_FLAGS "-Wl,-all_load"
    )
else ()
    set_target_properties(mainlib PROPERTIES
        LINK_FLAGS "-Wl,--whole-archive"
    )
endif ()

运行它(注意额外的-all_load):

$ make VERBOSE=1
[100%] Linking CXX shared library libmainlib.dylib
/usr/local/Cellar/cmake/3.7.1/bin/cmake -E cmake_link_script CMakeFiles/mainlib.dir/link.txt --verbose=1
/Library/Developer/CommandLineTools/usr/bin/c++   -dynamiclib -Wl,-headerpad_max_install_names -Wl,-all_load -o libmainlib.dylib -install_name @rpath/libmainlib.dylib  libsublib_a.a libsublib_b.a 
[100%] Built target mainlib

$ nm libmainlib.dylib | grep Func
0000000000001da0 T __Z5Func1v
0000000000001db0 T __Z5Func2v
0000000000001dc0 T __Z5Func3v
0000000000001dd0 T __Z5Func4v

请注意,到目前为止,我只实际测试了 -all_load/WHOLEARCHIVE 是 MSVC 2015 选项。

【讨论】:

我在 Linux 上,只是添加 -Wl,--whole-archive 导致大量与 libgcc.a 相关的“多重定义”错误 是的,/WHOLEARCHIVE 选项似乎也不能很好地工作,所以我只使用了对象库方法。【参考方案3】:

另一种方法。

这种方式看起来更简单,但我不确定它有多完美:

https://***.com/a/14347487/602340

【讨论】:

【参考方案4】:

另一种方法是提供所有项目的源文件和头文件的路径,并将它们一起构建以生成 .so 。这通常是推荐的方式,而不是创建静态库,然后从这些库中创建共享库。

基本上你应该做到以下几点:

FILE(GLOB subproject1_sources
  <sub_project1_lib_sources_dir>/file1.c
  <sub_project1_lib_sources_dir>/file2.c //... etc
)

FILE(GLOB subproject2_sources
  <sub_project2_lib_sources_dir>/file1.c
  <sub_project2_lib_sources_dir>/file2.c //... etc
)

FILE(GLOB topProject_sources
  <top_project_lib_sources_dir>/file1.c
  <top_project_lib_sources_dir>/file2.c //... etc
)

include_directories("<sub_project1_lib_sources_dir>")
include_directories("<sub_project2_lib_sources_dir>")
include_directories("<top_project_lib_sources_dir>") //should be "." if you're building from here

add_library(topProject SHARED $topProject_sources $subproject1_sources $subproject2_sources)

【讨论】:

这不太可能有用。问题是您经常混合使用旧的构建系统,这些系统会通过一些您不想更改的内部魔法来生成 .o 文件。您很少能够在任何生产系统中添加源列表。【参考方案5】:

我不确定这是否适合您的需求,但 cmake 还提供了 INTERFACE 库,这些库正好满足(以及其他)这种需求。

add_library(bundle INTERFACE)
target_link_libraries(bundle lib1 lib2)

将 lib1 和 lib2 捆绑到一个库中,并继承 lib1lib2PUBLICINTERFACE 部分。

更多信息here。

【讨论】:

【参考方案6】:

将以下宏添加到您的 cmake 脚本中。

MACRO (TARGET_LINK_LIBRARIES_WHOLE_ARCHIVE target)
  IF (WIN32)
    FOREACH (arg IN LISTS ARGN)
      SET_TARGET_PROPERTIES(
        $target PROPERTIES LINK_FLAGS "/WHOLEARCHIVE:$lib"
      )
    ENDFOREACH ()
  ELSE ()
    IF (APPLE)
      SET(LINK_FLAGS "-Wl,-all_load")
      SET(UNDO_FLAGS "-Wl,-noall_load")
    ELSE ()
      SET(LINK_FLAGS "-Wl,--whole-archive")
      SET(UNDO_FLAGS "-Wl,--no-whole-archive")
    ENDIF ()
    TARGET_LINK_LIBRARIES($target $LINK_FLAGS $ARGN $UNDO_FLAGS)
  ENDIF ()
ENDMACRO ()

然后,在 CMakeLists.txt/*.cmake 中,可以像这样使用TARGET_LINK_LIBRARIES_WHOLE_ARCHIVE(target libs...)

【讨论】:

以上是关于CMake:如何从子项目的所有静态库中创建一个共享库?的主要内容,如果未能解决你的问题,请参考以下文章

Clion(CMake工具)中创建父子项目,引入第三方库的方法

CMake:从函数内定义范围中的访问变量

没有使用ROS顶层CMakelists.txt,如何给所有子项目的cmake默认值?

如何在 Qt 中创建共享小部件的子目录?

cmake 共享库

子项目的 Xcode 环境变量