Declarative
Declarative packaging is all the rage lately. I stopped counting how often I was asked “Do you have something like nix packages?”
Back in 2019 Florian Festi gave a nice talk called Re-Thinking Spec Files. It showed many nice ideas on how packaging could be easier.
Since RPM 4.20 we actually have declarative builds. Let us explore this a bit.
Exploring the possibilities
One of the first packages in openSUSE, which switched to declarative builds was gnome-text-editor. But to focus on the basics we will use my converted minisign package.
Name: minisign
Version: 0.12
Release: 0
License: ISC
Summary: A dead simple tool to sign files and verify signatures
URL: https://jedisct1.github.io/minisign/
Group: Productivity/Networking/Security
Source0: https://github.com/jedisct1/minisign/archive/%{version}.tar.gz
BuildRequires: cmake
BuildRequires: pkgconfig(libsodium)
# end of normale spec file
BuildSystem: cmake
BuildOption: -DCMAKE_STRIP:BOOL=OFF
%description
[snip]
%files
%doc README.md
%license LICENSE
%{_bindir}/%{name}
%{_mandir}/man1/%{name}.1.*
Until the # end of normale spec file everything looks like a normal RPM spec file. Then we run into the a new statement in the preamble BuildSystem: cmake.
We instruct the package will use cmake for building. BuildOption: -DCMAKE_STRIP:BOOL=OFF is the commandline options passed to the cmake build system. Then we have a normal %description and %files section. Now you might say “It cannot be this short” … But it actually can be. And it builds.
So how does this work? For each BuildSystem we want to use we need to define some macros for RPM. Those macros will tell RPM which other macros to call for each section.
%buildsystem_cmake_conf() %cmake %*
%buildsystem_cmake_build() %cmake_declarative_build %*
%buildsystem_cmake_install() %cmake_install %*
%buildsystem_cmake_check() %ctest %*
%buildsystem_cmake_generate_buildrequires() %{nil}
%cmake_declarative_build \
cd %{__builddir} \
%cmake_build
The first thing you might notice that the %cmake macro is not assigned to the %build section. There is a %conf section! Who knew! As we skipped %prep.
That seems to have expanded to
%prep
%autosetup
Next step is the %build section we all know and love. As you see we are not using our normal %cmake_build macro. The override with the %cmake_declarative_build is needed because our %cmake_build assumes it is still in the %{__builddir}. Because it is in a different section, the current work directory is reset to the normal directory with the unpacked sources. For the %install section we get our expected %cmake_install. Last but almost least the expected %ctest for the %check section.
Then it gets interesting: %buildsystem_cmake_generate_buildrequires()
Yes! RPM can now autogenerate buildrequires. And we can even hook it up automatically with another macro. Sadly we do not have a generate yet for cmake.
Customizing the steps
One option to customize the steps we already encountered above: BuildOption
But you cannot only specify BuildOption like this, but also per section:
BuildOption(prep): -p1
BuildOption(prep): -n %{archive_name}
BuildOption(conf): -DCMAKE_STRIP:BOOL=OFF
BuildOption(conf): -DENABLE_TOOLS:BOOL=ON
This is a pattern we already know from Requires(post|postun|pre|preun).
In theory you could have multiple commandline options in a single statement, but as we learned from BuildRequires and having them in a very long line, it is a PITA for merging. So let us split them up to one per line.
If we need to call custom tools before or after the normal steps, we have 2 hook points:
-p== prepend-a== append
%conf -p
autoreconf -fi
%install -a
%find_lang %{name} %{?no_lang_C}
As you have seen above declaring how to handle a certain build system is easy.
The macros for cmake are not in our normal cmake package yet. You can even wrap more complex macros. For example we could define BuildSystem: kf6 and wrap the KDE %kf6* macros. Of course we do not have to reinvent the wheel in my places. Especially for the BuildRequires generators there is a lot of potential for cooperation with Fedora and other RPM based distributions.
Buuuuuuuuuuuuut?
I am sure to many of you this will now sound awesome. Shorter spec files. Avoiding a lot of duplicated code.
Now you might wonder: “What are the downsides to this”
Weeeeeeeeeell … it only works on 16.x and Tumbleweed. No support on 15.x or older. If you want to keep targeting older distributions, you cannot use the new ways yet.