目前感觉官网的rpm 方面的文档都不好, 比如spec文件很多东西没有细讲.
这里转载一篇外国小哥的文章, 感觉写的挺不错的.
原文链接: https://backreference.org/2011/09/17/some-tips-on-rpm-conditional-macros/
Some tips on RPM conditional macros
Posted by waldner on 17 September 2011, 3:48 pm
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.
Filed under linux, tips, worksforme Tagged macro, rpm, spec file
Comments are closed | Permalink
14 Comments
Paul Jakma says:
October 9, 2017 at 09:14
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?
waldner says:
October 9, 2017 at 10:50
I’d be interested too, the best I could find was the “spec file syntax” chapter of the Fedora RPM guide (https://docs-old.fedoraproject.org/en-US/Fedora_Draft_Documentation/0.1/html/RPM_Guide/ch-specfile-syntax.html), and in particular the short section about conditional macros (https://docs-old.fedoraproject.org/en-US/Fedora_Draft_Documentation/0.1/html/RPM_Guide/ch22s02s02.html).
Mayank says:
March 14, 2016 at 15:35
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: March 14, 2016 at 15:45 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)
bish says:
December 20, 2015 at 22:38
‘rather than’, if you could.waldner says:
December 20, 2015 at 22:54
Fixed, thanks.
Alexey Molchanov says:
October 16, 2012 at 13:51
Agree.
P.S. waldner, thank you once again, your post saved me a lot of time.
David Greaves says:
January 8, 2012 at 20:55
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 😄waldner says:
January 9, 2012 at 10:49
Agreed.
Adam Spiers says:
January 20, 2012 at 14:19
I agree with David too! Thanks a LOT, this was really useful.
Jim R says:
January 23, 2013 at 19:11
Agreed
spec files: insane
RPM documentation: major suckage
Riccardo says:
December 20, 2011 at 15:19
Wonderful post. It really provides some deep insight on the way RPM macros work.
Mark McKinstry says:
September 20, 2011 at 02:26
The ‘0’ method is what Fedora recommends for dist tags: http://fedoraproject.org/wiki/Packaging:DistTagbish says:
December 20, 2015 at 22:37
The dist tag is a horrible cop-out. I’m surprised they didn’t try a little harder.
参考
https://backreference.org/2011/09/17/some-tips-on-rpm-conditional-macros/