CMake: как создать единую разделяемую библиотеку из всех статических библиотек подпроектов?

У меня такой расклад:

top_project
    + subproject1
    + subproject2

Каждый из subproject1 и subproject2 создает статическую библиотеку. Я хотел бы связать эти статические библиотеки в единую разделяемую библиотеку на уровне top_project.

Информация, которую я собрал, такова:

  • Либо скомпилируйте с использованием -fPic (необходимо во всем, кроме Windows), чтобы создать независимый от позиции код, который позволит связать статические библиотеки в единую разделяемую библиотеку, либо распакуйте все статические библиотеки (например, используя ar) и повторно скомпонуйте их в общую библиотеку. (что, на мой взгляд, является неэлегантным и непереносимым решением)
  • Все исходные файлы должны быть явно переданы команде add_library: по какой-то причине, которую я не могу понять, простое написание add_library(${PROJECT_NAME} SHARED subproject1 subproject2) не работает должным образом (по сути, создается пустая библиотека и не регистрируются зависимости должным образом)
  • В CMake есть функция библиотеки OBJECT, но я не думаю, что ее цель - делать то, что я хочу.

Есть предположения?


person Deimos    schedule 11.07.2012    source источник
comment
Я использую cmake 3.4. +, И я просто добавляю статические библиотеки в общую библиотеку, и они компилируются в один файл :) Я тестировал это на android :)   -  person Dawid Drozd    schedule 16.11.2016
comment
есть ли у кого-нибудь подсказка, как это сделать в MSVC? Я использую qmake, а не cmake, но я могу справиться с шагами сам, если смогу их понять ...   -  person S. Paris    schedule 04.05.2017


Ответы (5)


Ладно, разобрался: это намного больнее, чем должно быть. До недавнего времени люди в Kitware не понимали, зачем кому-то вообще захочется создавать DLL из статических библиотек. Их аргумент состоит в том, что всегда должны быть исходные файлы в основном (например, top_project в моем случае) каталоге, потому что это, по сути, собственный проект. Я вижу вещи по-другому, и мне нужно разбить top_project на более мелкие подпроекты, которые не должны существовать независимо (т.е. нет смысла создавать для них полноценный проект и добавлять их с помощью ExternalProject_Add). Кроме того, когда я отправляю свою общую библиотеку (для использования, например, с собственным интерфейсом Java), я не хочу отправлять десятки общих библиотек, потому что это будет означать раскрытие внутренней структуры моего проекта. В любом случае, как мне кажется, обосновав необходимость создания разделяемой библиотеки из статических библиотек, я перейду к техническим деталям.

В CMakeLists.txt для subproject1 и subproject2 вы должны создать свою цель с помощью функции библиотеки OBJECT (представленной в CMake 2.8.8):

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

где SRC обозначает список исходных файлов (обратите внимание, что они должны быть явно установлены в файле CMakeLists.txt, поскольку он позволяет make повторно запустить CMake при обнаружении модификации CMakeLists.txt, например, при добавлении или удалении файла)

В 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, и, конечно, менее гибко.

person Deimos    schedule 12.07.2012
comment
Ба, досадно, что Ubuntu 12.04 застрял на CMake 2.8.7, есть ли альтернатива для более старых версий? Нужно ли нам просто ссылаться на все исходные файлы при определении библиотеки? - person Ibrahim; 21.11.2012
comment
Я работал над своими проблемами, скомпилировав свои статические библиотеки с помощью -fPIC, моя общая библиотека подключилась правильно, но я не знаю, работает ли она на самом деле, поскольку я еще не пробовал ее использовать. - person Ibrahim; 21.11.2012
comment
Это решение просто избавило меня от необходимости много печатать. - person Equilibrius; 01.11.2019

Мое решение - просто добавить /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.

person Timmmm    schedule 25.01.2017
comment
Я использую Linux, просто добавление -Wl,--whole-archive привело к множеству ошибок определений, связанных с libgcc.a - person nodakai; 20.03.2017
comment
Да, вариант /WHOLEARCHIVE, похоже, тоже не так хорошо работает, поэтому я просто выбрал подход библиотеки объектов. - person Timmmm; 20.03.2017

Другой способ сделать это.

Этот способ кажется проще, но я не уверен, насколько он идеален:

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

person matiu    schedule 15.01.2013

Другой способ сделать это - указать путь к исходным файлам и файлам заголовков всех ваших проектов и собрать их вместе для создания .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})
person sasfour    schedule 29.05.2015
comment
Вряд ли это будет полезно. Проблема в том, что у вас часто есть смесь старых систем сборки, которые создают файлы .o с помощью какой-то внутренней магии, которую вы НЕ хотите изменять. Вы редко сможете просто добавить список источников в любую производственную систему. - person James Moore; 05.01.2018

Я не уверен, что это то, что вам нужно, но cmake также предлагает INTERFACE библиотеки, которые обслуживают (среди прочего) именно эту потребность.

add_library(bundle INTERFACE)
target_link_libraries(bundle lib1 lib2)

объединит lib1 и lib2 в одну библиотеку и унаследует разделы PUBLIC и INTERFACE lib1 и lib2.

Дополнительная информация здесь.

person bartgol    schedule 01.05.2020