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

时间:2023-01-21 13:29:00

I have the following layout:

我有如下布局:

top_project
    + subproject1
    + subproject2

Each of subproject1 and subproject2 creates a static library. I would like to link these static libraries in a single shared library at the top_project level.

每个subproject1和subproject2创建一个静态库。我希望将这些静态库链接到top_project级别的单个共享库中。

The information I gathered so far is:

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

  • Either compile using -fPic (necessary on everything but Windows) in order to create position-independent code which will allow linking the static libraries into a single shared library or decompress all static libraries (e.g. using ar) and re-link them into a shared library (which I think is an inelegant & non-portable solution)
  • 编译使用- fpic(必要的一切但Windows)为了创建位置无关代码将允许静态库链接到一个共享库或解压所有静态库(如使用ar)和re-link成一个共享库(我认为这是一种不雅的&不可移植解决方案)
  • All source files must be given explicitly to the add_library command: for some reason which I cannot comprehend, simply writing add_library(${PROJECT_NAME} SHARED subproject1 subproject2) does not work as expected (it essentially creates an empty library & does not register the dependencies properly)
  • 所有源文件都必须显式地给add_library命令:由于某些原因,我无法理解,简单地编写add_library(${PROJECT_NAME}共享子project1 subproject2)并不像预期的那样工作(它实际上创建了一个空库&没有正确地注册依赖项)
  • There is an OBJECT library feature in CMake but I don't think it's purpose is really to do what I want.
  • CMake中有一个对象库特性,但我不认为它的目的是为了做我想做的事情。

Any thoughts?

任何想法吗?

4 个解决方案

#1


28  

OK, I figured it out: this is much more painful than it should be. Until very recently, people at Kitware didn't understand why anyone would ever want to create a DLL from static libs. Their argument is that there should always be source files in the main (e.g. top_project in my case) directory because it is effectively a project of its own. I see things differently & I need to break top_project into smaller subprojects which should not exist independently (i.e. there is no point in creating a full-blown project for them & add them using ExternalProject_Add). Besides, when I ship my shared library (for use, e.g. with a Java Native Interface), I don't want to ship dozens of shared libraries because that would amount to exposing the internal layout of my project. Anyway, having - I think - made a case for creating a shared library from static libraries, I'll proceed to the technical details.

好吧,我想出来了:这比想象中要痛苦得多。直到最近,Kitware的人们还不明白为什么有人想要从静态的libs中创建DLL。他们的观点是,在主目录中应该总是有源文件(例如,在我的案例中是top_project),因为它实际上是一个自己的项目。我有不同的看法&我需要将top_project分解为更小的子项目,这些子项目不应该独立存在(也就是说,为它们创建一个成熟的项目没有意义,并且使用ExternalProject_Add添加它们)。此外,当我发布我的共享库(如使用Java本机接口)时,我不希望发送数十个共享库,因为那样会暴露我的项目的内部布局。不管怎样,我认为——我认为——创建了一个从静态库创建共享库的案例,我将继续讨论技术细节。

In the CMakeLists.txt of subproject1 and subproject2, you should create your target using the OBJECT library feature (introduced in CMake 2.8.8):

CMakeLists。subproject1和subproject2的txt,您应该使用对象库特性(在CMake 2.8.8中引入)创建您的目标:

add_library(${PROJECT_NAME} OBJECT ${SRC})

where SRC designates the list of source files (note that these should be set explicitly in the CMakeLists.txt file as it allows make to re-launch CMake when a modification of CMakeLists.txt is detected, e.g. when adding or removing a file)

其中SRC指定源文件的列表(请注意,这些文件应该在CMakeLists中显式地设置)。当CMakeLists修改时,txt文件允许重新启动CMake。检测到txt,例如添加或删除文件时)

In the top_project, add the subprojects using:

在top_project中,使用以下方法添加子项目:

add_subdirectory(subproject1)
add_subdirectory(subproject2)

In order to see the symbols from the static library, use:

为了从静态库中看到符号,可以使用:

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

You can then create the shared library using:

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

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

I've found that any "normal" library (i.e. not object) needs to be added in a separate add_library command, otherwise it is simply ignored.

我发现,任何“正常”库(即非对象)都需要添加到一个单独的add_library命令中,否则就会被忽略。

For executables, you can use:

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

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}

I repeat that this only works as of version 2.8.8 of CMake. Just as well CMake manages the dependencies extremely well & is cross-platform because it's not much less painful than plain old Makefiles & certainly less flexible.

我重复一下,这只适用于CMake的2.8.8版本。与CMake一样,CMake对依赖项的管理非常好&是跨平台的,因为它比普通的makefile要痛苦得多,当然也不那么灵活。

#2


5  

My solution is simply to add /WHOLEARCHIVE, -all_load, or --whole-archive to the linker flags, so that when your main library is linked, all of the sub libraries are included, including all their symbols (the default behaviour is to only include symbols of the sub libraries that are used by the main library. For example:

我的解决方案是添加/ WHOLEARCHIVE -all_load,或者——whole-archive链接器的旗帜,所以当与主库,包括所有的子库,包括所有的符号(默认行为是只包括符号使用的子库,主要的图书馆。例如:

Source Files

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

Naive 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)

Running it (on OSX):

(在运行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
$

Correct CMakeLists.txt

Append this:

附加:

# 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 ()

Running it (note the extra -all_load):

运行它(注意额外的-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

Note that I've only actually tested -all_load so far, and /WHOLEARCHIVE is an MSVC 2015 option.

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

#3


4  

Another way of doing it.

另一种方法。

This way seems simpler, but I'm not sure how perfect it is:

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

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

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

#4


1  

Another way of doing it is to provide the path of the source files and the header files of all your projects, and build them together to produce the .so . This is usually the recommended way, instead of creating the static libraries and then a shared library out of those.

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

Basically you should do the following:

基本上你应该这样做:

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})

#1


28  

OK, I figured it out: this is much more painful than it should be. Until very recently, people at Kitware didn't understand why anyone would ever want to create a DLL from static libs. Their argument is that there should always be source files in the main (e.g. top_project in my case) directory because it is effectively a project of its own. I see things differently & I need to break top_project into smaller subprojects which should not exist independently (i.e. there is no point in creating a full-blown project for them & add them using ExternalProject_Add). Besides, when I ship my shared library (for use, e.g. with a Java Native Interface), I don't want to ship dozens of shared libraries because that would amount to exposing the internal layout of my project. Anyway, having - I think - made a case for creating a shared library from static libraries, I'll proceed to the technical details.

好吧,我想出来了:这比想象中要痛苦得多。直到最近,Kitware的人们还不明白为什么有人想要从静态的libs中创建DLL。他们的观点是,在主目录中应该总是有源文件(例如,在我的案例中是top_project),因为它实际上是一个自己的项目。我有不同的看法&我需要将top_project分解为更小的子项目,这些子项目不应该独立存在(也就是说,为它们创建一个成熟的项目没有意义,并且使用ExternalProject_Add添加它们)。此外,当我发布我的共享库(如使用Java本机接口)时,我不希望发送数十个共享库,因为那样会暴露我的项目的内部布局。不管怎样,我认为——我认为——创建了一个从静态库创建共享库的案例,我将继续讨论技术细节。

In the CMakeLists.txt of subproject1 and subproject2, you should create your target using the OBJECT library feature (introduced in CMake 2.8.8):

CMakeLists。subproject1和subproject2的txt,您应该使用对象库特性(在CMake 2.8.8中引入)创建您的目标:

add_library(${PROJECT_NAME} OBJECT ${SRC})

where SRC designates the list of source files (note that these should be set explicitly in the CMakeLists.txt file as it allows make to re-launch CMake when a modification of CMakeLists.txt is detected, e.g. when adding or removing a file)

其中SRC指定源文件的列表(请注意,这些文件应该在CMakeLists中显式地设置)。当CMakeLists修改时,txt文件允许重新启动CMake。检测到txt,例如添加或删除文件时)

In the top_project, add the subprojects using:

在top_project中,使用以下方法添加子项目:

add_subdirectory(subproject1)
add_subdirectory(subproject2)

In order to see the symbols from the static library, use:

为了从静态库中看到符号,可以使用:

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

You can then create the shared library using:

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

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

I've found that any "normal" library (i.e. not object) needs to be added in a separate add_library command, otherwise it is simply ignored.

我发现,任何“正常”库(即非对象)都需要添加到一个单独的add_library命令中,否则就会被忽略。

For executables, you can use:

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

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}

I repeat that this only works as of version 2.8.8 of CMake. Just as well CMake manages the dependencies extremely well & is cross-platform because it's not much less painful than plain old Makefiles & certainly less flexible.

我重复一下,这只适用于CMake的2.8.8版本。与CMake一样,CMake对依赖项的管理非常好&是跨平台的,因为它比普通的makefile要痛苦得多,当然也不那么灵活。

#2


5  

My solution is simply to add /WHOLEARCHIVE, -all_load, or --whole-archive to the linker flags, so that when your main library is linked, all of the sub libraries are included, including all their symbols (the default behaviour is to only include symbols of the sub libraries that are used by the main library. For example:

我的解决方案是添加/ WHOLEARCHIVE -all_load,或者——whole-archive链接器的旗帜,所以当与主库,包括所有的子库,包括所有的符号(默认行为是只包括符号使用的子库,主要的图书馆。例如:

Source Files

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

Naive 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)

Running it (on OSX):

(在运行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
$

Correct CMakeLists.txt

Append this:

附加:

# 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 ()

Running it (note the extra -all_load):

运行它(注意额外的-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

Note that I've only actually tested -all_load so far, and /WHOLEARCHIVE is an MSVC 2015 option.

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

#3


4  

Another way of doing it.

另一种方法。

This way seems simpler, but I'm not sure how perfect it is:

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

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

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

#4


1  

Another way of doing it is to provide the path of the source files and the header files of all your projects, and build them together to produce the .so . This is usually the recommended way, instead of creating the static libraries and then a shared library out of those.

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

Basically you should do the following:

基本上你应该这样做:

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})