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

时间:2012-07-11 09:00:39

标签: cmake shared-libraries

我有以下布局:

top_project
    + subproject1
    + subproject2

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

到目前为止我收集的信息是:

  • 使用-fPic进行编译(除了Windows之外的所有内容都需要)以创建与位置无关的代码,这将允许将静态库链接到单个共享库或解压缩所有静态库(例如使用{{1} })并将它们重新链接到一个共享库(我认为这是一个不优雅的和非便携式解决方案)
  • 所有源文件必须明确地提供给ar命令:由于某些我无法理解的原因,简单地写add_library不能按预期工作(它实际上创建了一个空库&不正确注册依赖项)
  • CMake中有一个OBJECT库功能,但我不认为它的目的是真正做我想做的事。

有什么想法吗?

5 个答案:

答案 0 :(得分:31)

好吧,我明白了:这比应该的痛苦得多。直到最近,Kitware的人才不理解为什么有人想要从静态库创建DLL。他们的论点是主要应该始终存在源文件(例如我的案例中为top_project),因为它实际上是一个自己的项目。我看到不同的东西&我需要将top_project分解为不应该独立存在的较小的子项目(即为他们创建一个完整的项目没有意义,并且使用ExternalProject_Add添加它们)。此外,当我运送我的共享库(供使用,例如使用Java Native Interface)时,我不想发送数十个共享库,因为这相当于暴露了我的项目的内部布局。无论如何,我想 - 从静态库创建一个共享库,我将继续讨论技术细节。

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非常好地管理依赖关系。是跨平台的,因为它比简单的旧Makefiles&amp;肯定不那么灵活。

答案 1 :(得分:6)

我的解决方案只是将/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选项。

答案 2 :(得分:4)

另一种方法。

这种方式似乎更简单,但我不确定它有多完美:

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

答案 3 :(得分:1)

另一种方法是提供源文件的路径和所有项目的头文件,并将它们一起构建以生成.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})

答案 4 :(得分:0)

我不确定这是否适合您的需求,但是cmake还提供INTERFACE库,这些库正是(除其他外)服务于此需求。

add_library(bundle INTERFACE)
target_link_libraries(bundle lib1 lib2)

将lib1和lib2捆绑到一个库中,并继承PUBLICINTERFACE的{​​{1}}和lib1部分。

更多信息here