RPM: дистрибутивы для Linux

The first 90% of the code accounts

for the first 90% of the development time.

The remaining 10% of the code accounts

for the other 90% of the development time

/The Jargon File/

Андрей Боровский, kylixportal@narod.ru

Создание дистрибутива - финальный этап в процессе разработки приложения. Казалось бы все задачи уже решены и дело за малым. Самое естественное желание - запаковать приложение в .tar.gz архив. Однако, такой способ распространения двоичных файлов неудобен и не соответствует стандартам Linux, а ведь дистрибутив приложения, эта та "одежка", по которой пользователь будет встречать вашу программу. Так что имеет смысл потратить на создание дистрибутива некоторое время, тем более что в Linux это займет не "вторые 90" и даже не 10 процентов времени, отведенного на разработку.

В ОС Linux возможны два подхода к созданию дистрибутивов: использование собственной утилиты инсталляции (или утилиты, разработанной сторонней компанией) или использование менеджера пакетов RPM (Red Hat Package Manager), разработанного компанией Red Hat и являющегося стандартным средством установки приложений в большинстве Linux-дистрибутивов. Использование собственных утилит инсталляции может быть оправдано в тех случаях, когда процесс установки приложения должен быть интерактивным (отсутствие интерактивных средств является, пожалуй, одним из немногих недостатков системы RPM), или когда требуется возможность установки приложений в каталоги отдельных пользователей. Во всех остальных случаях предпочтительным является использование RPM.

Сборка RPM-пакета

Если вы работаете в Linux, значит вам наверняка известно, какие возможности менеджер RPM предоставляет пользователям, и останавливаться на этом я не буду. Далее мы рассмотрим систему RPM исключительно с точки зрения программиста, желающего использовать RPM для создания дистрибутива своего приложения.

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

В качестве конкретного примера рассмотрим сборку пакета популярного приложения ORBit. ORBit - это свободно распространяемый CORBA-брокер, написанный на языке C++ (файл с исходными текстами можно загрузить с ftp://ftp.gnome.org/pub/GNOME/stable/sources/ORBit/ORBit-0.5.8.tar.gz).

Для того чтобы собрать пакет, нужно разместить его компоненты в определенных каталогах. Корневой каталог для сборки RPM-пакетов в Linux Red Hat - /usr/src/redhat/, а в Linux Mandrake - /usr/src/RPM/.

Корневой каталог содержит несколько подкаталогов, в которых и следует размещать компоненты будущего пакета:

/SOURCES/ - в этом каталоге размещаются исходные тексты приложения (в нашем случае - файл ORBit-0.5.8.tar.gz).

/SPECS/ - в этом каталоге размещаются spec-файлы, управляющие процессом сборки пакетов.

/BUILD/ - в этом каталоге выполняется процесс сборки.

/RPMS/ - в этот каталог программа rpm помещает готовые пакеты двоичных файлов.

/SRPMS/ - в этот каталог программа rpm помещает готовые пакеты исходных текстов.

Инструкции по сборке пакетов содержатся в специальных файлах (spec-файлы). Рассмотрим файл ORBit.spec, управляющий сборкой нашего пакета2.

#
# Example spec file for ORBit package
#
%define prefix /usr
Summary: High-performance CORBA Object Request Broker.
Name: ORBit
Version: 0.5.8
Release: 1
Copyright: GPL/LGPL
Group: System Environment/Libraries
Source: ftp://ftp.gnome.org/pub/GNOME/
Г
stable/sources/ORBit/ORBit-0.5.8.tar.gz
URL: http://www.labs.redhat.com/orbit/
BuildRoot: /var/tmp/orbit-%{PACKAGE_VERSION}-root
Prefix: %{prefix}
Docdir: %{prefix}/doc
Vendor: Red Hat, Inc.
Packager: Vasya Pupkin vasek@pupkin-network.ru

%description
Lots of advertising text here.

%prep
mkdir -p $RPM_BUILD_ROOT%{prefix}
zcat $RPM_SOURCE_DIR/ORBit-0.5.8.tar.gz | tar -xvf -

%build
cd ORBit-0.5.8
CFLAGS="$MYCFLAGS" ./configure --prefix=%prefix
make prefix=$RPM_BUILD_ROOT%{prefix}

%install
cd ORBit-0.5.8
make prefix=$RPM_BUILD_ROOT%{prefix} install
/sbin/ldconfig -n $RPM_BUILD_ROOT%{prefix}/lib
strip $RPM_BUILD_ROOT%{prefix}/bin/* || :
gzip -9 $RPM_BUILD_ROOT%{prefix}/info/*

%clean
rm -rf $RPM_BUILD_ROOT
rm -rf $RPM_BUILD_DIR/ORBit-0.5.8

%post -p /sbin/ldconfig

%postun -p /sbin/ldconfig

%files

%defattr(-,root,root)

%doc ORBit-0.5.8/AUTHORS ORBit-0.5.8/COPYING ORBit-0.5.8/ChangeLog ORBit-0.5.8/NEWS ORBit-0.5.8/README ORBit-0.5.8/TODO
%doc -P ORBit-0.5.8/libIDL/COPYING ORBit-0.5.8/libIDL/ChangeLog ORBit-0.5.8/libIDL/AUTHORS
%doc -P ORBit-0.5.8/libIDL/README* ORBit-0.5.8/libIDL/NEWS ORBit-0.5.8/libIDL/BUGS ORBit-0.5.8/libIDL/tstidl.c

%{prefix}/lib/lib*.so.*
%{prefix}/bin/orbit-event-server
%{prefix}/bin/orbit-name-server
%{prefix}/bin/name-client
%{prefix}/bin/orbit-ird

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

имя_тэга: значение

В нашем spec-файле преамбула содержит следующие тэги:

Summary - в этом тэге задается краткое описание содержимого пакета.

Name - имя пакета.

Version - версия упаковываемого приложения. Значение этого тэга используется rpm при генерации имени файла пакета.

Release - номер сборки пакета.

Значения тэгов Name, Version и Release используются программой rpm при генерации имени файла пакета.

Copyright - сведения о лицензии ПО

Group - название группы базы данных RPM, в которую следует поместить информацию о пакете. Название группы можно задавать произвольно.

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

URL - URL проекта.

BuildRoot - этот тэг позволяет задать специальный каталог для установки готового приложения. Зачем это нужно, мы увидим позднее.

Тэги Prefix и DocDir используют значение переменной prefix, определенной в начале файла. С помощью этих тегов нам легко будет задавать ссылки на различные компоненты пакета.

Тэг Vendor содержит сведения о поставщике ПО, а в тэге Packager вы по праву можете поставить свое имя.

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

За преамбулой spec-файла следуют разделы, содержащие команды и данные, необходимые в процессе сборки приложения и установки пакета.

Раздел %description содержит "длинное" описание пакета, которое может состоять из нескольких строк.

Если вы не хотите возиться с исходными текстами, а предпочитаете собирать пакеты на основе готовых двоичных файлов, можете пропустить дальнейшее изложение до абзаца, посвященного разделам %post и %postun.

Большинство разделов spec-файла содержат последовательности команд Linux, и очень похожи на файлы сценариев оболочки (shell scripts). Рассмотрим раздел %prep, в котором выполняются различные подготовительные операции. В этом разделе мы создаем "корневой" каталог приложения /var/tmp/orbit-0.5.8-root/usr (используя значения тэгов Prefix и BuildRoot) и распаковываем .tar.gz архив с исходными текстами приложения. Создание фиктивного корневого каталога для приложения не обязательно, но очень удобно. Дело в том, что в процессе сборки пакета по стандартной схеме выполняется не только компиляция и сборка, но также и установка приложения. Если вы не хотите, чтобы приложение ORBit устанавливалось в вашей системе всякий раз, когда вам нужно будет собрать пакет, лучше всего выполнить "псевдо-установку" в другой каталог, и удалить файлы приложения, когда они уже не будут нужны (мы еще вернемся к этому вопросу при рассмотрении раздела %filelist). Кроме команд Linux в разделе %prep можно использовать специальный макрос %setup, который подробно описан в документации.

Далее следует раздел %build в котором выполняется компиляция и сборка приложения. Вам может показаться странным, что процесс создания дистрибутива так тесно связан со сборкой приложения, однако не следует забывать, что RPM создавался для Linux-разработчиков, а отличительной чертой цикла разработки для Linux является выпуск большого числа промежуточных версий (snapshots). Кроме того, сборка приложений во время сборки пакета чрезвычайно облегчает процесс внесения обновлений (patches). В разделе %build мы сперва переходим в подкаталог ORBit-0.5.8, созданный в каталоге BUILD в процессе распаковки архива. Поскольку исходники ORBit предназначены для компиляции на разных платформах, перед сборкой приложения необходимо выполнить скрипт configure, который, кроме прочего, создаст make-файлы, соответствующие нашей системе. Далее запускаем утилиту make. Отмечу еще раз: использование make необязательно. Раздел %build может содержать любые команды, необходимые для сборки приложения, в том числе и прямые вызовы компилятора gcc.

За разделом %build следует раздел %install, в котором выполняется установка приложения. Обратите внимание, что приложение устанавливается в псевдо-корневой каталог /tmp/var/ORBit-0.5.8-root/usr, при этом в самой системе ничего не меняется.

Далее идет раздел %clean, который выполняется в самом конце процесса сборки, после записи готовых rpm-файлов на диск. В этом разделе мы рекурсивно удаляем псевдо-корневой каталог и подкаталог каталога BUILD, в котором содержались распакованные исходники.

Следующие разделы %post и %postun представляют собой post-install и post-uninstall скрипты. В отличие от рассмотренных выше разделов spec-файла, эти команды выполняются не в момент сборки пакета, а во время его установки на машине пользователя. Команды, содержащиеся в этих разделах, будут выполняться соответственно после установки и после удаления пакета из системы. В нашем файле оба раздела содержат только вызов утилиты ldconfig, в общем же случае сюда можно включать любые операции, например создание ярлыков приложения, настройку файлов конфигурации под конкретную систему и т. п. Для того, чтобы деинсталляция приложения выполнялась чисто, необходимо придерживаться следующего правили "все, что делается в %post, должно быть отменено в %postun".

Кроме разделов %post и %postun в spec-файлы можно включать разделы %pre и %preun, выполняемые соответственно перед установкой и удалением пакета. Например, в раздел %pre можно включить подготовительные действия, необходимые при установке приложения поверх уже установленной версии. К разделам, выполняющимся на машине пользователя, относится также раздел %verify, позволяющий проверить правильность установки пакета.

Заключает spec-файл раздел %files, являющийся, пожалуй, самой важной его частью. Этот раздел содержит список файлов, которые следует поместить в пакет. Файлы, не входящие в этот список, не будут помещены в пакет, даже если они были созданы в процессе сборки. В этом разделе мы используем несколько команд, призванных облегчить составления списка файлов. Директива %doc3 предназначена для переноса в пакет стандартной документации, т. е. файлов README, TODO, COPYING и иже с ними.

Обратите внимание на использование подстановочных символов (wildcards) в списке файлов. Здесь еще раз сказывается преимущество установки приложения в каталог BuildRoot. Если бы файлы переносились из каталогов /usr/bin и /usr/lib (что в принципе вполне допустимо), ссылка /usr/lib/lib*.so* привела бы к включению в пакет гораздо большего числа файлов, чем нам хотелось бы :-). При установке пакета пользователем, файлы, перечисленные в списке %files, будут устанавливаться в тех же каталогах, в которых они указаны в этом списке. Если тэг BuildRoot не используется, ссылки на файлы в разделе %files считаются абсолютными, так что, например, файл, указанный в списке %files как /usr/bin/somebinary, будет взят в пакет из каталога /usr/bin/ и будет установлен в одноименный каталог на машине пользователя. При использовании BuildRoot ссылки в разделе %files должны указываться относительно BuildRoot. Тогда при установке пакета пользователем эти файлы окажутся не в "фиктивном" корневом каталоге, а в "настоящем". Например, если тэг BuildRoot содержит значение /var/tmp/orbit-0.5.8-root/, файл /var/tmp/orbit-0.5.8-root/usr/bin/somefile должен указываться в разделе %files как /usr/bin/somefile. При установке в системе пользователя этот файл будет помещен в каталог /usr/bin/.

Теперь у нас есть все необходимое для сборки пакетов. Копируем файл ORBit.spec в каталог SPECS и командуем:

rpm -ba ORBit.spec

Если сборка пакета выполняется на компьютере с процессором Pentium III, то после завершения работы rpm в подкаталоге i686 каталога RPMS появится файл пакета ORBit-0.5.8-1.i686.rpm. Как видите пакету присвоено имя, соответствующее принятым в системе RPM традициям (необходимая для этого информация была взята из тэгов spec-файла). Если вы загляните в каталог SRPMS, то увидите там пакет ORBit-0.5.8-1.src.rpm. Таким образом было создано сразу два пакета: пакет двоичных файлов и пакет исходных текстов, в котором кроме архива ORBit.0.5.8.tar.gz содержится также наш spec-файл.

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

А как же собрать пакет, не имея исходных текстов? Очень и очень просто. Для этого нужно удалить из spec-файла разделы %prep, %build, %install и %clean и отредактировать соответствующим образом преамбулу. Фактически, spec-файл для сборки пакета может состоять только из преамбулы и раздела %files.

RPM library API

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

Прежде чем мы приступим к рассмотрению RPM library API, следует отметить один печальный факт: раздел фирменного руководства (которое, кстати, не обновлялось с 2000 года), посвященный этому API, безнадежно отстал от жизни. Так что основным источником сведений о RPM library API для нас станут заголовочные файлы из каталога /usr/include/rpm.

Далее следует исходный текст программы rpminfo, которая выводит на консоль некоторые сведения о переданном ей rpm-файле (версия API соответствует RPM 4.0).

#include <stdio.h>
#include <stdlib.h>
#include <rpm/rpmlib.h>
#include <rpm/rpmio.h>
#include <rpm/header.h>
int main( int argc, char * argv[] ) {
FD_t fd;
Header hdr;
int isSource, major, minor, i, type, count;
const char ** fileList;
void * data;
if (argc != 2) {
printf("usage: %s <packet.rpm>\n", argv[0]);
return 0;
}
if ((fd = Fopen(argv[1], "r")) == 0) {
printf("%s: no such file.\n", argv[1]);
return 1;
}
if (rpmReadPackageHeader(fd, &hdr, &isSource, &major, &minor) != 0) {
printf("%s: bad file format\n", argv[1]);
Fclose(fd);
return 1;
}
printf("This is the %s package.\n", isSource == 0 ? "binary" : "source");
rpmHeaderGetEntry(hdr, RPMTAG_DESCRIPTION, &type, &data, &count);
printf("Package description: %s\n", data);
free(data);
rpmHeaderGetEntry(hdr, RPMTAG_VENDOR, &type, &data, &count);
printf("Package vendor:
%s\n", data);
free(data);
rpmHeaderGetEntry(hdr, RPMTAG_PACKAGER, &type, &data, &count);
printf("Packed by %s\n", data);
free(data);
printf("press <Enter> to see the file list\n");
getchar();
rpmBuildFileList(hdr, &fileList, &сount);
printf("The files in the package are:\n");
for (i = 0; i < сount; printf("\t%s\n", fileList[i++]));
free(fileList);
Fclose(fd);
}

Строка для компиляции программы должна выглядеть так:

gcc rpminfo.c -lrpm -lrpmio -lpopt -o rpminfo

Обратите внимание на то, что открытие и закрытие файла выполняется не стандартными функциями fopen и fclose, а специальными Fopen и Fclose, объявленными в файле rpmio.h. Хотя функция Fopen, судя по всему, предназначена именно для открытия rpm-файлов, она не проверяет, является ли открываемый файл корректным rpm-файлом. Fopen сигнализирует об ошибке только в том случае, если файла с указанным именем нет на диске. Проверить корректность открытого файла можно, анализируя значение, возвращенное функцией rpmReadPackageHeader. Эта функция, кроме прочего, заполняет необходимыми значениями переменную hdr типа Header. Переменная hdr затем передается остальным функциям, работающим с пакетом. Функция rpmHeaderGetEntry позволяет получить значения тэгов, хранящихся в заголовке пакета и соответствующих тэгам, объявленным в spec-файле, а функция rpmBuildFileList создает список файлов, содержащихся в пакете.

Конечно, данная статья не претендует на всеохватность. RPM развивается непрерывно, включая в себя все новые и новые возможности. В своей статье я стремился показать, что менеджер RPM полезен и доступен всем Linux-программистам, а не только "гуру", собирающим дистрибутивы ОС.

Литература:

Maximum RPM: Taking the Red Hat Package Manager to the Limit, Edward C. Bailey. Доступно на сайте www.rpm.org


Статья была опубликована в журнале "Программист", #5, 2002 г. Перепечатка статьи возможна только с разрешения редакции журнала.