其實對于setup.py和setup.cfg的關注是從OpenStack的源碼包中開始的,OpenStack每個元件的釋出時都是一個tar.gz包,同樣,我們直接從github上clone代碼後也會發現兩個檔案的存在。當閱讀Nova或Ceilometer(其他元件可能也會涉及)的代碼時,發現setup.cfg中内容對于代碼的了解有很大的影響。那麼,到底setup.py和setup.cfg是幹什麼的?
我們從例子開始。假設你要分發一個叫foo的子產品,檔案名foo.py,那麼setup.py内容如下:
<code>from distutils.core import setup</code>
<code>setup(name='foo',</code>
<code>version='1.0',</code>
<code>py_modules=['foo'],</code>
<code>)</code>
然後,運作<code>python setup.py sdist</code>為子產品建立一個源碼包
<code>root@network:/kong/setup# python setup.py sdist</code>
<code>running sdist</code>
<code>running check</code>
<code>warning: check: missing required meta-data: url</code>
<code>warning: check: missing meta-data: either (author and author_email) or (maintainer and maintainer_email) must be supplied</code>
<code>warning: sdist: manifest template 'MANIFEST.in' does not exist (using default file list)</code>
<code>warning: sdist: standard file not found: should have one of README, README.txt</code>
<code>writing manifest file 'MANIFEST'</code>
<code>creating foo-1.0</code>
<code>making hard links in foo-1.0...</code>
<code>hard linking foo.py -> foo-1.0</code>
<code>hard linking setup.py -> foo-1.0</code>
<code>creating dist</code>
<code>Creating tar archive</code>
<code>removing 'foo-1.0' (and everything under it)</code>
在目前目錄下,會建立<code>dist</code>目錄,裡面有個檔案名為<code>foo-1.0.tar.gz</code>,這個就是可以分發的包。使用者拿到這個包後,解壓,到foo-1.0目錄下執行:<code>python setup.py install</code>,那麼,foo.py就會被拷貝到python類路徑下,可以被導入使用。
<code>root@network:/kong/setup/dist/foo-1.0# python setup.py install</code>
<code>running install</code>
<code>running build</code>
<code>running build_py</code>
<code>creating build</code>
<code>creating build/lib.linux-x86_64-2.7</code>
<code>copying foo.py -> build/lib.linux-x86_64-2.7</code>
<code>running install_lib</code>
<code>copying build/lib.linux-x86_64-2.7/foo.py -> /usr/local/lib/python2.7/dist-packages</code>
<code>byte-compiling /usr/local/lib/python2.7/dist-packages/foo.py to foo.pyc</code>
<code>running install_egg_info</code>
<code>Removing /usr/local/lib/python2.7/dist-packages/foo-1.0.egg-info</code>
<code>Writing /usr/local/lib/python2.7/dist-packages/foo-1.0.egg-info</code>
<code>root@network:/kong/setup/dist/foo-1.0# ll /usr/local/lib/python2.7/dist-packages/foo</code>
<code>foo-1.0.egg-info foo.py foo.pyc</code>
對于Windows,可以執行<code>python setup.py bdist_wininst</code>生成一個exe檔案;若要生成RPM包,執行<code>python setup.py bdist_rpm</code>,但系統必須有rpm指令的支援。可以運作下面的指令檢視所有格式的支援:
<code>root@network:/kong/setup# python setup.py bdist --help-formats</code>
<code>List of available distribution formats:</code>
<code>--formats=rpm RPM distribution</code>
<code>--formats=gztar gzip'ed tar file</code>
<code>--formats=bztar bzip2'ed tar file</code>
<code>--formats=ztar compressed tar file</code>
<code>--formats=tar tar file</code>
<code>--formats=wininst Windows executable installer</code>
<code>--formats=zip ZIP file</code>
<code>--formats=msi Microsoft Installer</code>
setup函數還有一些參數:
1、<code>packages</code>
告訴Distutils需要處理那些包(包含<code>__init__.py</code>的檔案夾)
2、<code>package_dir</code>
告訴Distutils哪些目錄下的檔案被映射到哪個源碼包。一個例子:<code>package_dir = {'': 'lib'}</code>,表示“root package”中的子產品都在lib目錄中。
3、<code>ext_modules</code>
是一個包含Extension執行個體的清單,Extension的定義也有一些參數。
4、<code>ext_package</code>
定義extension的相對路徑
5、<code>requires</code>
定義依賴哪些子產品
6、<code>provides</code>
定義可以為哪些子產品提供依賴
7、<code>scripts</code>
指定python源碼檔案,可以從指令行執行。在安裝時指定<code>--install-script</code>
8、<code>package_data</code>
通常包含與包實作相關的一些資料檔案或類似于readme的檔案。如果沒有提供模闆,會被添加到MANIFEST檔案中。
9、<code>data_files</code>
指定其他的一些檔案(如配置檔案)
<code>setup(...,</code>
<code>data_files=[('bitmaps', ['bm/b1.gif', 'bm/b2.gif']),</code>
<code>('config', ['cfg/data.cfg']),</code>
<code>('/etc/init.d', ['init-script'])]</code>
規定了哪些檔案被安裝到哪些目錄中。如果目錄名是相對路徑,則是相對于<code>sys.prefix</code>或<code>sys.exec_prefix</code>的路徑。如果沒有提供模闆,會被添加到MANIFEST檔案中。
執行sdist指令時,預設會打包哪些東西呢?
所有由<code>py_modules</code>或<code>packages</code>指定的源碼檔案
所有由<code>ext_modules</code>或<code>libraries</code>指定的C源碼檔案
由<code>scripts</code>指定的腳本檔案
類似于test/test*.py的檔案
README.txt或README,setup.py,setup.cfg
所有<code>package_data</code>或<code>data_files</code>指定的檔案
還有一種方式是寫一個manifest template,名為<code>MANIFEST.in</code>,定義如何生成MANIFEST檔案,内容就是需要包含在分發包中的檔案。一個MANIFEST.in檔案如下:
<code>include *.txt</code>
<code>recursive-include examples *.txt *.py</code>
<code>prune examples/sample?/build</code>
setup.cfg提供一種方式,可以讓包的開發者提供指令的預設選項,同時為使用者提供修改的機會。對setup.cfg的解析,是在setup.py之後,在指令行執行前。
setup.cfg檔案的形式類似于
<code>[command]</code>
<code>option=value</code>
<code>...</code>
其中,<code>command</code>是Distutils的指令參數,<code>option</code>是參數選項,可以通過<code>python setup.py --help build_ext</code>方式擷取。
需要注意的是,比如一個選項是--foo-bar,在setup.cfg中必須改成foo_bar的格式
符合Distutils2的setup.cfg有些不同。包含一些sections:
1、<code>global</code>
定義Distutils2的全局選項,可能包含commands,compilers,setup_hook(定義腳本,在setup.cfg被讀取後執行,可以修改setup.cfg的配置)
2、<code>metadata</code>
3、<code>files</code>
packages_root:根目錄
packages
modules
scripts
extra_files
4、command sections
上面的setup.py和setup.cfg都是遵循python标準庫中的Distutils,而setuptools工具針對Python官方的distutils做了很多針對性的功能增強,比如依賴檢查,動态擴充等。很多進階功能我就不詳述了,自己也沒有用過,等用的時候再作補充。
一個典型的遵循setuptools的腳本:
<code>from setuptools import setup, find_packages</code>
<code>setup(</code>
<code>name = "HelloWorld",</code>
<code>version = "0.1",</code>
<code>packages = find_packages(),</code>
<code>scripts = ['say_hello.py'],</code>
<code></code>
<code># Project uses reStructuredText, so ensure that the docutils get</code>
<code># installed or upgraded on the target machine</code>
<code>install_requires = ['docutils>=0.3'],</code>
<code>package_data = {</code>
<code># If any package contains *.txt or *.rst files, include them:</code>
<code>'': ['*.txt', '*.rst'],</code>
<code># And include any *.msg files found in the 'hello' package, too:</code>
<code>'hello': ['*.msg'],</code>
<code>},</code>
<code># metadata for upload to PyPI</code>
<code>author = "Me",</code>
<code>author_email = "[email protected]",</code>
<code>description = "This is an Example Package",</code>
<code>license = "PSF",</code>
<code>keywords = "hello world example examples",</code>
<code>url = "http://example.com/HelloWorld/", # project home page, if any</code>
<code># could also include long_description, download_url, classifiers, etc.</code>
<code># other arguments here...</code>
<code>entry_points = {</code>
<code>'setuptools.installation': [</code>
<code>'eggsecutable = my_package.some_module:main_func',</code>
<code>]</code>
<code>}</code>
<code>name="Project-A",</code>
<code>extras_require = {</code>
<code>'PDF': ["ReportLab>=1.2", "RXP"],</code>
<code>'reST': ["docutils>=0.3"],</code>
特性如何使用呢?需要與entry points結合使用:
<code>'console_scripts': [</code>
<code>'rst2pdf = project_a.tools.pdfgen [PDF]',</code>
<code>'rst2html = project_a.tools.htmlgen',</code>
<code># more script entry points ...</code>
<code>],</code>
或者被其他project依賴:install_requires = ["Project-A[PDF]"]
我想大家最熟悉的就是這個特性了吧。比如一個部落格系統想用不同的插件支援不同的語言輸出格式,那麼就可以定義一個“entry point group”,不同的插件就可以注冊“entry point”,插件注冊的示例:
<code># ...</code>
<code>entry_points = {'blogtool.parsers': ['.rst = some_module:a_func']}</code>
<code># 或者</code>
<code>entry_points = """</code>
<code>[blogtool.parsers]</code>
<code>.rst = some.nested.module:SomeClass.some_classmethod [reST]</code>
<code>""",</code>
<code>extras_require = dict(reST = "Docutils>=0.3.5")</code>
Setuptools有一個功能叫做 dependency_links
from setuptools import setup
<code>dependency_links = [</code>
<code>"http://packages.example.com/snapshots/",</code>
<code>"http://example2.com/p/bar-1.0.tar.gz",</code>
我們寫依賴聲明的時候需要在 setup.py 中寫好抽象依賴(install_requires),在 requirements.txt 中寫好具體的依賴,但是我們并不想維護兩份依賴檔案,這樣會讓我們很難做好同步。 requirements.txt 可以更好地處理這種情況,我們可以在有 setup.py 的目錄裡寫下一個這樣的 requirements.txt
<code>--index https://pypi.python.org/simple/</code>
<code>-e .</code>
這樣 pip install -r requirements.txt 可以照常工作,它會先安裝該檔案路徑下的包,然後繼續開始解析抽象依賴,結合 --index 選項後轉換為具體依賴然後再安裝他們。
這個辦法可以讓我們解決一種類似這樣的情形:比如你有兩個或兩個以上的包在一起開發但是是分開發行的,或者說你有一個尚未釋出的包并把它分成了幾個部分。如果你的頂層的包 依然僅僅按照“名字”來依賴的話,我們依然可以使用 requirements.txt 來安裝開發版本的依賴包:
<code>-e https://github.com/foo/bar.git#egg=bar</code>
Distutils is the standard tool used for packaging. It works rather well for simple needs, but is limited and not trivial to extend.
Setuptools is a project born from the desire to fill missing distutils functionality and explore new directions. In some subcommunities, it’s a de facto standard. It uses monkey-patching and magic that is frowned upon by Python core developers.
Distribute is a fork of Setuptools that was started by developers feeling that its development pace was too slow and that it was not possible to evolve it. Its development was considerably slowed when distutils2 was started by the same group. 2013-August update: distribute is merged back into setuptools and discontinued.
Distutils2 is a new distutils library, started as a fork of the distutils codebase, with good ideas taken from setup tools (of which some were thoroughly discussed in PEPs), and a basic installer inspired by pip. The actual name you use to import Distutils2 is packaging in the Python 3.3+ standard library, or distutils2 in 2.4+ and 3.1–3.2. (A backport will be available soon.) Distutils2 did not make the Python 3.3 release, and it was put on hold.
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函數需要的<code>install_requires/tests_require/dependency_links</code>
這裡需要注意,在requirements.txt檔案的頭部可以使用:<code>--index https://pypi.python.org/simple/</code>,這一行把一個抽象的依賴聲明如 requests==1.2.0 轉變為一個具體的依賴聲明 requests 1.2.0 from pypi.python.org/simple/
4、long_description。從README.rst, README.txt or README file中生成<code>long_description</code>參數
使用pbr很簡單:
<code>from setuptools import setup</code>
<code>setup_requires=['pbr'],</code>
<code>pbr=True,</code>
使用pbr時,setup.cfg中有一些配置。在[files]中,有三個key:
<code>packages</code>:指定需要包含的包,行為類似于setuptools.find_packages
<code>namespace_packages</code>:指定namespace packages
<code>data_files</code>: 指定目的目錄和源檔案路徑,一個示例:
<code>[files]</code>
<code>data_files =</code>
<code>etc/pbr = etc/pbr/*</code>
<code>etc/neutron =</code>
<code>etc/api-paste.ini</code>
<code>etc/dhcp-agent.ini</code>
<code>etc/init.d = neutron.init</code>
[entry_points]段跟setuptools的方式相同。
A collection of tools for internationalizing Python applications
1、compile_catalog
類似于msgfmt工具,takes a message catalog from a PO file and compiles it to a binary MO file.
<code>$ ./setup.py compile_catalog --directory foobar/locale --locale pt_BR</code>
<code>running compile_catalog</code>
<code>compiling catalog to foobar/locale/pt_BR/LC_MESSAGES/messages.mo</code>
2、extract_messages
類似于xgettext,it can extract localizable messages from a variety of difference source files, and generate a PO (portable object) template file from the collected messages.
<code>$ ./setup.py extract_messages --output-file foobar/locale/messages.pot</code>
<code>running extract_messages</code>
<code>extracting messages from foobar/__init__.py</code>
<code>extracting messages from foobar/core.py</code>
<code>writing PO template file to foobar/locale/messages.pot</code>
3、update_catalog
類似于msgmerge,it updates an existing translations catalog based on a PO template file (POT).
表面上,<code>python setup.py install</code>和<code>pip install</code>都是用來安裝python包的,實際上,<code>pip</code>提供了更多的特性,更易于使用。展現在以下幾個方面:
pip會自動下載下傳依賴,而如果使用setup.py,則需要手動搜尋和下載下傳;
pip會自動管理包的資訊,使解除安裝/更新更加友善和容易,使用<code>pip uninstall</code>即可。而使用setup.py,必須手動删除,有時容易出錯。
pip提供了對<code>virtualenv</code>更好的整合。
OK,講了這麼多瑣碎的東西,現在去看看Nova或Ceilometer的setup腳本,是不是一下清晰了很多?!但說實話,setup.py的使用,我還不能講的特别清楚,需要在後續的實戰中學習。