openSUSE:RPM conditional builds

跳转至: 导航, 搜索
此页面普及 RPM 条件依赖的知识,并示范如何在 openSUSE 打包中使用它们。
注意: 几乎所有内容都基于经验法则。因为这部分文档不全。所以请谨慎使用!


什么是条件依赖?为什么我们需要条件依赖?

软件包可以通过向 rpmbuild(RPM 打包的核心命令,你使用的 osc build 命令本身是一个宏,背后指向的真实命令就是它) 命令传送 --with(out)-"条件" 额外参数的方式来提供一些第三方/商业软件/特殊功能/非常用功能的支持。

这些支持体现在 configure 里就是我们常见的 configure --with-gui,体现在 cmake 里就是我们常见的 -DENABLE_GTK3_MODULE。而 --with-gui 这样的参数可以通过硬编码,也可以通过定义一些布尔宏(bool macro)来实现。

这些自定义的布尔宏,就是条件依赖。条件依赖主要涉及到在报头里对条件的定义,编译依赖、运行依赖、提供(Provides)、老旧(Obsoletes)的修改,%packages (-n) 子软件包和 %files 文件章节的相应修改。

条件依赖 VS. 硬编码

布尔宏的好处就是你可以在有效代码(不包括注释)里控制它的开启和关闭;硬编码则只能通过添加 # 来转换有效代码和注释状态来开关。使用布尔宏你只需修改一个数字 0/1 就可以实现控制,甚至像开头说的,传送额外参数而不必实际修改 spec 文件; 硬编码则必须实际修改 spec 文件,而且这样的修改可能涉及到很多的地方,也容易出错。

安装的方便性

如果你在 %build 章节不使用条件依赖,而是硬编码 --with-gui,那么你就需要在编译依赖(BuildRequires)里写上 BuildRequires: gtk2-devel。最终的二进制 RPM 也是强制带图形页面的,无法单独的再去掉图形页面,达不到「可选安装」的目的。除非你使用一些技巧来绕过它,比如使用子软件包 gui,并在其他子软件包中不去「运行依赖 Requires」它。这样做不好的原因,一是虽然你把选择的权利交给了用户,但最终用户很可能不知道去勾选它,比如:

  • 命名方式的不同

用户想要安装 pidgin,同时也需要命令行支持,他如果不知道 pidgin 的命令行程序叫做 finch 的话,那打死也找不到命令行指令;再比如他是使用 sudo zypper in pidgin 来安装的,自动依赖没去选 finch,那他也用不上。

  • 孤立包

如果你有使用一些像 rpmorphan 这样的孤立包检测程序的话,很可能把这个包误删掉。

而使用条件依赖,即使你在打包编译时设定了条件为开启,在用户实际安装时如果条件不满足,比如找不到 libgtk2, RPM 包管理器只取消安装图形页面相关的子软件包,而不会拒绝安装主软件包,即使你的主软件包明确要求条件所在的子软件包作为运行依赖而不是“推荐”它。

绕过构建服务黑名单的限制

有时如果你不使用条件依赖而是硬编码,开放构建服务就无法接受你的软件。比如 clementine 橘子播放器

它是通过闭源免费函数库 libspotify 来提供 Spotify 在线音乐播放的,但开放构建服务的开源策略无法接受这个库,你是无法硬编码开启它的(会报「找不到依赖」错误使编译无法进行)。

这时使用条件依赖达到「可选编译」的目的就会极大的方便想要全功能的用户。他们不用在辛苦的本地创建一个 libspotify16 包,使用 rpm -ivh 装上它和 -devel 包,再去修改你的 spec 文件,添加 BuildRequires: libspotify-devel 和相应的文件章节内容来编译出全功能的包。

打包者在报头添加条件依赖去寻找文件依赖 %{_libdir}/libspotify.pc,然后在 %files 章节同样使用条件依赖提供额外的文件名,用户就可以简单的使用 configure && make && sudo make install 的方法安装 libspotify,并使用 sudo rpmbuild --rebuild --with-spotify 命令,无需了解 spec 文件的写法或知道 source.tar.bz2 和补丁要放到 SOURCES 而 spec 文件要放到 SPECS,在 SPECS 文件夹下才能运行 sudo rpmbuild -ba clementine.spec,直接获得最终生成的二进制 RPM,方便了传统安装方式与 RPM 之间的互通有无,节省了最终用户的时间和精力。

发行版版本差异

有时由于发行版的版本差异,会造成不再需要依赖但有也不碍事的问题。一般情况下我们是使用:

   %if 0%{?suse_version} <= 1220 && 0%{?suse_version} > 1130
   # 注意这不是宏嵌套,而是并列
   BuildRequires: gtk2-devel
   %endif

来解决,但是这样做的不好在于,上面那种做法是针对不同版本的包名差异的兼容方法,而不是针对一些可有可无的包的兼容方法。比如:

你在 openSUSE 12.1 里 Requires: libktorrent3 是肯定不行的,因为源里是 libktorrent4;但是比如 digikam 的人脸识别 libkface 支持,libkface 系列包从来没有重命名过,只是在 openSUSE 11.4 以下由于项目伊始,很不稳定,所以去除了,但是你装上可不可以呢,也死不了人。所以定义条件依赖 with_libkface 并在 11.4 及以下版本默认设置为 0,YaST 提供的是没有人脸识别的,但你想要,rpmbuild --rebuild --with_libkface就可以了;

再比如 pidgin 的 mono 支持,11.4 以上是不需要的,因为 mono 的插件几乎没有,装了它又让主程序不稳定,还多装了不少依赖,所以去除了,但由于在 11.3 及以下已经给过这个支持,不方便要回来,就定义了一个 with_mono 宏并对 11.4 以下设置为开启,如果你非要在 11.4 以上用 mono 写 .net 插件,rebuild 一下就可以了。

使用条件依赖

首先你需要在 spec 文件的有效代码头部添加条件的定义行:

   %bcond_with video 1 
   # 条件,这里是 video; 设为默认开启,也可以用 0 表示默认关闭,什么都没有默认是 0

然后你要在报头的编译依赖部分添加:

   %if %{with video}
   BuildRequires: v4l2-devel
   %endif

你或许需要切分出一个新的子软件包:

   %if %{with video}
   %package video 或者 %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

然后在 %build 编译章节使用:

   %configure \
          %{?_with_video} \
          --with-gui # 硬编码的原因是你确定这软件没个图形界面简直没法用

或者

   cmake -DCMAKE_INSTALL_PREFIX=%{_prefix} \
   %if 0%{with video}
         -DENABLE_VIDEO \
   %endif
     ..

来配置它。

在 %files 文件章节使用:

   %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 
   (也可以把使用 %{_datadir}/%{name}/foo-video/ 来替换上面两行)
   %dir %{_sysconfdir}/%{name}/
   %config %{_sysconfdir}/%{name}/%{name}-video.conf
   (这里注意子包和主包必须同时声明拥有 %{_sysconfdir}/%{name} 文件夹)
   %endif
   ..

或者

   %if 0%{with video}
   %files -n libfoo-video
   %defattr(-,root,root)
   ..
   %endif

至此,你就能拥有条件依赖的全部好处了。

你不需要在 %make/%make_install 像传送 %{optflags} 一样对它们传送多余的参数,使用默认选项即可。如果依赖满足(即你设置它默认状态是开启),参数会自动传送并带参数编译。生成的二进制 RPM 包的内容是根据默认状态来的,而源代码 RPM 包则支持 --with(out) 条件参数。也就是说条件依赖是管配置而不是管编译的,编译只能带确定的条件而不能自己判断。

另外注意一点,条件依赖不是你定义了 %bcond_with 就自动可用 %bcond_without 的,也即你定义了 --with,那么想使用 --without 正确的方法是什么都不加,因为定义了 --with,那么默认状态就是 without。

条件依赖的变种

条件依赖存在很多的变种,比如 pidgin 中使用的自定义函数 %define 制作的条件依赖

   %define with_mono 1
   %if %with_mono
   %endif

该变种是使用 RPM 通用的定义宏方法来实现的: %define something 0/1。只不过这里定义的 something 是一个布尔值,为 0 或 1。之后的用法为 %if something %endif。

还有一种变种就是用 %with_something 0 来定义,然后使用:

   %if %{with_something} do something eg: --with-video %endif

来使用。

以及 gegl 中不声明直接用的条件依赖:

   %if 0%{?BUILD_ORIG}
   %if 0%{?BUILD_ORIG_ADDON}

该变种使用的是定义与使用混合在一起的方法:0 表示默认值,?表示判断,意思是如果满足 BUILD_ORIG 条件,则设置该 if 括起来的内容的编译状态为 1,也就是编译它们,如果不满足,则使用默认值 0,即不编译。

这里我们不鼓励在能被最终用户操作到的包中使用上面三种方式(geglgimp 的依赖库,最终用户不会重编译那个,即使需要,那么那个用户在某种意义上也不是我们定义的最终用户了),因为那样会给最终用户的重编译带来麻烦,必须使用:

   sudo rpmbuild -D 'BUILD_ORIG 1'

才行。一般使用该变种都是判断编译系统而不是用户选项的。

甚至有心的打包者会发现,%{?suse_version}/%{?fedora_version} 这些都是条件依赖,只不过被系统预定义好了检测代码比较复杂且捆绑在开放构建服务中了而已。