2

I have a spec file that uses a conditional like

%if "%{pkgname}" = "wobble"
  Requires: extra-thing
  ....
%endif

and now need to treat wobble-thing, wobble-otherthing and any other wobble* as satisfying the same conditon. Easy, you'd think.

I can't find any way to do it without giving up on the awfulness that is spec files and preprocessing the file.

Unfortunately preprocessing the file won't fly in this context, it'll upturn a whole build chain that expects to be able to just rpmbuild the spec.

There's lots of undocumented magic in rpm and rpmbuild, like the ${error:blah} and %{expand:..othermacros...} and %global stuff. And even simple relational operations like %if %{value} < 42 don't seem to actually be documented anywhere.

Does anyone know of a string-prefix or string-infix pattern matching operator?

I'm looking for the spec equivalent of bash's if [[ "${VAR}" == *"substring"* ]]; then construct.

Edit: To be clear, the reason I'm not just using shell conditionals is that I need to affect rpm metadata. I thought it was obvious that I'd use shell if if that was an option. I've edited to show more clearly above.

Edit: To help other people find this, this is about string pattern matching in rpm. Complex conditionals in rpm. Conditional sections in spec files. String prefix, infix or suffix operators and tests in rpmbuild.

Craig Ringer
  • 307,061
  • 76
  • 688
  • 778
  • As a starter, I've found [the builtin macros list in `rpmio/macro.c`](https://github.com/rpm-software-management/rpm/blob/ff4b9111aeba01dd025dd133ce617fb80f7398a0/rpmio/macro.c#L452) and the [related docs](https://github.com/rpm-software-management/rpm/blob/ff4b9111aeba01dd025dd133ce617fb80f7398a0/doc/manual/macros#L65). Based on this, my reading is "you can't". – Craig Ringer Sep 21 '18 at 13:44
  • In `lib/rpmds.c`, the "RichOps" list contains `"if"` etc and doesn't look helpful. The `ReqComparisons` list only has the usual equality and ordinal operators like `>=`. But maybe there's hope in the minimally documented `%{lua:...}` interpreter; see http://rpm.org/user_doc/lua.html – Craig Ringer Sep 21 '18 at 13:49

4 Answers4

3

You cannot use regexp or wildcards. But you can use "or".

%if "%{pkgname}" == "wobble" || "%{pkgname}" == "wobble-thing"
..
%endif

or you can do the evaluation in the shell

%global yourmacro   %(/usr/bin/perl ...%{pkgname}... )

where /usr/bin/perl ... can be any script and yourmacro is set to value of stdout of this script.

msuchy
  • 5,162
  • 1
  • 14
  • 26
2

Actually, you can do it without needing Lua, and you can use exactly the if [[ ... ]] construct in BASH that you mentioned wanting to use in your original question.

To illustrate how, let's look at the header section of the specfile for the tmux RPM I use. It must build unmodified on any host running RHEL/CentOS versions 6, 7, and 8 (and/or any other rebuilds, like Oracle's). This is what I have:

%global name tmux
%global version 3.1b
%global release 1%{?dist}

%global _hardened_build 1

Summary:        A terminal multiplexer
Name:           %{name}
Version:        %{version}
Release:        %{release}
# Mostly ISC-licensed, but some files in compat/ are 2-/3-clause BSD.
License:        ISC/BSD
URL:            https://tmux.github.io/
Source0:        https://github.com/tmux/%{name}/releases/download/%{version}/%{name}-%{version}.tar.gz
Source1:        bash_completion_tmux.sh
BuildRequires:  %(/bin/bash -fc 'if [[ %{name} == tmux* ]]; then echo make ; else echo nomake ; fi') %(/bin/bash -fc 'if [[ %{name} == wobble* ]]; then echo wobble ; fi')
BuildRequires:  gcc, ncurses-devel, %{expand:%(/bin/bash -c 'if [[ %{?rhel}%{!?rhel:9} -le 6 ]]; then echo libevent2-devel ; else echo libevent-devel ; fi')}
Requires:       %{expand:%(/bin/bash -c 'if [[ %{?rhel}%{!?rhel:9} -le 6 ]]; then echo libevent2 ; else echo libevent ; fi')} >= 2.0

The tricky part with this RPM is that RHEL6 supplies the libevent package, but it's version 1.4.13, which is too old for tmux to build against successfully. (It requires libevent 2.x.) The good news, though, is that RHEL6 actually has both versions! The libevent2 RPM has version 2.0.21, which is recent enough to work for tmux. But I can't just list libevent2-devel as my build dependency; that package is not available for RHEL 7 or 8 because their default libevent RPM is version 2 already. Nor can I list libevent-devel as the build dependency because, while the libevent and libevent2 packages can be installed side-by-side, the libevent-devel and libevent2-devel RPMs conflict.

As you can see in my specfile above, my solution to this challenge is to use RPM macros to inject either the string libevent-devel or libevent2-devel into the BuildRequires: header value based on the BASH-based comparison of the value of the %{rhel} macro (to which /etc/rpm/macros.dist assigns the value of 6, 7, or 8 on RHEL 6, 7, or 8, respectively). For RHEL6 (and prior, theoretically...RHEL5 is dead, and RHEL4 and earlier are super-duper-dead!), the latter value is used whereas the former value is used for RHEL7 and RHEL8.

I also added a couple contrived string-matching macro invocations just to illustrate that it works correctly. My actual specfile contains neither the first BuildRequires: line nor the final Requires: line (because it's unnecessary/redundant); I added them specifically for this exercise to demonstrate that the conditionals work for both build-time and run-time dependencies.

How do I know they worked? I checked:

$ rpm -qp --qf '[%|SOURCERPM?{%25{=NEVRA}}:{%21{=NEVR}.src}|.rpm:  %{REQUIREFLAGS:deptype}: %{REQUIRENEVRS}\n]' tmux-3.1b-1.el6.x86_64.rpm tmux-3.1b-1.el6.src.rpm tmux-3.1b-1.el8.x86_64.rpm tmux-3.1b-1.el8.src.rpm | fgrep 'manual:'
   tmux-3.1b-1.el6.x86_64.rpm:  manual: libevent2 >= 2.0
      tmux-3.1b-1.el6.src.rpm:  manual: make
      tmux-3.1b-1.el6.src.rpm:  manual: gcc
      tmux-3.1b-1.el6.src.rpm:  manual: ncurses-devel
      tmux-3.1b-1.el6.src.rpm:  manual: libevent2-devel
   tmux-3.1b-1.el8.x86_64.rpm:  manual: libevent >= 2.0
      tmux-3.1b-1.el8.src.rpm:  manual: gcc
      tmux-3.1b-1.el8.src.rpm:  manual: libevent-devel
      tmux-3.1b-1.el8.src.rpm:  manual: make
      tmux-3.1b-1.el8.src.rpm:  manual: ncurses-devel

Note that the correct libevent2 >= 2.0 dependency (with the 2 on the end) shows up in the RHEL6 RPM; the RHEL8 RPM, on the other hand, shows libevent >= 2.0 (without the 2 on the end), again exactly as it should. Additionally, a build dependency on make shows up in both SRPMs, and both nomake and wobble are absent, proving that the pattern match conditionals on tmux* and wobble* worked exactly as they should.

1

You can indeed use Lua scripting, though it requires some odd incantations. Here's how to add a starts_with function-like macro to your rpm spec file that you can use in %if conditions.

# Define the Lua starts_with function we want to expose
%{lua:
  function starts_with(str, start)
   return str:sub(1, #start) == start
  end
}

# Define the rpm parametric macro starts_with(str,prefix) that
# calls Lua and maps "false"=>"0" and "true"=>"1"
#
# Note that we need to inject the parameters %1 and %2 to a
# string-quoted version of the Lua macro, then expand the whole
# thing.
#
%define starts_with(str,prefix) (%{expand:%%{lua:print(starts_with(%1, %2) and "1" or "0")}})

# Finally we can use the parametric macro
#
%if %{starts_with "wobble-boo" "wobble"}
  Requires: wobble
%endif

What happens here is that:

%{starts_with "wobble-boo", "wobble}

expands to

%{expand:%%{lua:print(starts_with("wobble-boo", "wobble") and "1" or "0")}}

which expands to

%{lua:print(starts_with("wobble-boo", "wobble") and "1" or "0")}

which executes the Lua function starts_with, which tests if the left-anchore substring of "str" that's the same length as "start" is equal to "start". If that's true it returns "1"; if it's false it returns "0". That's because rpm doesn't recognise false as false.

So what we're doing here is calling a Lua function from a parametric rpm macro and adapting the return value.

Nifty. Painful that rpm needs this kind of hack for a simple task like this, and that it's almost totally undocumented. But nifty.

Craig Ringer
  • 307,061
  • 76
  • 688
  • 778
0

Here is a way to do it in the spec using %define.

%global original_string  SomeLongStringHere
%global matching_string  LongString
%global nomatch_string   NoMatchHere

%define matchTwoStrings(n:) \
string_one=%1 \
string_two=%2 \
if [[ "${string_one}" = *"${string_two}"* ]]; then \
   echo "It matches" \
else \
     echo "It doesn't match" \
fi \
%{nil}

# Then somewhere later in the .spec, e.g., I used the %post section

%post
%matchTwoStrings "%{original_string}" "%{matching_string}"
%matchTwoStrings "%{original_string}" "%{nomatch_string}"

Because this is in the %post section, it will print out during the .rpm installation.

It matches
It doesn't match
iamauser
  • 11,119
  • 5
  • 34
  • 52
  • This will not work for `Requires` etc. My question was insufficiently clear in that regard; it needs to work for rpm metadata. – Craig Ringer Sep 22 '18 at 00:39