openSUSE:打包 Python

跳转到:导航搜索



打包 Python 是一个逐步介绍如何使用 openSUSE Build Service 为 openSUSE 和其他系统构建 Python 软件包的指南。

使用 py2pack 和 osc 进行快速自动化打包

使用 py2packopenSUSE Build Service 命令行客户端 osc,可以简化 Python 模块的打包过程。

本指南概述了一种快速打包 Python 项目的方法,以 zope.interface 软件包为例。

第 1 步:创建软件包

首先,在 PyPI 上找到确切的软件包名称。官方名称可以在页面 URL 中找到(例如,https://pypi.ac.cn/project/zope.interface/),以及页面顶部的 pip install 命令中(例如,pip install zope.interface)。

然后,使用 OBS 命令行客户端创建软件包

$ osc mkpac python-zope.interface
$ cd python-zope.interface

osc mkpac 命令用于创建新软件包。请使用您的 home: 项目来测试软件包,然后再提交。

要自动获取源 tarball,请使用 py2pack

$ py2pack fetch zope.interface
downloading package zope.interface-5.4.0...
from https://files.pythonhosted.org/packages/source/z/zope.interface/zope.interface-5.4.0.tar.gz

第 2 步:生成 Spec 文件

下载源存档后,生成 RPM spec 文件。操作如下:

$ py2pack generate zope.interface -f python-zope.interface.spec

这将创建一个名为 python-zope.interface.spec 的 spec 文件,其中包含从下载的源 tarball 中提取的元数据。

建议审查并调整生成的 spec 文件

  • 根据上游清单和要求审查 spec 文件中指定的所有依赖项,
    • 例如,如果软件包不编译任何内容,则删除 %{python_module devel} 依赖项,或者在需要时添加 %{python_module setuptools}
  • 调整测试依赖项:上游项目可能会列出可选的测试依赖项(例如,在 setup.py 的 [test] 下),py2pack 会将其转换为 Recommends: python-foo。对于构建时测试,请将其转换为
    BuildRequires: %{python_module foo} to be able to actually run the tests at build time. 
注意:mypy、coverage、pytest-cov、flake8 或 black 等静态分析工具通常对于打包目的来说是不必要的,可以省略。

第 3 步:构建并提交

一旦 spec 文件定稿

  1. 使用 osc build 在本地构建软件包。
  2. 使用 osc vc 创建更改日志。
  3. 使用 osc commit 将软件包提交到构建服务。

根据上游元数据的质量,可能需要对生成的 spec 文件进行调整。py2pack 尝试自动化大部分过程,但元数据不完整或不准确可能需要手动调整。

如需进一步帮助,请查阅 help 命令

$ py2pack help

故障排除

初次构建可能会因缺少依赖项而失败。检查构建日志中的导入错误,这通常表示需要添加到 spec 文件的 BuildRequiresRequires 部分的 Python 模块。


手动打包 Python 模块

通常不建议手动创建 Python 模块的 RPM 软件包,因为 py2pack 和 osc 等自动化工具大大简化了过程(参见上文)。但是,对于那些喜欢或需要手动打包的人,提供了以下指导。

参考 Spec 文件

以下软件包可作为有用的参考

通常,single-spec 打包格式用于任何提供旨在由其他软件包导入的 Python 模块的分发软件包

什么是 Single-Spec

Single-spec 打包是一种从单个 RPM spec 文件构建 Python 模块的多个变体的方法。每个变体对应一个不同的 Python 解释器“版本”。历史上,这些版本包括 python2 和 python3,但现代用法已完全转向 Python 3 的多个版本。

在 openSUSE Tumbleweed 上,默认构建目标目前包括 python311、python312 和 python313,其中 python313 是主要版本。python2 和 python310 等旧版本已不再可用。SLE15 Leap 15.X 将 python36 或可选的 python311 作为其默认构建目标。

支持的 Python 版本的列表由 python-rpm-macros 软件包定义。要启用 single-spec 功能,需要包含以下构建要求

BuildRequires:  python-rpm-macros

兼容性垫片

对于旨在支持 openSUSE Leap 15 之前的发行版 的软件包,需要在 RPM spec 文件中包含 %python_module 宏的兼容性定义

%{?!python_module:%define python_module() python-%{**} python3-%{**}}

构建集和命名策略

SUSE 遵循 Python 模块软件包的标准化命名策略。在 Python 中,模块类似于 C 中的共享库——它是一个可重用组件,为其他程序提供功能,但通常不会独立执行。

所有 Python 模块软件包,无论它们是用纯 Python 实现还是包含 C 扩展,都应使用以下约定命名:python-模块名

<模块名> 应与 Python Package Index (PyPI) 上列出的项目官方名称一致。这确保了跨发行版的一致性,并使用户和工具更容易查找和引用软件包。

命名策略原理

历史上,Python 软件包通常根据 site-packages 层次结构中的目录名称命名。

这种方法不一致,因为单个 Python 模块可以安装多个目录,或者不同的软件包可以将内容安装到名称相似的目录中。例如,在 PyPI 上搜索“daemon”会显示多个不相关的模块,这些模块在基于目录的命名下可能会发生冲突。

当前的命名策略提供了几个主要优点

命名策略的适用性

此命名策略专门适用于 Python 模块和库,即旨在用作其他 Python 代码依赖项的软件包。

它不适用于不设计和计划用作库的最终用户应用程序。

在这种情况下,软件包应保留其正常的上游名称(如 PyPI 上所列),并且不应拆分为用于多个 Python 解释器版本的子软件包。

关于什么是应用程序,什么是模块,有一些特殊情况——例如,许多模块都带有一些简单的命令行工具,允许您直接使用其部分功能。

经验法则是:

  • 如果此软件包将成为其他 Python 应用程序的依赖项,请使用 single-spec 并应用命名策略,
  • 否则,只保留模块名称,并指定所有依赖项都来自主要的 Python 版本(python3)。

同样,此策略不适用于 Jupyter 内核、扩展和类似的不设计和不计划用作库的软件包。

  • 如果它们旨在仅在 Jupyter 环境的范围内使用,则应将其命名为 jupyter-模块名 而不是 python-模块名,其中 模块名 仍然是此模块在 Python Package Index 上的名称。此类软件包如果包含 Python 代码,还必须提供 python3-模块名 软件包。
  • 反之,主要为 Python 软件包但提供 Jupyter 接口的软件包,其 python3 版本应提供 jupyter-模块名,或者将其 Jupyter 数据目录中的组件拆分到其自己的 jupyter-模块名 子包中。

源 URL

对于可从 PyPI 获取的软件包,正确的源 URL 如下

https://files.pythonhosted.org/packages/source/t/tox/tox-%{version}.tar.gz

首选主机名为 files.pythonhosted.org。所有其他主机名(如 pypi.iopypi.orgpypi.python.org)最终都将重定向到它。为了简化此转换,您可以将下面提到的链接放入其中,它将通过 spec-cleaner 工具自动转换为正确的格式。

带哈希值的 URL

您从 PyPI 获取的链接(通过进入软件包的下载部分)将指向某种哈希值,例如

https://files.pythonhosted.org/packages/99/20/2fd208e75c05975bf6436258f469aa233045b93d195cdb442045de0923cc/tox-2.7.0.tar.gz

我们不希望这样。

“语义 URL”仍然可用

/packages/source/<first letter>/<full name>/<full name>-<version>.<extension>

未以 tarball 形式分发的软件包

在某些情况下,软件包仅通过 PyPI 以 wheel 形式分发。这些是扩展名为 .whl 的文件。在这些情况下,如果可用,应使用另一个上游源(例如来自 github,见下文)。

Icon-warning.png
警告:请勿打包预构建的二进制文件!有关更多信息,请参见 打包指南

然而,在某些情况下,PyPI 档案中缺少重要文件。例如,它们可能缺少测试或许可证文件。此类问题应向上游报告。如果是一个文件,例如需要许可证文件的许可证,并且上游有包含该文件的源(例如 GitHub),则可以在等待上游提供适当的修复时将其包含在内,并将源 URL 设置为该文件的“原始”版本。例如,

https://raw.githubusercontent.com/imatlab/imatlab/v%{version}/LICENSE.txt

然而,对于缺少许多文件或整个目录(特别是测试)的软件包,应使用上游源。对于 github,该源的形式为

https://github.com/numpy/numpy/archive/v1.16.2.tar.gz#/numpy-1.16.2.tar.gz

#/numpy-1.16.2.tar.gz结尾部分很重要。如果省略,openSUSE:Factory 源验证器将下载一个名为v1.16.2.tar.gz代替numpy-1.16.2.tar.gz这将导致源验证失败。

BuildRequires

BuildRequires 不是有条件的,适用于整个 spec 文件。没有“如果我为 Python 2 构建,我需要软件包 foo”这样的说法。所有变体的所有软件包都是必需的。

要自动要求构建集中所有 Python 版本的 Python 软件包,请使用 %python_module

BuildRequires: %{python_module setuptools}
BuildRequires: %{python_module py >= 1.4}

请注意,版本要求是宏的参数,因此如果您使用大括号(可选),它必须在其中。

这不应与 Requires 标签一起使用。 %python_module 宏只能用于 BuildRequires,因为它一次性引入所有版本。Requires: 应仅指定特定版本的软件包,并由 %python_subpackages 重写。

如果您的软件包只需要一个 Python 版本,请直接声明依赖项,而不要使用宏。

BuildRequires: python2-enum34
BuildRequires: python3-astroid

openSUSE Tumbleweed 已经删除了大部分 Python 2.x 软件包,并且不再在构建集中包含 python2。它默认定义了 osc build --without python2 的等价物。因此,如果您仍然需要支持为旧发行版构建 python2 软件包,可以通过以下方式排除 Tumbleweed 的要求

%bcond_without python2
%if %{with python2}
BuildRequires: python2-enum34
%endif

如果您需要 BuildRequirepython(解释器二进制文件),您可以使用 %pythons

BuildRequires: %{pythons}

如果您在某些条件下需要软件包,请使用 RPM 布尔依赖项。您可以使用伪宏 %python(您不得在其他任何地方定义 %python)来引用扩展版本的 Python 版本

BuildRequires: %{python_module aiocontextvars >= 0.2.2 if %python-base < 3.7}
Icon-warning.png
警告: %pythons%python_module 不仅需要最新的python-rpm-macros包,还需要在 OBS 项目配置中直接或通过继承提供相应的定义。
  • %pythons 目前为 openSUSE Leap 15.0 及更高版本定义。
  • %python_module 的布尔依赖支持目前定义在openSUSE:Factory, devel:languages:python:backportsdevel:languages:python适用于 15.4+,但不适用于普通的 SLE/Leap 项目。因此,此语法通常仅适用于 Tumbleweed 专用构建

自动运行时需求确定

一些其他 Linux 发行版(即 Fedora)更喜欢使用自动依赖生成器并使用以下构造

BuildRequires: python-rpm-generators
%{?python_enable_dependency_generator}

不要在应包含在 Factory 中的软件包中使用此类构造!

不幸的是,依赖生成器需要完美维护的上游元数据(这远非如此),即使如此,盲目依赖这些生成器也会导致数百种互斥情况(特别是当使用 < version 构造时),这将导致我们想要避免的维护噩梦。

此外,即使这行得通,任何构建时和测试依赖项仍然必须使用 BuildRequires 手动指定。

软件包 python-rpm-generators 提供了更多有用的宏,请参阅 Github 上的项目页面

Requires、Provides 及类似内容

在许多情况下,您无需做任何事情。single-spec 重写器会将您的 Requires 转换为与生成的软件包匹配。只需确保 Requires 内容确实匹配setup.pyrequirements.txt并且没有遗漏任何内容。

Icon-warning.png
警告:特别是,请勿在 Requires 中使用 %python_module

Python 软件包名称在Requires, Requires(pre)以及所有其他脚本运行时要求,提供的功能 (Provides), Recommends, Suggests, 废弃 (Obsoletes), Conflicts, 补充说明Enhances在由 %python_subpackages 自动生成的子包声明中,会自动转换为正确的版本。

如果需求或功能名称以 python- 开头,并且带有默认的 python 版本(Tumbleweed 中为 python313-,旧发行版中为 python3-),或者与您的软件包名称具有相同的 python 前缀(因此在软件包 python3-foo 中为 python3-bar),则 python 名称将更改以匹配生成的软件包。这对于 python 本身也适用。

如果要阻止名称重写,请将其隐藏在宏后面,例如使用 %oldpython,如下所示。

转换器考虑了 packageand 表达式以及 %requires_ge%requires_eq 宏。但是,对这些宏的支持必须明确编码。如果您发现未转换的表达式,请将其发布到opensuse-packaging邮件列表,或者针对python-rpm-macros提交 bug。从 python-rpm-macros 20210628.eccf3f2 版本开始,也支持 RPM 布尔要求。

特定于某个 Python 版本的依赖

您可以通过使用 %python_flavor 变量将某些软件包包裹在条件语句中,从而指定它们只应包含在某些 Python 版本中

Requires: python-idna
%if "%{python_flavor}" == "python2"
Requires: python2-enum34
%endif

作为快捷方式,每个版本都有一个 %ifpython 宏:%ifpython2%ifpython3%ifpypy3

%ifpython2
Requires: python2-enum34
%endif

请注意,快捷方式不得嵌套在其他条件语句中,否则您可能会收到 %endif without %if 错误消息。如果需要嵌套条件语句,请使用 %python_flavor 条件语句。

Icon-warning.png
警告:这是通过将 %ifpython 部分复制到生成的子包定义中来实现的,并在那里评估其条件应用。子包有其自己的运行时要求、文件列表和 %pre/%post/等脚本。来自%prep, %build, %install, %check部分和全局标签(如BuildRequires不是子包特有的。请勿将 %ifpython 用于这些。

通过 %ifpython3%if "%{python_flavor}" == "python3" 测试 python3 版本的做法已弃用。这些条件语句不适用于包含多个 Python 3 版本的存储库,即它们不适用于任何名为python38, python39等版本。如果您有一个只应适用于其中一个生成的 Python 3 软件包的部分,请使用

%if "%{python_flavor}" == "python310"
Requires: python310-specialfuturepackage
%endif

如果一个标签仅适用于主要的 python3 版本,直接在 SLE/Leap 中,并间接通过例如 TW 中的 python38,请使用

%if "%{python_flavor}" == "python3" || "%{python_provides}" == "python3"
Provides: foo
%endif

另请参阅 基于 Python 版本的条件

废弃和提供旧符号

以下序列

Obsoletes: python-distribute < %{version}
Provides:  python-distribute = %{version}

这意味着您的 python2-package 将废弃/提供 python2-distribute,您的 python3-package 将废弃/提供 python3-distribute,您的 pypy3 软件包将废弃/提供 pypy3-distribute。通常,这不是您想要的。

首先,在许多情况下,这仅适用于 Python 2,因此该序列应包含在 %ifpython2 条件块中。

其次,您会注意到没有任何软件包废弃/提供 python-distribute
这是您如何做到这一点的方法

%define oldpython python
%ifpython2
Obsoletes: %{oldpython}-distribute < %{version}
Provides:  %{oldpython}-distribute = %{version}
%endif

%python_module 在 Provides 中

如果您正在创建将适用于所有版本的子包(可以是 -doc 子包),有时您需要为所有版本提供一个符号。例如,您的软件包 python-foo-doc 也应该提供 python2-foo-docpython3-foo-doc 等。在这种情况下,您可以使用 %python_module

%package -n python-foo-doc
Provides: %{python_module foo-doc = %{version}}

这也是您可以在 Requires 中使用 %python_module 的一种情况。

(请参阅下文,关于声明带 -n 的包以防止自动生成。)


%python_subpackages

非常简单:将 %python_subpackages 宏单独放置在 spec 序言(您的软件包 %description 开始的部分)的末尾。

Provides: pylint
BuildArch: noarch

%python_subpackages

%description

此宏会发出所有子包描述、%files 和自动生成部分的 scriptlet 部分。


子包声明

子包会自动转换。如果您的子包是 %package foo,则 singlespec 会从中创建 python3-yourpackage-foo 和所有其他内容。

如果要阻止此操作,请使用 %package -n %{name}-foo,或者使用全名 %package -n python-yourpackage-foo。这将确保跳过该子包。

这也意味着名为 %package -n yourpackage-python 的软件包将不会被处理。将来会为这些提供一种机制。

通用文档包

通常,随附的文档或示例非常大,应放在单独的子包中。软件包可以包含以下部分,例如

%package -n %{name}-doc
Summary:        Documentation files for %name
Group:          Documentation/Other

%description -n %{name}-doc
HTML Documentation and examples for %name.

%files -n %{name}-doc
%doc examples docs/_build/html/

构建宏

要以现代的 PEP517/PEP518 方式构建软件包,请使用

%build
%pyproject_wheel

%install
%pyproject_install

这些宏依赖于 pip,并且可能需要安装程序后端,例如 setuptools/wheel 对、flit-corepoetry-corehatchling。后端在 pyproject.tomlbuild-system.requires 表中指定。

对于尚无pyproject.toml文件的软件包,请使用 setuptools, wheel 后端。

不再允许使用传统方法(即使用宏 %python_build%python_install)构建软件包。

通用

如果设置环境变量,请先导出它们

export CFLAGS="-fwrapv"
%pyproject_wheel

对于任何其他命令,您可以使用 %python_exec%python_expand。这也是运行 python 可执行文件的好方法。例如

%install
%python_exec setup.py extracommand
%python_expand ./make PYTHON=$python
%python_expand PYTHONPATH=%{buildroot}/%{$python_sitelib} my-alternative-cloned-entrypoint-%{$python_version}

%python_expand

对于比执行所有解释器更复杂的操作,请使用 %python_expand 宏。这将重复地将 %{$python}%{$python_sitelib} 等字符串扩展到当前使用的版本。

%python_expand rm -r %{buildroot}/%{$python_sitelib}/file.txt

结果是(除了一些构建目录操作外)

rm -r %{buildroot}/%{python2_sitelib}/file.txt
rm -r %{buildroot}/%{python3_sitelib}/file.txt
rm -r %{buildroot}/%{pypy3_sitelib}/file.txt

重要提示:您可以使用 %python_expand 替换宏定义,但请确保使用 %{$python_sitelib},即带有 $ 符号。如果使用纯 %{python_sitelib},宏将在 %python_expand 修改它之前展开。

%python_expand 的一个常见用途是与 fdupes 结合使用,例如

%python_expand %fdupes %{buildroot}/%{$python_sitelib}/mymodule

如果将行用 {} 括起来,则可以使用多行 %python_expand。唯一的限制是第一行不能为空(但可以是以 # 开头的注释)

%{python_expand # this will expand the following section
        %$python_install
        mv %{buildroot}/%{_bindir}/exename %{buildroot}/%{_bindir}/exename-%{$python_bin_suffix}
}

这在运行测试时需要一些前期步骤时很有用

%check
%{python_expand #
        rm -rf .testrepository
        $python -m unittest discover -v
}

命名特定于版本的F文件

特定于某个版本的可执行文件应使用 %python_bin_suffix 而不是 %python_version 来命名。这对于 CPython 扩展为 %python_version,对于 PyPy 扩展为 pp%{python_version}。如果支持 Jython,它也将获得一个特定的 bin_suffix

特定于某个版本的构建目录应使用 %python_prefix。这会扩展为版本名称。


文件列表

如果您的包名为 python-something(即名称前缀是 python 而不是特定版本),您必须用 %{python_files} 宏标记您的 %files 部分。

%files %{python_files}
%{python_sitelib}/foo
%{python_sitelib}/foo-%{version}*-info

%files %{python_files plugins}
%{python_sitelib}/foo-plugins

您可以使用 %ifpython2 和类似的宏来有条件地只在某些版本中包含某些文件。此外,您可以使用快捷方式

%files %{python_files}
%{python_sitelib}/foo
%{python_sitelib}/foorun.py*
%pycache_only %{python_sitelib}/__pycache__

使用 %pycache_only%ifpycache 来标记 __pycache__ 目录。


可执行文件

对于带有可执行文件(Python setuptools 称之为入口点)的多版本软件包,您需要确保它们不会创建文件冲突。这可以通过使用下面描述的 update-alternatives 来实现。在这种情况下,%python_clone 宏对于“正确”的 Python 版本是必需的。

一些软件包仍然依赖于 %python3_only 语法,它确保二进制文件只传递给特定版本。这种方法虽然更短,但在多 Python 解释器环境中存在无法工作的缺点,因此我们已弃用其使用。

替代方案

有时,让用户将未版本化的可执行文件名称切换到软件包的一个版本或另一个版本是有用的。

在现代 openSUSE 发行版中,我们使用 libalternatives,它支持事务性更新。python-rpm-macros 通过 bcond 支持使用新的 libalternatives 或旧的 update-alternatives。

通过将 --with libalternatives 设置为默认值来启用 *libalternative*

%bcond_without libalternatives

为了支持旧版本 suse 的 update-alternative,您可以使用条件语句

%if 0%{?suse_version} > 1500
%bcond_without libalternatives
%else
%bcond_with libalternatives
%endif

此示例表明 *libalternatives* 仅适用于 Tumbleweed/Leap 16。

在构建和运行时需要 alts 软件包

Requires:       alts
BuildRequires:  alts

如果我们要支持使用相同 spec 文件的旧发行版,也可以使用条件语句来完成此操作

%if %{with libalternatives}
Requires:       alts
BuildRequires:  alts
%else
Requires(post): update-alternatives
Requires(postun):update-alternatives
%endif

然后,您需要在 %install 部分设置替代方案。

  • 如果您正在使用 %python_clone 创建可执行文件,请传递 -a 选项:%python_clone -a %{buildroot}/%{_bindir}/executable
  • 如果不是,并且相关文件是 %{_bindir}/something,则调用 %prepare_alternative something
  • 如果文件不在 %{_bindir} 中,则必须指定路径:%prepare_alternative -t /path/to/file file。路径不包含 %buildroot

在转换为 libalternatives 期间清理旧的 update-alternatives 条目,这仅在存在使用 update-alternatives 的旧版本软件包时才需要

%pre
# removing old update-alternatives entries
%python_libalternatives_reset_alternative <name>

参数 *\<name\>* 与调用 *%python_uninstall_alternative* 时使用的相同。

最后,在文件列表中提及替代的、未版本化的文件

%python_alternative %{_bindir}/exename

您可以在 [1] 处查看包含可执行文件和手册页的完整 spec 文件。

使用 %python_group_libalternatives 对条目进行分组:(类似于在 %python_install_alternatives 中作为主从安装的内容,但不包括手册,因为这些不属于 group= 条目)

%install
...
%python_clone -a %{buildroot}/%{_bindir}/cmd1
%python_clone -a %{buildroot}/%{_binddir}/cmd2
%python_clone -a %{buildroot}/%{_mandir}/man1/cmd1.1
%python_group_libalternatives cmd1 cmd2
update-alternatives(旧版)

update-alternatives 允许您将其他内容(通常是手册页)与可执行文件一起切换。

作为先决条件,您需要提供版本特定的文件名,也就是说,对于您提供的每个 file,所有版本都应存在 file-%python_bin_suffix。(对于 manpage.1,适当的名称是 manpage-%python_bin_suffix.1

首先,您需要在 %install 部分设置替代方案。

  • 如果您正在使用 %python_clone 创建可执行文件,请传递 -a 选项:%python_clone -a %{buildroot}/%{_bindir}/executable
  • 如果不是,并且相关文件是 %{_bindir}/something,则调用 %prepare_alternative something
  • 如果文件不在 %{_bindir} 中,则必须指定路径:%prepare_alternative -t /path/to/file file。路径不包含 %buildroot

其次,创建相应的 %post%postun 部分。确保您有适当的要求

Requires(post):   update-alternatives
Requires(postun):  update-alternatives

然后分别使用 %python_install_alternative%python_uninstall_alternative

%post
%python_install_alternative exename

%postun
%python_uninstall_alternative exename

第三,在文件列表中提及替代的、未版本化的文件

%python_alternative %{_bindir}/exename

您可以在 [2] 处查看包含可执行文件和手册页的完整 spec 文件。

分组替代方案

update-alternatives 系统允许将同一组中的多个文件一起切换。如果您想安装一个可执行文件及其手册页,或者属于同一功能组的多个可执行文件,这将非常有用。

要利用这一点,请将文件指定为 %python_install_alternative 的多个参数

%{python_install_alternative pylint pylint.1 epylint epylint.1}

宏可以识别手册页名称并正确处理它们,但第一个参数始终需要是可执行文件。或者,您可以指定文件的完整路径。

%python_install_alternative 的第一个参数是组名。这是 %python_uninstall_alternative唯一参数;您通过一个名称卸载整个组。


决定是为单个还是多个 Python 版本构建

仅限 Python2 (Leap)

仅存在于 Python 2 的非单 Spec 软件包应保持原样。新软件包应命名为 python2-foo。旧软件包应保留为 python-foo,但您应添加 Provides: python2-foo。您还应确保所有 BuildRequires 和 Requires 都适用于python2python2-foo.

Python 3 (Leap 15 和 Tumbleweed)

仅存在于单个 Python 3 版本的软件包(应用程序)可以省略 single-spec 系统,或者在构建集中使用单个版本的 single-spec。模块应为 single-spec,包含所有可能的版本。有关应用程序和模块之间的区别,请参见 #构建集和命名策略。定义应在 specfile 的顶部进行。

对于应用程序

  • %define pythons python3

对于模块

  • 使用 %define skip_python2 1 以便 Leap 不会尝试构建 python2 版本。
  • 跳过所有不支持的版本,例如 %define skip_python310 1
  • 确保至少保留一个版本进行构建。

Python 3 Leap 15

Leap 15 有两个 Python3 解释器可供选择:3.6 和 3.11。为了保持向后兼容性,系统 python 将在 Leap 15 上保持 3.6,但通过使用以下宏可以使用现代版本。为了避免与当前版本冲突,将使用一个宏来覆盖默认的 pythons 宏,并且只为现代版本(本例中为 Python 3.11)构建,而不为旧版本(3.6)构建。

使用此宏使现代 python 软件包能够在 Leap 上构建,只使用最新的 python 解释器(3.11),而不使用旧的(3.6)

%{?sle15_python_module_pythons}

该宏在 OBS 的项目设置中定义。

为避免源包名称冲突,为主要 python (3.6) 构建的包应重命名为 python3-foo,并将继续提供带有 python3- 前缀的二进制文件。

这些是 OBS 项目设置中定义的宏

%sle15_python_module_pythons() %global pythons python311
%sle15allpythons() %global pythons python311 python3


如果您只想为单个现代 Python 版本(Leap 16/TW 的情况下为 3.11 或更新版本)拉取依赖项,则可以使用以下语法代替

 BuildRequires: %{modern_python}-Sphinx


仅针对单个依赖项,或使用

%{?single_pythons_311plus}

而不是 %{?sle15_python_module_pythons} 来选择现代 Python,但不要为发行版中任何其他可用的 Python 版本构建。对于 Tumbleweed,这意味着您的软件包将仅为主要的 Python 解释器构建,这对于纯 Python 应用程序(不为其他应用程序提供模块)来说最有意义。


基于 Python 版本的条件

如果您需要区分 Python 版本,可以使用 %python_version_nodots 宏,它会扩展到当前自动生成的版本软件包的 Python 主版本和次版本,并删除点。

例如

%if %python_version_nodots < 39
Requires: python-importlib_resources
%endif

如果需要确定特定通用提供者的版本,请使用宏的版本化变体,例如

示例
%python2_version_nodots 27
%python3_version_nodots 38

/usr/bin/python3 的依赖

对于使用 python-rpm-macros 的普通 python 软件包,这不是问题,不需要特殊操作。宏 %pyproject_install%python_install 将正确更新脚本 shebang。

目前,/usr/bin/python3 是由当前系统 python 软件包(在 Factory 中是 python313)提供的链接。任何提供带有 shebang #!/usr/bin/python3 的 python 脚本的软件包都将依赖于 /usr/bin/python3

python-rpm-macros 中有一个宏可以自动修复 python 脚本中的 shebang,它可以在安装阶段使用,就在 fdupes 调用之前,并指向真实的二进制文件而不是链接

 %python3_fix_shebang

此宏扩展为

 for f in /home/abuild/rpmbuild/BUILDROOT/%{NAME}-%{VERSION}-
 %{RELEASE}.x86_64/usr/bin/*; do 
   [ -f $f ] && sed -i "1s@#!.*python.*@#!$(realpath /usr/bin/python3)@" $f 
 done

并且确实会将 shebang 替换为指向 #!/usr/bin/python3.11。这将使脚本正常工作,即使发行版中的系统 python 发生变化,也可以避免与不同 python 运行而找不到 /usr/lib/python3.11/site-packages 中的依赖项的问题。

如果 python 脚本不在通常的 bin 路径中,则 fix_shebang 宏将不足够。在这种情况下,应使用特殊的宏 %python_fix_shebang_path,例如

 %python3_fix_shebang_path %{buildroot}%{_libexecdir}/*

常见陷阱

需要注意的事项(也可能导致您的提交被拒绝)

  1. 如果重新定义了 %python_module,请检查它是否包含 python-%{**} 而不是 python-%1
  2. %python_module 用于 BuildRequires
  3. 不要将 %python_module 用于 Requires
  4. 确保存在 %python_subpackages
  5. %files 部分必须标记为 %files %{python_files}
  6. %python_expand 中,确保使用 %{$python_} 宏而不是 %python_
  7. 使用 %python_bin_suffix 而不是 %python_version 来区分可执行文件或构建目录
  8. 如果行或代码块应适用于主要的 Python 3 版本(仅限或也适用于可能的其他版本),请勿使用 %ifpython3%python3_only

运行测试

Python 代码不像 C 应用程序那样真正被编译。因此,分发完全不起作用的东西非常容易。为了防止这种情况,所有应用程序都必须运行并通过其测试,除非上游自己没有提供任何测试。

运行测试最简单的方法是使用 unittest 发现模式,我们为此提供了宏(当使用最新的python-rpm-macros包时):无架构包使用 %pyunittest -v,平台相关包使用 %pyunittest_arch -v

当然,这仅在软件包支持常规 Python 单元测试时才有效。如果此调用不起作用,则必须深入研究并检查开发存储库中的各种文件,例如.github/workflows, .travis.yml,或者tox.ini.

要运行 pytest,可以使用 %pytest%pytest_arch 宏。

这些宏支持提供额外的命令行标志,例如用于取消选择不应运行的测试。

更多宏

single-spec 定义的所有宏的完整文档可在 python-rpm-macros 软件包的 GitHub 页面上找到。