openSUSE:打包指南
- 审查者有责任指出软件包中的具体问题,打包者有责任处理这些问题。 审查者和打包者共同确定问题的严重程度——是阻止软件包发布,还是可以在软件包进入仓库后解决。
- 打包指南是常见问题及其应有的严重程度的集合。 虽然不应忽略这些指南,但也不应盲目遵守。 如果您认为您的软件包应免除部分指南,请将问题提交到 openSUSE-packaging 邮件列表。
- 请注意,在构建服务中,许多规则将在成功构建后由一个名为 rpmlint 的工具强制执行或发出警告。 打包者应始终检查其输出,以了解常见的打包错误并获取有关如何改进打包的提示。 请参阅 打包检查,了解有关大多数 rpmlint 警告的说明。 该页面还包含有关如何抑制误报的说明。
- 如果需要更改指南,请按照概述的 更改流程 进行操作。
通用指南
有一些必须遵循的通用规则。
禁止包含预构建的二进制文件或库
包含在 openSUSE 软件包中的所有二进制文件或库必须从软件包中包含的源代码构建而来。 这样做是出于以下原因:
- 安全性:未从源代码构建的预打包二进制文件和库可能包含任何内容,恶意或危险的内容,或者只是纯粹的损坏。 此外,这些功能上无法修补和修复错误。
- 编译器标志:未从源代码构建的预打包二进制文件和库可能没有标准的 openSUSE 编译器标志,用于安全性和优化。
如果您不确定某项内容是否被视为二进制文件或库,以下是一些有用的标准:
- 它是可执行文件吗? 如果是,它可能是一个二进制文件。
- 它是否包含一个.so, .so.#, .so.#.#或.so.#.#.#扩展名? 如果是,它可能是一个库。
- 如有疑问,请在 openSUSE-packaging 邮件列表中提问。
需要非开源组件才能构建的软件包也不允许(例如,需要专有编译器)。
例外情况
- 一些软件(通常与编译器或交叉编译器环境相关)无法在不使用以前的工具链或开发环境(开源)的情况下构建。 如果您的软件包符合此标准,请联系 openSUSE 打包委员会以获得批准。
- 对于二进制固件,只要满足文档要求,则可以例外:BinaryFirmware
捆绑多个项目
openSUSE 中的软件包应尽一切努力避免将多个独立的上游项目捆绑在一个软件包中。
Specfile 指南
适用于 specfile 内容的规则在单独的文档中,称为 Specfile 指南。
架构支持
所有 openSUSE 软件包必须成功编译并构建为至少一个受支持架构的二进制 RPM 文件。 打包者应尽一切努力使软件包构建为受支持的架构。 代码——不需要编译或构建的代码——以及架构无关代码(noarch)是值得注意的例外。
可重定位软件包
强烈不建议使用 RPM 的生成可重定位软件包的功能。 很难使其正常工作,无法从安装程序或 zypper 和 yast 使用,并且通常没有必要如果遵循其他打包指南。
重复系统库
出于多种原因,软件包不应包含或构建对系统上存在的库的本地副本。 软件包应被修补以使用系统库。 这可以防止旧的错误和安全漏洞在核心系统库修复后继续存在。
法律
禁止软件
列在 构建服务应用程序黑名单 上的应用程序禁止打包。
捐赠请求
打包内容(描述/摘要/评论/…)不应包含直接的捐赠请求,无论是针对上游项目还是针对打包者。 即使我们不否认这些项目需要捐赠,它们也应在其网站上宣传活动,而不是使用下游打包来达到这些目的。
如果发现软件包在运行时请求捐赠,打包者有责任保留此代码或对其进行修补。
许可
软件许可应符合 开放源代码定义(当前版本为 1.9)。 如果您不确定特定许可是否符合开放源代码定义,您应该 提交错误,类别为 SUSE Tools -> SUSE Linux Legal Issues。
Spec 文件
打包者通常通过 spec 文件与软件许可联系。 Spec 文件可以声明整个软件包或单个子软件包的许可。 许可应使用 SPDX 短名称格式声明。 SPDX 及其相关的许可列表相对较新,但是,该列表有限,并且不能涵盖 openSUSE 打包者通常需要处理的所有许可。 为了暂时解决此限制,我们 创建了一个电子表格,其中包含现有的 SPDX 许可以及许多其他适用于 openSUSE Factory 或 openSUSE NonFree 的许可。 如果您仍然找不到合适的许可短名称,您应该 提交错误,类别为 SUSE Tools -> SUSE Linux Legal Issues。
除了使用用于声明许可的预定义语法外,openSUSE 还识别 spec 文件中的一种许可语法。 您可以使用诸如“and”之类的运算符来声明许可的聚合,或者使用“or”来表示应选择一个或另一个许可。 您还可以使用括号来组合许可,例如
License: (MIT or GPL-2.0) and LGPL-2.1+
可能是声明一个包含可执行二进制文件和相应的 LGPL-2.1+ 许可库的软件包。
在编写 spec 文件时需要注意的另一件事是,子软件包将继承软件包的主要许可,除非打包者省略为该子软件包输入许可。 这并不总是理想的。 例如,如果您决定为文档创建一个单独的子软件包,则应检查文档是否在例如 GFDL-1.1 而不是(例如)主软件包的 GPL-2.0+ 许可下许可。 始终为子软件包添加许可(即使子软件包恰好与主软件包具有相同的许可)是明智的。 这使得以后阅读 spec 文件的人可以立即了解。
最后,许可文本应始终复制到软件包中。 通常通过在 spec 文件的 %files 部分添加文件名使用 %license 宏来完成。 如果源软件包构建了多个子软件包,则每个软件包应包含其许可文本。 许可通常位于名为 COPYING、COPYING.LIB、LICENSE.txt 等的文件中。 请参阅 https://lists.opensuse.org/opensuse-factory/2016-02/msg00167.html,了解相关讨论。
代码与内容
区分计算机可执行代码和内容非常重要。 虽然允许代码(当然,假设它具有开源兼容的许可,在法律上没有问题等),但只有某些类型的内容是允许的。 规则是
如果内容增强了用户体验,则允许将内容打包到 openSUSE 中。 这意味着,例如,字体、主题、剪贴画和壁纸是可以的。
内容仍然需要经过审核才能包含。 它必须具有开源兼容的许可并且在法律上没有问题。 此外,内容还有一些额外的限制
- 内容不得包含色情内容或裸体,无论是动画、模拟还是照片。 在互联网上还有更好的获取色情内容的地方。
- 内容不应具有冒犯性、歧视性或贬义性。 如果您不确定某段内容是否属于这些类别之一,它可能就是。
一些允许的内容示例
- 用于办公套件的剪贴画
- 壁纸(不冒犯、不歧视、具有自由重新分发的权限)
- 字体(在开源许可下,没有所有权/法律问题)
- 游戏关卡不被认为是内容,因为没有关卡的游戏将无法正常运行。
- 与源 tarball 一起包含的声音或图形,这些声音或图形是程序或主题使用的(或文档使用的)是可以接受的。
- 只要内容可以自由分发且没有任何限制,游戏音乐或音频内容是可以允许的。
- 与源 tarball 一起包含的示例文件不被视为内容。
以下是一些不允许的内容示例
- 漫画书艺术文件
- 宗教文本
如果您不确定某项内容是否为批准的内容,请在 openSUSE-packaging 邮件列表中提问。
软件包特性
Systemd 服务
openSUSE 使用 systemd 来启动服务。有关如何打包服务文件的详细指南,请参阅 openSUSE:Systemd_packaging_guidelines。
桌面文件
如果软件包包含 GUI 应用程序,则需要包含一个正确安装的.desktop文件。就这些指南而言,GUI 应用程序被定义为绘制 X 窗口并在该窗口内运行的任何应用程序。安装的.desktop文件必须遵循 desktop-entry-spec,尤其要注意验证 Name、GenericName、Categories 和 StartupNotify 条目的正确用法。
桌面文件中的图标标签
图标标签必须是图标文件(们)的基本名称,因为它允许使用图标主题
Icon=comical
它假定.png作为默认值,然后尝试.svg最后.xpm.
.desktop文件创建
如果软件包尚未包含并安装自己的.desktop文件,则需要创建自己的文件,并将其作为源包含在内(例如 Source3: %name.desktop)。一个示例.desktop文件(comical.desktop)是
[Desktop Entry] Name=Comical GenericName=Comic Archive Reader Comment=Open .cbr & .cbz files Exec=comical Icon=comical Terminal=false Type=Application Categories=Graphics;
%suse_update_desktop_file 用法
以前的打包指南强制使用%suse_update_desktop_file宏。现在这已被弃用。
用户和组
请注意,目前,我们主要处理用户/组名称到 uid/gid 的映射由目标系统在软件包安装时动态决定的情况。下面还讨论了系统管理员使此映射静态化,即使软件包脚本使用动态方案的一些选项,并且正在研究更多选项,包括在软件包构建时使映射静态化的可能性。
由各种应用程序使用、标准文件系统目录或应存在于每个 Unix 兼容系统上的标准用户的系统用户应由特殊的 RPM 提供。
此 RPM 提供用户和组
Provides: user(<name>) Provides: group(<name>)
此 RPM 还负责创建和提供主目录。需要特殊系统用户的应用程序应要求它们
Requires(pre): user(<name>) Requires(pre): group(<name>)
这样,系统用户仅在需要时才会被创建。并且管理员可以轻松地确定是否仍然需要系统用户或可以删除它们。
systemd-sysusers (sysusers.d(5)) 用于创建这些帐户。这允许验证系统帐户应该是什么样子。
uucp 系统用户的示例 spec 文件应包含以下行
Source1: system-user-uucp.conf
BuildRequires: sysuser-tools
%package -n system-user-uucp
Summary: System user and group uucp
%sysusers_requires
%build
%sysusers_generate_pre %{SOURCE1} uucp system-user-uucp.conf
%install
install -D -m 0644 %{SOURCE1} %{buildroot}%{_sysusersdir}/system-user-uucp.conf
%pre -n system-user-uucp -f uucp.pre
%files -n system-user-uucp
%{_sysusersdir}/system-user-uucp.conf
%pre ... -f uucp.pre导致 uucp.pre 的内容被视为脚本。该文件在构建时由%sysusers_generate_pre宏生成。
仅当用户也应供其他软件包使用时,才需要将 sysusers 配置文件添加到子软件包中。如果只有该软件包本身需要新用户,则不需要子软件包,并且可以简化 spec 如下
Source1: %{name}-user.conf
BuildRequires: sysuser-tools
%sysusers_requires
%build
%sysusers_generate_pre %{SOURCE1} %{name} %{name}-user.conf
%install
install -D -m 0644 %{SOURCE1} %{buildroot}%{_sysusersdir}/%{name}-user.conf
%pre -f %{name}.pre
%files
%{_sysusersdir}/%{name}-user.conf
%{name}-user.conf 文件的示例
#Type Name ID GECOS Home directory Shell u uucp - "Unix-to-Unix CoPy system" /etc/uucp - m uucp lock - - -
更多信息可以在 freedesktop.org 页面上找到。在已安装的系统上,您还应该在 /usr/lib/sysusers.d 下找到其中一些文件。
主目录通常应由软件包创建和拥有的目录,并具有适当的限制性权限。对于目录的位置,一个好的选择是软件包的数据目录或 /var 下的目录,例如 /var/lib/NAME(如果它有的话)。
由软件包创建的用户帐户很少用于交互式登录,因此通常应使用 - 作为 Shell,这将设置/usr/sbin/nologin作为用户的 shell。不要使用/bin/false或/bin/true作为 shell,而是更改为/usr/sbin/nologin如上所述。
使用%sysusers_requires宏,不要手动要求 sysuser-shadow,因为 systemd 的旧版本规范发生了变化。
由软件包创建的用户或组永远不会被删除。没有合理的方法可以检查是否留下由这些用户/组拥有的文件——即使有,我们该如何处理它们?将这些留下,所有权指向那时不存在的用户/组,可能会导致稍后创建的语义上无关的用户/组重用 UID/GID 时出现安全问题。此外,在某些设置中,删除用户/组可能是不可能的或不受欢迎的,例如,在使用共享远程用户/组数据库时。未使用的用户/组的清理留给系统管理员自行决定(如果他们愿意)。
在某些情况下,希望仅创建一个组而无需用户帐户。通常,这是因为有一些系统资源,我们希望使用该组来控制对其的访问,而单独的用户帐户不会增加任何价值。此类常见案例的示例包括(但不限于)为共享高分文件或类似目的设置 gid 的游戏,以及/或需要对某些硬件设备具有特殊权限的软件,并且不适合将其授予所有系统用户甚至仅登录到控制台的用户。在这些情况下,仅应用 sysusers.d 规范的 g 行部分。
请注意,如果现有系统用户和/或组的名称与软件包使用的名称相同,则不创建用户/组的做法的缺点可能是无关但巧合相同名称的现有系统用户和/或组不必要且不受欢迎地访问软件包中的内容。此版本的用户/组指南不会以任何方式解决此问题,但如果找到一种好的方法来做到这一点,未来的修订可能会解决此问题。
迁移 / 升级
随着容器化部署的引入,在软件包安装期间完成所有配置和生成随机信息不是一个好主意,而是应该在选定的生成容器上完成,以确保数据的一致性和稳定性。
基本上,这意味着将所有使用二进制文件并影响结果数据的配置任务(例如,密钥链生成、数据库迁移)从脚本阶段移动到稍后在实时系统上执行。由于我们的要求是能够直接使用 rpm 安装软件包,因此我们有几种实现方法
systemd 服务(以 mariadb 软件包为例)
使用 systemd 服务实际执行初始配置和部署,或者甚至在启动之前迁移已经存在的现有内容。
常规 systemd 文件非常明显
[Unit] Description=MySQL server Wants=basic.target Conflicts=mysql.target After=basic.target network.target [Service] Restart=on-abort Type=simple ExecStartPre=/usr/lib/mysql/mysql-systemd-helper install ExecStartPre=/usr/lib/mysql/mysql-systemd-helper upgrade ExecStart=/usr/lib/mysql/mysql-systemd-helper start ExecStartPost=/usr/lib/mysql/mysql-systemd-helper wait [Install] WantedBy=multi-user.target
所有操作都由辅助 shell 脚本完成,如前所述,每次启动都会执行安装和升级步骤。为了说明这些要点,对于每个应用程序来说都会有所不同,但作为一个概念,这在 mariadb 中完成
# Create new empty database if needed
mysql_install() {
if [ ! -d "$datadir/mysql" ]; then
echo "Creating MySQL privilege database... "
mysql_install_db --user="$mysql_daemon_user" --datadir="$datadir" || \
die "Creation of MySQL databse in $datadir failed"
echo -n "$MYSQLVER" > "$datadir"/mysql_upgrade_info
fi
}
# Upgrade database if needed
mysql_upgrade() {
# Run mysql_upgrade on every package install/upgrade. Not always
# necessary, but doesn't do any harm.
if [ -f "$datadir/.run-mysql_upgrade" ]; then
echo "Checking MySQL configuration for obsolete options..."
sed -i -e 's|^\([[:blank:]]*\)skip-locking|\1skip-external-locking|' \
-e 's|^\([[:blank:]]*skip-federated\)|#\1|' /etc/my.cnf
# instead of running mysqld --bootstrap, which wouldn't allow
# us to run mysql_upgrade, we start a full-featured server with
# --skip-grant-tables and restict access to it by unix
# permissions of the named socket
echo "Trying to run upgrade of MySQL databases..."
# Check whether upgrade process is not already running
protected="$(cat "/run/mysql/protecteddir.$INSTANCE" 2> /dev/null)"
if [ -n "$protected" && -d "$protected" ]]; then
pid="$(cat "$protected/mysqld.pid" 2> /dev/null)"
if [ "$pid" && -d "/proc/$pid" ] &&
[ -z "$(readlink "/proc/$pid/exe" | grep -q "mysql")" ]; then
die "Another upgrade in already in progress!"
else
echo "Stale files from previous upgrade detected, cleaned them up"
rm -rf "$protected"
rm -f "/run/mysql/protecteddir.$INSTANCE"
fi
fi
protected="$(mktemp -d -p /var/tmp mysql-protected.XXXXXX | tee "/run/mysql/protecteddir.$INSTANCE")"
[ -n "$protected" ] || die "Can't create a tmp dir '$protected'"
# Create a secure tmp dir
chown --no-dereference "$mysql_daemon_user:$mysql_daemon_group" "$protected" || die "Failed to set group/user to '$protected'"
chmod 0700 "$protected" || die "Failed to set permissions to '$protected'"
# Run protected MySQL accessible only though socket in our directory
echo "Running protected MySQL... "
/usr/sbin/mysqld \
--defaults-file="$config" \
--user="$mysql_daemon_user" \
--skip-networking \
--skip-grant-tables \
$ignore_db_dir \
--log-error="$protected/log_upgrade_run" \
--socket="$protected/mysql.sock" \
--pid-file="$protected/mysqld.pid" &
mysql_wait "$protected/mysql.sock" || die "MySQL didn't start, can't continue"
# Run upgrade itself
echo "Running upgrade itself..."
echo "It will do some chek first and report all errors and tries to correct them"
echo
if /usr/bin/mysql_upgrade --no-defaults --force --socket="$protected/mysql.sock"; then
echo "Everything upgraded successfully"
up_ok=""
rm -f "$datadir/.run-mysql_upgrade"
[ -n "$(grep -q "^$MYSQLVER" "$datadir/mysql_upgrade_info" 2>/dev/null)" ] || \
echo -n "$MYSQLVER" > "$datadir/mysql_upgrade_info"
else
echo "Upgrade failed"
up_ok="false"
fi
# Shut down MySQL
echo "Shuting down protected MySQL"
kill "$(cat "$protected/mysqld.pid")"
for i in {1..30}; do
/usr/bin/mysqladmin --socket="$protected/mysql.sock" ping > /dev/null 2>&1 || break
done
/usr/bin/mysqladmin --socket="$protected/mysql.sock" ping > /dev/null 2>&1 && kill -9 "$(cat "$protected/mysqld.pid")"
# Cleanup
echo "Final cleanup"
if [ -z "$up_ok" ]; then
rm -rf "$protected" "/run/mysql/protecteddir.$INSTANCE"
else
die "Something failed during upgrade, please check logs"
fi
fi
}
crontab
类似于前一个选项,但使用 crontab 代替 systemd。
二进制调整
调整二进制文件,以便它们在首次运行/迁移时自行执行所有这些配置任务。但这可能会变得复杂,以便说服上游项目接受这些更改。
Logrotate 脚本
Logrotate 需要日志文件(或它们所在的目录)由 root:root 拥有,因此它不会容易受到符号链接技巧和其他攻击的影响。
如果日志文件必须由非 root 用户拥有,则应使用最近添加的“su”指令。Logrotate 进程将在旋转以匹配旋转的日志文件的权限之前切换用户:组。
示例
/var/log/radius/radius.log {
su radiusd radiusd
[...]
}
补丁
请参阅 Packaging Patches guidelines。
支持软件包的多个版本
请参阅 支持安装多个软件包版本
变更日志
请参阅 创建 (RPM) 更改文件。请注意,openSUSE 使用一个单独的文件来记录 RPM 变更日志。
Udev 规则
Udev 规则文件必须放置在 %{_udevrulesdir} 中。
不需要在 %post 和 %postun 中重新加载 udev,因为 udev 会自动检测规则文件的更改 (opensuse-packaging 讨论)。
如果您的软件包导出一些共享库 (.so),请查看 共享库打包策略。
静态库
如果必须这样做,请查看 静态库打包策略
Debuginfo
软件包应生成有用的 -debuginfo 软件包,或者在无法生成有用的软件包时明确禁用它们,但 rpmbuild 会这样做。
在可能的情况下,-debuginfo 软件包的生成是自动的,并且可以在构建服务上按(项目、存储库)或按(软件包、存储库)为基础显式禁用。例如,请查看 OBS Web UI 的 Repository 标签的 openSUSE:12.3:Update 项目或 该项目中的软件包。当然,您也可以使用 osc 编辑属于项目或软件包的元数据来更改这些标志。
显式禁用 -debuginfo 软件包时,需要在 spec 文件中说明原因。
内部工作原理
如果启用了 debuginfo 标志,bs-worker 会将其传递给 build(1)(某些环境变量?),并且 /usr/bin/build 会使用 --define '_build_create_debug 1' 调用 rpm。
/home/abuild/.rpmmacros 填充了 _build_create_debug 的定义,并将导致疯狂的宏魔术扩展,包括 %debug_package。
%debug_package 是一个标准化的 RPM 组件,标志着 BS 特定逻辑的结束。
rpm(仍然用于 SLE-11 构建)不支持 noarch 子软件包。这导致创建 debuginfo 文件,但没有-debuginfo生成。在为这些版本构建时,不得为子软件包使用 noarch。
软件包类型
一些应用程序有为其编写的特定指南,位于 Packaging/ 层次结构中的各自页面上。
体系结构交叉(baselib)软件包
又名-32bit, -64bit, -x86等等。
→ openSUSE:Build_Service_baselibs.conf
品牌
如何创建和打包品牌在 openSUSE:Packaging_Branding 中说明。
Emacs
在 %{_datadir}/emacs/site-lisp 中添加单个 elisp 文件 (.el)。如果您添加多个文件,请创建一个子目录 %{_datadir}/emacs/site-lisp/%{name} 并添加一个新初始化文件以将子目录添加到 load-path 列表中
%define _sitedir %{_datadir}/emacs/site-lisp
%define _startfile %{_sitedir}/suse-start-%{name}.el
cat <<EOF > %{buildroot}%{_startfile}
;; %{_startfile}
(add-to-list 'load-path "%{_sitedir}/%{name}")
;; %{_startfile} ends here
EOF
TODO:byte-compilation & autoloads
Fonts
Web 应用程序
在 openSUSE 中打包的 Web 应用程序应将其文件放置在 /etc、/usr、/var 等中,具体取决于类型,就像任何其他应用程序一样。
通常,软件包不得安装、删除或以其他方式修改 /srv 内容,因为根据 FHS[1],其使用权保留给管理员。在 openSUSE 中,如果软件的默认配置指向那里,则软件包创建 /srv 中的空目录是可以的。
特定语言 / 框架
| 名称 | 页面 |
|---|---|
| Eclipse 插件 | 待办事项 |
| 游戏 | openSUSE:打包游戏 |
| GNOME | 待办事项 |
| Go | openSUSE:打包 Go |
| GObject Typelibs | openSUSE:打包 Typelibs |
| Haskell | openSUSE:Packaging Haskell |
| Java | openSUSE:打包 Java |
| Lisp | openSUSE:打包 Lisp |
| Lua | openSUSE:打包 Lua |
| Meson | openSUSE:使用 Meson 打包 |
| Mono | 待办事项 |
| Mozilla | 待办事项 |
| OCaml | 待办事项 |
| OpenOffice.org 扩展 | 待办事项 |
| PAM(可插拔身份验证模块) | 待办事项 |
| Perl | openSUSE:打包 Perl |
| PHP | openSUSE:Packaging PHP |
| Python | openSUSE:打包 Python |
| PyQt5 和 SIP | openSUSE:打包 PyQt5 和 SIP |
| R | openSUSE:打包 R |
| Rust | 打包 Rust 软件 |
| Ruby | openSUSE:打包 Ruby |
| Tcl | 待办事项 |
| wxWidgets | openSUSE:打包 wxWidgets |
| Vala | openSUSE:打包 Vala |