Привет, Хабр!
Сегодня мы рассмотрим, как написать один 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 отпустить?