Skip to content

Warning

This document is a work in progress. Feedback and contributions welcome!

Using and writing RPM macros

Avoid control macros

Some macro setups rely heavily on defining other macros to set options. Avoid this antipattern when possible and prefer passing arguments to macros directly.

Avoid %if in macro definitions

%if is not a macro! It is a specfile construct. Using %if statements in macro definitions can lead to bugs or other confusing behavior, as they are evaluated by the specfile parser after the macro is expanded. Avoid %if statements in macro definitions in favor of %{?:} expansions and %[ ternary expressions.

Non-parametric macros

These macros preform no type of argument processing. The macro is expanded, and the rest of the line is left the same.

Definition
# ~/.rpmmacros
%abc 123
Usage
$ rpm -E '%abc'
123
$ rpm -E '%abc other text'
123 other text
$ rpm -E '%{abc}'
123
$ rpm -E '%{abc} other text'
123 other text

Parametric macros

These macros preform getopt-style argument processing. They consume the entire line and expose the arguments to the macro.

No arguments

The definition is the same as above, but has (), enabling parametric processing

Definition
# ~/.rpmmacros
%abc() 123
Usage
$ rpm -E '%abc'
123
# The macro processing "eats" "other text"
$ rpm -E '%abc other text'
123
$ rpm -E '%{abc}'
123
$ rpm -E '%{abc} other text'
123 other text

Notice that the second example eats the rest of the line.

Positional arguments

Inside of the macro definition, the %* can be used to access these positional arguments. Let’s try that!

Definition
# ~/.rpmmacros
%abc() 123 %{?*}
Usage
$ rpm -E '%abc other text'
123 other text

Argument flags

RPM macro processing also supports getopt-style processing of flags.

Defintion
%foo(x:y:v) x is %{?-x*}.\
-y is %{?-y*}.\
-v is %{-v:defined}%{!?-v:not defined}.
Part Description Explanation
%foo Macro name and start of macro definition
(x:y:v) Parameter list x: and y: accept arguments. v does not, due to the absence of :.
%{?-x*} and %{?-y*} Macro expansion Expand the value of the processed -x / -y flag if defined. Otherwise, expand to nothing.
\ Line continuation There are other way to create multi-line macros without escapes, notably %{expand:...}, but those have side effects.
%{-v:defined} Conditional macro expansion Expands to defined if -v is passed
%{?!-v:not defined} Conditional macro expansion Expands to not defined if %{-v} (the macro containing the -v flag) is not defined.
Macro breakdown
Usage
$ rpm -E '%foo -x 1'
-x is 1.
-y is .
-v is not defined.
$ rpm -E '%foo -x1 -y2 -v'
-x is 1.
-y is 2.
-v is defined.
$ rpm -E '%foo -z'
foo: invalid option -- 'z'
error: Unknown option z in foo(x:y:v)

Do not shy away from lua

  • Prefer the new RPM 4.17+ Lua macros and opts table over manual rpm.expand(...).
# ~/.rpmmacros
%show_abc(abc:) %{lua:
  if opt.a then
    print("-a was passed\\n")
  end
  if opt.b then
    print("-b was passed\\n")
  end
  if opt.c then
    print("-c " .. opt.c .. " was passed\\n")
  end
  if macros.macro1 then
    print("macro1 = " .. macros.macro1)
  else
    print("macro1 is undefined")
  end
}

Note the \\n. One \ is needed to escape the RPM parser and the other is for the actual escape code.

You can test macros with rpm -E.

$ rpm -E '%abc -c 123'
-c 123 was passed
macro1 is undefined
$ rpm -D 'macro1 brrr' -E '%show_abc'
macro1 = brrr
$ rpm -D 'macro1 brrr' -E '%show_abc -a -b -c xyz'
-a was passed
-b was passed
-c xyz was passed
macro1 = brrr

Macro error handling

Macro Explanation
%{warn:Message} Print a warning to stderr during spec processing
%{echo:Message} Print a warning to stdout during spec processing
%{error:Message} Print a message to stderr and abort spec processing

Be careful with %{error:...}. It prevents processing a specfile and building an SRPM.

Mutually exclusive arguments

Defintion
# ~/.rpmmacros
%bar(xy) %{-x:%{-y:%{error:-x and -y are mutually exclusive}}}
%bar_lua(xy) %{lua:
if opt.x and opt.y then
    rpm.expand("%{error:-x and -y are mutually exclusive}")
end
}

Nil vs undefined

Be careful of the difference between undefined macros and macros defined to %{nil} or 0.

To check for a defined macro in an %if statement:

Specfile fragment
%global macro1 0
%global macro2 %{nil}

%if %{defined macro1}
echo "Macro 1 is defined"
%endif

%if %{defined macro2}
echo "Macro 2 is defined"
%endif
Output
Macro 1 is defined
Macro 2 is defined

To check for a defined and non-zero macro in an %if statement

Specfile fragment
%global macro1 0
%global macro2 %{nil}
%global macro3 Hello, world

%if 0%{?macro1}
echo "Macro 1 is defined and neither 0 nor nil"
%endif

%if 0%{?macro2}
echo "Macro 2 is defined and neither 0 nor nil"
%endif

# Quotes have a special meaning in the %%if context.
# Use the "" syntax if macro3 may be a string, as 0%{?MACRO_NAME} will result
# in an error if MACRO_NAME is defined to a nonintegral value.
# If macro3 is 0, this will evaluate to true here, unlike the macro1 example above,
# as enclosing the expansion in "" checks whether or not it evaluates to an empty string.
%if "%{?macro3}"
echo "Macro 3 is defined and non-nil"
%endif
Output
Macro 3 is defined and non-nil

To check for an undefined macro in an %if statement:

Specfile fragment
%if %{undefined macro1}
echo "Macro 1 is undefined"
%endif
Output
Macro 1 is undefined

To check for a defined macro in an expansion:

Specfile fragment
%global macro1 Hello

%{?macro1:echo "Macro 1 is defined"}
%{?macro2:echo "Macro 2 is defined"}
Output
Macro 1 is defined

Development workflow

  • I much prefer maintaining macros in a separate upstream repository with proper versioning and CI instead of directly in distgit.
  • Proper testing is very important. One small change can break the entire buildroot. See forge-srpm-macros for an example of pytest-based unit testing for RPM macros.

Resources