openSUSE:Specfile 指南
通用规则
所有 spec 文件必须易于理解。如果其他打包人员无法阅读 spec 文件,将不可能执行审查并协作完成软件包。
Spec 文件模板
从头开始编写软件包时,应基于 spec 文件模板 (请参阅 rpmdevtools) 构建您的 spec 文件。 可以通过以下方式生成 MYPACK 软件包的基本设置:
(请参阅 osc-plugin-install 以获取更多信息)
或者 (根据您的 openSUSE 版本更改 URL)
完成此操作后
# A folder already created for a project, like home:user.
cd MYPROJECT
# Create a new package.
osc mkpac MYPACK
cd MYPACK
# Download source tarball from upstream (the group that develops the code).
wget http://upstream.example.org/source/.../MYPACK-1.0.tar.gz<br/>
# Create a spec file template for MYPACK.
rpmdev-newspec -t lib MYPACK
# Record changes to the MYPACK package.
osc vc
# Edit spec file.
vim MYPACK.spec
然后稍后
# Test it builds correctly
osc build
# Checkin/commit to OBS server
osc ci
请尽可能遵守此模板。
这并不是编写 spec 文件的唯一方法,但这样做可以使 QA 更容易发现错误并快速了解您尝试做什么。
特定语言的 spec 文件通常可以使用专门的工具创建,例如 cpanspec 或 gem2rpm-opensuse。 另请参阅
- openSUSE:Packaging_Perl
- openSUSE:Packaging_Python
- openSUSE:Packaging_Ruby
- openSUSE:Packaging_Rust_Software
Spec 文件编码
除非必要,否则使用 ASCII 字符 (如果需要,则保存为 UTF-8)。
如果您不确定哪些字符是 ASCII 字符,请参阅 ASCII 图表。
Spec 文件许可
出于法律原因,spec 文件必须具有许可标头。
使用以下模板
# # spec file for package $YOUR_PACKAGE # # Copyright (c) $CURRENT_YEAR $YOUR_NAME_WITH_MAIL_ADDRESS # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed # upon. The license for this file, and modifications and additions to the # file, is the same license as for the pristine package itself (unless the # license for the pristine package is not an Open Source License, in which # case the license is the MIT License). An "Open Source License" is a # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. # Please submit bugfixes or comments via http://bugs.opensuse.org/ #
如果还没有这样的标头,您可以运行
osc service run format_spec_file
以自动为您添加一个。
宏
使用宏而不是硬编码的目录名称。
可以在 openSUSE:Packaging Conventions RPM Macros 上找到宏列表及其作用。 在 Source: 或 Patch: 行中使用宏是一种风格问题。有些人喜欢源行易于阅读,而无需使用宏。另一些人则更喜欢在更新到新版本时使用宏的便利性。无论如何,请在您的 spec 文件中保持一致,并验证您列出的 URL 是否有效。如果您需要确定包含宏时的实际字符串,可以使用 rpm。例如,要确定实际的 Source: 值,您可以运行
$ rpm -q --specfile foo.spec --qf "$(grep '^Source[0-9]*:' foo.spec)\n"
或者,您可以使用 rpmdevtools 包中的 rpmdev-spectool
$ rpmdev-spectool -~sources foo.spec
宏与 RPM 变量
rpm 在 %prep、%build 和 %install 开始时隐式地将一些宏重新导出为 shell 变量。摘自 /usr/lib/rpm/macros
%___build_pre \
RPM_SOURCE_DIR=\"%{u2p:%{_sourcedir}}\"\
RPM_BUILD_DIR=\"%{u2p:%{_builddir}}\"\
RPM_OPT_FLAGS=\"%{optflags}\"\
RPM_ARCH=\"%{_arch}\"\
RPM_OS=\"%{_os}\"\
RPM_BUILD_NCPUS=\"%{_smp_build_ncpus}\"\
export RPM_SOURCE_DIR RPM_BUILD_DIR RPM_OPT_FLAGS RPM_ARCH RPM_OS RPM_BUILD_NCPUS\
RPM_DOC_DIR=\"%{_docdir}\"\
export RPM_DOC_DIR\
RPM_PACKAGE_NAME=\"%{NAME}\"\
RPM_PACKAGE_VERSION=\"%{VERSION}\"\
RPM_PACKAGE_RELEASE=\"%{RELEASE}\"\
export RPM_PACKAGE_NAME RPM_PACKAGE_VERSION RPM_PACKAGE_RELEASE\
LANG=C\
export LANG\
unset CDPATH DISPLAY ||:\
%{?buildroot:RPM_BUILD_ROOT=\"%{u2p:%{buildroot}}\"\
export RPM_BUILD_ROOT}\
%{?_javaclasspath:CLASSPATH=\"%{_javaclasspath}\"\
export CLASSPATH}\
PKG_CONFIG_PATH=\"${PKG_CONFIG_PATH}:%{_libdir}/pkgconfig:%{_datadir}/pkgconfig\"\
export PKG_CONFIG_PATH\
从技术上讲,可以使用两者;它们将在所有(合理的)情况下解析为相同的值。但是,并非所有宏(例如 %{_bindir})都映射到 shell 变量。不要在 openSUSE 包中混合这两种风格,因为这对于 QA 和可用性而言是不好的。
RPM 宏在运行某个部分之前被扩展,并且 shell 在运行时读取变量的速度较慢。
宏与 shell 命令
不要使用 shell 命令的宏版本。
对于许多常用的 shell 命令,都有一个宏等效项。例如 (摘自 /usr/lib/rpm/macros),
%__cat /usr/bin/cat %__chgrp /usr/bin/chgrp %__chmod /usr/bin/chmod %__chown /usr/bin/chown …
这些宏旨在提高与非 GNU 平台的兼容性,例如 Solaris 或 FreeBSD(其中 GNU 工具的名称可能为 gchgrp 而不是 chgrp)。由于 openSUSE 仅支持 GNU/Linux 平台,因此纯 shell 命令比宏更容易阅读。spec 文件清理机器人甚至会自动将宏转换为其 shell 命令对应项 自动。
条件语句
请参阅 openSUSE:RPM conditional builds。
有关不同 openSUSE 版本中版本宏的列表,请参阅 openSUSE RPM distro version macros。
例如,要检测 Leap 42.1
%if 0%{?sle_version} == 120100 && 0%{?is_opensuse}
# perform Leap 42.1 specific actions here
%else
# something else
%endif
对于其他发行版,请参阅 openSUSE:Build Service cross distribution howto。
序言
命名 / 版本控制
请参阅 openSUSE:Package naming guidelines。
元数据标签
- AutoReqProv:除非您想关闭自动依赖处理,否则不应使用。
- Source:应尽可能指定 HTTP 链接 (openSUSE:Package source verification)。
- Group:应仅使用 软件包组指南 中列出的软件包组。
- Summary:应是软件包的简短描述 (摘要指南)。
不要使用以下标签
- Copyright:已弃用,请改用 License (许可指南)。
- Packager:在其他地方重新构建 RPM 软件包会将此值强制给新的打包人员,从而导致后续混淆,不知道该联系谁。应在 changelog 条目中指定打包人员的身份。
- Vendor:与 Packager 相同的原因。如果您想覆盖 openSUSE 构建服务设置的默认值,请使用 prjconf 级别的
%define。
描述部分 (%description)
这应基本上回答软件的作用以及为什么有人需要或想要该软件包。有关更多信息,请参阅 openSUSE:Package description guidelines。
依赖关系
请参阅 openSUSE:Package dependencies。
补丁
openSUSE spec 文件中的所有补丁都应该在补丁文件(或其各自的“PatchN:”标签上方)中包含有关其状态的注释。有关详细信息,请参阅 openSUSE:Packaging Patches guidelines。
准备部分 (%prep)
%autosetup 宏。有关更多信息,请参阅 rpm 手册。安静的 %setup
应将 -q 选项传递给 %setup 宏。
这将大大减少构建日志文件的大小,尤其是对于提取大量文件的源存档。
构建部分 (%build)
编译器标志
用于构建软件包的编译器应遵守系统 rpm 配置中设置的适用编译器标志。
在实践中,这意味着 C、C++ 和 Fortran 编译器的 %{optflags}(变量:$RPM_OPT_FLAGS,见上文)。遵守意味着编译器实际使用的标志是该变量的内容的基础。如果出于充分的理由,可以添加、覆盖或过滤这些标志的一部分;应审查并记录这样做背后的原因 spec 文件中,尤其是在覆盖和过滤的情况下。
Werror
在某些情况下,上游可能会决定将 -Werror 添加到他们的 CFLAGS/CXXFLAGS/等中,以确保上游了解代码中的潜在问题。
这对于上游开发人员来说是一个好主意,但打包人员应禁用它。新的编译器版本会生成不同的警告(现在将成为错误!),这使得升级编译器工具链和支持多个 openSUSE 版本变得困难。
要删除 -Werror,可能需要使用 configure 开关,例如 --disable-werror,或者需要修补各种 Makefile 中的 -Werror 开关。
详细模式
建议使用 make 选项来打印构建过程中运行的命令的完整调用。这有助于识别使用的编译器标志以及是否正确应用了 %{optflags}。典型示例:make V=1。
并行 make
尽可能使用 %make_build 和 %make_install 宏。
旧版本
如果您针对的是 openSUSE 13.2、Leap 42.2 和 SLE 12 SP2 (rpm < 4.12),请以这种方式运行 make 以启用并行构建。
make %{?_smp_mflags}
这通常可以加快构建速度,尤其是在 SMP 机器上。它比 make %{?jobs:-j%jobs} 更好,因为前者允许使用替代 make 标志,例如 make -lN,并且不硬编码 -jN。
但是,请确保软件包可以这样干净地构建;某些 Makefile 不支持并行构建,或者具有损坏的依赖关系。
禁用并行构建
如果软件包不支持并行构建,请显式写入 -j1。
这有助于在 spec 文件组中搜索此类软件包。可能还有其他原因要使用 -j1,例如由于内存使用过多。(这种情况发生在为 MINGW 构建 Boost 时。)截至 2013 年 9 月,build.opensuse.org 上的常用工作站分配了 2 GB 内存。无论如何,请考虑添加注释说明导致使用 -j1 的原因,无论是因为损坏的依赖关系还是内存要求等。
构建约束
可以使用 OBS _constraints 文件来限制将用于构建给定软件包的 worker 类型。可以指定作业数、可用内存和每个作业的内存的最小值。
安装部分 (%install)
运行 make install 不得尝试更改任何文件的所有权。
由于 rpmbuild 以非特权用户身份运行,因此 %install 部分不得直接或间接运行 chown,例如,`install -o root...`)。文件所有权应在后面的 %files 部分中设置。
使用 %make_install 而不是 %makeinstall (没有下划线)。
%make_install 是一个宏,等效于 make install DESTDIR="%{?buildroot}"。如果您正在为较旧的 RPM 版本 (rpm < 4.10,例如 SLE 11) 创建软件包,请使用扩展版本而不是宏。
还有一个名称令人困惑的第二个宏,%makeinstall (注意:没有下划线)。避免使用它。
一个例子是 xapian-core-1.2.17,其中
# configure.ac contains: incdir=$includedir AC_SUBST(incdir) # include/Makefile.mk contains: inc_HEADERS = include/xapian.h
使用 %makeinstall 的扩展,“make install ... includedir=/buildroot/usr/include” 没有效果,因为奇怪的 Makefile 需要设置 incdir 而不是 includedir。由于 %makeinstall 未设置 DESTDIR,xapian-core 会尝试将 xapian.h 安装到默认位置 /usr/include,而不是 /buildroot/usr/include。可以说是双重失败。
移除构建根目录
不要尝试手动清理或移除构建根目录文件夹。
openSUSE 将在 %install 部分的开头使用 rm -rf %{buildroot} (或 rm -rf $RPM_BUILD_ROOT) 标记为 不良编码风格
%install rm -Rf "%buildroot" mkdir -p "%buildroot/%_prefix/..."
或
%install rm -Rf "%buildroot" make install
为什么?
%{buildroot} 通常位于 /var/tmp 目录下,而您刚刚打开了一个简单的本地攻击者利用漏洞,使其可以接管您的帐户(甚至 root 如果您以 root 用户身份构建)。最好不要在 %install 中使用 `rm -rf %{buildroot}` (而依赖 %clean 来执行此操作)。
以下方法稍好一些(不要这样做,它会自动为您完成)
%install rm -Rf "%buildroot"; mkdir "%buildroot"; mkdir -p "%buildroot/%_prefix/..."
或
%install rm -Rf "%buildroot"; mkdir "%buildroot"; make install
在这种情况下,如果攻击者尝试用他自己的符号链接替换构建根目录,mkdir %buildroot 将会失败,构建将会中止。
清理部分 (%clean)
%clean 部分——它不再必要。如果指定了 %clean 部分,它将在生成二进制 RPM 和源代码 RPM 之后运行。在 Open Build Service 中,此部分是不必要的,因为用于构建软件包的 chroot 和 VM 环境通常会被销毁。在非从头开始的环境中构建软件包通常不被 openSUSE 软件包支持 (boo#176528#c4)。
从 rpm 4.8 (openSUSE 11.2+) 开始,如果 spec 文件中完全缺少 %clean 部分,rpm 默认设置为 "%clean: rm -Rf %{buildroot}"。
如果软件包包含 %clean 部分,它应该可以安全地删除。如果您不确定,请先咨询其他维护者。
脚本片段
在使用脚本片段时应格外小心。一些常见的脚本片段在 openSUSE:Packaging scriptlet snippets 中有文档说明。
脚本片段要求
您的软件包必须要求脚本片段中使用的所有内容。
%pre*/%post* 脚本片段的表示法如下
Requires(pre): ... Requires(post): ...
脚本片段只能写入某些目录。
软件包的构建脚本只能更改 (创建、修改、删除) %buildroot、%_builddir 和有效的临时位置(如 /tmp、/var/tmp (或 rpmbuild 过程设置的 %_tmppath))下的文件,具体如下表所示
| /tmp, /var/tmp, %_tmppath | %_builddir | %buildroot | |
|---|---|---|---|
%prep |
yes | yes | no |
%build |
yes | yes | no |
%install |
yes | yes | yes |
%check |
yes | yes | no |
%clean |
yes | yes | yes |
进一步说明:无论构建者的 uid 如何,都必须保持这一点。
文件部分 (%files)
openSUSE 遵循 文件系统层次结构标准 (FHS),这定义了文件应放置在系统上的位置。任何偏离 FHS 的行为都应在 spec 文件中的注释中加以说明。
所有权
您的软件包应拥有作为 %install 过程安装的所有文件。
软件包不得拥有其他软件包已经拥有的文件(或者,如果它们合法地这样做,则软件包需要一个 Conflict: 标签)。这里的经验法则是第一个安装的软件包应拥有其他软件包可能依赖的文件。如果您认为您有充分的理由拥有某个文件,或者另一个软件包拥有该文件,请在软件包审查时提出。
目录所有权比文件所有权更复杂。虽然经验法则是相同的:拥有您创建的所有目录,但不拥有您所依赖软件包的目录,但也有一些实例,多个软件包拥有一个目录是可取的。例如:
与软件包未来版本的兼容性
您所依赖的软件包提供目录,可能会在后续版本中选择拥有不同的目录,而您的软件包将在此后版本中未经修改地运行。
一个常见的例子是 Perl 模块。假设 perl-A-B 依赖于 perl-A 并将文件安装到
/usr/lib/perl5/vendor_perl/5.8.8/i386-linux-thread-multi/A/B。基础 Perl 软件包保证它将拥有/usr/lib/perl5/vendor_perl/5.8.8/i386-linux-thread-multi,只要它与版本 5.8.8 保持兼容,但 perl-A 软件包的未来升级可能会安装到 (并因此拥有)/usr/lib/perl5/vendor_perl/5.9.0/i386-linux-thread-multi/A。因此,perl-A-B 软件包需要拥有/usr/lib/perl5/vendor_perl/5.8.8/i386-linux-thread-multi/A以及/usr/lib/perl5/vendor_perl/5.8.8/i386-linux-thread-multi/A/B,以保持适当的所有权。不相关软件包的通用目录
多个软件包可能在通用目录中包含文件,但不一定依赖于其中一个软件包。以下是一个例子:
- Package foo-animal-emu 将文件放入 /usr/share/Foo/Animal/Emu
- Package foo-animal-llama 将文件放入 /usr/share/Foo/Animal/Llama
这两个软件包不依赖于彼此。这两个软件包都不依赖于拥有
/usr/share/Foo/Animal目录的任何其他软件包。在这种情况下,每个软件包必须拥有/usr/share/Foo/Animal目录。
这可以防止 被安装到系统上。
openSUSE 软件包不得在 %files 列表中包含任何重复的文件。
在两个或多个子软件包中打包的文件以及包含相同内容的文件都被认为是重复的文件。
建议运行 %fdupes %buildroot (需要添加 BuildRequires: fdupes) 以智能地消除重复内容,通过将这些文件替换为彼此的硬链接来完成。
权限
必须正确设置文件权限。可执行文件应设置为可执行权限,脚本也应如此(那些带有 #! 行的)。
SUID 位
默认设置的 SUID 位需要 SUSE 安全团队的明确批准。
有关更多信息,请参阅 openSUSE 软件包安全指南。
文档文件
源发行版中包含的任何相关文档都应包含在软件包中。
不相关的文档包括构建说明、普遍存在的 INSTALL 文件,其中包含通用的构建说明,例如,以及针对非 Linux 系统的文档,例如 README.MSDOS。还要注意您将文档包含在哪个子软件包中,例如 API 文档属于 -devel 子软件包,而不是主软件包。或者,如果文档很多,请考虑将其放入自己的子软件包中。在这种情况下,建议使用 *-doc 作为子软件包名称,并使用 Documentation 作为 Group 标签的值。
任何标记为 %doc 的文件不得影响应用程序的运行时。
也就是说,如果它在 %doc 中,即使它不存在,程序也必须能够正常运行。
使用 Sphinx 构建文档
要使用 Sphinx 构建捆绑文档到 HTML,请添加以下构建要求:
BuildRequires: python3-Sphinx
在构建部分中,我们执行实际构建
%build ... pushd docs %make_build html rm _build/html/.buildinfo popd
在文件部分中,我们可以添加构建的文档
%files ... %doc docs/_build/html
如果文档太大,请创建一个 -doc 子软件包。
许可证文件
对于较新的产品,许可证文件应使用 %license 标记 (请参阅 https://lists.opensuse.org/opensuse-factory/2016-02/msg00167.html 以了解原因)。
应避免使用 %doc。
为了保持向后兼容性,可以使用如下定义:
%if 0%{?suse_version} < 1500
%doc LICENSE
%else
%license LICENSE
%endif
配置文件
配置文件必须在软件包中标记为配置文件。
作为经验法则,除非您确信这样做会破坏事情,否则请使用 %config(noreplace) 而不是纯 %config。换句话说,在覆盖软件包升级中的本地更改之前,请仔细考虑。
一个不使用 noreplace 的示例情况是,当软件包的配置文件发生更改,以便新软件包版本无法使用旧软件包版本中的配置文件时。
每当使用纯 %config 时,请在 spec 文件中添加简短的注释,解释原因。
不要在 /usr 下使用 %config 或 %config(noreplace)。在 openSUSE 中,/usr 不被认为包含配置文件。
文件应只列出一次。文件部分中的这些语句也会将 defaults.foo 标记为 noreplace。请注意,第一行中的通配符也匹配第二行中的文件。
%config(noreplace) *.foo %config defaults.foo
正确的替代方案是显式列出所有文件 - 或 - 动态创建一个文件,其中包含 %files 语句,然后让文件宏使用 %files -f filelist.txt 从中读取。
开发文件
如果打包的软件包含仅用于开发的的文件,则这些文件应放入 -devel 子软件包中。以下是应位于 -devel 中的文件类型的示例:
- 头文件(例如 .h 文件)
- 未版本化的共享库(例如 libfoo.so)。版本化的共享库,例如
libfoo.so.3、libfoo.so.3.0.0不应位于-devel中。 - pkgconfig 文件。一个合理的例外是,当主软件包本身是一个开发工具时,例如 gcc 或 gdb。
包含 pkgconfig (.pc) 文件的软件包必须使用 BuildRequires: pkgconfig,以便添加适当的运行时依赖项 Requires: pkgconfig。
本地化文件
openSUSE 提供了几个 RPM 宏,特别是 %lang_package 和 %find_lang,这些宏在打包包含翻译的软件时很有用。
%lang_package 宏特别有用,当翻译文件很大并占用大量磁盘空间时。此宏会自动生成一个 %{name}-lang 子软件包来包含本地化文件。
通常,%lang_package 放置在 (子) 包描述之后和 %prep 部分之前。
%description ... %lang_package %prep %autosetup -p1
%lang_package 的更多详细信息,请访问 Packaging Conventions RPM Macros。%find_lang 宏用于根据软件包名称查找与您的软件包关联的所有本地化文件,并将这些文件的列表输出到单独的文件中。然后可以使用此列表将所有本地化文件包含在打包过程中。
%find_lang 宏应在 spec 文件的 %install 部分中执行,在所有文件安装到构建根目录之后。
%install
...
%find_lang %{name} %{?no_lang_C}
同时使用 %lang_package 和 %find_lang 有助于简化 spec 文件并避免几个常见的打包错误。
不正确做法的示例包括:
- 不要使用
%_datadir/*在单行中收集本地化文件。这种方法会不正确地接管本地化目录的所有权,这是不允许的。 - 大多数带有翻译的软件包包含许多本地化文件。使用
%find_lang比手动列出它们容易得多,例如:
%files lang ... %_datadir/locale/ar/LC_MESSAGES/%name.mo %_datadir/locale/be/LC_MESSAGES/%name.mo %_datadir/locale/cs/LC_MESSAGES/%name.mo %_datadir/locale/de/LC_MESSAGES/%name.mo %_datadir/locale/es/LC_MESSAGES/%name.mo ...
- 当在后续版本中添加新的本地化文件时,
%find_lang会自动包含它们,从而减少了对 spec 文件进行手动更新的需求。 - 要包含
%find_lang找到的本地化文件,请将-f %{name}.lang选项添加到%files部分。部分标题应为:
%files
...
%files lang -f %{name}.lang
%changelog
%find_lang 的更多详细信息,请访问 Packaging Conventions RPM Macros。非 ASCII 文件名
包含非 ASCII 字符的文件名必须编码为 UTF-8。
由于无法指出文件名采用哪种编码,因此对所有文件名使用相同的编码是确保用户可以正确读取文件名的最佳方法。如果上游提供未采用 UTF-8 编码的文件名,则可以使用像 convmv (来自 convmv 软件包) 这样的工具在 %install 部分中转换文件名。
Libexecdir
GNU 构建系统 (autotools) 提供了一个 "libexecdir" 变量/选项,它指定了一个“用于安装由其他程序而不是用户运行的可执行程序的位置”。软件包通常会在 libexecdir 中创建一个子目录,它们也可能会在 Automake 文件中使用预定义的便捷变量 ${pkglibexecdir};您可能会在 Makefile.am 中找到 "pkglibexec_PROGRAMS = myhelper"。在默认运行中,该目录将是 /usr/local/libexec。
未修改的 rpm 实用程序以相同的方式镜像此行为,即 %_libexecdir 宏扩展为 %_prefix/libexec。 (由于 rpm 通常将 prefix 设置为 /usr,因此在实践中,结果路径是 /usr/libexec。)
长期以来,文件系统层次结构标准 (Filesystem Hierarchy Standard) 并没有明确规定 libexec,包括长期使用的版本 2.2 和 2.3。SUSE 产品通过对 rpm 的补丁,将 %_libexecdir 定义为 /usr/lib,从而避免使用 /usr/libexec。而其他发行版和操作系统,例如 RedHat/Linux 系列或 BSD 系统,则按照最初的意图使用 libexec。
FHS 版本 3.0 引入(或者说 *重新* 引入)了 libexec 到规范中。从 2020-08-25 开始的 openSUSE Tumbleweed,以及由此派生的产品,再次扩展到 /usr/libexec。这意味着 Leap 15.3 将继续使用 /usr/lib。
变更日志部分 (%changelog)
openSUSE 使用单独的 changelog 文件,而不是将其放在 spec 文件中。
更多信息请参阅 HOWTO 编写良好的变更记录。
更多资源
- RPM Spec Wizard - 一个用于创建 RPM spec 文件的交互式指南