Создание RPM-пакетов с нуля на примерах

Обновлено Обновлено: Опубликовано Опубликовано:

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

Мы будем работать в системе CentOS (Red Hat / Fedora).

Подготовка системы

Для работы по сборке пакетов лучше использовать отдельный компьютер, виртуальную машину или контейнер Docker.

1. Установим пакеты:

yum install rpmdevtools rpmlint

* где: 

  • rpmdevtools — позволит нам использовать утилиту rpmdev-setuptree, с помощью которой мы сможем создать рабочую среду в виде каталогов для сборки.
  • rpmlint — позволяет протестировать пакет RPM.

А также ставим:

yum group install "Development Tools"

* данная группа пакетов включает все необходимое для сборки. Ее не рекомендуется ставить на рабочий компьютер, так как устанавливается много ненужного для стандартной системы мусора.

2. Создаем пользователя.

Делать готовые установочные сборки пакетов очень опасно от пользователя root. Если мы допустим ошибку с путями, файлы могут перетереть или удалить важные для работы директории. Стоит создать отдельного пользователя и работать под ним. Однако, если мы работаем в виртуальной среде или контейнере Docker, нам это не страшно. Тогда данный пункт можно пропустить и работать из под root.

Выполняем команду:

useradd builder -m

* в данном примере мы создадим пользователя builder. Опция -m сразу создаст домашний каталог для пользователя.

Теперь заходим под данным пользователем — последующие команды мы будем выполнять от него:

su - builder

3. Создадим структуру каталогов для сборки:

rpmdev-setuptree

В нашей текущем каталоге должна появиться папка rpmbuild — а в ней:

  1. BUILD — содержит все файлы, которые появляются при создании пакета.
  2. RPMS — сюда будут складываться готовые пакеты.
  3. SOURCES — для исходников, из которых и будут собираться RPM-пакеты.
  4. SPECS — для файлов с описанием процесса сборки.
  5. SRPMS — для исходников RPM-файлов.

Мы готовы к сборке.

Сборка из исходников

Рассмотрим пример создания RPM из пакета, который нужно собирать из исходников с помощью команды make. Например, возьмем данную программу: github.com/brettlaforge/pg_redis_pubsub.

Создадим файл spec:

rpmdev-newspec rpmbuild/SPECS/pg_redis_pubsub.spec

Теперь откроем его и приведем к виду:

vi rpmbuild/SPECS/pg_redis_pubsub.spec

Name:           pg_redis_pubsub
Version:        1.0.2
Release:        1%{?dist}
Summary:        Redis Publish from PostgreSQL
License:        X11 License
URL:            https://github.com/brettlaforge/pg_redis_pubsub
Source0:        %{name}-%{version}.tar.gz

BuildRequires:  postgresql-devel postgresql-server-devel
BuildRequires:  hiredis-devel
Requires:       postgresql
%if 0%{?rhel} < 8
Requires:       hiredis-last >= 0.13.3-1
%else
Requires:       hiredis = 0.15
%endif

%define         _build_id_links none

%description
Redis Publish from PostgreSQL

%prep
%{__rm} -rf %{name}-%{version}
%{__mkdir} -p %{name}-%{version}
%{__tar} -xzvf %{SOURCE0} -C %{_builddir}/%{name}-%{version} --strip-components 1

%build
cd %{name}-%{version}
%{__make}

%install
cd %{name}-%{version}
%{__make} install DESTDIR=%{buildroot}

%clean
%{__rm} -rf $RPM_BUILD_ROOT
%{__rm} -rf $RPM_BUILD_DIR/*

%files
%defattr(-,root,root)
%{_libdir}/pgsql/redis.so
%{_datadir}/pgsql/extension/redis.control
%{_datadir}/pgsql/extension/redis--0.0.1.sql
%doc %{_datadir}/doc/extension/redis.mmd

%changelog
* Fri Jul  9 2021 root
-

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

Установим зависимости, которые необходимы для сборки (BuildRequires):

yum-builddep rpmbuild/SPECS/pg_redis_pubsub.spec

* утилита yum-builddep сама читает зависимости, необходимые для сборки и устанавливает недостающие пакеты.

Можно это сделать и вручную. В данном примере это: 

yum install epel-release

yum install postgresql-devel postgresql-server-devel hiredis-devel

* конкретно, в моем примере для установки hiredis-devel необходимо поставить репозиторий epel-release. Список пакетов, необходимый для сборки конкретного пакета необходимо уточнить в документации.

Теперь копируем исходник на свой компьютер. В моем примере клонируем репозиторий:

git clone https://github.com/brettlaforge/pg_redis_pubsub.git

Готовим архив и помещаем его в каталог rpmbuild/SOURCES:

tar -czvf rpmbuild/SOURCES/pg_redis_pubsub-1.0.2.tar.gz pg_redis_pubsub

Если бы в качестве Source мы указали внешний URL, можно было бы предварительно загрузить исходники командой:

spectool -g -R rpmbuild/SPECS/pg_redis_pubsub.spec

Данная команда разместит загруженные файлы в каталоге rpmbuild/SOURCES/. Если нужно указать другой путь, используем опцию -C:

spectool -g -R rpmbuild/SPECS/pg_redis_pubsub.spec -C /tmp/

Проверяем корректность SPEC-файла:

rpmlint rpmbuild/SPECS/pg_redis_pubsub.spec

В моем примере команда вернула ответ:

rpmbuild/SPECS/pg_redis_pubsub.spec: W: invalid-url Source0: pg_redis_pubsub-1.0.2.tar.gz
0 packages and 1 specfiles checked; 0 errors, 1 warnings.

Данное предупреждение можно проигнорировать.

Выполняем сборку:

rpmbuild -bb rpmbuild/SPECS/pg_redis_pubsub.spec

Если она пройдет без ошибок, мы должны найти RPM-пакет в каталоге rpmbuild/RPMS/x86_64, где x86_64 — архитектура пакета.

Описание файла SPEC

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

Опции заголовка

Определяют описание пакета, а также некоторые важные для сборки параметры.

Опция Описание Пример значения
Name Название для пакета RPM pg_redis_pubsub
Version Версия собираемого пакета 1.0.2
Release Релиз или версия программы 1%{?dist}
?dist обозначение версии доработки
Summary Краткое описание пакета Redis Publish from PostgreSQL
License Способ лицензирования X11 License
URL Адрес источника пакета https://github.com/brettlaforge/pg_redis_pubsub
Source0 Источник данных, из которых должен собираться пакет.
Можно указать несколько источников: Source1, Source2 ... SourceN
%{name}-%{version}.tar.gz
Данная запись означает, что сборщик будет искать архив 
pg_redis_pubsub-1.0.2.tar.gz в каталоге rpmbuild/SOURCES/
BuildRequires Требования к пакетам, которые нужны для сборки пакета. Можно написать большим списком, а можно добавить несколько отдельных строк BuildRequires. Также мы можем задать требование к версии пакета. postgresql-devel postgresql-server-devel hiredis-devel
Requires Требования к пакетам, которые нужны для установки собранного пакета. Можно написать большим списком, а можно добавить несколько отдельных строк Requires. Также мы можем задать требование к версии пакета. С версии rpm 4.13 поддерживаются логические операторы and, or, if. hiredis >= 0.13.3-1, (postgrespro-std-15-client or postgrespro-ent-15-client)
Provides Имя предоставляемого пакета. Данная опция важна для Requires других пакетов. Например, мы можем делать сборку пакета с разными назвваниями, которые зависят от версии или среды, но у всех этих пакетов будет общее поле Provides, таким образом, при зависимостях можно будет указать только данное общее значение. pg_pubsub
Obsoletes Объявляет пакеты устаревшими. Установщик выполнит их удаление при установке собранного пакета. hiredis < 0.13.3-1

Теги для определения переменных

С помощью тега %define можно определять переменные. Предоставлены разные возможности это сделать:

Пример определения Описание
%define debug_package 1 Простое создание переменной debug_package со значением 1. Обращение к данной перемнной возможно с помощью написания %{debug_package}.
%{!?osname: %define osname "redos"} Данная переменная osname будет определена со значением redos, если она не определена ранее.

Также мы можем определить переменную с помощью %global, например:

%global debug_package %{nil}

Такие переменные имеют глобавльный охват. Однако в настоящее время rpm не очищает область действия макросов и принципиальной разницы между global и define нет.

Основные рабочие разделы

В каждом разделе описывается своя часть логики процесса сборки пакета.

Раздел Описание
%description Описание пакета. Может состоять из нескольких строк, но каждая строка должна содержать до 73 символов.
%package -n <имя пакета> Будет создан дополнительный пакет с именем <имя пакета>. Для данного подраздела можно указать свои опции Summary, Requires и так далее. В одном файле spec можно указать сколько угодно данных разделов со своими поднастройками. Это значит, что при сборке будет созданно несколько пакетов с разными названиями.
%description -n <имя пакета> Описание для создаваемого дополнительного пакета.
%package devel Добавленный devel указывает на то, что будет создан дополнительный пакет devel.
%description devel Описание для пакета devel.
%prep Предварительная обработка. Используется для подготовки исходников. Как правило, в данном разделе происходит их распаковка. Также на данном этапе могут применяться патчи.
%build Этап сборки пакета. Как правило, это make.
%install Установка пакета. Это может быть make install или копирование конкретных файлов в конкретные директории или запуск произвольного скрипта.
%clean Раздел содержит инструкции по удалению устаревших файлов, которые больше не нужны.
%files Перечисляем файлы, которые должны попасть в конечную систему при установке пакета.
%files -n <имя пакета> Список файлов для дополнительного пакета.
%files devel Список файлов, которые должны войти в пакет devel.
%changelog Список изменений в работе программного обеспечения.

Макросы разделов

У каждого из разделов могут быть свои макросы.

%files:

Макрос Описание
<путь до файла или папки> Добавляем файл или каталог в пакет без указания дополнительных опций.
%doc Указываем, что конкретные файлы относятся к документации. Такие файлы будут установлены в раздел /usr/doc/
%config Помечаем файлы как конфигурационные. Это задает действие при удалении пакета — если файлы были изменены, они будут переименованы с добавлением .rpmsave.
%dir Обозначает директорию, которой владеет пакет. При удалении пакета, также будет удаляться данная директория.
%files -f Позволяет перечислить файлы во внешнем файле и передать его как аргумент.
%defattr(<file rights>,<user>,<group>,<dir rights>) Задаем права для файлов и каталогов, которые будут назначены при установке пакета. Данный макрос применяется глобально ко всем файлам. Например: %defattr(644,root,root).
%attr(<mode>,<user>,<group>) Задаем права на файл или каталог, которые должны быть назначены при установке пакета. Данный макрос применяется к конкретному перечню файлов. Например: %attr(644,postgres,postgres) %{_libdir}/pgsql/redis.so.
%attr(<mode>,<user>,<group>) %config(noreplace) В данном примере мы зададим и атрибут, и укажем, что файл является конфигурационным.
%license Указываем, что файл является файлом лицензии.

Сценарии

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

Сценарий Описание
%pre Выполняется перед установкой пакета в систему
%post Выполняется после установки пакета в систему
%preun Выполняется перед удалением пакета из системы
%postun Выполняется после удаления пакета из системы

Пример для %post 

%post
case "$1" in
    1)
      post_install.sh
    ;;

    2)
      post_upgrade.sh
    ;;

    *)
        echo "postinst called with unknown argument \`$1'" >&2
        exit 1
    ;;
esac
exit 0

* макрос %post принимает аргумент $1, который говорит нам как был установлен пакет — новая установка (1) или обновление (2). Это важно, так как действия, как правило, отличаются, в зависимости от того, чистая это установка или обновление.

В зависимости от момента запуска сценария (%pre, %preun ...) значения для $1 меняются. Используйте в качестве шпаргалки данную таблицу:

  Установка Обновление Удаление
%pre $1 == 1 $1 == 2 -
%post $1 == 1 $1 == 2 -
%preun - $1 == 1 $1 == 0
%postun - $1 == 1 $1 == 0

Макросы для сценариев

Внутри сценариев могут быть запущены свои макросы:

Сценарий Макрос Описание
%post %systemd_post Запускается процедура установки и регистрации сервиса.
%preun  %systemd_preun Запрещается автозапуск сервиса (systemctl --no-reload disable ...)
%postun %systemd_postun_with_restart Отмечает сервис для перезагрузки

Макросы для команд

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

Макрос Команда Описание
%{__rm}  rm Удаление файлов
%{__mv} mv Перенос файлов
%{__tar} tar Распаковка или создание архивов формата gz
%{__unzip} unzip Распаковка архивов формата zip
%{__sed} sed Поиск по шаблону текста и его замена
%{__ln_s} ln -s Создание симлинка
%{__mkdir} mkdir Создание каталога
%{__mkdir_p} mkdir -p Рекурсивное создание каталога (создает папки по пути)
%{__chmod} chmod Назначение прав доступа на файл или каталог
%{__chown} chown Назначение владельца на файл или каталог
%{__install} install Копирование файлов с выставлением аттрибутов
%{__cp} cp Копирование файлов
%{__cat} cat Отображение содержимого файла

* полный список макросов можно получить командой rpm --showrc.

Макросы для каталогов

Каталоги лучше писать не буквально, а через макросы:

Макрос Путь Итоговый путь
%{_prefix} /usr /usr
%{_usr} /usr /usr
%{_libdir}  %{_prefix}/lib64 /usr/lib64
%{_datarootdir} %{_prefix}/share /usr/share
%{_datadir} %{_datarootdir} /usr/share
%{_sysconfdir} /etc /etc
%{_var} /var /var
%{_localstatedir} /var /var
%{_sharedstatedir} /var/lib /var/lib
%{_docdir} %{_datadir}/doc /usr/share/doc
%{_unitdir} /usr/lib/systemd/system /usr/lib/systemd/system
%{_exec_prefix} %{_prefix} /usr
%{_bindir} %{_exec_prefix}/bin /usr/bin
%{_sbindir} %{_exec_prefix}/sbin /usr/sbin

* обратите внимание, что некоторые макросы ведут не на конкретные пути, а на другие макросы.
* полный список макросов можно получить командой rpm --showrc.

Раздел %files

Стоит немного подробнее рассмотреть раздел %files. В нем мы описываем файлы, которые должны быть упакованы в пакет. Приведем пример:

%files
%defattr(0640,postgres,postgres,0750)
%attr(0644,postgres,postgres) %{_libdir}/pgsql/redis.so
%{_datadir}/pgsql/extension/redis.control
%{_datadir}/pgsql/extension/redis--0.0.1.sql
%attr(0640,root,root) %doc %{_datadir}/doc/extension/redis.mmd
%config(noreplace) %{prefix}/redis_ext.conf

* в данном примере:

  • По умолчанию всем файлам присваиваются права 0640, каталогам — 0750. В качестве пользователя-владельца по умолчанию назначается postgres.
  • Для файла redis.so мы решили поменять права на 0644.
  • Файлу redis.mmd будет назначен владелец root.
  • Файл redis_ext.conf обозначен как конфигурационный файл и он не будет заменяться при обновлении пакета.

Служебные переменные

Ранее мы рассмотрели возможность определения переменных в файле spec с помощью директив %define или %global. Переменные могут нами использовать для подстановки значений в определенные блоки сценария. Но также, мы можем определять некоторую логику с помощью специальных переменных. Рассмотрим это на конкретных примерах:

Переменная Описание Варианты
debug_package Определяем необходимость создания пакета debuginfo (с отладочной информацией). По умолчанию значение 1 и при сборке пакета также собирается пакет с суффиксом debuginfo.
При значении %{nil} сборка проходит без создания debuginfo.
__python Путь до интерпретатора python. Если Наша система использует по умолчанию python не той версии, для которой собрано приложение, мы можем переопределить его сами. Можно задать как короткий путь, таки и полный:
python3
/usr/bin/python3
_build_id_links Позволяет указать, нужно ли создавать идентификаторы сборки в /usr/lib. Если не указан, то сборка будет выполняться с созданием идентификаторов.
Если задать значение none, то идентификаторы создаваться не будут.
__brp_check_rpaths При создании пакета rpmbuild проверяет пути библиотек с помощью check-rpaths. Данная опция позволяет указать, нужно ли это делать. Для отключения проверки путей задаем переменную со значением %{nil}.
__requires_exclude Инструмент rpmbuild при создании пакета автоматически генерирует зависимости (помимо того, что мы перечислим Requires). Данная опция позволяет исключить некоторые значения. Принимаются регулярные выражения, например:
^libssl.so.1.0.0
^libwidevinecdm.so.*
[.]so[.][0-2][a-f]
[.]so[.][0-2][a-f]*|^libwidevinecdm.so.*
* в последнем варианте перечислено 2 паттерна, разделенных символом |

Дополнительные опции

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

Опция Описание Значения
AutoReqProv Указывает, нужно ли делать автоматическую обработку зависимостей RPM. yes/no или 1/0.

Операторы сравнения

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

Пример Описание
%if 0%{?rhel} < 8
Requires:       hiredis-last >= 0.13.3-1
%else
Requires:       hiredis
%endif
В данном примере мы проверяем версию системы, на которой идет сборка. Если rhel (релиз системы) меньше 8, то мы указываем в качестве требования hiredis-last. В данном примере это имеет смысл, так как в CentOS 8 пакет hiredis-last переименовали в hiredis.
%if 0%{?rhel} == 8
...
%endif
В данном условии мы проверяем, является ли версия релиза 8.
%if %{?osname} != "el"
...
%endif
Проверяем значение переменной osname. Если она не равна "el", выполняем действие.

Возможные ошибки

Рассмотрим примеры ошибко, с которыми мы можем столкнуться.

Installed (but unpackaged) file(s) found

Ошибка появляется в конце процесса сборки пакета.

Причина: обнаружены файлы, которые были установлены с помощью make install, но которые не были перечислены в %files. Таким образом, сборщик пакета не знает, что с ними делать.

Решение: секция %files должна содержать все файлы, необходимые для работы приложения. Их нужно перечислить.

Но если у нас есть полная уверенность, что мы перечислили все необходимое, а оставшиеся файлы нам ни к чему, то добавляем в файл spec:

%define _unpackaged_files_terminate_build 0

* в верхнюю часть.

# CentOS # DevOps
Дмитрий Моск — частный мастер
Была ли полезна вам эта инструкция?

Да            Нет