CMake:如何从子项目的所有静态库创build单个共享库?
我有以下布局:
top_project + subproject1 + subproject2
每个subproject1
和subproject2
创build一个静态库。 我想将这些静态库链接到top_project
级别的单个共享库中。
我目前收集到的信息是:
- 要么编译使用
-fPic
(除了Windows之外的所有必要的),以创build位置无关的代码,这将允许将静态库链接到单个共享库或解压缩所有静态库(例如使用ar
)并将它们重新链接到共享图书馆(我认为这是一个不雅的和非便携式的解决scheme) - 所有的源文件必须明确地给予
add_library
命令:出于某种我不理解的原因,简单地写add_library(${PROJECT_NAME} SHARED subproject1 subproject2)
不能按预期工作(它本质上创build一个空的库&不注册依赖关系正确) - CMake中有一个OBJECT库特性,但是我不认为它的目的是真正做我想做的事情。
有什么想法吗?
好吧,我明白了:这比应该更痛苦。 直到最近,Kitware的人才不明白为什么有人会想从静态库创build一个DLL。 他们的论点是,在主目录(例如top_project
中总是应该有源文件,因为它实际上是它自己的项目。 我看到了不同的东西,我需要把top_project
分解成更小的子项目,这些子项目不应该独立存在(也就是说没有必要为它们创build一个完整的项目并使用ExternalProject_Add
添加它们)。 此外,当我运送我的共享库(例如使用Java本地接口)时,我不想运送数十个共享库,因为这将暴露我的项目的内部布局。 无论如何,我想 – 从静态库创build一个共享库的情况下,我会进入技术细节。
在subproject1
和subproject2
的CMakeLists.txt中,应该使用OBJECT库特性(在CMake 2.8.8中引入)创build目标:
add_library(${PROJECT_NAME} OBJECT ${SRC})
SRC
指定源文件列表(注意,这些文件应该在CMakeLists.txt文件中明确设置,因为它允许在检测到CMakeLists.txt的修改时重新启动CMake,例如添加或删除文件时)
在top_project
,添加子项目使用:
add_subdirectory(subproject1) add_subdirectory(subproject2)
为了看到静态库中的符号,使用:
set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--export-all-symbols")
然后可以使用以下命令创build共享库:
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也非常好地pipe理依赖关系,并且是跨平台的,因为它比普通的Makefiles不那么痛苦,当然也不太灵活。
我的解决scheme是简单地将/WHOLEARCHIVE
, -all_load
或-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, eg 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 ()
运行它(注意extra -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
请注意,我目前实际上只testing了-all_load
,而/WHOLEARCHIVE
是一个MSVC 2015选项。
另一种方法是提供所有项目的源文件和头文件的path,并将它们一起构build以产生.so。 这通常是推荐的方法,而不是创build静态库,然后创build一个共享库。
基本上你应该做以下几点:
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})