Международная конференция разработчиков
и пользователей свободного программного обеспечения

Удобства и особенности OpenBSD Ports

Vadim Zhukov, Moscow, Russia

LVEE Winter 2014

OpenBSD acquired Ports framework from FreeBSD many years ago, and since then those frameworks diversed very much. Current OpenBSD Ports infrastructure makes it possible to have many up-to-date ports, including "heavy" ones like GNOME, LibreOffice and KDE, by relatively few number active maintainers. This talk is about features of current OpenBSD ports system: bulk package builds, package signing and updating, manual library versions handling, modules framework and other stuff.

Как в любой нормальной современной ОС общего назначения, в OpenBSD есть механизм для установки стороннего ПО. В данном случае – OpenBSD Ports, происходящие, как многие знают, от FreeBSD Ports. Однако, сохранив ряд внешних признаков прародителя, OpenBSD Ports кардинально отличаются от FreeBSD Ports внутри. Проще всего это показать, перечислив то, что сохранилось:

  1. Использование Makefile для описания порта.
  2. Названия основных операций и ряд переменных, используемых в этом Makefile.

Во всём остальном OpenBSD Ports заметно ушли вперёд – впрочем, разработчики FreeBSD раскачались и стали догонять, что видно на примере свежевышедшей FreeBSD 10.

Ключевые особенности OpenBSD Ports:

  1. Сборка пакета – обязательный этап. Пакет сначала собирается, и только потом ПО устанавливается из него в систему.
  2. Контроль сборки посредством systrace(1), sudo(8), fake-фреймворка и pkg_create(1). Файлы для пакета помещаются в отдельный каталог, а не прямо на рабочую систему. В ходе “фальшивой установки” отлавливается запись за пределы рабочего каталога (кроме /tmp и /var/tmp).
  3. Поддержание целостности дерева портов посредством непрерывной сборки пакетов с использованием dpb(1).
  4. Четыре вида зависимостей: BUILD_DEPENDS, LIB_DEPENDS, RUN_DEPENDS, TEST_DEPENDS.
  5. Опции и субпакеты, см. “FLAVORS AND MULTI_PACKAGES” в bsd.port.mk(5).
  6. Модули, см. port-modules(7).
  7. Реально работающий механизм обновления пакетов, с учётом переименований, требований к конкретным версиям, наличия конфликтов и т.д.
  8. Ручной контроль версий “публичных” разделяемых библиотек.

Благодаря имеющимся средствам автоматизации большинства рутинных операций и жёстком контролю в виде dpb(1), на поддержание высокого качества пакетов уходит сравнительно небольшое количество сил. Как результат, в OpenBSD при сравнительно небольшом количестве мейнтейнеров поддерживается большая часть современного софта. Текущие примеры:

  • порты GNOME и XDG-стек поддерживаются всего двумя людьми
  • порты LibreOffice и Chromium – одним и тем же человеком
  • KDE 3 и 4 – одним и тем же человеком
  • все порты Mozilla-приложений – одним и тем же человеком

О субпакетах и опциях:

OpenBSD Ports, как и другие аналогичные системы, позволяет задавать для пакетов опции сборки. Основные отличия OpenBSD в этой области следующие:

  • Количество опций OpenBSD старается поддерживать на минимальном уровне. Это позволяет при разумных усилиях тестировать все или почти все возможные варианты пакетов.
  • Используемые при сборке опции записываются в имя пакета через дефис, после номера версии. Например:

kdelibs-4.11.5-debug
vim-7.4.45p2-huge-gtk2-python

Благодаря этому в репозитории пакетов вместе могут одновременно находиться разные варианты пакетов, и не возникает вопросов вроде “какие опции были использованы для пакета?”.

Изначально в FreeBSD Ports было чёткое соответствие: одна сборка – один пакет. Когда требовалось собирать несколько разных пакетов на базе одного порта (например: postgresql-client и postgresql-server), то один порт включал себя директивой .include другой, незначительно модифицировал переменные и предоставлял альтернативный plist. Таким образом, сборка этих двух пакетов должна была бы происходить дважды, либо требовала довольно “грязных” хаков.

В OpenBSD Ports имеется возможность создавать из одного порта несколько пакетов. Строго говоря, корректнее было бы говорить о субпортах, а не субпакетах, так как создаваемые пакеты являются изначально независимыми и могут иметь любые (допустимые) имена. Однако менять название переменной теперь никто не собирается.

Названия самих субпакетов начинаются с дефиса. Это делается для возможности отличать субпакеты от опций в FULLPKGPATH – своеобразном “пути”, используемом для пакета с портом, из которого он собирается. FULLPKGPATH – своего рода идентификатор (суб-)пакета и играет большую роль на всех этапах работы с пакетами.

Наравне с FULLPKGPATH имеется FULLPKGNAME. Если PKGNAME содержит собственно название пакета и его версию, то в FULLPKGNAME к этому добавляются номера ревизии порта (REVISION) и эпохи порта (EPOCH), а также используемые опции. Именно FULLPKGNAME и становится именем конечного файла, содержащего собранный пакет (не считая расширения).

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

Так как отдельные субпакеты могут выпадать и по другим причинам (например, непригодность для какой-то аппаратной архитектуры) посредством переменных вида BROKEN-subpkg и IGNORE-subpkg, для конечного определения параметров сборки имеется вспомогательный механизм bsd.port.arch.mk(5). Хотя внешне он кажется аналогом bsd.port.pre.mk из FreeBSD, назначение у них всё же разное: bsd.port.arch.mk используется только для двух вещей:

  1. Определение аппаратной архитектуры, под которую собирается пакет.
  2. Вычисление переменной BUILD_PACKAGES, содержащей список подлежащих сборке пакетов. Порт, после включения , может использовать проверки вида .if ${BUILD_PACKAGES:M-subpkg} для определения, нужно ли включать сборку определённого субпакета.

Результаты данных проверок обычно используются для установки специфичных параметров конфигурации и наборов зависимостей.

О модулях:

Модули – Makefile, подключаемые OpenBSD Ports по просьбе порта. Модули могут как модифицировать переменные, так и устанавливать или добавлять обработчики стандартных операций. Для подключения модуля достаточно добавить его имя в переменную MODULES. Примеры модулей:

  1. Один из самых простых модулей – converters/libiconv. Всё, что он делает – добавляет ряд зависимостей для порта и дополняет WANTLIB (список используемых портом разделяемых библиотек).
  2. Более сложный модуль – devel/cmake. Он используется портами, которые, как не трудно догадаться, собираются при помощи CMake. Данный модуль устанавливает ряд переменных, используемых CMake при конфигурировании и сборке, в том числе для: выбора движка сборки (поддерживаются make и Ninja), перекрытия версий разделяемых библиотек, настройки вывода прогресса сборки и т.д.
  3. Модуль lang/ruby – пожалуй, самый сложный в дереве портов; он даже получил персональную man-страницу: ruby-module(5). С его помощью собираются как обычные программы, зависящие от Ruby, так и Ruby gems, с платформо-зависимым кодом и без. Из возможностей модуля:
  • Поддержка использования одним портом различных версий Ruby (включая JRuby и Rubinius) через механизм опций.
  • Санация путей к интерпретатору Ruby в скриптах (замена #!/usr/bin/env или #!/usr/bin/ruby на то, с чем должен работать порт).

Функциональность, подобная описанной выше, присутствует и в других аналогичных модулях: lang/python, lang/tcl…

О ручном контроле версий разделяемых библиотек:

Речь здесь пойдёт о самих библиотеках как объектах компиляции, а не о дистрибутивах ПО. Также следует сказать, что нижесказанное относится лишь к “публичным” библиотекам, которые используются при компиляции ПО. Всевозможные плагины и им подобные сущности не используются при компиляции, а загружаются динамически, посредством dlopen(), и располагаются в отдельных подкаталогах.

Для разделяемых библиотек существует соглашение о нумерации: MAJOR.MINOR[.OTHER…], со следующими правилами изменения версии:

  • Изменение в библиотеке, приводящее к изменению ABI без сохранения обратной совместимости: увеличение MAJOR.
  • Изменение в библиотеке, не приводящее к изменению ABI, или же сохраняющее обратную совместимость ABI: увеличение MINOR.

Номер версии записывается в имени файла и/или в SONAME библиотеки. Когда требуется найти подходящую для программы версию библиотеки, поиск осуществляется с учётом вышеуказанного соглашения: MAJOR должна совпадать с тем, что требуется программе, а MINOR – быть не меньше требуемого программой. Казалось бы, всё довольно просто, но:

  • Часто разработчики смешивают версию дистрибутива, поставляющего библиотеку, с её внутренним номером. В результате, смена внутреннего номера библиотеки находится в полном отрыве от реальности.
  • В других случаях не производится изменение внутреннего номера библиотеки, или изменяется не та часть номера. В результате, после обновления установленной версии библиотеки, использующее её ПО может оказаться неработоспособным.
  • Наконец, иногда в самой операционной системе могут происходить изменения, влияющие на ABI. В случае OpenBSD, правда, обычно делается увеличение MAJOR для libc, что автоматически “разделяет” разные поколения пакетов.

В силу вышеперечисленных причин, в OpenBSD введена практика использования собственной нумерации разделяемых библиотек. Ответственность за соблюдение правил ABI в этом случае ложится на того, кто подготавливает порт или его обновление. Это может показаться большим усложнением, однако на самом деле для всех популярных систем сборки в OpenBSD имеется поддержка перекрытия версий библиотек. Внешне эти изменения сводятся к следующему: когда система сборки готовится слинковать конечную версию библиотеки libfoo, то проверяется наличие переменной окружения LIBfoo_VERSION и, если такая установлена, использует её значение вместо заданного в конфигурации сборки разработчиком. Таким образом, максимум, что приходится делать при подготовке (обновления) порта – это второй раз его собрать, если были пропущены какие-то библиотеки; обычно инфраструктура портов сама обнаруживает и уведомляет о таковых при выполнении операции “make update-plist”.

Версии разделяемых библиотек играют большую роль при обновлении ПО, так как позволяют осуществлять плавную миграцию между версиями использующего библиотеки ПО.

Abstract licensed under Creative Commons Attribution-ShareAlike 3.0 license

Назад