Как писать один .spec для Fedora, RHEL и openSUSE одновременно

от автора

Привет, Хабр!

Сегодня мы рассмотрим, как написать один 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.


ссылка на оригинал статьи https://habr.com/ru/articles/928414/