Создание 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 — а в ней:
- BUILD — содержит все файлы, которые появляются при создании пакета.
- RPMS — сюда будут складываться готовые пакеты.
- SOURCES — для исходников, из которых и будут собираться RPM-пакеты.
- SPECS — для файлов с описанием процесса сборки.
- 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
* в верхнюю часть.