Python 打包

跳转至: 导航, 搜索

Python 打包专案会一步一步指导您如何在构建服务上为 openSUSE 和其他发行版打包 Python 软件包。

快速和自动化的方式

假设你想要打包 zope.interface 模块,你不知道它准确的名字,也不知道去哪里下载它。首先,搜索它,如果能找到正确的 Python 模块那么就下载它的源代码。

$ py2pack search zope.interface
搜索 zope.interface 模块中...
找到 zope.interface-3.6.1
$ py2pack fetch zope.interface
下载压缩包 zope.interface-3.6.1...
从 http://pypi.python.org/packages/source/z/zope.interface/zope.interface-3.6.1.tar.gz

下一步你需要为你的发行版生成一个打包引导文件。对于基于 RPM 的发行版来说,你需要生成一个名为 python-zopeinterface.spec 的 spec 文件:

$ py2pack generate zope.interface -t opensuse.spec -f python-zopeinterface.spec

有了源代码包和打包引导文件之后,你就可以生成 RPM 二进制安装包了。这个最后一步取决于你使用什么发行版。对 使用构建服务的 openSUSE 来说,完整的步骤是:

$ osc mkpac python-zopeinterface
$ cd python-zopeinterface
$ py2pack fetch zope.interface
$ py2pack generate zope.interface -f python-zopeinterface.spec
$ osc build
$ osc vc
$ osc commit

第一行使用了 osc,构建服务的命令行工具来生成一个新打包环境(在你的私人车库下)。py2pack 的两步你已经知道了。最后,本地编译测试,生成变更日志(通过 osc vc),然后把最终结果发回给构建服务让大家都能用到。另外,因模块而异,你可能需要稍微改动下生成的 spec 文件。Py2pack 是个智慧型程序,可以自动处理所有它可以做的,但是它需要依靠模块提供的元数据来做。因此,错误的元数据会产生坑爹的结果。可以通过下面命令去了解它:

$ py2pack help

手动打包 Python 模块的技巧

一个简单的实例

如下是一个简单的 spec 例子(来自于 python-Jinja2 软件包)。你可能不需要依赖 'python-distribute'。


 #
 # spec file for package python-Jinja2
 #
 # Copyright (c) 2012 SUSE LINUX Products GmbH, Nuernberg, Germany.
 #
 # 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/
 #
 
 
 Name:           python-Jinja2
 Version:        2.6
 Release:        0
 Summary:        A fast and easy to use template engine written in pure Python
 License:        BSD-3-Clause
 Group:          Development/Languages/Python
 Url:            http://jinja.pocoo.org/
 Source:         http://pypi.python.org/packages/source/J/Jinja2/Jinja2-%{version}.tar.gz
 BuildRequires:  fdupes
 BuildRequires:  python-devel
 BuildRequires:  python-distribute
 Provides:       python-jinja2 = %{version}
 Obsoletes:      python-jinja2 < %{version}
 BuildRoot:      %{_tmppath}/%{name}-%{version}-build
 %if 0%{?suse_version} && 0%{?suse_version} <= 1110
 %{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
 %else
 BuildArch:      noarch
 %endif
 
 %description
 Jinja2 is a template engine written in pure Python.  It provides a Django
 inspired non-XML syntax but supports inline expressions and an optional
 sandboxed environment.
 
 %package vim
 Summary:        Jinja2 syntax files for Vim
 License:        BSD-3-Clause
 Group:          Productivity/Text/Editors
 Requires:       %{name} = %{version}
 
 %description vim
 Vim syntax highlighting scheme for Jinja2 templates.
 
 %package emacs
 Summary:        Jinja2 syntax files for Emacs
 License:        GPL-2.0+
 Group:          Productivity/Text/Editors
 Requires:       %{name} = %{version}
 
 %description emacs
 Emacs syntax highlighting scheme for Jinja2 templates.
 
 %prep
 %setup -q -n Jinja2-%{version}
 
 %build
 python setup.py build
 sed -i 's/\r$//' LICENSE # Fix wrong EOL encoding
 
 %install
 python setup.py install --prefix=%{_prefix} --root=%{buildroot}
 install -Dm644 ext/Vim/jinja.vim %{buildroot}%{_datadir}/vim/site/syntax/jinja.vim # Install VIM syntax file
 install -Dm644 ext/jinja.el %{buildroot}%{_datadir}/emacs/site-lisp/jinja.el # Install Emacs syntax file
 %if 0%{?suse_version} > 1010
 %fdupes %{buildroot}%{_prefix}
 %endif
 
 %files
 %defattr(-,root,root,-)
 %doc AUTHORS CHANGES LICENSE artwork examples ext/JinjaTemplates.tmbundle.tar.gz
 %{python_sitelib}/*
 
 %files vim
 %defattr(-,root,root,-)
 %{_datadir}/vim
 
 %files emacs
 %defattr(-,root,root,-)
 %{_datadir}/emacs/site-lisp/jinja.el
 
 %changelog

命名策略

openSUSE 针对 Python 模块包有一个特殊的命名策略。Python 的模块和 C 语言的共享库是一样的 - 一小段代码,自己不工作,只是构建到其他 Python 程序的桥梁。

所有的 Python 模块包,不管是纯 Python 的还是基于 C 语言,都应以 python- 前缀 + 模块名来命名。模块名 应是此模块在 Python 软件包名录,Python 编程语言官方第三方软件仓库,中的名称。

此前,Python 软件包被以 site-packages 文件夹中的文件夹名称冠名。但这经常会变得很随意,因为一些模块会在那个目录下安装大于一个文件夹。另外,Python universe 上也有好多模块共享一个文件夹名称的情况(在 PyPI 上搜索 daemon),这样就不容易找出那些文件是软件包带来的了。此外,该新命名方式有如下好处:

但是该命名策略不适用于面向最终用户的软件 - 因此如果你打包的是在应用程序菜单里有图标的程序,你直接命名为源代码包的名字就可以。注意,许多这样的程序安装它自身的一部分到 Python 的 site-packages 目录,这时就会触发命名策略检查。你可以忽略这样的报警。

也有一些很难区分它究竟是应用程序还是模块的例子 - 比如,许多模块附带了简单的命令行程序,允许你直接使用它的一部分功能。经验法则是这样的:如果你认为用户安装你的软件包是为了用那个命令行程序,那么就正常的命名。如果这个软件包是作为其他 Python 应用的依赖存在的,那么就按照 Python 模块命名策略来命名。

BuildRequires 编译依赖

使用 BuildRequires: python-devel。技术上 BuildRequires: python-base 对于纯 Python 模块是足够的(没有 C 代码这种),但是它增加了复杂性却没有得到太多的好处。

一些模块需要 setuptools 才能编译。在这种情况下在编译依赖里加上 python-setuptools (最好是它的集大成者 python-distribute) 软件包。

Python 版本

在 openSUSE 里,即使和版本无关的 Python 软件包也需要依赖特定版本系列的 Python。Python 版本系列由主要和次要版本号组成。例如,目前的 Python 版本系列是稳定版本 2.7,python 3 是 3.2。(2012.05) 这是因为你的软件包会装文件到版本相关的目录 /usr/lib(64)/pythonX.Y/site-packages

%py_requires 宏会自动帮你选择正确的版本。你也可以使用 Require: python-base = %py_ver

文件位置

openSUSE 的 distutils 默认配置会安装到 /usr/local 路径,和 autotools 类似。因此当使用 install 命令时,你需要指定 --prefix=%{_prefix},这样软件包才能安装到正确的 /usr 路径。

所有的 Python 源代码和 bytecode 二进制预编译文件都应该安装到 /usr/lib(64)/pythonX.Y/site-packages,或 /usr/lib(64)/yourapp。FHS 标准认定 /usr/share 目录只能存放数据,所以不能把 Python 脚本放到那里。 但这只是个建议而不是强制要求。如果你的软件包上游安装到了 /usr/share,请试着说服他们,但请也不要觉得责任感上头去补缀软件包到死,只是为了让它能安装到 /usr/lib

文件列表

有两个宏对列示文件非常有用:

  • %python_sitelib 会展开成 /usr/lib/pythonX.Y/site-packages。 这是与操作系统架构无关的模块的安装位置。
  • %python_sitearch 会展开成 %{_libdir}/pythonX.Y/site-packages,也就是,根据您的操作系统架构展开称 /usr/lib 或 /usr/lib64。这是与操作系统架构相关的模块的安装位置。

对于平台无关的 Python 软件包,下面是一个简单的例子:

%install
python setup.py install --prefix=%{_prefix} --root=%{buildroot}

%files -f INSTALLED_FILES
%defattr(-,root,root)
%{python_sitelib}/*

系统架构

如果你的软件包只包含 python 源代码If your package only contains python sources (.py), bytecode 预编译二进制文件 (.pyc and .pyo) 和与平台无关的数据,你应该把它标记为 noarch(no arch 平台无关)。在你的 spec 范式文件的前半部分加入 BuildArchitectures: noarch。这样的模块应该完全安装到 %python_sitelib。

否则,整个模块就应该安装到 %python_sitearch。注意,不可以让模块的一部分在 %python_sitelib,另一部分在 %python_sitearch。在大多数情况下,即使软件包包含了多个模块,它们也都应该在一个路径里。

记住这样一句话: 整个模块要么是平台无关的,要么不是。 即使一个 C 的二进制函数库或者纯 python 模块里面依赖特定平台的配置文件会把整个模块标记为平台相关的,而整个模块的代码是平台无关的,这时,你也不能把它标记为平台无关的 noarch。这通常由 distutils 处理,所以你多数时候不需要担心太多。

某些软件包或许会把它的一部分安装到 %python_sitelib,另一部分安装到 %python_sitearch。这种设定是 bug 和问题的常见来源。如果你不明确的知道你在做什么,你应该修正这样的软件包,把所有东西都安装到 %python_sitearch。

setuptools/eggs

注意: 这段文本引自 Fedora Guidelines ,目前还没有在 openSUSE 确认可用。风险自负。

过去,Fedora 对于支持上游的 eggs 做的很少。由于 eggs 被上游广泛的接受使用,我们需要更易读的文档,描述如何处理这个问题。以下是给复核者看的简要指南。 完整的策略里会包含例子和我们的做法的理由。

  • Python eggs 必须从源代码编译。打包者不能从上游下载一个 egg 然后手动复制到某个文件夹里。
  • Python eggs 必须不能在编译过程中下载任何依赖。
  • 如果 egg-info 文件由模块的编译脚本生成,那么必须把它们也放到软件包里。
  • 当编译When building a compat 兼容包时,必须使用 easy_install -m 安装,这样它就不会和主包冲突了。
  • 当为 compat 兼容包编译多个版本时,其中的一个包必须包含无需任何其他设定就可以通过 "import MODULE" 使用的默认版本。
  • 一个被其他软件包通过 egg 界面使用的软件包应该提供 egg info。

bytecode 二进制预编译文件

Python 运行时会自动尝试把源代码文件进行二进制预编译,以便于下次运行的更快。这些二进制预编译文件以 .pyc (编译过的)或 .pyo(优化编译过的) 扩展名保存。这些文件是二进制的,因此可以移植到各种系统上去。如果你么有在软件包中包含它们,python 就会在用户运行程序时尝试创建它们。如果是系统管理员权限运行的程序,那么这些文件就能够成功写入到文件系统。但是当软件包移除时,它们就会被遗留在文件系统上。为了防止这类垃圾的产生,你需要在建包时预编译这些文件,然后把它们写进 %files 章节。

许多软件包自己会安装二进制预编译文件。如果你的软件包没有那么做,你应该使用 %{py_compile} <Python 源代码所在的文件夹> 命令来创建 .pyc 和/或使用 %{py_compile -O} <Python 源代码所在的文件夹> 命令创建 .pyo 文件。

注意,这么创建只能在 %build 章节里用。要是在 %buildroot 里用,就会带上 %buildroot 的路径,这是 rpmlint 所严格不允许的,正确的做法是先定位到那个文件夹再运行 %{py_compile}。

pushd %{buildroot}%{python_sitearch}/%{name}/
%{py_compile} ./
popd

通常,.pyo 文件和 .pyc 文件一模一样。你可以运行 fdupes 来把它们硬链接到一起来节省软件包体积。

BuildRequires: fdupes
(...)

%install
(...)
%fdupes $RPM_BUILD_ROOT%{py_sitedir}

实用 RPM 宏汇总

  • %py_ver - python 版本系列(格式是'主版本号.次版本号',如2.7)
  • %py_compile - 二进制预编译指定文件夹中的 python 源代码。使用 %{py_compile -O} 来生成优化过的二进制预编译文件(.pyo)。
  • %py_requires - 指定 python 解释器为 BuildRequires 编译依赖,同时指定特定 python 版本系列的运行依赖 Requires (实际上是 PreReq)。使用 %py_requires -d 也会把 python-devel 作为编译依赖。
  • %python_sitelib - 与平台无关的模块的 site-packages 目录。指向/usr/lib/python%{py_ver}/site-packages
  • %python_sitearch - 与平台相关的模块的 site-packages 目录。指向 %{_libdir}/python%{py_ver}/site-packages

与旧版本发行版的兼容

在 11.1 里, python 软件包被切分了。如果你想要为老版本的发行版打包(主要是 SLES,目前 openSUSE 最低的支持版本是 11.4,这些对它都不重要了),你不能依赖 python-base。注意 python-base 软件包几乎没有编译/运行依赖。这意味着它能在重编译轮回里快速脱身(重编译轮回是指在 openSUSE:Factory 里如果某个基础包有了新版本,所以依赖它的软件包都会自动重新编译一遍,有的奇葩基础依赖会导致这一次轮回的时间长达一周,你的软件要是不幸依赖上了它,那就会一直是 blocked 等待依赖状态),反过来说,如果你的软件包本身不依赖 python 软件包,使用 base 作为依赖,重编译时会快。

编译 noarch 平台无关软件包,以及 %python_sitelib%python_sitearch 宏是在 11.2 里引入的。如果你需要与旧版本发行版兼容,你必须自己定义你使用到的这两个宏。把下面两行放到你的 spec 范式文件的前面

%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")}
%{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")}

如果你的软件包是 noarch 包,你必须把 noarch 声明用条件语句括起来。

这样只有 openSUSE 11.2 和更高版本才会作为 noarch 编译:
%if 0%{?suse_version} >= 1120
BuildArchitectures: noarch
%endif
注意,这会把你的软件包在 openSUSE 11.2 和更高版本,以及 其他非 SuSE 发行版上编译为 noarch。(下面在为 Fedora 打包时很有用)
%if %{?suse_version: %{suse_version} > 1110} %{!?suse_version:1}
BuildArchitectures: noarch
%endif

打包 Python 3 模块的技巧

「不断完善中」详见 openSUSE 打包邮件列表 上的这个会话。风险自负。本章节列出的技巧在未来可能会变更。

Python3 模块适用上面所有的规则,但也有一些变动:


一次编译 Python2 和 Python3 模块

  • Python 2.7 依然是默认的
  • Python 运行包提供 Python(abi) 符号 (目前是 2.7 和 3.2), 模块依赖相关的符号(通过 RPM 依赖关系自动生成器)
  • 想要编译针对特定版本 python 的 python 模块,你需要添加 pythonX-devel 到 BuildRequires 编译依赖(python2-devel/python3-devel 或当软件包提供了所有版本的模块时两者都要)

如果你的 Python 源代码兼容两个版本,参考如下的建包流程:

  1. 开始打包 python-foo.
  2. 创建 python-foo.spec.
  3. 编译之,解决错误。
  4. 使用 osc linkpac 创建一个新项目:
    osc home:YOUR_REPO python-foo home:YOUR_REPO python3-foo
  5. 在新项目里创建 python3-foo.spec
  6. 针对 python3 打补丁(如果有的话)