openSUSE:RPM conditional builds
Build Service Tutorial · Tips & Tricks · Cross Distribution Howto · Packaging checks
Desktop menu categories · RPM Macros · Scriptlets · Init scripts · How to write good changes
What are conditionals? Why do we need them?
Packages can provide supports for third party/commercial/special/uncommon features by passing boolean conditionals into rpmbuild
, which look like --with(out) "conditions"
. rpmbuild is the core command of RPM packaging, the osc build
you use is actually a macro pointing to it.
To the autotools build system, conditionals is the common configure --with-gui
in configure we have seen here or there. To CMake it's -DENABLE_GTK3_MODULE
stuff. Such --with-gui
parameters can be implemented by hardcoding or by defining bool macros.
The boolean we defined, is conditionals. To use conditionals, you need to define in the preamble, modifying BuildRequires/Requires/Provides/Obsoletes, %package (-n) sub-packages and %files file section.
Conditionals vs. hardcoding
The advantage of using conditionals is that you can control their on/off status in valid spec codes while when hardcoding values, you need to add/remove comment markers in the specfile (#) to disable/enable them. bool macros enable you to control the whole specfile by modifying only one number which is 0 or 1. You can even leave the specfile, just pass parameters to control. But hardcoding needs to change many places in a specfile every time, so it increases the chance of build failure.
Ease of package installation
If you don't use conditionals in the %build section, but use hard-coded --with-gui
, then you need to add BuildRequires: gtk2-devel in the preamble. Thus the output RPM has GUI support by default which can't be removed separately unless you use some tricks to avoid it, like introduce a sub-package *-gui, and don't "Requires" it in other sub-packages and the main package. The bad part of this trick is that even if you transfer the choice to end users, they may of a great chance aren't aware to select it. e.g.:
- Different Naming schemas
If a user wants to install pidgin and needs CLI support, and if they don't know that the CLI version of pidgin is named finch, they won't easily find the CLI program. And if they run sudo zypper in pidgin
, the automatic dependency resolver won't select finch.
- Orphan package
if the user uses some automatic orphan package detector and cleaner like rpmorphan or YaST (but possibly with different results,), she may delete it by chance.
But if you use conditional builds, even if you setup the conditionals to be true, when the user installs the package while the prerequisites can't be met like libgtk2 not found, the RPM package manager just cancels installation of GUI-related sub-packages and installs the main package, unless you explicitly declares "Requires" of *-gui sub-package instead of "Recommends" it.
Wise way to avoid Build Service blacklist
Sometimes you have to use conditionals instead of hard coding, or our Build Service can't host your application for sure. There is plenty of instances across media players, e.g.: clementine.
Clementine provides Spotify online music streaming (for US paid customers only) through a commercial but free to use library called libspotify. But OBS can't host patent everything. So you can't hard-code it (It'll fails and complains "unresolvable" of libspotify).
Under such circumstances, conditionals can finish the "optional builds" task and please users who want a full version to the most extent. They don't need to build a libspotify16 package locally, rpm -ivh
it and its -devel package, then add BuildRequires: libspotify-devel to your specfile's preamble and %files section to compile the full version.
As a packager, you can add conditionals and file BuildRequires: %{_libdir}/libspotify.pc to your preamble, then optionally provides files in %files section. Users then can easily use configure && make && sudo make install
method to install libspotify, then sudo rpmbuild --rebuild --with libspotify
to install the full version without need knowing the rules of specfiles, to place source.tar.bz2 and patches into SOURCES and specfile into SPECS, then she can run sudo rpmbuild -ba clementine.spec
. She can just get the RPM output this time without knowing the packaging magics.
In this way we connect traditional installations with RPM, so save the time and energy for the end newbie users.
Cross Distribution
Sometimes because of differences across distributions, some dependencies may come to a status that "don't need but don't harm with it". Usually we use:
%if 0%{?suse_version} <= 1220 && 0%{?suse_version} > 1130 # Notice: it's not thread macros but parallel macros. macro can not thread. BuildRequires: gtk2-devel %endif
to solve that problem. But the shortcoming is, that method targets at compatibility of naming difference across distribution, it's not designed to deal with the "maybe" package; e.g.:
You certainly can't Requires: libktorrent3 in openSUSE 12.1, because it's libktorrent4; but another instance is the face recognition of digikam using libkface. libkface was never renamed, but below openSUSE 11.4, it's unstable, so we remove it. But if you install it, it doesn't harm too much. So we can define a conditional with_libkface and set it 0 below 11.4. YaST by default doesn't ship this feature, but if you want it, rpmbuild --rebuild --with libkface
may help you;
A third instance is the mono support in pidgin. We don't need that above 11.4, because there are few mono plugins, and its support will make main program unstable, and it requires many dependencies, so we planned to removed it. But as we just added support in 11.3, it was too soon to take the support away from users again, so we defined a with_mono conditional and turned it on for 11.4 and below. To use mono for writing .net plugins for Pidgin on openSUSE above 11.4, a rebuild will suffice.
Using conditionals
Firstly you need to add %define line in the preamble of your specfile:
%bcond_with video # Condition here is video; it's default off and needs to be activated with # --with video command line switch. That is to be set to 1, by default is undefined:
Here macro %bcond_with() initializes global with_video
to 1, if rpm-building --with video
(or if _with_video
was already defined in specfile, of course).
# To get the default enabled variant use %bcond_without instead of %bcond_with.
And add these in BuildRequires section:
%if %{with video} BuildRequires: v4l2-devel %endif
Here, if with_video
exists, %with() expands to 1, 0 otherwise.
As you may want to split a new sub-package:
%if %{with video} %package video or %package -n libfoo-video Summary: video plugin for package foo Group: System/GUI/KDE Provides: foo-visual = %{version} Obsoletes: foo-visual < %{version}
%description video blabla %endif
then in the %build section:
%configure \ %{?_with_video} \ --with-gui # Here we hard coded because we are sure this is not usable without gui
or:
cmake -DCMAKE_INSTALL_PREFIX=%{_prefix} \ %if 0%{with video} -DENABLE_VIDEO \ %endif ..
to configure it.
In the %files section, use:
%files -n %{name}.lang .. %if 0%{with video} %{_libdir}/%{name}/libfoo-video.so.1.2.3 %dir %{_datadir}/%{name}/foo-video/ %{_datadir}/%{name}/foo-video/example (Can use %{_datadir}/%{name}/foo-video/ to replace those two lines above) %dir %{_sysconfdir}/%{name}/ %config %{_sysconfdir}/%{name}/%{name}-video.conf (Notice: sub and main package both need to declare to own %{_sysconfdir}/%{name} directory) %endif ..
Or:
%if 0%{with video} %files -n libfoo-video .. %endif
Voila! you have conditional builds setup now.
You don't need to pass any parameters into %make or %make_install macros like passing %{optflags}. Default is helpful. If the dependencies are met(you set it on), parameters will be automatically passed and built. The content of the output RPM is based on default configuration. Yet source RPM (.srpm) supports --with(out) parameters. That is, conditionals are used to govern configuration instead of build, the build can only use "sure statement" instead of judging by itself.
An other important tip is that you have to take care, conditionals are not like "if I defined %bcond_with, then %bcond_without is also available". Actually they're two totally different conditionals. That is, if you defined --with, and now you want to use --without, the right way to do is do nothing, because if you define --with, the default will leave as without. Since, by omitting --with foo
in command, because of %bcond_with()
behavior, %{with foo}
will expand to 0 (unless you manually defined with_foo
to some value, of course).
Conditional Variants
There are some variants of conditionals commonly used by our packagers too, like the conditionals created from %define in pidgin:
%define with_mono 1 %if %with_mono %endif
This variant is implemented by the RPM universal way to define macros:
%define something 0/1.
But here something can be only a boolean, 0 or 1. The afterward usage is %if something.
A second variant is defined by %with_something 0
, then use:
%if %{with_something} do something eg: --with-video %endif
to use.
As well as the conditionals without declaration/definition in gegl:
%if 0%{?BUILD_ORIG} %if 0%{?BUILD_ORIG_ADDON} ... %endif %endif
This variant use a way that combines definition and usage: 0 means default boolean value, ? means "to judge". So the whole statement means if BUILD_ORIG condition is met, then set the build status of the if quoted content to 1, which is to build them; if it's not met, then use default, which is 0, not to build.
But we do not encourage using those three variants in the packages that may be open to end users (gegl is the depend library of gimp, end users will not compile it, if they do, then they're not end users any more, because it's very hard to tweak), because it'll cause troubles to them. They have to use:
to rebuild. Usually such variants are used to judge the build environments instead of user options.
Even, as you may have found, macros like %{?suse_version}/%{?fedora_version} are all conditionals, but predefined conditionals by openSUSE, and they're more complex and bundled in our Open Build Service.
Combining Conditional Expressions
It is often necessary to combine several conditional expressions. Conditionals can be combined with the && 'and' and || 'or' operators. The have a left to right associativity. && has precedence over ||. Expressions can be grouped by parentheses. ! is the negation operator, it has right to left associativity and the highest precedence.