如何制作python的安装包?python模块的打包工具又有哪些?在OpenStack源码包中到底setup.py和setup.cfg是干什么的?
Demo
我们从例子开始。假设你要分发一个叫 foo
的模块,文件名 foo.py
、setup.py
内容如下:
[root@xiexianbin_cn py]# ll
total 8
-rw-r--r-- 1 root root 13 Nov 15 22:07 foo.py
-rw-r--r-- 1 root root 106 Nov 15 22:07 setup.py
[root@xiexianbin_cn py]# cat foo.py
print 'echo'
[root@xiexianbin_cn py]# cat setup.py
from distutils.core import setup
setup(name='foo',
version='1.0',
py_modules=['foo'],
)
然后,创建一个源码包:
$ python setup.py sdist
在当前目录下,会创建 dist
目录,里面有个文件名为 foo-1.0.tar.gz
,这时就是可以分发的包。操作如下:
[root@xiexianbin_cn py]# ll dist/
total 4
-rw-r--r-- 1 root root 399 Nov 15 22:08 foo-1.0.tar.gz
[root@xiexianbin_cn py]# cd dist/
[root@xiexianbin_cn dist]# tar -zxvf foo-1.0.tar.gz
foo-1.0/
foo-1.0/foo.py
foo-1.0/PKG-INFO
foo-1.0/setup.py
[root@xiexianbin_cn dist]# cd foo-1.0/
[root@xiexianbin_cn foo-1.0]# python setup.py install
running install
running build
running build_py
creating build
creating build/lib
copying foo.py -> build/lib
running install_lib
copying build/lib/foo.py -> /usr/lib/python2.7/site-packages
byte-compiling /usr/lib/python2.7/site-packages/foo.py to foo.pyc
running install_egg_info
Writing /usr/lib/python2.7/site-packages/foo-1.0-py2.7.egg-info
[root@xiexianbin_cn foo-1.0]# ll /usr/lib/python2.7/site-packages/foo
foo-1.0-py2.7.egg-info foo.py foo.pyc
这时 foo.py
就会被拷贝到python类路径下,可以被导入使用。
执行sdist命令时,默认会打包哪些东西呢?
- 所有由py_modules或packages指定的源码文件
- 所有由ext_modules或libraries指定的C源码文件
- 由scripts指定的脚本文件
- 类似于test/test*.py的文件
- README.txt或README,setup.py,setup.cfg
- 所有package_data或data_files指定的文件
setup.cfg 介绍
setup.cfg 提供一种方式,可以让包的开发者提供命令的默认选项,同时为用户提供修改的机会。对setup.cfg 的解析,是在 setup.py 之后,在命令行执行前。
setup.cfg 文件的形式类似如下:
[command]
option=value
...
其中,command 是 Distutils 的命令参数,option 是参数选项,可以通过 python setup.py --help build_ext
方式获取。需要注意的是,比如一个选项是--foo-bar
,在setup.cfg中必须改成 foo_bar
的格式。
上面的 setup.py 和 setup.cfg 都是遵循python标准库中的Distutils,而setuptools工具针对Python官方的distutils做了很多针对性的功能增强,比如依赖检查,动态扩展等。很多高级功能我就不详述了,自己也没有用过,等用的时候再作补充。
一个典型的遵循setuptools的脚本:
from setuptools import setup, find_packages
setup(
name = "HelloWorld",
version = "0.1",
packages = find_packages(),
scripts = ['say_hello.py'],
# Project uses reStructuredText, so ensure that the docutils get
# installed or upgraded on the target machine
install_requires = ['docutils>=0.3'],
package_data = {
# If any package contains *.txt or *.rst files, include them:
'': ['*.txt', '*.rst'],
# And include any *.msg files found in the 'hello' package, too:
'hello': ['*.msg'],
},
# metadata for upload to PyPI
author = "Me",
author_email = "me@example.com",
description = "This is an Example Package",
license = "PSF",
keywords = "hello world example examples",
url = "http://example.com/HelloWorld/", # project home page, if any
# could also include long_description, download_url, classifiers, etc.
)
如何让一个egg可被执行?
setup(
# other arguments here...
entry_points = {
'setuptools.installation': [
'eggsecutable = my_package.some_module:main_func',
]
}
)
如何定义一个可选依赖?
setup(
name="Project-A",
...
extras_require = {
'PDF': ["ReportLab>=1.2", "RXP"],
'reST': ["docutils>=0.3"],
}
)s
entry points使用:
setup(
name="Project-A",
...
entry_points = {
'console_scripts': [
'rst2pdf = project_a.tools.pdfgen [PDF]',
'rst2html = project_a.tools.htmlgen',
# more script entry points ...
],
}
)
或者被其他project依赖:install_requires = [“Project-A[PDF]”]
插件式开发
我想大家最熟悉的就是这个特性了吧。比如一个博客系统想用不同的插件支持不同的语言输出格式,那么就可以定义一个“entry point group”,不同的插件就可以注册“entry point”,插件注册的示例:
setup(
# ...
entry_points = {'blogtool.parsers': ['.rst = some_module:a_func']}
)
或者
setup(
# ...
entry_points = """
[blogtool.parsers]
.rst = some.nested.module:SomeClass.some_classmethod [reST]
""",
extras_require = dict(reST = "Docutils>=0.3.5")
)
Setuptools有一个功能叫做 dependency_links
from setuptools import setup
setup(
# ...
dependency_links = [
"http://packages.example.com/snapshots/",
"http://example2.com/p/bar-1.0.tar.gz",
],
)
这一功能除去了依赖的抽象特性,直接把依赖的获取url标在了setup.py里。就像在Go语言中修改依赖包一样,我们只需要修改依赖链中每个包的 dependency_links 。