Python程序最常见的是用distutils打包发布,只需要两步

  1. 写一个setup.py脚本
  2. 创建源代码包

当然,如果需要也可以创建二进制包,最后说。

Python中有两种module:pure Python module和extension module。pure Python module就是用Python写的.py文件,extension module就是用C/C++写的动态链接库,一般是.so文件。

许多module按目录层级放在一起,构成一个大包,叫package。每个目录下都有__init__.py文件说明该package中包含的内容。

setup.py长什么样子呢?先看个例子:

#!/usr/bin/env python2

from distutils.core import setup

setup(name='Distutils',
      version='1.0',
      description='Python Distribution Utilities',
      author='Greg Ward',
      author_email='gward@python.net',
      url='http://www.python.org/sigs/distutils-sig/',
      packages=['distutils', 'distutils.command'],
     )

很简单吧。然后创建源代码包只需要运行

python2 ./setup.py sdist

这是一个按package发布的配置,我们从更基本的module说起。以下我们假设setup.py放在代码的根目录。

  • 发布pure Python module

如果setup.py当前目录下有mod1.py文件和pkg/mod2.py文件,那么setup.py可以这样写:

py_modules = ['mod1', 'pkg.mod2']

表示我们要发布两个modules,一个是mod1,另一个是pkg.mod2。那么setup.py到什么地方去找对应的文件呢?从setup.py当前目录开始,把.换成/就是文件路径。

如果mod2.py不在pkg目录下而在pkg3目录下,但我们还想打成pkg.mod2这个module呢?用package_dir指定包所在路径:

package_dir = {'pkg': 'pkg3'}

同理,如果mod1.py不在setup.py当前目录下而在root4目录下,那么也要用package_dir指定路径。因为没有包名(以下我们假想setup.py所在目录是一个包,叫root package),键值为空字符串:

package_dir = {‘’: ‘root4’, ‘pkg’: ‘pkg3’}

然后,我们把mod2.py再放回到pkg目录下,并且不再在setup.py中指定pkg包的路径:

package_dir = {'': 'root4'}

会发现mod2发布失败了。因为我们定义了root package的路径,因为所有包都以root package为前缀,所以会自动按root package定义的路径向下去找,除非另行指定。所以setup.py会去找root4/pkg/mod2.py,当然找不到。

最后说明,py_modules的py_前缀特指这是一个pure Python module。

  • pure Python module按照package发布

上面我们说了如何按照modules发布。当然也可以按照packages发布。例如要发布pkg目录及其下所有内容作为一个包pkg,只需:

packages = ['pkg']

如果包pkg的路径不是pkg,仍然可以用package_dir修改:

package_dir = {'pkg': 'pkg3'}
  • 发布extension module

distutils可以自动对extension module的C/C++源代码进行编译链接。为了发布一个extension module,我们建立一个Extension对象:

Extension('foo', ['src/foo1.c', 'src/foo2.c'])

一个名为foo的extension module,用两个C文件编译而成。写在setup.py里就是:

ext_modules = [Extension('foo': ['src/foo1.c', 'src/foo2.c'])]

当然ext_modules可以包含多个Extension,只是这里只写了一个。

ext_前缀说明是extension module/package,是和py_前缀对照的。

如果有多个Extension要打在同一个包内,可以用ext_package指定包名:

ext_package = 'pkg'

就把foo打成pkg.foo了。

  • extension module发布参数

因为发布extension module要编译链接,麻烦事会比较多,例如指定头文件目录、动态链接库什么的。幸好都可以当成Extension的构造参数来指定:

Extension('foo', ['foo.c'], define_macros=[('NDEBUG', 1)], undef_macros=['HAVE_FOO'], \
include_dirs=['include'], libraries=['gdbm', 'readline'], library_dirs=['/usr/X11R6/lib'])
  • 安装脚本

有时我们希望我们发布的包可以包含可执行文件,这可以用scripts来指定:

setup(...,
scripts = ['scripts/xmlproc_parse', 'scrips/xmlproc_val'],
)

至于这些脚本被安装到了什么地方去,在用setup.py进行安装时会说。

  • 安装包内数据

有时我们希望发布的包里可以放置与程序相关的数据文件,用package_data来指定:

setup(...,
package_data = {'pkg': ['data/*.dat']},
)
  • 安装其他文件

有时我们还想往包里塞点东西,但又不属于上面的任何一类,这用data_files来指定:

setup(..,
data_files = [
    ('bitmaps', ['bm/b1.gif', 'bm/b2.gif']),
    ('config', ['cfg/data.cfg']),
    ],
)

data_files的每个元素是(dir, files)结构,files是要塞到包里的文件,dir是塞到什么地方。

  • 包的元信息

像name/version/url什么一看就知道怎么填了。有些fields是必须的,用sdist发布如果不过记得检查是不是忘填了某个field。

  • 发布源代码包

上面讲过了用sdist发布。具体地,你还可以指定一个MANIFEST包含要发布的文件,或者指定一个MANIFEST.in然后setup.py会自动生成相应的 MANIFEST。或者你也可以像上面一样什么都不指定,setup.py会按照默认规则生成一个MANIFEST。

你也可以用–formats指定打包的格式。

  • 包的安装

只要一行:

python2 ./setup.py install

包里的东西就去了它们各自该去的地方。

如果想分开build和install,也可以分开写:

python2 ./setup.py build
python2 ./setup.py install

包里的东西都去了什么地方呢?pure Python module去了prefix/lib/pythonX.Y/site-packages,extension module去了exec-prefix/lib/pythonX.Y/site-packages。prefix和exec-prefix可以用sys.prefix和sys.exec-prefix查看,一般是/usr。你可能会想scripts应该去了/usr/bin吧,嗯就是这样的。每个文件都去了它该去的地方。

distutils还包含了一些常用的schemes,如–user/–home/–prefix等等,可以将包内文件安装到不同的地方。如果你喜欢自定义,也可以用–install-purelib/–install-scripts等参数对每一类文件进行分别控制,总之是很自由的。

  • 发布二进制包

由于包的原作者不大可能在所有的平台上工作,所以原作者一般只发布源代码包以及他所使用的平台的二进制包。那么各个平台的中间维护者拿到源代码包之后可以打成该平台的二进制包,这样最终使用者就不用自己编译链接了。打二进制包用:

python2 ./setup.py bdist

可以用–format指定格式,如rpm等。

发布到PyPI就不说了,有兴趣的自行参看置底链接。

Links: