Правильно пакетим ПО для Linux

Одной из самых важнейших проблем для разработчика в мире Linux является создание правильных пакетов. Это обычно вызывает множество вопросов, поэтому в данной статье мы решили подробно их разобрать.

Введение

Большинство разработчиков считают, что правильным является создание одного RPM и одного DEB пакета, однако это в корне неверно. Как RPM, так и DEB являются лишь контейнерами с метаданными для того, что находится у них внутри.

В каждом дистрибутиве RPM/DEB пакеты имеют свои, зачастую ни с кем не совместимые, особенности. Также каждый дистрибутив имеет совершенно разные наборы и версии библиотек.

Например если мы соберём пакет в Fedora 29 и динамически привяжемся к Qt 5.11 или ffmpeg 4.0, то он в большинстве случаев не будет работать в более старой версии Fedora 26 с Qt 5.6 и ffmpeg 3.1. Это же касается и Debian/Ubuntu. Обратная совместимость API/ABI внутри библиотек работает обычно лишь в более новых версиях относительно старых, однако некоторые на неё вообще забивают.

Также RPM пакет, собранный для Fedora, скорее всего не будет работать в openSUSE или ALT Linux, поэтому если и собирать пакеты, то это нужно делать для каждого дистрибутива в отдельности и в пределах как минимум двух поддерживаемых версий.

Способы линковки

Существует два способа компоновки приложения с необходимыми ему библиотеками:

  • динамическая — компоновщик линкует бинарник с уже существующими в системе динамическими библиотеками. При запуске они подгружаются в адресное пространство;
  • статическая — все необходимые зависимости вшиваются внутрь бинарника статически.

Каждый способ имеет как достоинства, так и недостатки. Рассмотрим их более подробно.

Динамическая линковка

Преимущества:

  • позволяет использовать разделяемую память библиотек, т.е. если какое-то запущенное приложение уже использует её, она не дублируется в памяти, что ускоряет запуск и снижает общее потребление памяти;
  • позволяет оперативно исправлять уязвимости в сторонних библиотеках. Например если в libfoo обнаружена уязвимость, её мейнтейнеры в дистрибутиве выпустят обновление и после его установки будет достаточно лишь перезапустить наше приложение, чтобы оно подействовало.

Недостатки:

  • мы привязываем свой бинарник к конкретной версии библиотеки, которая затем должна быть установлена в системе и иметь ту же, либо более новую версию (если имеется обратная совместимость по API/ABI);
  • сложность распространения проприетарного ПО. В данном случае потребуется каким-то способом включать все используемые динамические библиотеки в дистрибутив (об этом далее).

Статическая линковка

Преимущества:

  • всё, от чего зависит бинарник, вшивается непосредственно в него, поэтому он может быть запущен где угодно, на любой системе;
  • нет проблем с зависимостями.

Недостатки:

  • если в используемой библиотеке libfoo будет найдена уязвимость, потребуется полная пересборка и перелинковка всего проекта, а затем распространение обновлённой версии. Часто такое ПО остаётся уязвимым навсегда;
  • нет возможности использования разделяемой памяти. Все библиотеки загружаются в адресное пространство процесса, даже если их копии уже присутствуют в памяти;
  • огромный размер бинарника;
  • проблемы с лицензированием для проприетарного ПО. Ряд лицензий, например GPL, запрещают статическую линковку с собственническим ПО. Лицензия LGPL (под ней распространяется например Qt) статическую линковку допускает, но при этом требует предоставления хотя бы объектных файлов всем покупателям для возможности перелинковки.

Альтернативы RPM/DEB пакетам

Чтобы не собирать пакеты под каждый из дистрибутивов по отдельности, были придуманы так называемые самодостаточные пакеты: Flatpak, AppImage, Snap.

Flatpak

Это наверное самый лучший из самодостаточных пакетов. Поддерживает динамическую линковку с большим количеством библиотек из рантаймов, что решает проблемы с лицензированием, их поддержкой в актуальном состоянии и исправлением в них ошибок, а также уязвимостей. Рантаймы оперативно обновляются мейнтейнерами в пределах версии. Например мы можем слинковаться с Qt версии 5.11 и openssl 1.1.0 и указать их в манифесте, тогда они будут автоматически загружены и установлены у пользователей. Ошибки и уязвимости в них будут исправляться автоматически.

Библиотеки, для которых нет рантаймов, могут быть упакованы внутрь флатпака и подгружаться по мере необходимости.

Flatpak позволяет установить разные версии приложений и библиотек одновременно. К тому же для установки не требуются права администратора.

Также Flatpak поддерживает изоляцию приложения внутри контейнера. В манифесте может быть указано к каким каталогам и ресурсам будет иметь доступ приложение. Всё остальное для него будет запрещено. Права очень гибко настраиваются.

Созданные флатпаки могут загружаться и использоваться на любом дистрибутиве GNU/Linux, поддерживающем их использование.

Репозиторий Flatpak пакетов создаётся достаточно легко и может хоститься на любом ресурсе, позволяющем размещение статических файлов по прямым ссылкам (даже на GitHub pages).

Документацию по Flatpak можно найти на официальном сайте вместе с готовыми примерами.

Нужно ли вообще собирать RPM/DEB пакеты?

Для разработчиков свободного ПО однозначно нет. Когда проект наберёт популярность,  мейнтейнеры дистрибутивов опакетят его согласно их официальных гайдлайнов и добавят в основные репозитории. От разработчика здесь требуется лишь не чинить препятствий этому и внедрять исправления, которые могут предложить мейнтейнеры.

Для разработчиков собственнического ПО есть несколько вариантов:

  1. использовать динамическую линковку и собирать пакеты для каждого популярного дистрибутива в пределах поддерживаемых версий;
  2. использовать статическую линковку (если нет проблем с лицензиями) и создавать статические бинарники внутри generic RPM/DEB пакетов;
  3. использовать динамическую линковку и использовать RPATH при сборке бинарника (либо вместо RPATH использовать механизм LD_PRELOAD), а затем упаковывать все необходимые библиотеки внутрь generic RPM/DEB пакета вне стандартной library path, чтобы не вызывать проблем с зависимостями у общесистемных пакетов;
  4. использовать самодостаточные пакеты, например вышеупомянутый Flatpak.

Для всех разработчиков проприетарного ПО мы настоятельно рекомендуем внедрять Flatpak и по возможности максимально использовать его рантаймы.

2 commentary to post

  1. Что насчет остальных самодостаточных пакетов (AppImage/Snap)? Стоит их рассматривать вообще?

    1. AppImage — это особый контейнер, в котором размещён бинарник приложения, а также все его зависимости в виде shared библиотек. При запуске контейнер монтируется посредством FUSE и запускается содержащийся бинарник.

      Snap — это контейнер, в котором размещён бинарник, слинкованный статически со всеми его зависимостями.

      В обоих случаях если в одной из зависимых библиотек будет найдена уязвимость, потребуется полная пересборка пакета и доставка результата клиентам. К тому же абсолютно все зависимости дублируются как внутри пакетов, так и в памяти при работе.

Обсуждение закрыто.