Declarative RPM: Cleaning Up Your Spec Files

Share
Share

This article was written by Marcus Rueckert, Build Service Engineer at SUSE. This article originally appeared on the ‘Nordisch by Nature‘ blog under the same title and has been slightly updated for the suse.com blog.

The End of Spec File Sprawl? Enter Declarative RPM

For decades, the RPM spec file has been the “Swiss Army Knife” of Linux packaging—powerful, but often cluttered with boilerplate shell scripts and repetitive macros. As developers, we’ve watched the rise of purely functional and declarative systems like Nix with a bit of envy. We’ve asked ourselves: Why does a simple CMake project still require 50 lines of repetitive %setup%build, and %install instructions?

The community has been listening. The push for a “cleaner” spec file isn’t just about aesthetics; it’s about reducing maintenance overhead and making packaging accessible to a new generation of developers.

Back in 2019, Florian Festi gave an insightful talk titled Re-Thinking Spec Files,” where he shared some visionary ideas on how to simplify this process. Today, those ideas are becoming a reality. Since the release of RPM 4.20, we finally have native support for declarative builds.

Let’s dive in and see how this changes the game for openSUSE maintainers.

Exploring the Possibilities

One of the first packages in openSUSE to make the switch was gnome-text-editor. However, to keep things simple, we’ll use my converted minisign package as an example.

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)

# This is where the magic happens
BuildSystem:    cmake
BuildOption:    -DCMAKE_STRIP:BOOL=OFF
%description
[snip]

%files
%doc README.md
%license LICENSE
%{_bindir}/%{name}
%{_mandir}/man1/%{name}.1.*

Everything up to the

# This is where the magic happens 

comment looks standard. But then we hit a new statement in the preamble: BuildSystem: cmake. By adding this, we instruct RPM to use CMake for the build process. The BuildOption: -DCMAKE_STRIP:BOOL=OFF line simply passes command-line options directly to the CMake build system. Aside from that, we just have the usual %description and %files sections.

You might be thinking, “It can’t be that short!”… but it actually is. And it builds perfectly.

How does it work?

To make this happen, we need to define specific macros for RPM for each BuildSystem we want to support. These macros 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’ll notice is that the %cmake macro isn’t assigned to the %build section anymore. Instead, there’s a %conf section! (Who knew?) Because we skipped the explicit %prep section it automatically expands to:

%prep
%autosetup -C -p1

Next is the %build section we all know and love. You’ll notice we aren’t using the standard %cmake_build macro here. We use an override called %cmake_declarative_build because the standard version assumes it is still inside the %{__builddir}. Since declarative builds move between sections, the current working directory resets to the source root, so we have to cd back in. Finally, we get our expected %cmake_install for the %install section, and %ctest for the %check section.

The coolest part? %buildsystem_cmake_generate_buildrequires()! Yes, RPM can now autogenerate BuildRequires! We can even hook it up automatically with another macro, though we don’t have a generator for CMake quite yet.

Customizing the Steps

One option to custo

mize the steps is the BuildOption we encountered above. However, you can specify BuildOption not only globally, 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 put multiple command-line options in a single statement, but as we learned from BuildRequires, having them in one very long line is a PITA for merging. So, let’s split them up into one per line.

If you need to call custom tools before or after the standard steps, you have two 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 standard cmake package yet, but 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 many places. Especially regarding BuildRequires generators, there is a lot of potential for cooperation with Fedora and other RPM-based distributions.

The Catch?

I’m sure this sounds awesome—shorter spec files and way less duplicated code. But you’re probably wondering: “What’s the downside?”

Well… it only works on SUSE Linux 16 based products, openSUSE Leap 16.x and Tumbleweed. There is no support for 15.x or older. If you need to target older distributions, you’ll have to stick to the “old way” for a little longer.

Share
(Visited 1 times, 1 visits today)
Avatar photo
19 views
Meike Chabowski Meike Chabowski works as Documentation Strategist at SUSE. Before joining the SUSE Documentation team, she was Product Marketing Manager for Enterprise Linux Servers at SUSE, with a focus on Linux for Mainframes, Linux in Retail, and High Performance Computing. Prior to joining SUSE more than 25 years ago, Meike held marketing positions with several IT companies like defacto and Siemens, and was working as Assistant Professor for Mass Media. Meike holds a Master of Arts in Science of Mass Media and Theatre, as well as a Master of Arts in Education from University of Erlangen-Nuremberg/ Germany, and in Italian Literature and Language from University of Parma/Italy.