openSUSE:RPM conditional builds
tagline: From openSUSE
Build Service Tutorial · Tips & Tricks · Cross Distribution Howto · Packaging checks
Desktop menu categories · RPM Macros · Scriptlets · Init scripts · How to write good changes
Contents |
What is Conditionals? why 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 buildsystem, 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 hard coding or by defining some 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. Hard coding
The goodness of using conditionals is that you can control its on/off status in valid spec codes while hard coding can only be switched by adding # to control its comment or valid code status. bool macros enables 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 hard coding needs to change many places in a specfile every time, so it's a common lead to build failure.
Ease of package installation
If you don't use conditionals in %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 while need CLI support, and if she doesn't know the CLI program of pidgin is named finch, she can't find the CLI program forever. And if she sudo zypper in pidgin, while the automatic dependency resolver doesn't select finch, she may does not even aware of it.
- 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 so much dependencies, so we planned to removed it. But as we just gave support in 11.3, it's too hurry to take something back from users, so we defined a with_mono conditional and set on for 11.4 and below. But if you want to use mono in writing .net plugin for pidgin on openSUSE above 11.4, just rebuild will be enough.
Use conditionals
Firstly you need to add %define line in the preamble of your specfile:
%bcond_with video 1 # Condition here is video; it's set to be on, you can set it off by 0. # if you just leave it blank, it'll be 0
And add these in BuildRequires section:
%if %{with video}
BuildRequires: v4l2-devel
%endif
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
%defattr(-,root,root)
..
%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
%defattr(-,root,root)
..
%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.
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 to use 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.
