Привет, Хабр!
Сегодня мы рассмотрим, как написать один RPM .spec
-файл так, чтобы он одинаково успешно собирался на Fedora, RHEL и даже на openSUSE.
Но для начала: зачем вообще поддерживать несколько дистрибутивов одной спецификацией?
Есть несколько кейсов:
CI/CD на всех платформах сразу.
Вы ведёте опенсорс. У вас.gitlab-ci.yml
и в нём matrix build: Fedora 39, EPEL9, openSUSE Leap 15.6. Если.spec
один, можно тривиально написатьrpmbuild -ba
на каждой ноде и забыть.Патчи upstream прилетают разом.
Если у вас три разные.spec
: на Fedora нужна версия 3.x библиотеки, на SUSE — 1.1, а на RHEL ещё и со старым автотулзом. И в каждой специи — свои костыли. В единой специи ты хотя бы видишь, чем они отличаются, и можешь аккуратно поднятьif
илиbcond
.Снижение числа ошибок.
Три спеки — это три точки для багов. Особенно когда люди начинают забывать их синхронизировать. Кто-то забыл обновить релиз на SUSE — и всё, пользователи пишут тикеты.
Так что мысль простая: один файл — один источник истины.
Conditional-макросы: %if 0%{?fedora}, %if 0%{?rhel}, %bcond_with tests
Вот тут большинство задумываютися. Ты либо напишешь свои if
-ы топорно — и через полгода сам их не прочитаешь, либо аккуратно заведёшь глобалы и обернёшь в bcond
.
0%{?…}
нужен именно для того, чтобы %if
не свалился в синтаксическую ошибку, если макроса нет. SUSE не определяет ни %fedora
, ни %rhel
, а только %suse_version
. Поэтому всегда начинаем спецификацию примерно так:
%global distro fedora
%if 0%{?rhel}
%global distro rhel
%endif
%if 0%{?suse_version}
%global distro suse
%endif
Почему не %distname
? В Fedora %distname
в будущем может конфликтовать, а %distro
— наш, локальный.
Дальше — правило хорошего тона: не пишем большие вложенные if
, лучше разносить логические блоки и добавлять комментарии:
# У Fedora >=39 нужен OpenSSL3
%if 0%{?fedora} >= 39
BuildRequires: openssl-devel >= 3
%endif
# SUSE уже давно на OpenSSL3
%if 0%{?suse_version} >= 1550
BuildRequires: libopenssl-devel >= 3
%endif
С bcond_with
история тоже не всегда очевидная: он позволяет гибко отключать и включать опциональные блоки, не правя спецификацию. Самое классное — в OBS они отображаются как чекбоксы прямо в веб-интерфейсе. Вот так можно описать опциональные тесты:
%bcond_with tests
%prep
%autosetup
%build
%configure
make %{?_smp_mflags}
%check
%if %{with tests}
make check
%endif
На проде обычно tests
выключают, а в CI включают.
Раздел Source/BuildRequires: как выбирать разные URL и патчи
На этом этапе уже важно помнить, что Fedora любит bleeding-edge, SUSE — более консервативна, а RHEL — это всегда «не спешим».
Например, в Fedora пакет собирается на GCC 13, а на RHEL всё ещё живёт GCC 8. Значит — не стесняемся брать патчи из апстрима под более старые версии и заворачивать их условно:
%if 0%{?rhel} == 8
Patch0: rhel8-gcc8-fix.patch
%endif
%if 0%{?suse_version} <= 1500
Patch1: suse15.5-backport.patch
%endif
С URL аналогично — иногда приходится брать другие зеркала или форки для SUSE:
%if "%{distro}" == "suse"
Source0: https://download.opensuse.org/repositories/home:/myproject/my-awesome-tool-%{version}.tar.gz
%else
Source0: https://github.com/myorg/my-awesome-tool/archive/v%{version}.tar.gz
%endif
На уровне BuildRequires
не боимся явно указывать разные пакеты — Fedora и SUSE часто расходятся по неймингу:
%if "%{distro}" == "fedora"
BuildRequires: pkgconfig(libfoo)
%endif
%if "%{distro}" == "suse"
BuildRequires: libfoo-devel
%endif
На Fedora принято писать pkgconfig(...)
, на SUSE — старомодно -devel
.
Секция %package и %files: split-subpackages и различия в /usr/lib64 vs /usr/lib
Это частый косяк: «у меня билд прошёл, а файлы не поставились». Потому что в Fedora всё строго /usr/lib64/
, а в SUSE часто /usr/lib/
.
Проверяем это заранее:
%files
%if "%{distro}" == "suse"
/usr/lib/my-awesome-tool
%else
/usr/lib64/my-awesome-tool
%endif
Или ещё лучше — определем глобальный макрос libdir
и используем везде его:
%global libdir %{_libdir}
%files
%{libdir}/my-awesome-tool
Так читать проще, менять тоже.
Для split-пакетов, вроде -devel
, -doc
, -tests
, заводим отдельные секции %package
и %files
, как положено:
%package doc
Summary: Documentation for %{name}
BuildArch: noarch
%files doc
%doc README.md docs/
Так пакеты выглядят аккуратнее и позволяют пользователям не тащить лишнее.
Обработка системных юнитов, tmpfiles и скриплетов: systemd vs legacy init
Пакет, который ставит сервис — почти всегда таит в себе проблему: systemd у всех свой, пути разные, да и tmpfiles.d, sysusers.d и прочие штуки далеко не одинаково реализованы. Особенно в openSUSE, где любят systemd-rpm-macros
, но всё равно что-нибудь отличается от Fedora.
Описываем установку системного юнита:
Requires(post): systemd
Requires(preun): systemd
Requires(postun): systemd
%post
%systemd_post myservice.service
%preun
%systemd_preun myservice.service
%postun
%systemd_postun_with_restart myservice.service
Но в openSUSE свои макросы: %service_*
вместо %systemd_*
, и если бездумно вставлять Fedora-шные макросы — на SUSE всё взорвётся с ошибкой Unknown %systemd_post
.
Правильный кросс-подход:
%if "%{distro}" == "suse"
%define systemd_post() /usr/lib/rpm/postun-systemd %{*}
%define systemd_preun() /usr/lib/rpm/preun-systemd %{*}
%define systemd_postun_with_restart() /usr/lib/rpm/postun-systemd %{*}
%endif
Или ещё лучше — использовать %{?systemd_post:...}
как условную конструкцию. Для tmpfiles:
%post
%tmpfiles_create %{_tmpfilesdir}/myservice.conf
Не забываем:
Requires: systemd
Requires(post): systemd
Requires: systemd-tmpfiles
Всё это — чтобы сервис заработал, каталоги создались, а юнит был корректно включён и переинициализирован.
Ещё нюанс: SUSE кладёт юниты в /usr/lib/systemd/system/
, Fedora — туда же, но RHEL 7 любит /lib/systemd/system/
. На это тоже можно писать if
или использовать %{_unitdir}
.
Заключение
В итоге, единый .spec
— это меньше дублирования, проще поддержка, чище CI и предсказуемое поведение пакета на Fedora, RHEL и SUSE. Используйте макросы, bcond_with
, аккуратные if
, заведите libdir
и тестируйте всё в COPR и OBS.
Приглашаем вас ознакомиться со специализацией «Administrator Linux», в рамках которой рассматриваются современные методы и инструменты администрирования Linux‑систем.
Для выбора других образовательных программ рекомендуем посетить каталог курсов.
Кроме того, в календаре открытых уроков вы найдёте мероприятия, посвящённые различным аспектам работы с Linux. Это хорошая возможность получить практические знания и ознакомиться с материалами курса.
Чтобы оставаться в курсе самых актуальных технологий и трендов, подписывайтесь на Telegram-канал OTUS.
Комментарии (2)
13werwolf13
21.07.2025 22:04вам бы освоить openbuildservice. некоторые разработчики уже освоили и собирают в одном месте пакеты и для центоси и для суси и для debian и даже всякое неоднозначное вроде appimage и flatpack. пример.
да и по тексту есть неточности:
1) pkgconfig() прекрастно применяется в спеках для suse
2) использовать билдреки из других репозиториев это задача obs а не rpmbuild
3) /usr/lib/ vs /usr/lib64 - так не надо, есть макрос которому виднее что там в конкретном дистрибутиве для libexecвообще по тексту впечатление что знания о сборке пакетов для suse/opsensuse у вас несколько устаревшие.
однако в целом статья полезная и закладывает для новичков понимание как обернуть несколько дистрибутивов в один spec, и за это вам спасибо.
ajijiadduh
может пора 7 отпустить?