Комментарии 37
Натолкнула меня написать этот пост следующая история. Я хотел собрать минимального Telegram-бота на Си++, и всё, что мне нужно было, — это подключить библиотеку tgbot-cpp
понимаю, меня это натолкнуло написать свою библиотеку для тг ботов, где эта проблема решена https://github.com/bot-motherlib/TGBM
Аналогичная ситуация. Когда захотел внедрить в свой проект «МедиаТекст» (не опубликован, но скриншот – http://scholium.webservis.ru/Pics/MediaText.png ) код опенсорсного видео FFPlay.c, то, даже просто скомпилировать этот видеопроигрыватель под «Форточки», со всеми зависимостями, оказалось затеей не для слабонервных. А нужно было вставить это видео в выделенную часть клиентской области приложения. Причем, все подобные решения на Гитхабе оказали нереализуемыми, «из коробки».
Задачу решил путем переделки Си-кода в С++-классы и компиляцией в своем проекте.
Вообще, это общая проблема, использование С++ кода из Линкуса в Виндоус.
Таким образом, динамические библиотеки экономят нам память, загружаясь лишь в единственном экземпляре.
Не упомянули один важный момент - при динамической линковке мы автоматически подхватим оптимизации и фиксы в новых версиях тех же libc или libssl при апдейте системы. И ещё нюанс с использованием LGPL библиотек в проприетарных продуктах.
Верно подмечено. Не просто так динамическая линковка везде является дефолтом если на смену подхода нет очень веских причин.
А вот по поводу LGPL меня эта информация обходила стороной и стала новостью:
LGPL позволяет использовать библиотеку в проприетарном продукте, если библиотека подключается динамически (например, через динамические библиотеки .dll
, .so
).
Приятная статья, спасибо большое!
Лично для меня ldd было спасением, когда я пеовый раз компилировал на серваке с кучей версий компиляторов и библиотек.
Есть в Gentoo пакеты, которые позволяют установить несколько версий и выбрать через симлинк дефолтную версию.
java:
# eselect java-vm list
Available Java Virtual Machines:
[1] openjdk-bin-8
[2] openjdk-bin-11
[3] openjdk-bin-17
[4] openjdk-bin-21 system-vm
gcc:
# eselect java-vm list
Available Java Virtual Machines:
[1] openjdk-bin-8
[2] openjdk-bin-11
[3] openjdk-bin-17
[4] openjdk-bin-21 system-vm
Но должно быть нечто, позволяющее выбрать необходимую версию. В основном это модули для eselect, который позволяет контролировать используемую версию. Именно такие пакеты с модулями для eselect обычно имеют слоты (до двоеточия версия, после - номер слота):
sys-kernel/gentoo-sources-6.13.0:6.13.0
Ни boost ни cmake не относятся к подобным пакетам, у которых можно было бы установить одновременно несколько версий:
# equery list -p cmake
* Searching for cmake ....
[-P-] [ ] dev-build/cmake-3.28.5:0
[-P-] [ ] dev-build/cmake-3.30.5:0
[-P-] [ ] dev-build/cmake-3.30.6:0
[-P-] [ ] dev-build/cmake-3.31.3:0
[-P-] [ ] dev-build/cmake-3.31.4-r1:0
[IP-] [ ] dev-build/cmake-3.31.5:0
[-P-] [ -] dev-build/cmake-9999:0
При непонимании темы лучше попросить объясненений, а не приправлять это непонимание хамством для пущей эффектности.
никто не рассказал, что через "make install" можно поставить в отдельный префикс и не трогать всё остальное.
Как поведет себя FindPackage() если cmake тупо ляжет в отдельном префиксе? Или же немного нужно потрогать и даже поплясать чтобы сборщик смог им нормально воспользоваться?
CMake можно тыкать в префикс, где лежат отдельно собранные как надо библиотеки.
Практически каждый модуль CMake дополнительно имеет свой аргумент <Package>_ROOT, чтобы ему дополнительно ткнуть пальцем в конкретный модуль.
Конкретно связка Boost+CMake прошла тяжелый путь от CMake-модуля до CMake-конфига, и какие-то сочетания друг с другом плохо стыкуются. Это да. Ничего не мешает в отдельный префикс положить целый CMake нужной версии.
Собственно о статье - начали с нестыковок между системный окружением и требованиями конкретного проекта, а потом перешли на базовые принципы линковки, заметя проблему под ковер Docker-а. А после сборки в Ubuntu 20, как бинарь в хост-Gentoo использовать?
Опишу собственный подход к такой проблеме:
Системное окружение остается системным окружением, согласно завету пакетного менеджера. Проект или навязывает необходимые зависимости и окружение, или мы сами выбираем под что будем собирать и запускать. В отдельный префикс вручную по инструкциям собираем все то, что или недоступно в системе, или нас не устраивает. На этом шаге больше всего проблем, ибо даже одну систему сборки разные проекты используют по разному, а еще есть meson, autotools, make, и прочий зоопарк, и никто не читает инструкции для инструмента. После страданий с указанием префикса для тех зависимостей, которые не могут с префиксом и out-of-source сборку, берем свой проект, тыкаем CMake в нужные префикс и полетели. И это для разработки. Для релиза/деплоя все пройденные этапы упаковываем в скрипты сборки под целевую платформу, и объединяем со сборкой нашего проекта, кладя в тот же префикс. Итого, имеет почти самодостаточную директорию проекта со всеми зависимостями. Отсюда уже варианты - упаковка в контейнеры, в пакеты для целевой платформы, архивы и все такое.
Описанный подход прекрасно работает для Linux семейства, позволяя деволопить в одной системе, а деплоить в другие, где зависимости нужных версий не доступны. Чуть менее комфортно поддерживать или девелопить в Windows, Mingw сглаживает трудности, и если оставаться в пределах Mingw окружения - то все то же самое. А при намерении нативно собираться MSVC и следовать заветам Windows окружения, приходится собирать зависимости 'под Windows', уговаривать CMake дополнительными флагами и опциями, и страдать над GNU библиотеками, которым для MSVC сборки надо через Cygwin присовывать в autotools черте-что, и не косячить с путями и слэшами.
про cmake ничего не понял
Значит нужно собрать старый boost отдельно от системы, подключив к проекту через cmake. Опа! Но cmake тоже новый, и на старую версию boost он ругается ошибками.
что мешает скачать старый cmake с cmake.org или github (gitlab)? Распаковать в какую-нибудь папку, добавить в PATH и вуаля, я так делал.
Но не тут-то было. Оказалось, что в новых версиях такого мощного и любимого всеми сборщика проектов Cmake изменились политики поиска библиотек через функцию FindPackage, что он категорически отказался находить старые версии, как я ни плясал над ним
это видимо про политику CMP0167, но что мешает вернуть старое поведение через cmake_policy
? Я посмотрел в версии 3.31, FindBoost.cmake
пока на месте.
Такой cmake будет использовать модули поиска пакетов из системной директории потому что понятия не будет иметь как найти эти скрипты "в какой-нибудь папке". Вот у меня он найдет и использует вот этот потому что PATH касается только основного бинаря:
/usr/share/cmake/Modules/FindBoost.cmake
Самый простой и надежный вариант изолировать сборщик и зависимости от системы это Docker.
А какие версии boost и libcurl в итоге нужны? В conancenter около десятка разных имеется под разные ОС и архитектуры. Cкачиваются и ставятся одной командой, сonan install —requires=...
Эта команда также создает все нужные файлы, чтобы CMake нормально все собрала
За вторую часть статьи по библиотекам спасибо.
Но что касается первой части - все это давно решено пакетными менеджерами cpp, вот например: https://vcpkg.link/ports/tgbot-cpp/v/1.7.3/0
Соберет все что нужно со всеми зависимостями, хоть статик хоть динамик.
Также, насчет статической линковки, поправьте меня, но насколько я помню, в статье неправильно написано - из .a файла в бинарь слинкуются не все объектники, а только те, что реально используются.
хм... "давно решено"?
Ну вот я сижу на маке, проект собираю под винду, нужно собрать openssl.
vcpkg install openssl:x64-windows внезапно не работает!
Даже если просунуть тулчейн (которым прекрасно собирается весь проект под винду) - тоже не работает!
А че за агрессия? 😁
Потому что во первых, мой комментарий в контексте статьи, а значит в рамках одной платформы.
Во вторых, Ваш кейс явно очень специфический и у мейнтейнеров есть куда более перспективные задачи, а Ваша стопудов решается установкой винды в Parallels и сборкой нативным MSVC.
В третьих, если таки нужна кросс сборка, то недостаточно просто использовать публичный триплет, разумеется он не будет ничего знать сходу про нужный тулчейн. К тому же в этом случае и сами порты может перекособочить, если там в тулчейне не msvc, а спец. компилятор.
Тем не менее для Вашего случая вот пример для кросс сборки под линукс, может на его основе как-то можно сделать триплет кастомный для кросс сборки мак-винда: https://stackoverflow.com/questions/58777810/how-to-integrate-vcpkg-in-linux-with-cross-build-toolchain-as-well-as-sysroot
Вы если попробуете - напишите потом мне ответ или даже статью плиз, было бы интересно почитать, зачем такое нужно, почему не решается другими путями и получилось ли что с триплетом. Для сообщества будет открыт еще один кейс.
Нет, тут дело в том, что vcpkg - далеко не "серебряная пуля". В код многих портов лучше вообще не заглядывать, настолько там всё костыльно. А openssl - тот ещё подарок для сборки. У него собственная система конфига на perl, а vcpkg лишь добавляет пару своих "слоёв абстракции".
Однако независимо от openssl - он сам довольно кривой. В режиме кросс-сборки у него компилятор хоста "просачивается" в цель, игнорируя заданный в тулчейне.
Есть такое к сожалению. С другой стороны, доя нативной сборки хотя бы - уже лучше, чем руками писать все эти скрипты по сборке всех зависимостей, а потом переносить на сборочные ПК. К vcpkg можно привыкнуть, порой хотя бы порт допилить можно под свои нужды, на основе документации библиотеки. OpenSSL - да, это ужос)
А про Ваш кейс правда было бы интересно узнать, зачем такое может понадобиться и как такое решается.
самое страшное случится, если все программы, которые собраны статически, попытаются загрузиться в память одновременно. Каждая из программ будет содержать копии кода используемых библиотек
А вы в реальности сталкивались с этой "проблемой"?
Я вот динамические разделяемые библиотеки использую, но совсем для другого: как средства расширения функциональности (плагины и т.п.), а также в случае, когда статическая линковка невозможна (С - библиотека и какой-нибудь Бэйсик-интерпретатор, например), или когда реально нужно сократить объем передаваемых по сети данных при обновлении софта (до сих пор есть весьма медленные каналы связи), для разделения логики, или по лицензионным причинам. С нехваткой памяти из-за объема кода ни разу не сталкивался.
Не сталкивался потому что никому в голову не приходило собрать статически бинарь от какого-то браузера или аналогичной софтины. На практике часть библиотек даже невозможно слинковать статически. Вот берем chromium-browser:
ls -l /usr/lib64/chromium-browser/chrome
-rwxr-xr-x 1 root root 272288544 Jan 20 18:45 /usr/lib64/chromium-browser/chrome
272 мегабайта с динамической линковкой, Карл!
А теперь смотрим список сколько библиотек он тянет из системы:
ldd /usr/lib64/chromium-browser/chrome
linux-vdso.so.1 (0x00007ffeebaff000)
libgobject-2.0.so.0 => /usr/lib64/libgobject-2.0.so.0 (0x00007f6b05efa000)
libglib-2.0.so.0 => /usr/lib64/libglib-2.0.so.0 (0x00007f6b05dac000)
libsmime3.so => /usr/lib64/libsmime3.so (0x00007f6b05d7d000)
libnss3.so => /usr/lib64/libnss3.so (0x00007f6b05c3d000)
libnssutil3.so => /usr/lib64/libnssutil3.so (0x00007f6b05c0e000)
libnspr4.so => /usr/lib64/libnspr4.so (0x00007f6b05bcd000)
libdbus-1.so.3 => /usr/lib64/libdbus-1.so.3 (0x00007f6b05b7a000)
libatk-bridge-2.0.so.0 => /usr/lib64/libatk-bridge-2.0.so.0 (0x00007f6b05b3b000)
libatk-1.0.so.0 => /usr/lib64/libatk-1.0.so.0 (0x00007f6b05b14000)
libcups.so.2 => /usr/lib64/libcups.so.2 (0x00007f6b05a83000)
libgio-2.0.so.0 => /usr/lib64/libgio-2.0.so.0 (0x00007f6b05894000)
libfontconfig.so.1 => /usr/lib64/libfontconfig.so.1 (0x00007f6b05846000)
libz.so.1 => /usr/lib64/libz.so.1 (0x00007f6b05829000)
libzstd.so.1 => /usr/lib64/libzstd.so.1 (0x00007f6b0576c000)
libexpat.so.1 => /usr/lib64/libexpat.so.1 (0x00007f6b05742000)
libpng16.so.16 => /usr/lib64/libpng16.so.16 (0x00007f6b05708000)
libwebpdemux.so.2 => /usr/lib64/libwebpdemux.so.2 (0x00007f6b05701000)
libwebpmux.so.3 => /usr/lib64/libwebpmux.so.3 (0x00007f6b056f4000)
libwebp.so.7 => /usr/lib64/libwebp.so.7 (0x00007f6b05681000)
libfreetype.so.6 => /usr/lib64/libfreetype.so.6 (0x00007f6b055b6000)
libjpeg.so.62 => /usr/lib64/libjpeg.so.62 (0x00007f6b054fc000)
libharfbuzz-subset.so.0 => /usr/lib64/libharfbuzz-subset.so.0 (0x00007f6b0538e000)
libharfbuzz.so.0 => /usr/lib64/libharfbuzz.so.0 (0x00007f6b0524e000)
libopenh264.so.7 => /usr/lib64/libopenh264.so.7 (0x00007f6b0514e000)
libm.so.6 => /usr/lib64/libm.so.6 (0x00007f6b0509c000)
libX11.so.6 => /usr/lib64/libX11.so.6 (0x00007f6b04f54000)
libXcomposite.so.1 => /usr/lib64/libXcomposite.so.1 (0x00007f6b04f4f000)
libXdamage.so.1 => /usr/lib64/libXdamage.so.1 (0x00007f6b04f4a000)
libXext.so.6 => /usr/lib64/libXext.so.6 (0x00007f6b04f35000)
libXfixes.so.3 => /usr/lib64/libXfixes.so.3 (0x00007f6b04f2d000)
libXrandr.so.2 => /usr/lib64/libXrandr.so.2 (0x00007f6b04f1e000)
libXtst.so.6 => /usr/lib64/libXtst.so.6 (0x00007f6b04f16000)
libgbm.so.1 => /usr/lib64/libgbm.so.1 (0x00007f6b04f0f000)
libxcb.so.1 => /usr/lib64/libxcb.so.1 (0x00007f6b04ee3000)
libxkbcommon.so.0 => /usr/lib64/libxkbcommon.so.0 (0x00007f6b04e9a000)
libffi.so.8 => /usr/lib64/libffi.so.8 (0x00007f6b04e8d000)
libpango-1.0.so.0 => /usr/lib64/libpango-1.0.so.0 (0x00007f6b04e1f000)
libcairo.so.2 => /usr/lib64/libcairo.so.2 (0x00007f6b04cdb000)
libudev.so.1 => /usr/lib64/libudev.so.1 (0x00007f6b04c85000)
libasound.so.2 => /usr/lib64/libasound.so.2 (0x00007f6b04b97000)
libpulse.so.0 => /usr/lib64/libpulse.so.0 (0x00007f6b04b41000)
libFLAC.so.12 => /usr/lib64/libFLAC.so.12 (0x00007f6b04adf000)
libxml2.so.2 => /usr/lib64/libxml2.so.2 (0x00007f6b04985000)
libatspi.so.0 => /usr/lib64/libatspi.so.0 (0x00007f6b0494c000)
libminizip.so.1 => /usr/lib64/libminizip.so.1 (0x00007f6b0493e000)
libxslt.so.1 => /usr/lib64/libxslt.so.1 (0x00007f6b048fb000)
libgcc_s.so.1 => /usr/lib/gcc/x86_64-pc-linux-gnu/14/libgcc_s.so.1 (0x00007f6b048cd000)
libc.so.6 => /usr/lib64/libc.so.6 (0x00007f6b046f3000)
/lib64/ld-linux-x86-64.so.2 (0x00007f6b165fd000)
libpcre2-8.so.0 => /usr/lib64/libpcre2-8.so.0 (0x00007f6b0464e000)
libplc4.so => /usr/lib64/libplc4.so (0x00007f6b04647000)
libplds4.so => /usr/lib64/libplds4.so (0x00007f6b04641000)
libgnutls.so.30 => /usr/lib64/libgnutls.so.30 (0x00007f6b04443000)
libgmodule-2.0.so.0 => /usr/lib64/libgmodule-2.0.so.0 (0x00007f6b0443c000)
libmount.so.1 => /usr/lib64/libmount.so.1 (0x00007f6b043c7000)
libsharpyuv.so.0 => /usr/lib64/libsharpyuv.so.0 (0x00007f6b043be000)
libbz2.so.1 => /usr/lib64/libbz2.so.1 (0x00007f6b043a9000)
libbrotlidec.so.1 => /usr/lib64/libbrotlidec.so.1 (0x00007f6b0439a000)
libgraphite2.so.3 => /usr/lib64/libgraphite2.so.3 (0x00007f6b04375000)
libstdc++.so.6 => /usr/lib/gcc/x86_64-pc-linux-gnu/14/libstdc++.so.6 (0x00007f6b040f7000)
libXrender.so.1 => /usr/lib64/libXrender.so.1 (0x00007f6b040ea000)
libdrm.so.2 => /usr/lib64/libdrm.so.2 (0x00007f6b040d3000)
libXau.so.6 => /usr/lib64/libXau.so.6 (0x00007f6b040cd000)
libXdmcp.so.6 => /usr/lib64/libXdmcp.so.6 (0x00007f6b040c5000)
libfribidi.so.0 => /usr/lib64/libfribidi.so.0 (0x00007f6b040a3000)
libxcb-render.so.0 => /usr/lib64/libxcb-render.so.0 (0x00007f6b04093000)
libxcb-shm.so.0 => /usr/lib64/libxcb-shm.so.0 (0x00007f6b0408e000)
libpixman-1.so.0 => /usr/lib64/libpixman-1.so.0 (0x00007f6b03fee000)
libcap.so.2 => /usr/lib64/libcap.so.2 (0x00007f6b03fe1000)
libpulsecommon-17.0.so => /usr/lib64/pulseaudio/libpulsecommon-17.0.so (0x00007f6b03f55000)
libogg.so.0 => /usr/lib64/libogg.so.0 (0x00007f6b03f4b000)
libicui18n.so.76 => /usr/lib64/libicui18n.so.76 (0x00007f6b03c15000)
libicuuc.so.76 => /usr/lib64/libicuuc.so.76 (0x00007f6b03a10000)
libXi.so.6 => /usr/lib64/libXi.so.6 (0x00007f6b039fc000)
libidn2.so.0 => /usr/lib64/libidn2.so.0 (0x00007f6b039c7000)
libunistring.so.5 => /usr/lib64/libunistring.so.5 (0x00007f6b037e1000)
libtasn1.so.6 => /usr/lib64/libtasn1.so.6 (0x00007f6b037cc000)
libhogweed.so.6 => /usr/lib64/libhogweed.so.6 (0x00007f6b0377f000)
libnettle.so.8 => /usr/lib64/libnettle.so.8 (0x00007f6b0372d000)
libgmp.so.10 => /usr/lib64/libgmp.so.10 (0x00007f6b03685000)
libblkid.so.1 => /usr/lib64/libblkid.so.1 (0x00007f6b03625000)
libbrotlicommon.so.1 => /usr/lib64/libbrotlicommon.so.1 (0x00007f6b03602000)
libsndfile.so.1 => /usr/lib64/libsndfile.so.1 (0x00007f6b03576000)
libasyncns.so.0 => /usr/lib64/libasyncns.so.0 (0x00007f6b03570000)
libicudata.so.76 => /usr/lib64/libicudata.so.76 (0x00007f6b0170b000)
libvorbis.so.0 => /usr/lib64/libvorbis.so.0 (0x00007f6b016db000)
libvorbisenc.so.2 => /usr/lib64/libvorbisenc.so.2 (0x00007f6b01643000)
libopus.so.0 => /usr/lib64/libopus.so.0 (0x00007f6b015e1000)
libmpg123.so.0 => /usr/lib64/libmpg123.so.0 (0x00007f6b01598000)
libmp3lame.so.0 => /usr/lib64/libmp3lame.so.0 (0x00007f6b01520000)
libmvec.so.1 => /usr/lib64/libmvec.so.1 (0x00007f6b01425000)
Да это же половину всего дистрибутива нужно будет в память загрузить! И это только код не считая уже самого контента, для которого места уже совсем не останется.
При чем тут библиотеки, которые уже есть в системе. Явно же речь о причинах для создания собственных.
PS;
Ой, что это что такое хотя бы самая первая библиотека в вашем списке: linux-vdso.so.1? А где она находится ?
Ой, что это за циферки рядом с именем "файла" - 0x00007f6b05efa000? Неужели размер?
0x00007f6b05efa000 == 16 952 335 507 456 (dec) байт, серьезно? Почти 17 терабайт, какой кошмар!
Странный комментарий. Я вообще не понимаю в чем вопрос и какие должны быть причины для создания собственных библиотек в контексте динамической или статической линковки.
Рядом с SO файлами в выводе ldd пишутся не террабайты, а контрольная сумма.
И по поводу VDSO тоже немного ликбеза:
linux-vdso.so.1 — это виртуальный общий объект, предоставляемый ядром Linux. Это не физический файл, расположенный на диске, а механизм, предоставляемый ядром.
Цель vDSO (virtual dynamic shared object) — оптимизировать производительность определённых системных вызовов, позволяя им выполняться непосредственно в пользовательском пространстве, без необходимости переключения контекста в режим ядра.
Библиотека позволяет быстро выполнять определённые функции ядра, такие как функции времени, для доступа к которым не требуется какой-либо особый уровень привилегий. Вызов этих функций позволяет получить общедоступную системную информацию без фактического вызова системного вызова.
Имя vDSO отличается на разных архитектурах и часто его можно увидеть в выводе утилит, подобных ldd.
Если у вас серьёзный проект с кучкой бинарников, которые переиспользуют одни библиотеки и всё в целом занимает сотни мегабайт - проблемы те же. В какой нибудь SAP HANA отдельные soшки больше сотни мег весят.
докер-образы тоже с собой тащат всё нужное. И однако никто по этому поводу не возмущается, а, наоборот, довольны.
Вот почему так-же нет в Java?! Один файл подправил - две трети файлов в проекте перекомпилируются.
Про динамические библиотеки в linux забыли рассказать довольно важную часть с soneme
Крутая статья, спасибо. Полезно было.
Извините, довольно далек от разработке на линуксе и сборкой на С\++ тоже не каждый день приходится заниматься.. А на одном из первых этапов нельзя было подложить телеграмм библиотеке бинарники нужных версий curl/boost? Или их просто не принято держать в таком виде, и только сорсы, только хардкор? Или у неё статическая линковка на них, и бинарники в этом случае не помогли бы?
Если понимать процесс, обеспечить гарантию что не подцепится другая версия из системы, а подключится именно из нужной директории, то так можно разок вручную собрать если никому не рассказывать. Но по уму нужно написать универсальный рецепт, которым легко можно будет воспользоваться независимо от ОС, с рассчетом на долгосрочную поддержку, с пониманием что может измениться в новых версиях зависимостей.
Видел, что в репозиторий добавляют бинарники зависимостей и не парятся с их обновлением вообще. Но это тупик. Тот проект, например, что я видел, можно было собрать только GCC не старше 5.5.
Бинарники в репозитории - это треш. Лучше всё таки свой бранч third-party библиотеки, с которым всё точно собирается (и который периодически обновляется с проверкой, что ничего не сломалось).
Сборка проектов Си и Си++: от простого к сложному. Часть I. Библиотеки