@redcircle

Как включить в статическую библиотеку все зависимости из других стат.библиотек в CMake проекте?

Требуется платформенно-независимым образом собрать с помощью CMake статическую библиотеку, в которой уже включены все зависимости из других статических библиотек.

Упрощённый пример, как сейчас выглядит CMakeLists.txt :
cmake_minimum_required(VERSION 3.5)
find_package(Boost COMPONENTS system filesystem thread REQUIRED)
add_library (mylib mysource.cpp)

Проект создаёт статическую библиотеку mylib, но в неё не включена требуемая реализация boost, то есть при линковке приложения с mylib нужно явно указывать, что нужно линковать boost.
Этого хотелось бы избежать, то есть нужно, чтобы все зависимости от boost были уже включены в mylib.

Сделать это хотелось бы средствами CMake (без доп.скриптов, только расширяя CMakeLists.txt) и кроссплатфрорменно.
  • Вопрос задан
  • 1028 просмотров
Пригласить эксперта
Ответы на вопрос 3
@4rtzel
Хотя я бы конечно советовал использовать find_package/add_subdirectory для подключение этой либы к программе, но если всё же стоит задача получить static либу, то можно попробовать что-то вроде этого (на основе этого ответа https://stackoverflow.com/a/37941544/9953527):

CMakeLists.txt:
cmake_minimum_required(VERSION 3.2 FATAL_ERROR)
project(mylib_combined VERSION 0.1 LANGUAGES CXX)

# Нам нужны статические либы
set(Boost_USE_STATIC_LIBS ON) 
find_package(Boost COMPONENTS system filesystem REQUIRED) 

# Наша собственная либа.
# К сожалению мы не может использовать add_library OBJECT либу, т.к.
# CMake не умеет работать с IMPORTED OBJECT либами, т.е. он не умеет
# экстрактить (ar -x) существующею static либу, чтобы собрать её потом 
# в одну большую (ar -qcs).
add_library(mylib mylib.cpp) 
add_custom_command(
    TARGET mylib
    POST_BUILD
    # Экстрактим нашу свежесобранную либу. Не самое красивое решение, но
    # что поделать ¯\_(ツ)_/¯
    COMMAND ar -x $<TARGET_FILE:mylib>
    # Теперь наши буст либы
    COMMAND ar -x ${Boost_SYSTEM_LIBRARY}
    COMMAND ar -x ${Boost_FILESYSTEM_LIBRARY}
    # А теперь собираем всё обратно
    COMMAND ar -qcs lib${PROJECT_NAME}.a *.o
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)


Для работы с этой библиотекой с другого CMake проекта понадобиться add_library IMPORTED таргет. Но если вы всё-таки планируете использовать её в других CMake проектов, то лучше сделать что-то вроде этого:

CMakeLists.txt:
cmake_minimum_required(VERSION 3.2 FATAL_ERROR)
project(mylib_combined VERSION 0.1 LANGUAGES CXX)

set(Boost_USE_STATIC_LIBS ON) 
find_package(Boost COMPONENTS system filesystem REQUIRED) 

add_library(mylib mylib.cpp) 

add_custom_target(mylib_packer
    COMMAND ar -x $<TARGET_FILE:mylib>
    COMMAND ar -x ${Boost_SYSTEM_LIBRARY}
    COMMAND ar -x ${Boost_FILESYSTEM_LIBRARY} 
    COMMAND ar -qcs lib${PROJECT_NAME}.a *.o
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
    DEPENDS mylib
)

add_library(mylib_combined STATIC IMPORTED GLOBAL)
add_dependencies(mylib_combined mylib_packer)
set_target_properties(mylib_combined
    PROPERTIES
        IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/lib${PROJECT_NAME}.a
)


И можно использовать mylib_combined таргет.

Очевидно, код выше будет работать только на unix системах. Для Windows/Mac OS есть свои тулзы, но тут я ничего подсказать не могу. Могу только посоветовать вынести всю работы с ar/libtool/lib в отдельный .cmake файл для дальнейшего переиспользования и подключать его с помщью include.

Полезные ссылки:
https://gitlab.kitware.com/cmake/community/wikis/d...
https://pabloariasal.github.io/2018/02/19/its-time...
https://cmake.org/pipermail/cmake/2014-August/0582...
https://cmake.org/gitweb?p=cmake.git;a=blob;f=Test...
Ответ написан
Nipheris
@Nipheris Куратор тега C++
Господи, что ж тут за костыли насоветовали))

Этого хотелось бы избежать, то есть нужно, чтобы все зависимости от boost были уже включены в mylib.

Как верно отметил jcmvbkbc , обычно одна стат. библиотека не тащит в себе другую. Более того, такая возможность - включить одну стат. библиотеку в состав другой - мало где существует, я знаю про поддержку такой фичи только в Visual Studio. Ну т.е. это нечто из ряда вон выходящее, а не привычный workflow. По-нормальному вы должны указывать при линковке стат. библиотеки все её зависимости. Следовательно, вопрос в том, как эту информацию о транзитивных зависимостях статической библиотеки передавать и использовать при линковке с ней.

pkg-config в общем вполне нормальное решение, но это уже не совсем "в рамках CMake".
В рамках CMake всё делается с помощью target_link_libraries и экспорта симейк-конфига в паре с файлом симейковских таргетов. Например, вот такой незамысловатый скрипт:
cmake_minimum_required(VERSION 3.5)
find_package(Boost COMPONENTS system filesystem thread REQUIRED)
add_library (mylib mysource.cpp)
target_link_libraries(mylib
	PRIVATE
		Boost::system
		Boost::filesystem
		Boost::thread
)

install(TARGETS mylib EXPORT mylib_export)
export(EXPORT mylib_export FILE cmake/mylib-targets.cmake)

сгенерирует в поддиректории cmake в директории сборки файл mylib-targets.cmake, где помимо прочего будет следующая любопытная информация:
# Create imported target mylib
add_library(mylib STATIC IMPORTED)

set_target_properties(mylib PROPERTIES
  INTERFACE_LINK_LIBRARIES "\$<LINK_ONLY:Boost::system>;\$<LINK_ONLY:Boost::filesystem>;\$<LINK_ONLY:Boost::thread>"
)


Таким образом, Симейк сообщает потребителям библиотеки mylib, что при линковке с ней нужно ещё добавить бустовские таргеты. Причём благодаря LINK_ONLY это коснётся только процесса линковки, в целом зависимость от библиотек буста будет приватной, как и указано в скрипте. Если поменять PRIVATE на PUBLIC и сделать тем самым зависимость от буста публичной, то LINK_ONLY уйдёт и будет обычное перечисление интерфейсных зависимостей таргета:
# Create imported target mylib
add_library(mylib STATIC IMPORTED)

set_target_properties(mylib PROPERTIES
  INTERFACE_LINK_LIBRARIES "Boost::system;Boost::filesystem;Boost::thread"
)


Вам остаётся лишь добавить генерацию и установку тривиального конфига (где будет только find_package для буста и инклуд сгенерированного mylib-targets.cmake) и в потребителях делать find_package(mylib). Всё это кроссплатформенно и является стандартной практикой, не делайте лишних извращений со слиянием статических библиотек.
Ответ написан
Комментировать
jcmvbkbc
@jcmvbkbc
"I'm here to consult you" © Dogbert
Проект создаёт статическую библиотеку mylib, но в неё не включена требуемая реализация boost, то есть при линковке приложения с mylib нужно явно указывать, что нужно линковать boost.

Так обычно библиотеки и делают -- ни одна нормальная библиотека не тащит с собой часть другой.
А вот для того, чтобы указать, что у библиотеки есть зависимости используют pkg-config. Такая библиотека устанавливает в системе свой архив (*.a), свои заголовочные файлы (*.h) и описание для pkg-config (*.pc). Пользователь библиотеки вызывает pkg-config --libs <имя библиотеки>и получает список ключей для линковки.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы