Введение в POSIX'ивизм

Три волшебных слова


Теперь, разобравшись с принципами, посмотрим, как сборка пакетов осуществляется на практике.

Понятное дело, что перво-наперво тарбалл исходников следует декомпрессировать и развернуть в каком-либо подходящем каталоге. Для самостоятельно собираемых исходников я использую обычно каталоги вроде $HOME/src или, в некоторых случаях, /usr/local/src (разумеется, для этого нужно обладать правами на запись в тот или иной). Как станет ясным из дальнейшего, удаление развернутого дерева исходников установленных программ очень нежелательно, поэтому следует озаботиться наличием достаточного количества свободного места в той файловой системе, в которой выполняется распаковка.

Сама по себе распаковка делается обычным образом, например, командой tar:

$ tar xzpvf /path_to_src/tarball.tar.gz

или

$ tar xjpvf /path_to_src/tarball.tar.bz2

в зависимости от использовавшейся для компрессии программы (gzip или bzip2, соответственно). Кратко остановлюсь на смысле опций (команда tar будет предметом отдельного рассмотрения впоследствие).

Опция x (от eXtract) предписывает развертывание архива. Однако поскольку он был ранее сжат утилитой компрессии, его предварительно нужно декомпрессировать - этому служит опция z при gzip или j при bzip2. Опция f имеет своим значением имя подвергающегося развертыванию/декомпрессии файла - в примере tarball.tar.*. Опция v не обязательна - она заставляет выводить на экран сообщения о ходе распаковки.

А вот опция p может быть важной: она предписывает сохранять атрибуты доступа и принадлежности теми же, что были у оригинальных файлов до их упаковки в тарбалл. Без нее хозяином всех новораспакованных файлов оказался бы пользователь, выполняющий процедуру распаковки. Обычно это не имеет значения, но в некоторых случаях - нежелательно, или просто не должно быть (например, при распаковке прекомпилированных тарбаллов stage1-3 в Gentoo). Так что лучше взять себе за правило не забывать про эту опцию никогда.

Если пакет состоит из нескольких тарбаллов, все они должны быть распакованы.
Повторять несколько раз какую- либо из приведенных выше команд было бы скучно - однако эту процедуру можно выполнить в один присест. ИМХО, самый простой способ для этого - прибегнуть к универсальной утилите find, что в данном случае будет выглядеть примерно так:

$ find /path_to_src -name *.tar.gz -exec tar xzpvf {} \;



В результате любой из описанных процедур в текущем каталоге должен образоваться подкаталог вида package_name-version, то есть соответствующий имени пакета с указанием номера его версии и, иногда, реализации (реже - просто имени пакета). Но это - только в том случае, если исходный тарбалл был сформирован корректно, с включением корня дерева исходников. Редко, но бывает так, что разработчик забывает о такой мелочи. И потому, дабы не получить в текущем каталоге неудобопонятной мешанины файлов, перед собственно распаковкой лучше выполнить проверку на вшивость, командой

$ tar -tzvf tarball.tar.gz

где опция t (от lisT) и предписывает вывести список файлов тарбалла вместо его развертывания.

Дальнейшие действия по сборке пакета в большинстве случаев осуществляются путем последовательной отдачи трех команд - ./configure, make, make install.

Первую из этих трех команд следует давать, перейдя предварительно в корень дерева исходников нужного пакета:

$ cd /path2srcpkg $ ./configure

Это - запуск того самого конфигурационного скрипта, о котором давеча говорилось. Обращаю внимание на ./ - эти символы являются указателями на текущий каталог (/path_to_srcpkg), в котором расположен файл сценария (а мы помним, что текущий каталог, как правило, не включается в число значений переменной $PATH).

Сценарий configure имеет некоторое количество опций. Число их и назначение определяются разработчиком, однако некоторые - встречаются практически всегда. И важнейшая из них - это опция --help, выводящая полный список всех других опций. Строго говоря, именно с команды

$ ./configure --help

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


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

В аккуратно написанных программах вывод команды ./configure --help обычно распадается на несколько секций. Первой, как правили, идет секция

Installation directories:

в которой указываются каталоги, куда в дальнейшем будут устанавливаться отдельные компоненты собранного пакета. Важнейшей опцией здесь является --prefix=PREFIX. Значением PREFIX будет выступать ветвь корневого каталога, в подкаталоги которого запишутся исполняемые файлы, конфиги, библиотеки и т.д. По умолчанию эта опция в большинстве случаев имеет значение /usr/local. То есть в случае, если значение опции --prefix при запуске скрипта ./configure не задано, то исполнимые файлы пакета будут инсталлированы в /usr/local/bin, конфиги - в >/usr/local/etc, и так далее.

Так что если желательно размещение компонентов собираемого пакета в каталоге, отличном от умолчального (например, в /usr), значение перфикса следует задать в явном виде, скажем, так:

./configure --prefix=/usr

В последнее время некоторые пакеты предполагают установку по умолчанию в подкаталоги каталога /opt - /opt/pkgname/bin, /opt/pkgname/lib, и так далее. А для KDE-приложений последних версий умолчальное значение перфикса - /opt/kde (/opt/kde/bin, /opt/kde/lib и так далее). Для таких программных комплексов лучше его не менять - во избежание осложнений при поиске библиотек.

А вообще, значение опции --prefix может быть любым. В частности, если предполагается сборка пакета для дальнейшего автономного его распространения в бинарном виде, целесообразно сосредоточить все его компоненты в отдельном подкаталоге, например, вида $HOME/my_pkg/pkg_name. Аналогично следует поступать и при тестировании пакета, предшествующем его инсталляции.

Далее в той же секции обычно имеет место быть опция --bindir=DIR. И здесь значением DIR выступает обычно PREFIX/bin. Однако в некоторых случаях исполнимые файлы пакета целесообразно поместить в иные ветви файловой системы.


Например, если вручную собирается командная оболочка, которая будет выступать в дальнейшем как login shell, или любимый (=общесистемный) текстовый редактор, очень желательно, чтобы их исполняемые бинарники находились непосредственно в каталоге корневой файловой системы (иначе они могут быть недоступны в аварийных случаях или при старте в однопользовательском режиме). И тут, вне зависимости от того, задано ли значение опции --prefix или нет, конфигурационный скрипт следует запускать в такой форме:

$ ./configure --bindir=/bin

или, для программ административного назначения, -

$ ./configure --bindir=/sbin

Нередко в секции Installation directories можно обнаружить и другие опции, предписывающие размещение библиотек, страниц документации и тому подобных компонентов пакета.

Следующая почти непременная секция вывода помощи конфигурационного скрипта -

Optional Features:

Именно в ней, как легко понять из названия, перечисляются опции, позволяющие подключить/отключить дополнительные возможности собираемого пакета. Они имеют вид

--enable-FEATURE

и

--disable-FEATURE

Первая, естественно, подключает возможность с именем FEATURE, а вторая - отключает оную. Причем одна из этих опций может быть принята по умолчанию. Например, большинство свободных программ ныне собирается с поддержкой национальных языков (National Labguage Support - NLS), обеспечивающих вывод сообщений, пунктов меню, помощи т т.д. на языках, отличных от американского (при наличии соответствующих ресурсов, разумеется - если систему помощи некоего пакета никто не удосужился перевести на русский язык, то включай NLS, не включай - все едино, русского хелпа от нее не получишь). Однако в ряде случаев это может показаться нежелательным - и тогда при конфигурировании программы нужно задать соответствующею опцию:

$ ./configure --disable-nls

Обычно допустима и иная форма этой опции:

$ ./configure --enable-nls=no

Функции, подключаемые (или отключаемые) посредством опции --enable(disable)-FEATURE, берутся из библиотечных пакетов.


В частности, за поддержку NLS отвечает библиотека gettext (подчеркну, что сама по себе эта библиотека не дает возможности волшебным образом получать сообщения на русском или там эскимосском языке, а только обеспечивает принципиальную возможность вывода таковых).

Сходный смысл имеют опции, входящие в секцию

Optional Packages:

общий вид которых таков:

with-PACKAGE

или

without-PACKAGE

Отличие опций этой секции от тех, что перечислены в секции Optional Features - в том, что в качестве значений PACKAGE выступаю не некие абстрактные функции, а имена конкретных пакетов, возможности которых добавляются к собираемому (или отнимаются от оного).

Опции вида with-PACKAGE могут также иметь значения - yes, no или auto. Последняя обычно принята по умолчанию. То есть, если в ходе выполнения конфигурационного скрипта пакет с именем PACKAGE будет обнаружен в данной системе, его возможности будут подключены автоматически, если нет - проигнорированы.

Так, в приводимом ранее примере с поддержкой указующе-позиционирующих свойств мыши при сборке консольных программ типа links или mc они будут автоматически задействованы по умолчанию, если в Linux-системе (к BSD это не относится) обнаружится установленный пакет gpm. Если же он имеется, но поддержка мыши для данного пакета представляется нежелательной, это следует указать в явном виде:

$ ./configure --without-gpm

И последняя секция, практически всегда присутствующая в выводе помощи конфигурационного скрипта -

Some influential environment variables:

Как следует из названия, здесь перечисляются различные переменные окружения, могущие оказывать влияние на процесс компиляции. Наиболее часто в качестве таких переменных предусматриваются флаги компилятора gcc типа CFLAGS и CXXFLAGS (для программ на языке Си и Си++, соответственно). Обычное употребление таких флагов - задание всякого рода оптимизаций - общего ее уровня, архитектуры целевого процессора, конкретных наборов его команд, и так далее. Например, оказание опции

$ ./configure CFLAGS="-O3 -march=pentium4"



обеспечит максимальный уровень оптимизации (значение -O3) для процессора Pentium4 (значение -march=pentium4) - опять же заостри внимание на кавычках, в которые эти значения заключены (дабы восприниматься как единый аргумент). Впрочем, тема оптимизации будет предметом особого рассмотрения.

Ранее я говорил, что при сборке пакеты могут связываться с библиотечными функциями как статически, так и динамически. Так вот, характер связи также определяется на стадии начального конфигурирования. По умолчанию во всех известных мне случаях используется динамическая линковка. Что связать исполняемый модуль с библиотекой статически, требуется специальный флаг - LDFLAGS. Значениями его в данном случае будут

$ ./configure LDFLAGS="-static"

или, в случае линковки с несколькими библиотеками -

$ ./configure LDFLAGS="-all-static"

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

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

Другая возможная причина отсутствия скрипта configure - то, что предварительное конфигурирование уже выполнено разработчиком. В этом случае в дереве исходников можно обнаружить уже готовый Makefile, рассчитанный на некоторые типовые ситуации, или несколько его вариантов - для разных архитектур, операционных систем и так далее. Если же ни один из предложенных автором вариантов не отвечает в полной мере реалиям пользователя - у него остается последний выход: ручная правка Make-файла (обычно такие случаи документируются в файлах типа README или INSTALL./p>



Предварительное конфигурирование пакета - очень важный момент в его сборке: можно сказать, что успех ее на 90% определяется именно в результате исполнения скрипта configure. Однако рано или поздно оно завершается удачно (о случаях фатального невезения я скажу несколько позже). И наступает время собственно сборки, для чего предназначено второе из наших магических заклинаний - команда make.

Сама по себе команда make не выполняет ни компиляции (это - дело компилятора gcc, ни линковки (с этой ролью справляется редактор связей ld), ни каких-либо иных действий по превращению исходного текста в машинный код. Задача ее - интеграция всех требуемых средств (а в процессе сборки могут задействоваться и т.н. препроцессоры, и языковые анализаторы, возможно, и иные инструменты), чтобы автоматически получить (почти) готовые к употреблению бинарные компоненты пакета. Собственно говоря, от пользователя требуется только дать директивное указание - набрать в командной строке make и нажать Enter - все остальное произойдет как бы само собой. С другой стороны, у него нет и возможности вмешаться в процесс сборки (разве что прервать его комбинацией клавиш Control+C:-)).

Конечно, и команда make способна воспринимать многие параметры командной строки. Правда, в большинстве случаев они дублируют опции, заданные при конфигурировании. Так, при отдаче директивы make можно задать флаги оптимизации

$ make CFLAGS="-O3 -march=pentium4"

предписать статическую линковку с разделяемыми библиотеками

$ make LDFLAGS="-static"

или предписать последующую установку компонентов пакета в каталог, отличный от умолчального. Однако - и все: далее остается только дожидаться успешного (надеюсь) окончания сборки.

Однако у команды make есть еще один важный вид аргументов командной строки - так называемые цели (target). Собственно для сборки по умолчанию они обычно не требуются. Хотя некоторые пакеты требуют их задания в явном виде. Так, оконная система X штатным образом собирается с указанием цели world:



$ make world

В других случаях для достижения того же результата может применяться и иная цель, например

$ make all

или указание на конкретную архитектуру, операционную систему, и т.д. Все такого рода исключения, как правило, описаны в сопроводительной документации. На худой конец (если таковой не имеется), допустимые для данного пакета цели команды make можно подсмотреть в Makefile - там они будут определены обязательно.

Однако есть у команды make и практически непременная цель - install, предписывающая выполнить установку компонентов пакета в надлежащие места файловой системы (умолчальные или определенные на этапе конфигурирования или сборки). И это - третье, и последнее, из наших шаманских заклинаний:

$ make install

После чего мы наконец получаем готовую к употреблению программу.

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

В принципе команду make install можно было бы дать и сразу по исполнении сценария ./configure. В этом случае сначала будет исполнена умолчальная цель make - компиляция и линковка пакета, а затем его инсталляция. Однако делать это не всегда целесообразно. Во-первых, такое совмещение целей затрудняет отслеживание ошибок. Во-вторых, их совместному исполнению может помешать отсутствие должных прав доступа.

Дело в том, что стадии конфигурирования и сборки обычно могут быть выполнены от имени обычного пользователя - это определяется правами доступа к каталогу, в который было распаковано дерево исходных текстов пакета. А вот установка собранных компонентов почти наверняка потребует административных привилегий. Ведь исполняемые файлы пакета после директивы make install копируются (если придерживаться умолчальной схемы) в каталог /usr/local/bin, документация - в /usr/local/share, и так далее. А все они закрыты для изменения кем бы то ни было, кроме root'а.



Так что, прежде чем начинать установку собранного пакета, следует озаботиться получением соответствующих полномочий командой

$ su

или, в некоторых случаях, даже

$ su -

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

Впрочем, иногда полномочия администратора могут оказаться необходимыми и при сборке программы или ее конфигурировании. Типичный тому пример - сборка ядра Linux штатными средствами, в ходе которой задействуются скрипты, требующие root-доступа. Впрочем, это - тема отдельной беседы.

Вернемся к сборке "обычных", если так можно выразиться, пакетов. Все вышесказанное относилось к случаю сборки без ошибок на любом этапе. От каковых, однако же, никто не гарантирован. И что делать, если ошибки появляются?

В случае ошибки при сборке пакета перед пользователем, как обычно, появляется два выхода: а) бросить это занятие, попробовав отыскать и установить прекомпилированный вариант пакета, и б) разобраться в причинах ошибки и попытаться ее устранить.

Наиболее часто сообщение об ошибке возникает в ходе предварительного конфигурирования. И, в большинстве случаев, связано с нарушением зависимостей, жестких или "мягких" - обычно это можно понять, внимательно читая экранный вывод.

С нарушением жестких зависимостей все ясно - нужно установить пакет, от которого зависит собираемый, и все - вариантов тут не предлагается. Нарушения же "мягких", но тем не менее принятых разработчиком по умолчанию, зависимостей обычно можно избежать посредством явного указания опций конфигурирования - типа disable-FEATURE и --without-PACKAGE.

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


Разрешение такой коллизии - очень простое: создание символической ссылки вида

$ ln -s libname.xxx libname.yyy

где xxx - номер старой версии библиотеки, а yyy - актуальный ее вариант.

Другой случай - когда сценарий конфигурирования пакета ищет библиотеку, от которой он зависит, не в том каталоге, где она реально располагается. Так, старые приложения KDE могут ожидать требуемых им библиотек в каталогах типа /usr/local/qt и /usr/local/kde, тогда как ныне они, скорее всего, будут располагаться в ветвях каталога /opt.

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

LDPATH="/opt/qt:/opt/kde"

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

Во-вторых, эти значения можно просто указать в качестве опций конфигурационного скрипта:

$ ./configure --with-qt-dir=/opt/qt \ --with-kde-dir=/opt/kde

Реже бывают ошибки при исполнении команды make. Однако бороться с ними труднее. Общий рецепт тут дать очень трудно. Можно только посоветовать внимательно читать вывод сообщений о ходе компиляции, непосредственно предшествующих ее обрыву.

Ошибки при инсталляции связаны, почти всегда, с отсутствием прав на запись в каталоги, в которые помещаются устанавливаемые компоненты пакета. Иногда же они возникают вследствие того, что целевой каталог просто не существует. Например, в таких дистрибутивах Linux, как CRUX и Archlinux, из пакетов штатного комплекта изъята вся документация, кроме man-страниц. И, соответственно, отсутствуют каталоги для помещения документации в форматах info и html. А поскольку практически любая программа проекта GNU сопровождается info-документацией, попытка инсталляции ее вызывает ошибку. Побороть которую очень просто: нужно только уничтожить в дереве исходников соответствующие подкаталоги.

На какой бы стадии ни возникла ошибка, перед повторной сборкой пакета дерево исходников следует очистить от побочных продуктов сборки предыдущей. Резонные люди рекомендуют просто стереть каталог с исходными текстами и развернуть его из тарбалла по новой.


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

$ make clean

Она вычистит дерево исходников от объектных модулей, собранных при предыдущей компиляции. Сохранив, тем не менее, созданные при конфигурировании Make-файлы. Если нужно избавиться и от них - существует цель

S make distclean

по исполнении которой дерево исходников теоретически должно приобрести первозданный вид.

Пакеты, как правило, устанавливаются для того, чтобы запускать входящие в них программы. Однако не исключено, что первый же запуск новой программы показывает, что она делает что-то не то или не так, нежели это нужно пользователю. Или просто ему не нравится. И возникает вопрос - а как удалить такой неподходящий пакет?

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

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

$ make uninstall

реже -

$ make deinstall

Любую из этих команд нужно дать в корне дерева исходников - именно поэтому его желательно сохранять и после сборки, несмотря на непроизводительный расход дискового пространства: иначе единственным способом удаления пакета останется ручной. При деинсталляции команда make отыскивает установленные компоненты пакета в дереве файловой системы и удаляет их.

Важно, что make uninstall не затрагивает пользовательских настроечных файлов - т.н. dot-файлов в его домашнем каталоге, которые часто генерируются автоматически при первом запуске программы. Такие файлы при необходимости в любом случае придется удалять вручную. Казалось бы - неудобство, однако сейчас мы увидим, что это не лишено резонов.



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

Так вот, единственный способ обновления собственноручно собранной программы - это собрать и инсталлировать ее по новой. При этом теоретически старые компоненты должны затереться новыми. Однако на практике от старой версии могу остаться хвосты, не только бесполезные, но, возможно, и конфликтующие с файлами новой версии.

И потому при обновлении версий вручную обычно рекомендуется сначала удалить старую версию, и лишь затем инсталлировать новую. Предварительно убедившись, конечно, в успешности сборки ее (то есть выполнив стадии ./configure и make - это еще одна причина для обособления цели make install) и работоспособности (для чего можно пробно запустить исполняемый файл пакета прямо из каталога исходников).

В то же время при смене версий, как правило, желательно сохранить все выполненные ранее настройки пакета - подчас это весьма трудоемкая процедура. И вот тут-то и оказывается, что пользовательские настройки удаленной версии остались в неприкосновенности в домашнем каталоге./p>


Содержание раздела