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
subproject1
和 subproject2
中的每一个都创建一个静态库。我想将这些静态库链接到 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 原生接口)时,我不想发布数十个共享库,因为这等于暴露了我项目的内部布局。无论如何,我认为已经为从静态库创建共享库做了一个案例,我将继续讨论技术细节。
在 subproject1
和 subproject2
的 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 捆绑到一个库中,并继承 lib1
和 lib2
的 PUBLIC
和 INTERFACE
部分。
更多信息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工具)中创建父子项目,引入第三方库的方法