关于python中的setup.py

本博客欢迎转发,但请保留原作者信息!

博客地址:http://lingxiankong.github.io/

其实对于setup.py和setup.cfg的关注是从OpenStack的源码包中开始的,OpenStack每个组件的发布时都是一个tar.gz包,同样,我们直接从github上clone代码后也会发现两个文件的存在。当阅读Nova或Ceilometer(其他组件可能也会涉及)的代码时,发现setup.cfg中内容对于代码的理解有很大的影响。那么,到底setup.py和setup.cfg是干什么的?

我们从例子开始。假设你要分发一个叫foo的模块,文件名foo.py,那么setup.py内容如下:

 
  
  1. from distutils.core import setup
  2. setup(name='foo',
  3. version='1.0',
  4. py_modules=['foo'],
  5. )

然后,运行python setup.py sdist为模块创建一个源码包


  
  1. root@network:/kong/setup# python setup.py sdist
  2. running sdist
  3. running check
  4. warning: check: missing required meta-data: url
  5. warning: check: missing meta-data: either (author and author_email) or (maintainer and maintainer_email) must be supplied
  6. warning: sdist: manifest template 'MANIFEST.in' does not exist (using default file list)
  7. warning: sdist: standard file not found: should have one of README, README.txt
  8. writing manifest file 'MANIFEST'
  9. creating foo-1.0
  10. making hard links in foo-1.0...
  11. hard linking foo.py -> foo-1.0
  12. hard linking setup.py -> foo-1.0
  13. creating dist
  14. Creating tar archive
  15. removing 'foo-1.0' (and everything under it)

在当前目录下,会创建dist目录,里面有个文件名为foo-1.0.tar.gz,这个就是可以分发的包(如果使用命令python setup.py bdist_egg,那么会在dist目录中生成foo-1.0-py2.7.egg包,setup.py中第一句引入需要改为from setuptools import setup)。使用者拿到这个包后,解压,到foo-1.0目录下执行:python setup.py install,那么,foo.py就会被拷贝到python类路径下,可以被导入使用(如果安装是egg文件,会把egg文件拷贝到dist-packages目录下)。

  
  1. root@network:/kong/setup/dist/foo-1.0# python setup.py install
  2. running install
  3. running build
  4. running build_py
  5. creating build
  6. creating build/lib.linux-x86_64-2.7
  7. copying foo.py -> build/lib.linux-x86_64-2.7
  8. running install_lib
  9. copying build/lib.linux-x86_64-2.7/foo.py -> /usr/local/lib/python2.7/dist-packages
  10. byte-compiling /usr/local/lib/python2.7/dist-packages/foo.py to foo.pyc
  11. running install_egg_info
  12. Removing /usr/local/lib/python2.7/dist-packages/foo-1.0.egg-info
  13. Writing /usr/local/lib/python2.7/dist-packages/foo-1.0.egg-info
  14. root@network:/kong/setup/dist/foo-1.0# ll /usr/local/lib/python2.7/dist-packages/foo
  15. foo-1.0.egg-info foo.py foo.pyc

对于Windows,可以执行python setup.py bdist_wininst生成一个exe文件;若要生成RPM包,执行python setup.py bdist_rpm,但系统必须有rpm命令的支持。可以运行下面的命令查看所有格式的支持:

  
  1. root@network:/kong/setup# python setup.py bdist --help-formats
  2. List of available distribution formats:
  3. --formats=rpm RPM distribution
  4. --formats=gztar gzip'ed tar file
  5. --formats=bztar bzip2'ed tar file
  6. --formats=ztar compressed tar file
  7. --formats=tar tar file
  8. --formats=wininst Windows executable installer
  9. --formats=zip ZIP file
  10. --formats=msi Microsoft Installer

setup函数还有一些参数:

1、packages
告诉Distutils需要处理那些包(包含__init__.py的文件夹)
2、package_dir
告诉Distutils哪些目录下的文件被映射到哪个源码包,感觉好像是一个相对路径的定义。一个例子:package_dir = {'': 'lib'},表示以lib为主目录。
3、ext_modules
是一个包含Extension实例的列表,Extension的定义也有一些参数。
4、ext_package
定义extension的相对路径
5、requires
定义依赖哪些模块
6、provides
定义可以为哪些模块提供依赖
7、scripts
指定python源码文件,可以从命令行执行。在安装时指定--install-script
8、package_data
通常包含与包实现相关的一些数据文件或类似于readme的文件。

  
  1. package_data = {'': ['*.txt'], 'mypkg': ['data/*.dat'],}

表示包含所有目录下的txt文件和mypkg/data目录下的所有dat文件。

9、data_files
指定其他的一些文件(如配置文件)

  
  1. setup(...,
  2. data_files=[('bitmaps', ['bm/b1.gif', 'bm/b2.gif']),
  3. ('config', ['cfg/data.cfg']),
  4. ('/etc/init.d', ['init-script'])]
  5. )

规定了哪些文件被安装到哪些目录中。如果目录名是相对路径,则是相对于sys.prefixsys.exec_prefix的路径。如果没有提供模板,会被添加到MANIFEST文件中。

执行sdist命令时,默认会打包哪些东西呢?

  • 所有由py_modulespackages指定的源码文件
  • 所有由ext_moduleslibraries指定的C源码文件
  • scripts指定的脚本文件
  • 类似于test/test*.py的文件
  • README.txt或README,setup.py,setup.cfg
  • 所有package_datadata_files指定的文件

还有一种方式是写一个manifest template,名为MANIFEST.in,定义如何生成MANIFEST文件,内容就是需要包含在分发包中的文件。一个MANIFEST.in文件如下:

  
  1. include *.txt
  2. recursive-include examples *.txt *.py
  3. prune examples/sample?/build

setup.cfg提供一种方式,可以让包的开发者提供命令的默认选项,同时为用户提供修改的机会。对setup.cfg的解析,是在setup.py之后,在命令行执行前。

setup.cfg文件的形式类似于

  
  1. [command]
  2. option=value
  3. ...

其中,command是Distutils的命令参数,option是参数选项,可以通过python setup.py --help build_ext方式获取。
> 需要注意的是,比如一个选项是–foo-bar,在setup.cfg中必须改成foo_bar的格式

符合Distutils2的setup.cfg有些不同。包含一些sections:
1、global
定义Distutils2的全局选项,可能包含commands,compilers,setup_hook(定义脚本,在setup.cfg被读取后执行,可以修改setup.cfg的配置,pbr就用到了这个)
2、metadata
3、files

  • packages_root:根目录
  • packages
  • modules
  • scripts
  • extra_files

上面的setup.py和setup.cfg都是遵循python标准库中的Distutils,而setuptools工具针对Python官方的distutils做了很多针对性的功能增强,比如依赖检查,动态扩展等。很多高级功能我就不详述了,自己也没有用过,等用的时候再作补充。详情可参见这里

一个典型的遵循setuptools的脚本:

  
  1. from setuptools import setup, find_packages
  2. setup(
  3. name = "HelloWorld",
  4. version = "0.1",
  5. packages = find_packages(),
  6. scripts = ['say_hello.py'],
  7. # Project uses reStructuredText, so ensure that the docutils get
  8. # installed or upgraded on the target machine
  9. install_requires = ['docutils>=0.3'],
  10. package_data = {
  11. # If any package contains *.txt or *.rst files, include them:
  12. '': ['*.txt', '*.rst'],
  13. # include any *.msg files found in the 'hello' package, too:
  14. 'hello': ['*.msg'],
  15. },
  16. # metadata for upload to PyPI
  17. author = "Me",
  18. author_email = "me@example.com",
  19. description = "This is an Example Package",
  20. license = "PSF",
  21. keywords = "hello world example examples",
  22. url = "http://example.com/HelloWorld/", # project home page, if any
  23. # could also include long_description, download_url, classifiers, etc.
  24. )

setuptools相对distutils,增强的关键字:
include_package_data:为True时自动添加受版本控制的数据文件,可替代package_data,同时,exclude_package_data可以排除某些文件。当你需要加入没有被版本控制的文件时,还是老老实实使用package_data吧。
install_requires:代替require函数。表示当前包的安装依赖于哪些分发包,这些信息会写入egg的元信息中,这些包在安装时会自动(从PyPI)下载并安装。如果包在PyPI中找不到,则会从dependency_links标识的URL中获取。
extras_require:当前包的高级/额外特性需要依赖的分发包。
entry_points:这个很经典。见下面的讲解。
setup_requires: 安装脚本执行时需要依赖的分发包,主要用于构建过程。注意,这里列出的包不会自动安装,如果需要,同时要在install_requires中指定。
dependency_links:URL地址。这些地址在安装setup_requirestests_require指定的包时使用。会写入egg的metadata信息中。

  
  1. setup(
  2. # other arguments here...
  3. entry_points = {
  4. 'setuptools.installation': [
  5. 'eggsecutable = my_package.some_module:main_func',
  6. ]
  7. }
  8. )
  
  1. setup(
  2. name="Project-A",
  3. ...
  4. extras_require = {
  5. 'PDF': ["ReportLab>=1.2", "RXP"],
  6. 'reST': ["docutils>=0.3"],
  7. }

  
  1. )

表示如果系统安装docutils了,那么可提供reST特性。当然,docutils包不会自动安装,只有第三方包依赖本包的reST特性时,才会下载安装。

特性如何使用呢?需要与entry points结合使用,在上一个setup.py脚本中增加:

  
  1. setup(
  2. name="Project-A",
  3. ...
  4. entry_points = {
  5. 'console_scripts': [
  6. 'rst2pdf = project_a.tools.pdfgen [reST]',
  7. 'rst2html = project_a.tools.htmlgen',
  8. # more script entry points ...
  9. ],
  10. }
  11. )

表示如果要使用rst2pdf脚本,就要安装docutils。

或者被其他project依赖:install_requires = ["Project-A[PDF]"]

我想大家最熟悉的就是这个特性了吧。比如一个博客系统想用不同的插件支持不同的语言输出格式,那么就可以定义一个“entry point group”,不同的插件就可以注册“entry point”,插件注册的示例:

  
  1. setup(
  2. # ...
  3. entry_points = {'blogtool.parsers': ['.rst = some_module:a_func']}
  4. )
  5. # 或者
  6. setup(
  7. # ...
  8. entry_points = """
  9. [blogtool.parsers]
  10. .rst = some.nested.module:SomeClass.some_classmethod [reST]
  11. """,
  12. extras_require = dict(reST = "Docutils>=0.3.5")
  13. )

我们写依赖声明的时候需要在 setup.py 中写好抽象依赖(install_requires),在 requirements.txt 中写好具体的依赖,但是我们并不想维护两份依赖文件,这样会让我们很难做好同步。 requirements.txt 可以更好地处理这种情况,我们可以在有 setup.py 的目录里写下一个这样的 requirements.txt

  
  1. --index https://pypi.python.org/simple/
  2. -e .

这样 pip install -r requirements.txt 可以照常工作,它会先安装该文件路径下的包,然后继续开始解析抽象依赖,结合 –index 选项后转换为具体依赖然后再安装他们。

这个办法可以让我们解决一种类似这样的情形:比如你有两个或两个以上的包在一起开发但是是分开发行的,或者说你有一个尚未发布的包并把它分成了几个部分。如果你的顶层的包 依然仅仅按照“名字”来依赖的话,我们依然可以使用 requirements.txt 来安装开发版本的依赖包:

  
  1. --index https://pypi.python.org/simple/
  2. -e https://github.com/foo/bar.git#egg=bar
  3. -e .

这会首先从 https://github.com/foo/bar.git 来安装包 bar , 然后进行到第二行 -e . ,开始安装 setup 中的抽象依赖,但是包 bar 已经安装过了, 所以 pip 会跳过安装。

pbr是setuptools的辅助工具,最初是为OpenStack开发(https://launchpad.net/pbr),基于d2to1
> A library for managing setuptools packaging needs in a consistent manner.

pbr会读取和过滤setup.cfg中的数据,然后将解析后的数据提供给setup.py作为参数。包含如下功能:
1、从git中获取Version、AUTHORS and ChangeLog信息
2、Sphinx Autodoc。pbr会扫描project,找到所有模块,生成stub files
3、Requirements。pbr会读取requirements.txt,生成setup函数需要的install_requires/tests_require/dependency_links
>这里需要注意,在requirements.txt文件的头部可以使用:--index https://pypi.python.org/simple/,这一行把一个抽象的依赖声明如 requests==1.2.0 转变为一个具体的依赖声明 requests 1.2.0 from pypi.python.org/simple/

4、long_description。从README.rst, README.txt or README file中生成long_description参数

使用pbr很简单:

  
  1. from setuptools import setup
  2. setup(
  3. setup_requires=['pbr'],
  4. pbr=True,
  5. )

使用pbr时,setup.cfg中有一些配置。在[files]中,有三个key:
packages:指定需要包含的包,行为类似于setuptools.find_packages
namespace_packages:指定namespace packages
data_files: 指定目的目录和源文件路径,一个示例:

  
  1. [files]
  2. data_files =
  3. etc/pbr = etc/pbr/*
  4. etc/neutron =
  5. etc/api-paste.ini
  6. etc/dhcp-agent.ini
  7. etc/init.d = neutron.init

[entry_points]段跟setuptools的方式相同。

表面上,python setup.py installpip install都是用来安装python包的,实际上,pip提供了更多的特性,更易于使用。体现在以下几个方面:

  • pip会自动下载依赖,而如果使用setup.py,则需要手动搜索和下载;
  • pip会自动管理包的信息,使卸载/更新更加方便和容易,使用pip uninstall即可。而使用setup.py,必须手动删除,有时容易出错。
  • pip提供了对virtualenv更好的整合。

OK,讲了这么多琐碎的东西,现在去看看Nova或Ceilometer的setup脚本,是不是一下清晰了很多?!但说实话,setup.py的使用,我还不能讲的特别清楚,需要在后续的实战中学习。

参考文档: http://docs.python.org/2/distutils/introduction.htmlhttp://pythonhosted.org/setuptools/

comments powered by Disqus


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值