О блоге

Все новые материалы размещаются на Блогосайте alv.me. Старые - в процессе переноса.

23.07.2008

FreeBSD: управление портами

Citkit, 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
  • URL мастер-сайта портируемого пакет, или иного сервера, с которого можно получить его исходники, а также имя каталога, в котором он там находится; обращаем внимание - значение первой переменной определяется также через некую переменную, а сами адреса перечислены в одном из группы файлов, общих для всей системы (о чем речь пойдет дальше):
  •  MASTER_SITES=   ${MASTER_SITE_SOURCEFORGE}
    MASTER_SITE_SUBDIR= joe-editor
  • e-mail майнтайнера порта и очень краткое его описание:
  •  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 от скачанных для этого порта файлов.