我有以下布局:
top_project
+ subproject1
+ subproject2
每个
subproject1
和subproject2
都会创建一个静态库。我想将这些静态库链接到 top_project
级别的单个共享库中。
目前我收集到的信息是:
-fPic
进行编译(除了 Windows 之外的所有系统都需要),以创建与位置无关的代码,该代码将允许将静态库链接到单个共享库中,或者解压缩所有静态库(例如使用 ar
)并重新链接将它们放入共享库(我认为这是一个不优雅且不可移植的解决方案)add_library
命令:出于某种我无法理解的原因,简单地编写 add_library(${PROJECT_NAME} SHARED subproject1 subproject2)
并不能按预期工作(它本质上创建了一个空库并且没有正确注册依赖项)有什么想法吗?
好吧,我想通了:这比应有的痛苦得多。直到最近,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 少多少痛苦,而且肯定不太灵活。
我最终没有使用下面的方法,而是使用了 CMake 对象库,它更容易、更可靠。看评论。
我的解决方案只是将
/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
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
$
附加此:
# 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 选项。
另一种方法是提供所有项目的源文件和头文件的路径,并将它们一起构建以生成 .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})
我不确定这是否适合您的需求,但 cmake 还提供
INTERFACE
库,它(除其他外)正是满足这种需求。
add_library(bundle INTERFACE)
target_link_libraries(bundle lib1 lib2)
将把 lib1 和 lib2 捆绑到一个库中,并继承
PUBLIC
和 INTERFACE
的 lib1
和 lib2
部分。
更多信息这里。
将以下宏添加到您的 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...)