Алексей Федорчук
27 июня 2005 г
В заметке FreeBSD: управление пакетами были рассмотрены инструменты для установки прекомпилированных бинарных пакетов. Однако этот метод не является основным при наращивании функциональности операционок BSD-клана: средства пакетного менеджмента суть производные систем сборки пакетов из исходников.
Таковых в BSD две: система портов (ports
), зародившаяся во FreeBSD и заимствованная в OpenBSD, и система pkgsrc
, разработанная для NetBSD, но ныне превратившаяся в кросс-платформенное средство. Предметом настоящей заметки будет первая из них, рассмотренная на примере FreeBSD. Однако все сказанное приложимо и аналогичной системе OpenBSD.
О портах FreeBSD написано немало. И для любого пользователя этой системы обращение с портами — дело своеобычное, прочно вошедшее в привычку. В концептуальном плане немало общего с портами найдут и пользователи Source Based дистрибутивов Linux (еще бы — ведь и портежи Gentoo, и ABS из Archlinux, и Sorcery из Sorcerer сотоварищи возникли под их влиянием). Однако для пользователей пакетных дистрибутивов Linux и, тем более, тех, кто приобщается к POSIX’визму впервые, концепция портов может показаться необычной.
Итак, система портов — это набор правил для отыскания в Сети исходников устанавливаемых программ, их получения на локальную машину, конфигурирования, сборки (компиляции и линковки), инсталляции в файловую систему и, возможно, постинсталляционного конфигурирования. То есть по этим правилам выполняется весь тот цикл действий, которые мы проделываем при ручной сборке любой программы (три волшебных заклинания ./configure
, make
, make install
), но — в автоматическом режиме. Однако плюс к этому важный компонент системы портов — средства отслеживания зависимостей, как «жестких», обязательных, так и «мягких», опциональных, коррекции последних в ту или иную сторону, ну и, конечно же, автоматического удовлетворения тех и других.
Возможности системы портов могут показаться просто волшебными, особенно тем, кому знакомы мучения с зависимостями при использовании чистого rpm, незамутненного никакими apt-get
или yum
. За счет чего же они реализуются?. За счет обычной утилиты make
, или, точнее, BSD make
— весьма близкого аналога GNU make, знакомого всем пользователям Linux, хотя бы единожды в жизни собиравшим программы из исходников. Правда, следует помнить, что это все-таки разные программы, и при установке некоторых портов как зависимость может потребоваться именно GNU make
— хотя вручную они прекрасно соберутся и посредством BSD make
.
Однако сама по себе утилита make
— лишь одна сторона портов. Вторая же, и главная, ее составляющая — это собственно описание правил, по которым совершаются вышеупомянутые действия (получение, сборка, инсталляция и прочие) для каждой портированной программы — а программ этих нынче более десяти тысяч. Так вот, набор файлов, содержащих эти самые правила, и представляет собой порт конкретной программы.
Файлы, составляющие порт программы — простые текстовые файлы, которые можно просмотреть утилитами типа less
или more
, можно (а при необходимости — и нужно) поправить в обычном текстовом редакторе. То есть, не смотря на фантастические результаты работы системы портов, сам порт не содержит в себе ничего сверхъестественного, недоступного пониманию обычного пользователя.
Обязательный набор любого порта составляют четыре файла: distinfo
, pkg-descr
, pkg-plist
и Makefile
. первый содержит краткую информацию о пакете, послужившем предметом портирования: имя тарбалла исходников, его размер и контрольную сумму. Например, для текстового редактора joe
(порт joe-devel
, описывающий установку его версии 3.X) это выглядит так:
MD5 (joe-3.1.tar.gz) = 2a6ef018870fca9b7df85401994fb0e0 SIZE (joe-3.1.tar.gz) = 381201
Файл distinfo
нужен на стадии получения исходных текстов портированного пакета и проверки его подлинности.
Содержание файла pkg-descr
представляет собой краткую (как правило, в один абзац) характеристику портируемой программы, из которой обычно легко понять ее назначение. Обычно здесь же указываются URL мастер сайта проекта и имя и e-mail сборщика (майнтайнера) порта. Пример для того же joe-devel
:
JOE is the professional freeware ASCII text screen editor for UNIX. It makes full use of the power and versatility of UNIX... ... WWW: http://sourceforge.net/projects/joe-editor/ -Pete petef@databits.net
Файл pkg-descr
предназначен в основном для ориентации пользователя в развесистой кроне дерева портов.
Ну а pkg-plist
— это просто список файлов, которые соберутся во время установки порта, и указание путей к каталогам, куда они будут инсталлированы. Во все том же joe-devel
его содержание примерно такое:
bin/jmacs bin/joe bin/jpico bin/jstar bin/rjoe ...
Пути к файлам указываются относительно некоторого каталога, представляющего корень всех установленных портов, в большинстве случаев по умолчанию — /usr/local
. Содержимое файла pkg-plist
требуется для регистрации порта в базе данных установленных пакетов — в var/db/pkg
, что в дальнейшем позволяет отслеживать зависимости иных пакетов, удалять и обновлять инсталлированные порты.
Наконец, сам Makefile
— это главный файл каждого порта. Это в сущности набор переменных, значения которых (продолжаю приводить примеры для joe-devel
):
- имя сортируемого пакета, номер его текущей версии (соответствующей версии разработчика пакета), ревизии порта (определяемой майнтайнером порта):
PORTNAME= joe PORTVERSION= 3.1 PORTEPOCH= 1
CATEGORIES= editors
MASTER_SITES= ${MASTER_SITE_SOURCEFORGE} MASTER_SITE_SUBDIR= joe-editor
MAINTAINER= petef@FreeBSD.org COMMENT= Development version of Joe's own editor
Эти строки составляют, так сказать, первую секцию Make-файла, построенную однотипно для всех портов. Дальше же описываются вещи, специфичные для конкретного пакета. В первую очередь это его зависимости, подчас с разделением на категории — зависимости для построения, зависимости для запуска, зависимости от библиотек, и так далее. Фигурируют тут и опции конфигурирования пакета (при исполнении сценария ./configure
), которые также могут добавить или убавить зависимостей. В общем, содержимое второй части Make-файла проще посмотреть для интересующего пакета, нежели описывать в деталях.
Наконец, третья часть Make-файла — строки, подобные этой:
.include
Они указывают, какие файлы из набора, описывающего общие особенности системы портов, должны быть включены в данный порт.
Кроме того, многие порты включают в себя дополнительные компоненты. Так, в составе порта часто можно обнаружить подкаталог files
. В нем располагаются специфичные для данной операционке патчи, призванные обеспечить корректную установку и работу пакета именно в ней. Ведь подавляющее большинство портируемых программ создавались отнюдь не специально для FreeBSD или, тем более, DFBSD, а в расчете на некую абстрактную POSIX-систему, и их исходники подчас могут подвергнуться адаптации к реалиям системы.
Все файлы, составляющие порт, объединены в одном каталоге, носящем имя портируемой программы, без версии, например, joe
. В ряде случаев для обеспечения зависимостей необходимо поддерживать две версии программы — классическими примерами являются пары Gtk1/Gtk2 и Freetype1/Freetype2. В таком случае для каждой из них создается собственный каталог с набором портообразующих файлов.
Кроме того, в портах, как правило, представлена последняя версия программы, именуемая разработчиком стабильной (насколько уж она отвечает своему титулу — это другой вопрос). Однако в ряде случаев наряду с ней может присутствовать и версия разрабатываемая — может быть, не столь прилизанная, но более функциональная. И как раз редактор joe
тому примером: под этим именем в дерево портов присутствует древняя (уж даже не помню, какого года розлива) версия 2.8, давно прекратившая свое развитие. Современная (3.1) же версия этого редактора, поддерживающая UTF-8, подсветку синтаксиса и имеющая множество иных усовершенствований, в системе портов называется joe-devel
.
Во избежание недоразумений подчеркну еще раз: сами по себе исходники портируемой программы в состав порта не входят ни в каком виде — они скачиваются из Сети в процессе его построения. Хотя, как будет показано ниже, не возбраняется получить эти исходники и заранее.
Индивидуальные порты объединяются в категории по назначению портируемых программ: editors
, audio
, graphics
, и так далее. Каждая такая категория образует собственный каталог в дереве портов (/usr/ports
), а индивидуальные порты входят в них как подкаталоги. Так что в полном виде номенклатура порта выглядит так: editors/joe-devel
. А уж каталоги, соответствующие категориям, и образуют собой дерево системы портов.
Кроме индивидуальных портов, в составе дерева портов присутствуют так называемые метапорты. Это — крупные программные комплексы, такие, как XFree86, Xorg, KDE и GNOME. И их make-файлы описывают не процесс сборки отдельных пакетов, а лишь их набор, взаимоотношения и последовательность сборки. А уж за саму сборку каждого компонента отвечает его собственный порт.
Бывает, что на базе одного такого программного комплекса создается несколько метапортов. Например, для KDE существует два метапорта: x11/kde3
и x11/kde-lite
. Первый охватывает все компоненты KDE, второй же — лишь наиболее существенные.
Таково в общих чертах устройство системы портов FreeBSD. Теперь перейдем к практической части — как распорядиться доставшимся по наследству богачеством? Конечно, для начала неплохо бы им обзавестись — хотя порты FreeBSD и входят в состав дистрибутива этой операционки, распространяемого на CD, но, скорее всего, они в той или иной мере на текущий момент времени устарели.
Актуальный срез дерева портов FreeBSD в виде тарбалла (ports.tar.gz
, около 25 Мбайт) можно скачать с любого ftp-сервера проекта, после чего развернуть его в каталоге /usr
обычным образом:
$ cd /usr $ tar xzvf path_to/ports.tar.gz
Дальнейшее же обновление его можно производить с помощью cvsup
. Прототипы соответствующих конфигов — также на серверах проекта. Имеются они и дистрибутиве — в каталоге /usr/share/examples/cvsup
. Описывать их все было бы скучно, поэтому для примера приведу свой:
$ less /root/free-ports-supfile # Ports Collections of FreeBSD *default host=cvsup.no.FreeBSD.org *default base=/usr *default prefix=/usr *default release=cvs tag=. *default delete use-rel-suffix # If your network link is a T1 or faster, comment out the following line. #*default compress ## Ports Collection. # # The easiest way to get the ports tree is to use the "ports-all" # mega-collection. It includes all of the individual "ports-*" # collections, ports-all
Разумеется, все порты скачивать отнюдь не обязательно — только людям с весьма специфическими интересами потребуются средства поддержки китайского, вьетнамского, арабского и прочих языков одновренно — а они составляют весомую долю в дереве. И потому вместо указания ports-all
можно просто перечислить необходимые категории.
Есть, однако, и другой способ — создать так называемый файл исключений — refuse
.
Следующий шаг — настройка общих средств обращения с портами. Она осуществляется редактированием файла /etc/make.conf
. Многие его переменные имеют отношение и к системе портов тоже. Это, например, указание на версию компилятора CCVER
и определение его флагов CPUTYPE
, CFLAGS
и CXXFLAGS
(но не COPTFLAGS
— эта переменная влияет только на флаги компиляции ядра). Подавляющее большинство портов успешно собирается при значении CFLAGS=-O3
— другое дело, насколько это оправданно с точки зрения повышения быстродействия (по моим наблюдениям — в большинстве случаев неоправданно абсолютно).
Другие же переменные из /etc/make.conf
относятся только к системе портов. Это в первую очередь дублирующий состав — адреса сайтов для скачивания исходников, которые должны проверяться прежде тех, что определены для портов в целом (или в Make-файлов конкретных портов). Далее — адреса ftp- и http-прокси. И наконец — каталоги для помещения временных продуктов компиляции при построении порта, о чем еще будет говориться далее.
Третий из предварительных шагов — знакомство с Make-файлами конкретных портов, предполагаемых к сборке, и, при необходимости, внесение в них необходимых коррективов. Впрочем, если вы дошли до этой стадии, то, скорее всего, в моих рекомендациях уже не нуждаетесь. Тем более, что желательные параметры конфигурирования и сборки могут быть заданы непосредственно в командной строке (и об этом еще придет время поговорить).
Завершив подготовительную стадию, пора переходить к установке (или, как еще говорят, построению) порта интересующей программы. На практике это обычно сводится к следующей последовательности действий: а) переходу в каталог нужного порта, например:
$ cd /usr/ports/editors/joe
и отдаче команды make
в сопровождении двух волшебных слов (targets, целей сборки):
$ make install clean
Что влечет за собой такие последствия:
- соединение с одним из серверов, указанных в конфигурационном файле
/etc/make.conf
,/usr/ports/Mk/bsd.port.mk
или make-файле порта, и получение с него тарбалла исходников пакета; - проверка контрольной суммы полученного тарбалла;
- распаковка тарбалла в подкаталог
port_name/work/pkg_name
; - наложение, в случае необходимости, на дерево исходников патчей, специфичных для данного порта (располагающихся, как уже говорилось, в подкаталоге (
port_name/files
); - конфигурирование пакета, то есть выполнение сценария ./configure (в корне дерева исходников, то есть
port_name/work/pkg_name
), и определение его зависимостей; - обработка портов, необходимых для сборки и/или функционирования данного, осуществляемая по той же схеме;
- компиляция пакета и его линковка с потребными библиотеками;
- инсталлирование, то есть инкорпорация новообразованных бинарников в должные ветви дерева файловой системы DFBSD, и регистрация порта в базе данных —
/var/db/pkg
, той самой, где фиксируются и пакеты, установленные из бинарников; - очистка порта от промежуточных продуктов построения порта, то есть попросту уничтожение каталога
port_name/work
.
Результатом же всей описанной процедуры будет включение в систему новой, полностью готовой к употреблению, программы, и всего, что требуется для ее функционирования. Обратим внимание на то, что процедура сборки порта атомарна, и для каждого из описанных выше этапов ее предусмотрена своя цель, выполняющая как бы кумулятивно единичную, предписанную, операцию и всю серию предшествующих. Цели эти — следующие:
fetch
— получение исходников и помещение их в каталог/usr/ports/distfiles
;checksum
— проверка контрольной суммы;extract
— распаковка тарбалла исходников в подкаталогpotr_name/work
(если место для промежуточных продуктов компиляции не переопределено в переменнойWRKDIRPREFIX
файла/etc/make.conf
);patch
— наложение патчей, если таковые предусмотрены в составе порта, на дерево исходников;configure
— выполнение сценария конфигурирования в корне дерева исходников;build
— компиляция и линковка пакета;install
— инсталляция собранных компонентов и регистрация пакета в базе данных/var/db/pkg
;clean
— очистка дерева исходных текстов от промежуточных продуктов сборки.
Кроме этого, существует интегральная цель all
— именно она выполняется по умолчанию, если команда make
дана без указания целей вообще. Одна включает в себя такую последовательность действий:
fetch checksum extract patch configure build
то есть все, вплоть до построения порта, но не его инсталляцию (цель install
) и очистку (clean
). Дело в том, что два последних действия являются не только не обязательными, но иногда и нежелательными. Так, инсталляция построенного порта не требуется, если мы собираем бинарные пакеты для автономного распространения (в том числе и не на этой машине). Для чего предусмотрена специальная цель —
$ make package
Именно таким способом создаваются обычно существующие бинарные пакеты для FreeBSD.
Сохранение же дерева исходников (то есть отказ от цели clean
) может потребоваться для целей отладки и внесения собственных изменений в них. Правда, в любом случае его желательно освободить от продуктов компиляции, то есть объектных модулей (файлов вида *.o
). Для чего команду
$ make clean
достаточно выполнить, перейдя предварительно в подкаталог port_name/work/pkg_name
. Нужно только иметь ввиду, что сохранение рабочих подкаталогов для многих портов, особенно таких крупных, как x11/kde
, способно очень быстро загромоздить ветвь /usr/ports
. В этом одна из причин того, что последнюю целесообразно помещать на собственном дисковом разделе.
Если на стадии конфигурирования порта будет выявлено отсутствие в системе пакетов, с которыми он связан зависимостями, они будут автоматически скачаны и установлены, то есть для каждого такого порта будет выполнена команда
$ make install
Легко догадаться, что описанная схема будет успешно функционировать только при условии подключения к Сети: если такового не имеется — сразу же вслед за командой make ...
последует сообщение об ошибке вида
...Host not found => Couldn't fetch it - please try to retrieve this => port manually into /usr/ports/distfiles/ and try again. *** Error code 1
Однако оно и подсказывает решение — нужно получить каким-либо образом исходники программы и поместить их в каталог /usr/ports/distfiles/
, специально предназначенный для хранения оных (он возникает автоматически при первом же использовании системы портов и проверяется в первую очередь — до обращения к мастер-сайтам порта). Как это сделать?
Если мы в пределах физической досягаемости имеем BSD-машину, то все просто: отправляемся к ней, переходим в каталог нужного порта и даем команду
$ make fetch
Правда, таким образом получается исходник только конкретной портированной программы, без учета ее зависимостей. Не беда, для получения исходников пакетов, с которыми данный порт связан зависимостями, существует специальная цель:
$ make fetch-recursive
Правда, тут следует учитывать одно обстоятельство. Зависимости, определяемые при построении порта по полной программе, берутся из результатов реального его конфигурирования (выполнения сценария configure
. При исполнении же цели fetch-recursive
источником для определения зависимостей будет база данных установленных пакетов — /usr/db/pkg
. Так что результаты обеих процедур не обязательно будут идентичными. Хотя на практике этим обстоятельством в большинстве случаев можно пренебречь.
Однако предположим, что доступа в сетевой BSD-машине у нас нет, и штатными средствами системы портов для получения исходников мы воспользоваться не можем. Тоже не смертельно — можно получить список необходимых файлов исходников, и скачать их любым ftp-клиентом — хоть из под Windows. Для единичного тарбалла это делается командой
$ make fetch-list
ответом на что будет нечто вроде:
/usr/bin/env /usr/bin/fetch -ARr -S 252636 ftp://ftp.rxvt.org/pub/rxvt/./rxvt-2.6.4.tar.bz2 || /usr/bin/env /usr/bin/fetch -ARr -S 252636 ftp://ftp.rxvt.org/pub/rxvt/old/rxvt-2.6.4.tar.bz2 || /usr/bin/env /usr/bin/fetch -ARr -S 252636 ftp://ftp.rxvt.org/pub/rxvt/devel/rxvt-2.6.4.tar.bz2 || ... || echo rxvt-2.6.4.tar.bz2 not fetched
А для учета всех зависимостей существует особая цель —
$ make fetch-recursive-list
Перенаправив ее вывод в файл, несложно получить лист заданий для ftp-клиентов, таковые поддерживающих.
Следует только учесть, что при указании цели fetch
в любых ее вариантах проверяется наличие соответствующих файлов исходников в каталоге /usr/ports/distfiles
, поэтому любую из указанных команд нужно выполнять на машине, где их заведомо не имеется (или временно переименовывать каталог /usr/ports/distfiles
). К сожалению, в системе портов FreeBSD не имеется аналога опций empty
и empty-tree
для emerge
из портежей Gentoo, которые определяют зависимости при допущении, что база данных установленных пакетов пуста.
Любая уважающая себя система пакетного менеджмента должна обеспечивать не только инсталляцию оных, но и максимально чистое удаление установленного хозяйства. И система портов FreeBSD отвечает этому требованию в полной мере: для этого в ней предусмотрена цель deinstall
. О цели clean
я уже говорил. А еще есть более радикальная цель — distclean
, очищающая не только рабочий каталог порта, но и каталог distfiles
от скачанных для этого порта файлов.