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.
$ 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
$ 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!
Argument flags¶
RPM macro processing also supports getopt-style processing of flags.
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.
|
$ 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
andopts
table over manualrpm.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¶
# ~/.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:
%global macro1 0
%global macro2 %{nil}
%if %{defined macro1}
echo "Macro 1 is defined"
%endif
%if %{defined macro2}
echo "Macro 2 is defined"
%endif
To check for a defined and non-zero macro in an %if
statement
%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
To check for an undefined macro in an %if
statement:
To check for a defined macro in an expansion:
%global macro1 Hello
%{?macro1:echo "Macro 1 is defined"}
%{?macro2:echo "Macro 2 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.