Skip to content
 

Some tips on RPM conditional macros

Admittedly, I hadn't been messing around with spec files too much, but recently I had to and I found some things that bit me and make me spend some time trying to figure out. Here I'm talking about RPM version 4.x, which is still the default in most (all?) distros.

Specifically, my troubles were with the %if conditional macro, whose exact behavior and syntax is sparsely documented and may appear to do strange things at times, so I'll just put my findings here to remember them (and hopefully they may perhaps be useful to others).

It all started when I naively tried to use this conditional to check whether a macro was defined (as most documentation that can be found on the net seems to suggest):

%if %{mymacro}
# ... do something
%endif

When the macro was defined, that worked; when it was not defined, I was getting the dreaded error

error: parse error in expression
error: /usr/src/foo/foo.spec:25: parseExpressionBoolean returns -1

A similar situation happened with this test for equality:

%if %{mymacro} == 100
# ... do something
%else
# ... do something else
%endif

When the macro was undefined, I was expecting either the whole %if/%else block to be skipped, or at least the %else branch to be executed (since something that's undefined is clearly not equal to 100). What was happening instead, was again the same "parse error in expression" error as before.

Trying to make it work, I did this:

%if "%{mymacro}" == "100"
# ... do something
%else
# ... do something else
%endif

Which appeared to work indeed - but as we'll see it wasn't working for the reason I thought, and could even produce false matches under some special circumstances (see below).

It still wasn't satisfactory, because I felt I wasn't really understanding what was going on, so I had that feeling that it all was working by chance. And still, I hadn't found a way to check something as simple as whether a macro is defined or not.

Finally, it occurred to me to add the following line at the very beginning of the %prep section in the spec file to see what was going on:

echo "the value of mymacro is --%{mymacro}--"

That helped a lot, because I discovered that if the macro is defined the expansion is

the value of mymacro is --100--

but when the macro is undefined, this is what happens:

the value of mymacro is --%{mymacro}--

that is, it is left untouched (rather than removed). So this is the reason why adding double quotes to the test seems to work: when the macro is undefined, the test executed is

"%{mymacro}" == "100"

(double quotes included) which is false. However, should one one day need to test against the literal string %{mymacro}, the test would actually succeed when the macro is undefined! Let's confirm this:

# spec file
%if "%{mymacro}" == "%%{mymacro}"
echo "equality test succeeded"
%endif

%% is used to put a literal percent sign.

If we invoke the spec file without defining %{mymacro}, the test succeeds:

$ rpmbuild -ba foo.spec
...
+ echo 'mymacro is --%{mymacro}--'
mymacro is --%{mymacro}--
+ echo 'equality test succeeded'
equality test succeeded
...

Granted, this is not very likely to happen in practice, but it still looks somewhat not clean.

What's needed, then, is a real way to check whether a macro is defined or not. We can't rely on the equality test we used above to determine that either, because the macro may have been defined, and have the literal value %{mymacro} assigned to it. Again this is very unlikely, but it's always better to make things in a clean way when possible.

The solution: conditional expansion

(I don't know if that is the right definition for this feature). There's a useful tool that the macro language used in the spec files has, and it's the %{?mymacro:value1} and %{!?mymacro:value2} syntax. The result of the expansion of those macros is value1 if %{mymacro} is defined, and value2 if %{mymacro} is undefined, respectively. If the condition they check is false, they expand to nothing.

Since the macro processor is recursive, this allows for the conditional definition of macros, for example:

%{?onemacro:%define anothermacro 100}

That defines the macro %{anothermacro} only if %{onemacro} is defined. But let's focus on the task at hand: determining whether a macro is defined (and after that, possibly do further tests on its value).

If we only need to check whether a macro is defined or not, these idioms seem to work:

%if %{?mymacro:1}%{!?mymacro:0}
# ... %{mymacro} is defined
%else
# ... %{mymacro} is not defined
%endif

The above expands either to 1 or 0 depending on whether %{mymacro} is defined or not.
This also seems to work:

%if 0%{?mymacro:1}
# ... %{mymacro} is defined
%else
# ... %{mymacro} is not defined
%endif

That expands to either 01 or 0, again corresponding to true or false for the %if.

If instead, we need to check that a macro has a specific value, this should work:

%if "%{?mymacro:%{mymacro}}%{!?mymacro:0}" == "somevalue"
# ... %{mymacro} is defined and has value "somevalue"
%endif

That expands to either the actual value of the macro, or 0 (in double quotes), either of which can safely be compared to "somevalue". If there can be no spaces in the values, the double quotes can be omitted. Obviously, the value returned when the macro is not defined doesn't have to be 0; it can be any value that's not somevalue.

Putting it all together, if we need to differentiate between the macro being unset or being set to a specific value, we can do this:

%if 0%{?mymacro:1}
# ... %{mymacro} is defined
%if "%{mymacro}" == "somevalue"
# ... %{mymacro} is defined and has value somevalue
%else
# ... %{mymacro} is defined, but has a value != somevalue
%endif
%else
# ... %{mymacro} is not defined
%endif

Again, if values can have no spaces, the double quotes can be omitted.

More elaborate variation of conditional expansion can be found in the official rpm documentation. For example, they use some sort of "function-like" macros as follows:

# Check if symbol is defined.
# Example usage: %if %{defined with_foo} && %{undefined with_bar} ...
%defined()      %{expand:%%{?%{1}:1}%%{!?%{1}:0}}
%undefined()    %{expand:%%{?%{1}:0}%%{!?%{1}:1}}

%{1} expands to the first "argument" passed to the macro. So when doing %{defined with_foo} what is done is actually

%{expand:%{?with_foo:1}%{!?with_foo:0}}

"expand" is like "eval", so the above effectively re-evaluates the part after expand: and eventually expands to either 1 or 0 depending on whether %{with_foo} was defined or not.

Update 27/09/2011: after some tests it looks like in conditional expansion the macro expands to its value by default if it's defined and to nothing if it's not, so the idiom

%{?mymacro:%{mymacro}}

can also be written just as

%{?mymacro}

Update 07/03/2017: thanks to Mihai for sending the following useful additions:

You're over-complicating things. The canonical way is to use something like

%if 0%{?var} <op> <val>

The only caveat is that if %{var} is not defined, this will essentially decay
into a check like:

%if 0 <op> <val>

which is problematic if your op is a logical operator.

For example:

%if 0%{?waldner} >= 23

works fine, whether %{waldner} is defined or not, but

%if 0%{?waldner} < 42

will evaluate to true if %{waldner} is either *really* less than 42, *or* undefined.

In that case, something like

%if 0%{?waldner} && 0%{?waldner} < 42

is more appropriate, as it will catch the undefined value early.

A lesser-known feature of RPM (I don't know why, but it's rarely ever used,
despite even "ancient" systems like SLE 11 or RHEL 5 supporting it) is grouping
of expressions.

With that, a statement such as

%if ( 0%{?waldner} && 0%{?waldner} < 42 ) || 0%{?ionic} >= 23

can be composed.

14 Comments

  1. Paul Jakma says:

    Is there any comprehensive documentation anywhere on the %... syntax? In particular, %{?label}, %{!?label}, and the comparisons and logical ops that are allowed with %if?

    I've maintainer a spec file for a package of mine for years, and I've never seen comprehensive documentation for this - mostly acquired features using these things via contributions and cargo-culting. I'm writing a spec file for a new package, and went to try find comprehensive reference documentation for the RPM macro language, and... it just doesn't seem to exist?

  2. Mayank says:

    This post helped me a bit, however I need not only to check for a numeric value like 100 in the example but even the possibility of the values being a character like 'x'. The value is based on external input.

    Is there a way to handle this for 'x'?

    • waldner says:

      Can't you just check for eg

      %if "%{mymacro}" == "100" || "%{mymacro}" == "x"
      ...
      %endif
      

      (I'm surely getting the syntax or the logic wrong, but you get the idea)

  3. bish says:

    'rather than', if you could.

  4. Alexey Molchanov says:

    Agree.

    P.S. waldner, thank you once again, your post saved me a lot of time.

  5. David Greaves says:

    Thanks for this post (seriously - I actually have to write spec files)

    It does however confirm that .spec files are insane and that rpm documentation sucks beyond belief :D

  6. Riccardo says:

    Wonderful post. It really provides some deep insight on the way RPM macros work.

  7. Mark McKinstry says:

    The '0' method is what Fedora recommends for dist tags: http://fedoraproject.org/wiki/Packaging:DistTag