Python 打包
构建服务教学 - 技巧和花样 - 跨发行版打包 - Debian 打包指南 - 打包检查
桌面菜单分类 - 打包常用的 RPM 宏 - 小脚本片段 - SysVinit 脚本 - 源代码服务
OBS 打包互助问答 - 打包黑名单
目录
快速和自动化的方式
假设你想要打包 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),这样就不容易找出那些文件是软件包带来的了。此外,该新命名方式有如下好处:
- 脚本(比如 py2pack)知道去哪里撷取元数据和新的 tar 压缩包,PyPI 有 API 支持。
- 包名搜索 install_required, 例如搜索 setup.py 文件 和 pip 需求文件 使得找出编译依赖变得更加容易
- 用户知道到哪里查询 API 或使用文档:http://pypi.python.org/pypi/$PYPYNAME
但是该命名策略不适用于面向最终用户的软件 - 因此如果你打包的是在应用程序菜单里有图标的程序,你直接命名为源代码包的名字就可以。注意,许多这样的程序安装它自身的一部分到 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 对于支持上游的 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 源代码兼容两个版本,参考如下的建包流程:
- 开始打包 python-foo.
- 创建 python-foo.spec.
- 编译之,解决错误。
- 使用 osc linkpac 创建一个新项目:
osc home:YOUR_REPO python-foo home:YOUR_REPO python3-foo - 在新项目里创建 python3-foo.spec
- 针对 python3 打补丁(如果有的话)