Extracing RPM Build Dependencies From RPM Spec Files
Jan 9, 2019
4 minute read

Specifying build dependencies for RPM packages

Build dependencies in an RPM spec are denoted by BuildRequires: ${DEPENDENCY}.

You’ll typically find a random spec file with dependencies like this:

Name: foo

BuildRequires: make
BuildRequires: bar

# We're running something on a SuSE distribution
%if 0%{?suse_version}
BuildRequires: suse-specific-stuff
%else
BuildRequires: not-on-suse
%endif

The normal way to extract dependencies

So for every tutorial that you see to build an RPM package one tool stands above all the rest when it comes to extract / install build dependencies:

$ yum-builddep

But how do you run it?

$ yum-builddep -y ${SPEC_FILE}

Okay that was too easy, why even write a blog post about this?

Well, my traveler, for all the good that yum-builddep does, it has the most common flaw of them all: it’s not available everywhere.

For a tool as good as it is, the reliance on underlying package managers like yum make it basically unusable on distributions that do not have yum installed as their package manager.

Notable examples of distributions that do not have yum as their package manager are fedora and anything built by SuSE.

So what’s the solution on Fedora?

Fedora was fairly easy to solve for, since they’re fairly close to RedHat they have tooling that is comparable with yum-builddep in the form of:

$ dnf builddep

Usage is pretty much the same so if you were writing a script to handle both you’d probably write something fairly similar to:

#!/usr/bin/env bash

SPEC_FILE=$1

dep_install=""
if dnf builddep --version >/dev/null 2>/dev/null; then
    dep_install='dnf builddep'
elif yum-builddep --version >/dev/null 2>/dev/null; then
    dep_install='yum-builddep'
fi

${dep_install} -y "${SPEC_FILE}"

But what about SuSE based distributions?

Well this is where it gets a bit tricky.

SuSE doesn’t really have a concept of a builddep tool and when I asked around about a potential tool that could work I was pointed in the direction of installing yum on my SuSE build box and then copying the repositories over to the yum specific /etc directory.

A common solution one might try if their spec file is relatively simple would be something like, searching the file for lines with BuildRequires and then taking those requirements and piping them into xargs as an argument for package installation.

That’d probably look something like:

$ zypper -n install $(grep BuildRequires ${SPEC_FILE} | cut -d' ' -f2 | xargs)

Something like this is pretty easy to maintain and understand and doesn’t really require any esoteric tooling, but it’s relatively incomplete and doesn’t cover use cases outside of the simplest use case (which in my case has never actually been the use case).

But what about if / else statements and RPM macros?

Well as I’ve said before, most distributions are different and in being different they require different dependencies. In the example SPEC file I gave above we can clearly see that for SuSE based distributions one of the build requirements we have is different than every other distribution we build for.

If we did our simple grep approach our command would look something like:

$ zypper -n install make bar suse-specific-stuff not-on-suse

Obviously this errors out because the not-on-suse dependency isn’t on our our SuSE based distribution and in a script we wouldn’t be able to omit that dependency without a special case.

mfw things don’t work on every distribution I build for

why

A solution that works everywhere

Evaluating an RPM specification without actually executing anything is surprisingly difficult. Common knowledge dictates that to build an RPM spec one would use rpmbuild like so:

$ rpmbuild -ba ${SPEC_FILE}

But what command do you execute if you just want to see what the SPEC file looks like if you expanded all the macros?

Introducing rpmspec

rpmspec is a tool that’s been around forever but wasn’t really known to me until after I started to try to solve this issue we had.

Usage of rpmspec for our exact use case is:

$ rpmspec --parse ${SPEC_FILE}

This will parse our spec file, expand all the macros (like if/else statements) and output the resulting spec file to stdout (which we can then do our grep on!)

Our resulting command looks something like:

$ zypper -n install \
    $(rpmspec --parse ${SPEC_FILE} | grep BuildRequires | cut -d' ' -f2 | xargs)

How about for everything else?

Unsurprisingly this also works for other distributions to gather build dependencies, so you can write a simple script like so to cover all of your bases:

#!/usr/bin/env bash

SPEC_FILE=$1
NO_OUTPUT='>/dev/null 2>/dev/null'

pkg_manager='yum -y'
if zypper --version ${NO_OUTPUT}; then
    pkg_manager="zypper -n"
elif dnf --version ${NO_OUTPUT}; then
    pkg_manager="dnf -y"
fi

${pkg_manager} install \
    $(rpmspec -P ${SPEC_FILE} | grep BuildRequires | cut -d' ' -f2 | xargs)

The solution isn’t exactly pretty but it’ll work pretty much anywhere!

Have any questions? ping me over at @_seemethere