Home Wiki > 通用打包指南
Sign up | Login

通用打包指南

tagline: 来自openSUSE

打包指南规范了为 openSUSE 打包所需要注意的各种实际问题。它包括一般规则,一个软件包法律方面的规则,关于 spec 规范配置文件特性和特殊种类的软件包的规则。
  • 复核者有责任指出一个软件包存在的特殊问题,而打包者对应地有责任去更正这些问题。复核者和打包者一起去决定问题的严重性 —— 是否需要冻结一个软件包,或者先允许软件包进入开发源再修复问题。
  • 打包指南是一些常见问题的集合,所以请郑重其事的对待它们。但即使这些指南不能被无视,它们也不应该被盲目遵守。如果你认为你的软件包应该从指南的某些部分豁免,请把争议提交到 openSUSE打包 邮件列表。
  • 还需请您注意的是,在开放式编译服务里,许多规则是强制性施用的、或者是警告性的,在成功编译后,由一个叫做 rpmlint 的工具进行。打包者一定要检查它的输出以便于排查常规打包错误和得到如何优化打包的提示。可以从 打包检查 查询到大部分 rpmlint 警告的解释。那个页面也包括关于哪些貌似正确的错误会导致打包被中断的指南。

通行指南

有些必须被遵守的规则:

不要内嵌预编译好的二进制文件或库文件

openSUSE 软件包中包含的所有二进制文件或库文件都必须是从源代码包中被编译的。这个要求出于以下理由:

  • 安全性:不是从源代码中编译的预编译二进制文件和库文件可能包含任何内容、病毒或危险的内容,或者是损坏的内容。而且功能上说他们不可能被补缀和修正。
  • 编译器参数:不是从源代码中现编译的预编译二进制文件和库文件可能不是使用出于安全性和优化性能考虑的标准 openSUSE 编译器参数编译的。

如果你不能确定一个文件是不是二进制文件或者库文件,这里有些有用的评判标准:

  • 它可执行吗? 如果是,那它可能是一个二进制包。
  • 它包含 .so, .so.#, .so.#.# 或者 .so.#.#.# 后缀吗? 如果是,那它可能是一个库文件。
  • 如果仍存疑问,请咨询 openSUSE打包 邮件列表。

那些要求非开源部分才能编译的包也是不允许的(例如,需要私有版权的编译器)。

豁免情况

  • 有些软件包(通常是和编译器或交叉编译环境相关的)如果不用那些预编译的工具集或开发环境(它们必须也是开源的)就不能被编译。如果你遇到了满足这一条件的软件包,请联络 openSUSE 打包委员会来获准。
  • 满足下面文件要求的二进制固件是豁免的二进制固件

多个项目的捆绑包

openSUSE中的软件包应该竭力避免多个、分离的、上游的项目被捆绑到一个软件包中。

Spec 规范配置文件指南

关于 spec 规范配置文件内容的规则由另外一篇文档详述:Spec 规范配置文件指南.

处理器架构支援

所有的 openSUSE 包必须成功的在至少一个支援的处理器架构中被配置和编译成二进制RPM文件。支援的处理器架构有 i586 和 x86_64。打包者应该尽力支持两种处理器架构。内容 - 不需要被配置和编译的代码 - 和独立于处理器架构的代码(noarch)自然是豁免的。

可重新定位的软件包

使用 RPM 技术生成可重新定位的软件包是强烈不鼓励的。这会使得它运行困难,不可能被安装管理器使用或者 zypper 和 yast 使用,并且,如果你遵守了其他的打包原则的话使它可重新定位一般是不必要的。然而,有些不常见情况下,你有很好的理由去使得一个软件包可重新定位,你一定必须永远要在请求软件包复核时说明你这么做的意图和原因。


法律指南

禁止的软件

列举在 编译服务禁止应用程序列表 上的应用程序或其他软件是禁止你打包的。

授权许可

授权许可必须兼容开源行动。这个开源授权许可名录 列举了被开源行动 (OSI) 认可的授权许可。openSUSE 和一些其他的发行版通过软件包数据互通计划标准化了一系列常见授权许可的缩写。这些也被开源行动收录了。

请使用开源行动授权许可名录或者软件包数据互通计划中认可的标准授权许可缩略名。还有,软件包数据互通计划也引入了通告多个授权许可的简略语法。例如,如果你想打包的软件是以GPL授权许可第二版以及以后版本和MIT授权许可双重授权的,请在你的 spec 规范配置文件中这么写:

License:     GPL-2.0+ or MIT

请注意软件包数据互通计划的简略语法也支持‘and(和)‘,以及在更加复杂怪异的情况下使用括号(你就当它是布尔值组成的表达式 ;-) )。如果仍存疑问,随时垂询我们的法律团队。

代码还是数据内容

把计算机可执行的代码和数据内容区分开是重要的。代码肯定是允许的(假设,当然啦,它是以开源兼容的许可授权的,法律上清清白白的),但只有几种数据内容是允许的。规则是:

如果数据内容提升了用户体验,那么这些数据内容可以被打包进 openSUSE 。这是指,举例说,像这些东西:字体、主题、插画和壁纸,都是行得通的。

数据内容在被包含进来之前也应该被复核。它必须是开源兼容的许可授权的,必须在法律上没有任何问题。 另外对数据内容还有如下限制:

  • 数据内容必须不能是色情的,或者包含裸体的,无论它是不是动画形式、拟真形式、还是写真形式。
  • 数据内容不能是无礼的、歧视的或者贬损的。当你在不能确定它们是不是的时候,它很可能就是。

这是获准进入的数据内容的实例:

  • 办公套件中使用的插画
  • 壁纸 (不冒犯他人、歧视他人,被获准自由分发的)
  • 字体 (以开源许可授权,没有所有权/版权的限制)
  • 游戏的关卡不是数据内容,因为没有关卡的游戏是残废的。
  • 源代码中包含的供程序、主题或者文档使用的声音和图像是可以接受的。
  • 源代码中包含的演示文件不是数据内容。

这是被禁止的数据内容的实例:

  • 漫画书
  • 宗教文字
  • mp3 文件(这种格式是有专利的)

如果你仍然不清楚一些东西算不算数据内容,请问 openSUSE打包 邮件列表。


软件包特性

初始化脚本

目前,openSUSE 只支持 SystemV 样式的初始化脚本。针对 SysV 样式的初始化脚本有更详细的指南在这里 openSUSE:Packaging_init_scripts

Desktop 桌面导航文件

如果一个软件包包含图形化程序,那么它需要包含一个被妥善安装的 .desktop 桌面导航文件。为了指南的需要,“图形化程序”这里我们定义它是任何一个可以显示为X窗口并在该窗口里运行的程序。安装的 .desktop 桌面导航文件必须遵守 桌面条目规范,一定要仔细验证你是否正确使用了Name, GenericName, Categories StartupNotify 标签。

Desktop 桌面导航文件的 Icon 标签

icon 标签必须是图标文件的去掉格式后缀的基础名称,因为图标是按格式自动查找的:

  • Icon=comical

这默认查找 .png 格式, 然后去试 .svg 格式,最后试 .xpm 格式。

.desktop 桌面导航文件制作

如果软件包不能自动引入并安装它自己的 .desktop 桌面导航文件,那你就必须做一个,并把它作为第二个源代码引入(例如在 spec 规范配置文件中用 Source2: %name.desktop)。一个简单的 .desktop 桌面导航文件(comical.desktop) 的内容有:

#!/usr/bin/env xdg-open
[Desktop Entry]
#软件名称
Name=Comical
#泛用名
GenericName=Comic Archive Reader
#鼠标悬浮时显示的小帮助
Comment=Open .cbr & .cbz files
#点击时执行的命令
Exec=comical
#软件的图标
Icon=comical
#是否在系统默认的终端中打开
Terminal=false
#软件类型,一共三种类型:Application(程序)、Link(快捷方式)、Directory(文件夹)。
Type=Application
#软件分类
Categories=Graphics;

%suse_update_desktop_file 用法

在软件包中简单引入 .desktop 桌面导航文件是不够的。打包者必须在 spec 规范配置文件中的 %install 章节运行 %suse_update_desktop_file 命令,并添加BuildRequires: update-desktop-files 到头部。这样 .desktop桌面导航文件才能被正确安装并与 spec 规范配置文件兼容。 如果 .desktop桌面导航文件没有被软件本身去安装或者你想修改它的内容 (例如添加/删除分类 categories 等等),%suse_update_desktop_file 必须被运行。简单的用法:

  • 检查 desktop 桌面导航文件
%suse_update_desktop_file %{name}
  • 安装 desktop 桌面导航文件并修改它的分类
%suse_update_desktop_file -r %{name} System Utility Core GTK FileManager

用户和组

请注意现在我们主要阐述的是用户名/组别名到 uid/gid 的绑定是在软件包安装时由目标系统动态地分配的这种情况。即使软件包里的脚本是用了动态格式,一些提供给系统管理员的选项也能将它们静态绑定的情况也在下面讨论了,并且进一步研究了在编译时绑定的可能性。(译者注:需要创建或绑定用户和组的一般是在网络管理软件如 wireshark 、etherape 里较常用,除非你确定它是需要的且必须这么做,或者上游的打包说明告诉你这么做,请不要滥用此章节内容,因为它可能会造成使用经验很少的最终用户根本无法运行你的软件。)

在软件包中创建用户和组,使用下面命令:

Requires(pre): pwdutils

[...]

%pre
getent group 组名 >/dev/null || groupadd -r 组名
getent passwd 用户名 >/dev/null || useradd -r -g 组名 -d 家目录 -s /sbin/nologin -c "user for 软件包名" 用户名
exit 0

[...]

家目录 一般情况下应该是由软件包创建和拥有的,有合适的权限限制的文件夹。此类文件夹适合被创建在软件包的数据文件夹(/usr/share/软件包名)或者 /var 下面的文件夹如 /var/cache/软件包名 或者 /var/lib/软件包名, 如果软件包文件系统层级中有这样的文件夹的话。

软件包建立的用户账户很少被用于交互式登陆,所以一般情况下应该使用 /bin/false 或者 /sbin/nologin 作为用户的命令窗口。


结尾的 exit 0 会让 %pre 里面的代码片段返回成功的消息,即使用户/组创建操作由于各种各样的原因失败了。这种做法不是最好的,但是它降低了允许它出错造成系统层面的崩溃的可能性。如果解压软件包时用户/组不可用,rpm 会退而求其次的用根用户来持有它们。

getentgroupadd/useradd 之前被运行,作用是检查你想创建的用户/组是否已经存在,如果存在就跳过创建操作。这样做是给本地系统管理员提供一种可能性就是在他们想要把这些用户静态绑定到事先指定好的 UID/GID 时可以预先创建用户/组。

%pre 里面的 groupadd/useradd 是肯定会被运行的 — 安装和升级时都是。 这是通过进行上面说的 getent 检查来实现的,并且能够修复掉用户/组在待升级软件安装时消失的问题(就像升级时重置文件权限一样)。

软件包创建的用户或组永远不会被删除。目前没有一种合理的方法去检查由这些用户/组拥有的文件是不是被遗留了 —— 如果遗留了,我们又能对它们做点什么?遗留这些所有权指向不存在的用户/组的文件,当一个语义上和那些不存在的用户/组无关的用户/组被创建并重新使用这些 UID/GID 的时候会导致系统出问题。另外,在一些设置过程中,可能不能删除用户/组或者删除它们不合适,例如当使用一个共享的远程用户/组数据库时。清理无用的用户/组应该留给系统管理员去操心,如果他们愿意去的话。

在一些情况下,只创建一个没有用户账户的组是合适的。通常情况下,这是因为一些我们想要控制的系统资源是由那个组来接触的,一个独立的用户账户没有意义。最寻常的这种情况的例子包括但不限于一些游戏,它们的执行包设置了一个组用来分享类似高分排名文件这样的东西,还有/或者一些软件,需要额外的权限来接触硬件设备,但把权限给所有的系统用户或者终端登陆的用户是不合适的。在这些情况下,你可以只使用上面代码片段里的 groupadd 部分。

注意这一实际问题就是如果用户/组已存在那么不要二次创建它们。这可能会导致这样一种后果,无关的但是却共存的同名系统用户和/或组不必要的且不想要的去拥有了一个使用同名用户/组的软件包里的东西。这个版本的用户/组向导没有以任何方式讨论那个话题,但是如果发现了一个足够好的方法来解决它,我们会在以后的增补中提供它。

补丁

详见 补丁制作指南

在一个包中支持多个版本

详见 支援多版本安装


GConf 小脚本挂件

SuSEfirewall2 防火墙服务过滤条例

授权许可

  [[1]]
  [[2]]

软件包类型

有些程序在己的页面的Packaging 打包标签下面有专为它们写的打包指南。

32位 (baselib) 软件包

openSUSE:Build_Service_baselibs.conf

自有品牌相关包

调试信息包

软件包应该生成有用的 -debuginfo 后缀的调试信息包,或者在不能生成有用的这样的调试信息包时明确的禁用掉它,不然 rpmbuild 就会包含这些信息和接口到其他包里。一旦 -debuginfo 被明确的禁用了,那你必须在 spec 规范配置文件中说明为什么要这么做。关于调试信息包的更详细讨论有一份单独的文件: [3]

Eclipse 插件

Emacs

字体

常见字体格式如 Type1, OpenType TT (TTF) 或者 OpenType CFF (OTF) 的打包指南看这里: packaging guidelines。字体只能被打包到系统层面的字体目录,绝对不能被打包到一个程序的私有目录。详情请见:Package layout for fonts.

游戏

openSUSE:Packaging_Games 解释了如何打包游戏。

GNOME

Go

如何打包 Go 语言的软件包可见 openSUSE:Packaging_Go.

Haskell

Java

如何打包 Java 的软件包可见 openSUSE:Packaging_Java

JPackage

Lisp

Mono

Mozilla

OCaml

OpenOffice.org extensions

PAM (热插授权模块)

Perl

如何打包 Perl 的软件包可见 openSUSE:Packaging_Perl

PHP

关于如何打包 PHP 的软件包可见: openSUSE:Packaging_PHP

Python

关于如何打包 Python 的软件包可见: openSUSE:Packaging_Python

R

Ruby

关于如何打包 Ruby 的软件包可见: openSUSE:Packaging_Ruby

wxWidgets

关于如何打包 wxWidgets 的软件包可见:wxWidgets 打包指南

Libraries

Shared Libraries

openSUSE:Shared library packaging policy

静态库文件

包含库文件的软件包应该把静态库扔的越远越好 (例如: 使用 --disable-static 参数来配置它)。静态库文件应该只在豁免情况才被收纳进来。对库文件的软链接也应该尽量去链接共享库文件而不是静态库文件。

Libtool 的存档,foo.la 这样的文件,不应该被软件包收纳。使用了 libtool 的软件包会默认安装它们即使你使用了 --disable-static 参数,所以在打包之前你应该删除它们。由于老版本 libtool 或者老版本的使用 libtool 的软件的问题,有时经常不能在不改动程序的情况下移除所有的 foo.la 文件。大多数时候上游修复这些问题是很简单的。需要注意的是如果你是在升级稳定openSUSE版本中的库文件,软件包又已有了 *.la 文件的话,删除 *.la 文件会被认为是ABI/API更改。 换句话说,删除它们会改动库文件提供给其他程序的接口,因此不能随随便便这么做。

豁免情况

我们需要能够知道哪些包使用了静态库文件,从而能够知道在一个静态库中的安全漏洞被修复的时候哪些包需要重新编译。如果你一定要打包静态库文件的话,你必须遵守以下指南

静态库文件必须被放到一个 *-devel-static 样子的子软件包中,这个子软件包依赖 Requires *-devel 子软件包。把静态库文件从在 *-devel 中的其他开发文件分离出来,使得我们能够通过检查哪些包使用了 BuildRequires 标签引入了 ""*-devel-static""包 来追查它的使用情况。这样做的意图是,在某一天,软件包可以不用这些静态库而转向共享库文件。如果一个软件包只提供了静态库文件,那么略过对 *-devel 子软件包的依赖 Requires。明确的需要链接静态库文件的软件包必须加上 BuildRequires: foo-devel-static,以便追查静态库的使用。

如果并只是如果,一个软件包使用共享库,但这些共享库依赖静态库文件来工作,那么这些静态库文件可以被包括在 *-devel 包里。但这些软件包必须有一个 Provide: *-devel-static 这样的标签告诉大家它同时也提供静态库,依赖这些开发包的软件包必须使用 BuildRequires: *-devel-static 而不是 BuildRequires: *-devel来引入它。

与系统库重复

由于一些原因,软件包不能随附或者自行编译一份已有的系统库文件。软件包应该被补缀以使用已有系统库文件。这能防止在核心系统文件库已经除掉了老臭虫和安全漏洞的时候,你的那份又把它们带回来了。

Tcl

Web Applications

给openSUSE打包的网络程序应该把它们的文件放在 /srv/www/%name 而不是 /var/www。这是应该的,因为:

  • /var 是用来存放可变数据文件和日志文件的。/srv/www 更合适于网络程序。
  • 用户可能在 /var/www 里面已经有了文件,我们不想任何的openSUSE软件和它们混放。
  • /var/www 不再被文件系统层级标准支持了。

Vala

如何在 spec 规范配置文件中确定 vala 的版本

首先,你不是在编译 vala 自身,因为如果这样的话,vala 的版本将是由你手动指定的,用 %{version} 就能简单的使用它,问题根本不存在。 我们这里面对的问题是:

软件包在编译时会使用到 vala,并且编译可以在全系列 vala 下通过(这样你就不能画蛇添足地使用 BuildRequires: vala >= 0.14 这样的参数),这种情况下将如何确定 vala 的版本。

自动化编译服务在这里遇到了它的瓶颈,vala 目前并不能够像 perl 或者 python 那样简单地使用 BuildRequires: vala 引入它后,用 %{py_ver} 这样的自动化参数简单迅速地获取它的版本号。而一些软件又需要知道它的版本号来完成一些工作。比如 babl 或 gegl 会默认安装 .vapi 文件到 /usr/share/vala,但在标准openSUSE系统中这些文件夹是没有意义的,相反 openSUSE 使用的是 /usr/share/vala-0.14 这样的以版本号结尾的文件夹。这就需要你在打包时从软件包默认的安装文件夹复制它们到 openSUSE 适用的文件夹。

当然你可以使用 %if 0?{%suse_version} >= 1140 这样的标签来以 openSUSE 版本分类分别复制文件到该版 openSUSE 默认安装的 vala 版本的文件夹。但是这会使得你的工作量加大,spec 规范配置文件臃肿,且难于维护(每有一个新版本 openSUSE 释出你就需要添加一段这样的代码)。

我们使用了 spec 规范配置文件的自定义函数 %define 功能,如下:

 #引入编译依赖 vala
 BuildRequires: vala
 #自定义函数用来输出 vala 的版本
 %define vala_version %(rpm -q --queryformat='%{VERSION}' vala | sed 's/\.[0-9]*$//g')

我们使用了 rpm -q --queryformat='%{VERSION}' vala 来从编译时以标准RPM方式安装的vala中取得它的版本号 0.14.0 ,然后使用 sed 通过正则表达式来裁剪掉后缀 .0 ,将返回值输入到 vala_version 中以便通过 %{vala_version} 来使用,例如:

 #创建新文件夹, -pv 参数意思是如在日志文件中打印创建的文件夹以方便排错,如果装载子文件夹的母文件夹不存在,那么依次向上创建它
 %{__mkdir} -pv %{buildroot}%{_datadir}/vala-%{vala_version}/
 #移动文件
 %{__mv} %{buildroot}%{_datadir}/vala/* %{buildroot}%{_datadir}/vala-%{vala_version}/
 #删除原文件夹
 %{__rm} -rf %{buildroot}%{_datadir}/vala/

还有别忘了在 %files 标签下,你也可以用:

 %dir %{_datadir}/vala-%{vala_version}/
 %{_datadir}/vala-%{vala_version}/*

来简化你的代码。

由于 %{buildroot} 下的文件全部会被按文件系统层级打包进最终输出的 RPM 文件,这样你的打包更加用户友好度+1了。