Python深入:Distutils釋出Python模組
Distutils可以用來在Python環境中構建和安裝額外的模組。新的模組可以是純Python的,也可以是用C/C++寫的擴充套件模組,或者可以是Python包,包中包含了由C和Python編寫的模組。
一:Distutils簡介
1.1概念和術語
對於模組開發者以及需要安裝模組的使用者來說,Distutils的使用都很簡單,作為一個開發者,除了編寫原始碼之外,還需要:
編寫setup指令碼(一般是setup.py);
編寫一個setup配置檔案(可選);
建立一個原始碼釋出;
建立一個或多個構建(二進位制)釋出(可選);
有些模組開發者在開發時不會考慮多個平臺釋出,所以就有了packagers的角色,它們從模組開發者那取得原始碼釋出,然後在多個平臺上面進行構建,併發布多個平臺的構建版本。
1.2簡單例子
由python編寫的setup指令碼一般都非常簡單。作為autoconf型別的配置指令碼,setup指令碼可以在構建和安裝模組釋出時執行多次。
比如,如果需要釋出一個叫做foo的模組,它包含在一個檔案foo.py,那setup指令碼可以這樣寫:
from distutils.core import setup
setup(name='foo',
version='1.0',
py_modules=['foo'],
)
setup函式的引數表示提供給Distutils的資訊,這些引數分為兩類:包的元資料(包名、版本號)以及包的資訊(本例中是一個Python模組的列表);模組由模組名錶示,而不是檔名(對於包和擴充套件而言也是這樣);建議可以提供更多的元資料,比如你的名字,email地址和專案的URL地址。
編寫好setup.py之後,就可以建立該模組的原始碼釋出了:
python setup.py sdist
對於Windows而言,命令是:
setup.py sdist
sdist命令會建立一個archive 檔案(比如Unix上的tar檔案,Windows上的zip檔案),它包含setup.py, foo.py。該archive檔案命名為foo-1.0.tar.gz(zip),解壓之後的目錄名是foo-1.0。
如果一個使用者希望安裝foo模組,他只需要下載foo-1.0.tar.gz,解壓,進入foo-1.0目錄,然後執行:
python setup.py install
該命令最終會將foo.py複製到Python環境存放第三方模組的目錄中。在linux環境下,執行該命令的輸出是:
# 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
該命令生成的檔案是:
/usr/lib/python2.7/site-packages/foo-1.0-py2.7.egg-info
/usr/lib/python2.7/site-packages/foo.py
/usr/lib/python2.7/site-packages/foo.pyc
這個簡單的例子展示了Distutils的基本概念。第一,開發者和安裝者有同樣的使用者介面,也就是setup指令碼,但他們使用的Distutils命令不同,sdist命令幾乎只有開發者使用,而install對於安裝者更常用。
如果希望使用者的使用盡可能的簡單,則可以建立多個構建釋出。比如,如果在Windows中,可以使用bdist_wininst命令建立一個exe安裝檔案,下面的命令會在當前目錄中建立foo-1.0.win32.exe檔案:
python setup.py bdist_wininst
其他的構建釋出有RPM(由bdist_rpm命令實現),Solaris pkgtool(bdist_pkgtool),以及HP-UX swinstall (bdist_sdux)。
比如,下面的命令將會建立RPM檔案foo-1.0.noarch.rpm(bdist_rpm命令必須運行於基於RPM的系統,比如Red Hat Linux, SuSE Linux, Mandrake Linux):
python setup.py bdist_rpm
可以通過下面的命令得到當前支援的釋出格式:
python setup.py bdist --help-formats
1.3基本術語:
模組(module): Python中可複用的基本程式碼單元,可由其他程式碼import的一塊程式碼,這裡我們只關注三種類型的模組:純python模組,擴充套件模組和包。
純python模組(pure Python module): 由python編寫的模組,包含在單獨的py檔案中(或者是pyc/pyo檔案)。
擴充套件模組(extension module):由實現Python的底層語言編寫的模組(C/C++ for Python, Java for Jython)。通常包含在單獨的動態載入檔案中,比如Unix中的so檔案,windows中的DLL檔案,或者是Jython擴充套件的java類檔案。(注意,目前為止Distutils只能處理Python的C/C++擴充套件)
包(package):包是含其他模組的模組,經常由包含__init__.py檔案的目錄釋出。
Root包(root package): 包層次關係中的根(它不是真正的包,因為它不包含__init__.py檔案)。
1.4 Distutils術語
模組釋出(module distribution):一些Python模組的集合,它們將會被一起安裝。一些常見的模組釋出有Numeric Python,PyXML,PIL,mxBase。
純模組釋出:一個只包含純python模組和包的模組釋出。
非純模組釋出:至少包含一個擴充套件模組的模組釋出。
釋出根:原始碼樹的根目錄;setup.py所在的目錄。
二:編寫setup指令碼
setup指令碼是使用Distutils構建、釋出和安裝模組的核心。setup指令碼的作用是向Distutils描述釋出模組的資訊。從上面那個簡單的例子中可知,setup指令碼主要是呼叫setup函式,而且模組開發者向Distutils提供的模組資訊多數是由setup函式的關鍵字引數提供的。
下面是一個更高階一些的例子:Distutils模組本身的setup指令碼:
setup(name='Distutils',
version='1.0',
description='Python Distribution Utilities',
author='Greg Ward',
author_email='[email protected]',
url='https://www.python.org/sigs/distutils-sig/',
packages=['distutils', 'distutils.command'],
)
上面這個指令碼有更多的元資料,列出的是兩個包(packages),而不是列出每個模組。因為Distutils包含多個模組,這些模組分成了兩個包;如果列出所有模組的話則是冗長且難以維護的。
注意,在setup指令碼中的路徑必須以Unix形式來書寫,也就是由”/”分割的。Distutils會在使用這些路徑之前,將這種表示方法轉換為適合當前平臺的格式。
2.1列出整個包
Setup函式的packages引數是一個列表,其中包含了Distutils需要處理(構建、釋出、安裝等)的所有包。要實現此目的,那麼包名和目錄名必須能夠相互對應,比如包名是distutils,則意味著在釋出的根目錄(setup指令碼所在目錄)下存在distutils子目錄;再比如在setup指令碼中packages = ['foo'],意味著要在setup指令碼所在目錄下存在相應的foo目錄和foo/__init__.py檔案。
比如如果setup指令碼內容如下:
setup(name='foo',
version='1.0',
packages = ['foo']
)
而setup指令碼所在目錄並沒有foo目錄(只有一個setup.py指令碼),則在執行python setup.py bdist命令時,會列印如下錯誤:
error: package directory 'foo' does not exist
如果建立了foo目錄,但是沒有foo/__init__.py檔案,則Distutils會產生下面的警告,但是仍會處理該包:
package init file 'foo/__init__.py' not found (or not a regular file)
可以使用package_dir選項來改變這種預設的對應規則。package_dir是個字典,其中的key是要安裝的包名,如果為空,則表明是root package,value就是該包(key)對應的原始碼樹的目錄。
比如如果setup.py內容如下:
setup(name='foo',
version='1.0',
package_dir = {'':'lib'},
packages = ['foo']
)
則必須在目錄中存在lib子目錄,lib/foo子目錄,以及檔案lib/foo/__init__.py。所以原始碼樹如下:
setup.py
lib/
foo/
__init__.py
foo.py
最後生成的檔案是:
\usr\local\lib\python2.7\dist-packages\ foo-1.0.egg-info
\usr\local\lib\python2.7\dist-packages\foo\__init__.py
\usr\local\lib\python2.7\dist-packages\foo\__init__.pyc
\usr\local\lib\python2.7\dist-packages\foo\foo.py
\usr\local\lib\python2.7\dist-packages\foo\foo.pyc
另外一個例子,foo包對應lib目錄,所以,foo.bar包就對應著lib/bar子目錄。所以如果在setup.py中這麼寫:
package_dir = {'foo':'lib'},
packages = ['foo',’foo.bar’]
則必須存在lib/__init__.py, lib/bar/__init__.py檔案。原始碼樹如下:
setup.py
lib/
__init__.py
foo.py
bar/
__init__.py
bar.py
最後生成的檔案是:
\usr\local\lib\python2.7\dist-packages\ foo-1.0.egg-info
\usr\local\lib\python2.7\dist-packages\foo\__init__.py
\usr\local\lib\python2.7\dist-packages\foo\__init__.pyc
\usr\local\lib\python2.7\dist-packages\foo\foo.py
\usr\local\lib\python2.7\dist-packages\foo\foo.pyc
\usr\local\lib\python2.7\dist-packages\foo\bar\__init__.py
\usr\local\lib\python2.7\dist-packages\foo\bar\__init__.pyc
\usr\local\lib\python2.7\dist-packages\foo\bar\bar.py
\usr\local\lib\python2.7\dist-packages\foo\bar\bar.pyc
2.2列出單獨的模組
如果釋出中僅包含較少的模組,你可能更喜歡列出所有模組,而不是列出包,特別是在root package中存在單一模組的情況(或者根本就沒有包)。可以使用py_modules引數,比如下面的例子:
setup(name='foo',
version='1.0',
py_modules = ['mod1', 'pkg.mod2']
)
它描述了兩個模組,一個在root package中,另一個在pkg包中。根據預設的包/目錄對應規則,這兩個模組存在於檔案mod1.py和pkg/mod2.py中,並且要存在pkg/__init__.py檔案(不存在的話,會產生報警:package init file 'pkg/__init__.py' not found (or not a regular file))。當然,也可以使用package_dir選項改變這種對應關係。所以,原始碼樹如下:
setup.py
mod1.py
pkg/
__init__.py
mod2.py
最終生成的檔案是:
\usr\local\lib\python2.7\dist-packages\foo-1.0.egg-info
\usr\local\lib\python2.7\dist-packages\mod1.py
\usr\local\lib\python2.7\dist-packages\mod1.pyc
\usr\local\lib\python2.7\dist-packages\pkg\__init__.py
\usr\local\lib\python2.7\dist-packages\pkg\__init__.pyc
\usr\local\lib\python2.7\dist-packages\pkg\mod2.py
\usr\local\lib\python2.7\dist-packages\pkg\mod2.pyc
2.3擴充套件模組
在Distutils中描述擴充套件模組較描述純python模組要複雜一些。對於純python模組,僅需要列出模組或包,然後Distutils就會去尋找合適的檔案,這對於擴充套件模組來說是不夠的,你還需要指定副檔名、原始碼檔案以及其他編譯/連結需要的引數(需要包含的目錄,需要連線的庫等等)
描述擴充套件模組可以由setup函式的關鍵字引數ext_modules實現。ext_modules是Extension例項的列表,每一個Extension例項描述了一個獨立的擴充套件模組。比如釋出中包含一個獨立的擴充套件模組稱為foo,由foo.c實現,且無需其他編譯連結指令,那麼下面的語句就可以描述該擴充套件模組:
Extension('foo', ['foo.c'])
Extension可以從distutils.core中隨setup一起引入。因此,對於僅包含一個擴充套件模組的釋出來說,setup指令碼如下:
from distutils.core import setup, Extension
setup(name='foo',
version='1.0',
ext_modules=[Extension('foo', ['foo.c'])],
)
底層的擴充套件構建機制是由build_ext命令實現的。Extension類在描述Python擴充套件時具有很大的靈活性。
2.3.1 副檔名和包
通常,Extension類的建構函式的第一個引數都是擴充套件的名字,比如下面的語句:
Extension('foo', ['src/foo1.c', 'src/foo2.c'])
如果執行python setup.py bdist,就會呼叫相應的編譯器和聯結器命令,最終根據生成foo.so檔案,存放在釋出包的根目錄中,最終生成的檔案是:
\usr\local\lib\python2.7\dist-packages\foo.so
\usr\local\lib\python2.7\dist-packages\foo-1.0.egg-info
又比如下面的語句:
Extension('pkg.foo', ['src/foo1.c', 'src/foo2.c'])
使用的原始檔是一樣的,最終生成的結果檔案也是一樣的foo.so,唯一的不同是最終的結果檔案存放的目錄,是在釋出包的根目錄下的pkg目錄下。因此最終生成的檔案是:
\usr\local\lib\python2.7\dist-packages\pkg\foo.so
\usr\local\lib\python2.7\dist-packages\foo-1.0.egg-info
如果一個包下有多個擴充套件,而且要把這些擴充套件都放在統一的目錄下,則可以使用ext_package關鍵字,比如下面的語句:
setup(...,
ext_package='pkg',
ext_modules=[Extension('foo', ['src/foo.c']),
Extension('subpkg.bar', ['src/bar.c'])]
)
上面的描述將會編譯src/foo.c為pkg.foo,將src/bar.c編譯為pkg.subpkg.bar。因此原始碼樹如下:
setup.py
src/
foo.c
bar.c
最終生成的檔案是:
\usr\local\lib\python2.7\dist-packages\foo-1.0.egg-info
\usr\local\lib\python2.7\dist-packages\pkg\foo.so
\usr\local\lib\python2.7\dist-packages\pkg\subpkg\bar.so
2.3.2 擴充套件的原始碼檔案
Extension構建函式的第二個引數是原始檔的列表。目前Distutils僅支援C、C++和Objective-C擴充套件,所以這些原始碼檔案就是C、C++和Objective-C的原始碼檔案。(C++原始碼檔案的副檔名可以是.cc和.cpp,Unix和Windows編譯器都支援)
不過還可以在列表中包含SWIG介面檔案(.i檔案),build_ext命令知道如何處理SWIG介面檔案。儘管會發生報警,但是可以像下面這樣傳遞SWIG選項:
setup(...,
ext_modules=[Extension('_foo', ['foo.i'],
swig_opts=['-modern', '-I../include'])],
py_modules=['foo'],
)
或者是使用如下命令:
> python setup.py build_ext --swig-opts="-modern -I../include"
在一些系統上,該列表中還可以包含能由編譯器處理的非原始碼檔案。當前只支援Windows message 文字檔案(.mc)和Visual C++的資源定義檔案(.rc)。它們將會編譯為二進位制檔案.res並且連結進可執行檔案中。
2.3.3其他選項
Extension還可以指定其他選項,比如可以指定標頭檔案目錄,define或undefine巨集、需要連結的庫,連結時和執行時搜尋庫的路徑等等。具體可參閱:
https://docs.python.org/2/distutils/setupscript.html#preprocessor-options
https://docs.python.org/2/distutils/setupscript.html#library-options
https://docs.python.org/2/distutils/setupscript.html#other-options
2.4釋出和包的關係
釋出和包有三種關係:它依賴其他包,它服務於其他包,它淘汰其他包。這些關係可以分別用setup函式的引數requires ,provides 和obsoletes 來指定,具體參閱:https://docs.python.org/2/distutils/setupscript.html#relationships-between-distributions-and-packages
2.5安裝指令碼
模組通常不自己執行,而是由指令碼引入。除了可以安裝模組之外,還可以安裝能直接執行的指令碼,具體參閱https://docs.python.org/2/distutils/setupscript.html#installing-scripts
2.6安裝package data
有時包中還需要安裝其他檔案,這些檔案與包的實現密切相關,或者是包含文件資訊的文字檔案等,這些檔案就叫做package data。
使用setup函式中的package_data引數可以向packages中新增package data。該引數的值必須是個字典,字典的key就是package name,value是個list,其中包含了需要複製到package中的一系列路徑。這些路徑都是相對於包目錄而言的(比如package_dir),所以,這些檔案必須存在於包的原始碼目錄中。在安裝時,也會建立相應的目錄。
比如,如果包中有一個包含資料檔案的子目錄,原始碼樹如下:
setup.py
src/
mypkg/
__init__.py
module.py
data/
tables.dat
spoons.dat
forks.dat
相應的setup函式可以這樣寫:
setup(...,
packages=['mypkg'],
package_dir={'mypkg': 'src/mypkg'},
package_data={'mypkg': ['data/*.dat']},
)
2.7安裝其他檔案
可以通過data_files選項來安裝除了上面提到過的檔案之外的其他檔案,比如配置檔案,資料檔案等。data_files是個列表,列表中的元素是(directory, files),比如:
setup(...,
data_files=[('bitmaps', ['bm/b1.gif', 'bm/b2.gif']),
('config', ['cfg/data.cfg']),
('/etc/init.d', ['init-script'])]
)
(directory, files)中,directory表示檔案最終要被安裝到的地方,如果它是相對路徑的話,則是相對於installation prefix而言(對於純python包而言,就是sys.prefix;對於擴充套件包,則是sys.exec_prefix)。files是要安裝的檔案,其中的目錄資訊(安裝前)是相對於setup.py所在目錄而言的,安裝時,setup.py根據files的資訊找到該檔案,然後將其安裝到directory中。
2.8元資料
Setup指令碼可以包含很多釋出的元資料,比如名稱、版本、作者等資訊,具體列表和注意資訊,參閱https://docs.python.org/2/distutils/setupscript.html#additional-meta-data
2.9除錯setup指令碼
如果在執行setup指令碼是發生了錯誤,則Distutils會打印出簡單的錯誤資訊,對於開發者而言這些錯誤資訊可能不足以找到錯誤的原因。所以可以通過設定環境變數DISTUTILS_DEBUG,將其置為任意值(不能是空字串),Distutils就會列印其執行過程的詳細資訊,並且在發生異常時列印全部的traceback,並且在像C編譯器這樣的外部程式發生錯誤時,列印整個命令列。
三:配置檔案
一般情況下,在構建釋出時無法將所有的選項都確定下來,有些選項的值可能來自於使用者,或者使用者的系統。這也就是配置檔案setup.cfg存在的目的,使用者可以通過修改該配置檔案進行選項的配置。
在構建時,選項的處理順序是setup指令碼、配置檔案,命令列。所以,安裝者可以通過修改setup.cfg檔案來覆蓋setup.py中的選項;也可以通過執行setup.py時的命令列選項,來覆蓋setup.cfg。
配置檔案的基本語法如下:
[command]
option=value
...
command就是Distutils的命令(比如build_py,install等),option就是命令支援的選項。配置檔案中的空行、註釋(以’#’開頭,直到行尾)會被忽略。
可以通過--help選項得到某個命令支援的選項,比如:
> python setup.py --help build_ext
[...]
Options for 'build_ext' command:
--build-lib (-b) directory for compiled extension modules
--build-temp (-t) directory for temporary files (build by-products)
--inplace (-i) ignore build-lib and put compiled extensions into the
source directory alongside your pure Python modules
--include-dirs (-I) list of directories to search for header files
--define (-D) C preprocessor macros to define
--undef (-U) C preprocessor macros to undefine
--swig-opts list of SWIG command line options
[...]
注意,命令列中的選項”--foo-bar”,在配置檔案中要寫成”foo_bar”。
比如,執行以下命令:
python setup.py build_ext --inplace
如果不希望每次執行命令時都輸入”--inplace”選項,則可以在配置檔案中寫明:
[build_ext]
inplace=1
其他例子和注意事項,可以參閱https://docs.python.org/2/distutils/configfile.html
四:原始碼釋出
之前已經提到過,使用sdist命令可以建立包的原始碼釋出,該命令最終生成一個archive檔案。Unix上預設的檔案格式是.tar.gz,在Windows上的是ZIP檔案。可以使用”--formats”選項指定生成的格式,比如:python setup.py sdist --formats=gztar,zip,執行該命令後,就會生成兩個檔案foo-1.0.tar.gz 和foo-1.0.zip。
支援的格式有:
Format |
Description |
zip |
zip file (.zip) |
gztar |
gzip’ed tar file (.tar.gz) |
bztar |
bzip2’ed tar file (.tar.bz2) |
ztar |
compressed tar file (.tar.Z) |
tar |
tar file (.tar) |
當在Unix上使用tar格式時(gztar,bztar,ztar或tar),可以通過owner和group選項指定使用者和群組。比如:
python setup.py sdist --owner=root --group=root
4.1指定釋出的檔案
如果沒有明確的列出需要釋出的檔案,則sdist命令預設在原始碼釋出中包含下列檔案:
由py_modules和packages選項指定的所有python原始碼檔案;
由ext_modules或libraries選項指定的所有C原始碼檔案;
由scripts指定的指令碼;
測試指令碼:test/test*.py;
README.txt (或者README), setup.py 和setup.cfg;
package_data指定的所有檔案;
data_files指定的所有檔案。
如果還需要釋出其他額外的檔案,典型的做法是編寫一個叫做MANIFEST.in的manifest模板。manifest模板包含如何建立MANIFEST檔案的一系列指令,sdist命令會解析該模板,根據模板中的指令,以及找到的檔案生成MANIFEST。
檔案MANIFEST中明確的列出了包含在原始碼釋出中的所有檔案。比如下面就是一個MANIFEST檔案的內容:
# file GENERATED by distutils, do NOT edit
setup.py
lib/__init__.py
lib/foo.py
lib/bar/__init__.py
lib/bar/bar.py
4.2 Manifest相關選項
sdist命令的執行步驟如下:
if the manifest file (MANIFEST by default) exists and the first line does not have a comment indicating it is generated from MANIFEST.in, then it is used as is, unaltered;
if the manifest file doesn’t exist or has been previously automatically generated, read MANIFEST.in and create the manifest;
if neither MANIFEST nor MANIFEST.in exist, create a manifest with just the default file set;
use the list of files now in MANIFEST (either just generated or read in) to create the source distribution archive(s).
如果僅僅需要(重新)建立MANIFEST檔案,則可以使用如下命令:
python setup.py sdist --manifest-only
4.3 MANIFEST.in模板
如果存在MANIFEST.in檔案,則sdist命令就會根據該檔案生成MANIFEST。在MANIFEST.in檔案中,一行一個命令,每一個命令指定了原始碼釋出中需要包含或者需要排除的檔案,比如下面的例子:
include *.txt
recursive-include examples *.txt *.py
prune examples/sample?/build
很容易看出,上面的命令的意思是:包含所有的.txt檔案;包含examples目錄下的所有.txt或者.py檔案;排除所有匹配examples/sample?/build的目錄。所有這些過程,都是在標準規則執行之後執行的,所以可以在模板檔案中排除標準集合中的檔案。關於MANIFEST檔案的其他內容,參閱https://docs.python.org/2/distutils/sourcedist.html
五:構建釋出(Built Distributions)
所謂的構建釋出(built distribution),即是指二進位制包,或是指安裝檔案。當然它未必真的是二進位制,而有可能包含Python原始碼和位元組碼。
構建釋出是為了方便安裝者而建立的,比如對於基於RPM的Linux使用者來說,它可以是二進位制RPM包,而對於Windows使用者來說,它可以是一個可執行的安裝檔案等。
建立包的構建釋出,是前面介紹的packager的主要職責。它們拿到包的原始碼釋出之後,使用setup指令碼以及bdist命令來生成構建釋出。比如,在包的原始碼樹中執行下面的命令:
python setup.py bdist
Distutils就會建立釋出,執行“偽”安裝(在build目錄中),並且建立當前平臺下的預設格式的構建釋出。構建釋出在Unix中的預設格式是一個”dumb”的tar檔案(之所以稱之為”dumb”,是因為該tar檔案只有解壓到特定的目錄下才能工作),而在Windows上是一個簡單可執行安裝檔案。
所以,在Unix上執行上面的命令之後,就會在dist目錄中生成foo-1.0.linux-i686.tar.gz檔案,在合適的位置解壓該檔案,就安裝了foo模組,等同於下載了該模組的原始碼釋出之後執行python setup.py install命令。所謂合適的位置,要麼是檔案系統的根目錄,要麼是Python的prefix目錄,這取決於bdist_dump的命令選項。
bdist命令有一個--formats選項,類似於sdist命令,該選項可用於指定生成的構建釋出的格式,比如命令:
python setup.py bdist --format=zip
在Unix上執行該命令,就會建立foo-1.0.linux-i686.zip檔案,在根目錄下解壓該檔案就安裝了foo模組。構建釋出支援的格式如下:
gztar |
gzipped tar file (.tar.gz) |
ztar |
compressed tar file (.tar.Z) |
tar |
tar file (.tar) |
zip |
zip file (.zip) |
rpm |
RPM |
pkgtool |
Solaris pkgtool |
sdux |
HP-UX swinstall |
wininst |
self-extracting ZIP file for Windows |
msi |
Microsoft Installer. |
當然,也可以不使用--formats選項,而是用bdist的子命令,直接建立相應的格式。比如使用bdist_dump命令可以生成所有的dumb archive格式(tar,ztar,gztar和zip),bdist_rpm會生成原始碼和二進位制的RPM包,bdist的子命令如下表:
Command |
Formats |
bdist_dumb |
tar, ztar, gztar, zip |
bdist_rpm |
rpm, srpm |
bdist_wininst |
wininst |
bdist_msi |
msi |
具體的子命令資訊,可以參閱https://docs.python.org/2/distutils/builtdist.html
六:Distutils與PYPI
PYPI,也就是Python Package Index,它是Python第三方模組的集中營,Python開發者可以向PYPI上傳自己的Python模組。PYPI中存放了釋出檔案以及釋出的元資料。
Distutils提供了register和upload命令,來直接向PYPI推送元資料和釋出檔案,詳細內容可以參閱https://docs.python.org/2/distutils/packageindex.html
七:簡單示例
7.1純Python釋出(模組)
如果只是釋出幾個模組,這些模組沒有放在包中,可是使用py_modules選項。比如原始碼樹如下:
setup.py
foo.py
bar.py
setup指令碼如下:
from distutils.core import setup
setup(name='foobar',
version='1.0',
py_modules=['foo', 'bar'],
)
安裝之後,會生成以下檔案:
\usr\lib\python2.7\site-packages\foobar-1.0-py2.7.egg-info
\usr\lib\python2.7\site-packages\ foo.py
\usr\lib\python2.7\site-packages\ foo.pyc
\usr\lib\python2.7\site-packages\ bar.py
\usr\lib\python2.7\site-packages\ bar.pyc
7.1純Python釋出(包)
如果有很多模組需要釋出,則可以將這些模組放到統一的包中,然後在setup指令碼中指明要釋出的包,而不是列出所有的模組。
即使模組沒有放到包中,也可以通過向setup指令碼宣告root包的方法來發布,與實際的包不同,根目錄下可以沒有__init__.py檔案。比如上面的例子,原始碼樹保持不變,setup指令碼也可以這樣寫:
from distutils.core import setup
setup(name='foobar',
version='1.0',
packages=[''],
)
空字串就意味著root包。安裝之後,生成的檔案跟上面是一樣的。
如果將原始檔放到釋出根目錄下的子目錄中,比如原始碼樹:
setup.py
src/
foo.py
bar.py
這種情況依然可以用宣告root包的方式來發布,只不過需要使用package_dir選項來指明包和目錄的關係:
from distutils.core import setup
setup(name='foobar',
version='1.0',
package_dir={'': 'src'},
packages=[''],
)
安裝之後生成的檔案跟之前是一樣的。
更常見的做法是將多個模組組織在同一個包中,比如在包foobar中包含foo和bar模組,原始碼樹如下:
setup.py
foobar/
__init__.py
foo.py
bar.py
setup指令碼如下:
from distutils.core import setup
setup(name='foobar',
version='1.0',
packages=['foobar'],
)
安裝之後,會生成以下檔案:
\usr\lib\python2.7\site-packages\foobar-1.0-py2.7.egg-info
\usr\lib\python2.7\site-packages\foobar\ __init__.py
\usr\lib\python2.7\site-packages\foobar\ __init__.pyc
\usr\lib\python2.7\site-packages\foobar\foo.py
\usr\lib\python2.7\site-packages\foobar\foo.pyc
\usr\lib\python2.7\site-packages\foobar\bar.py
\usr\lib\python2.7\site-packages\foobar\bar.pyc
如果不想以模組所在的子目錄名來定義包名,則可以使用package_dir選項,比如原始碼樹如下:
setup.py
src/
__init__.py
foo.py
bar.py
則相應的setup指令碼如下:
from distutils.core import setup
setup(name='foobar',
version='1.0',
package_dir={'foobar': 'src'},
packages=['foobar'],
)
安裝之後生成的檔案與上面的例子是一樣的。
或者,直接將所有模組放到釋出的根目錄下:
setup.py
__init__.py
foo.py
bar.py
setup指令碼如下:
from distutils.core import setup
setup(name='foobar',
version='1.0',
package_dir={'foobar': ''},
packages=['foobar'],
)
安裝之後生成的檔案與上面的例子是一樣的。
如果涉及到子包的話,則必須在packages選項中明確的指出。不過,package_dir中的值卻會自動擴充套件到其子目錄。比如原始碼樹如下:
setup.py
src/
__init__.py
foo.py
bar.py
subfoo/
__init__.py
blah.py
setup指令碼如下:
from distutils.core import setup
setup(name='foobar',
version='1.0',
package_dir = {'foobar':'src'},
packages=['foobar', 'foobar.subfoo'],
)
安裝之後,生成檔案如下:
\usr\lib\python2.7\site-packages\foobar-1.0-py2.7.egg-info
\usr\lib\python2.7\site-packages\foobar\ __init__.py
\usr\lib\python2.7\site-packages\foobar\ __init__.pyc
\usr\lib\python2.7\site-packages\foobar\foo.py
\usr\lib\python2.7\site-packages\foobar\foo.pyc
\usr\lib\python2.7\site-packages\foobar\bar.py
\usr\lib\python2.7\site-packages\foobar\bar.pyc
\usr\lib\python2.7\site-packages\foobar\subfoo\__init__.py
\usr\lib\python2.7\site-packages\foobar\subfoo\__init__.pyc
\usr\lib\python2.7\site-packages\foobar\subfoo\blah.py
\usr\lib\python2.7\site-packages\foobar\subfoo\blah.pyc
7.3單獨的擴充套件模組
擴充套件模組由選項ext_modules指定。package_dir選項對擴充套件模組的原始碼檔案沒有作用,它隻影響純Python模組。比如原始碼樹如下:
setup.py
foo.c
如果setup指令碼如下:
from distutils.core import setup
from distutils.extension import Extension
setup(name='foobar',
version='1.0',
ext_modules=[Extension('foo', ['foo.c'])],
)
則生成的檔案是:
\usr\lib\python2.7\site-packages\foobar-1.0-py2.7.egg-info
\usr\lib\python2.7\site-packages\ foo.so
如果原始碼樹不變,setup指令碼如下:
from distutils.core import setup
from distutils.extension import Extension
setup(name='foobar',
version='1.0',
ext_modules=[Extension('foopkg.foo', ['foo.c'])],
)
則生成的檔案是:
\usr\lib\python2.7\site-packages\foobar-1.0-py2.7.egg-info
\usr\lib\python2.7\site-packages\foopkg\ foo.so
八:其他
執行install命令,會首先執行build命令,然後執行子命令install_lib,install_data和install_scripts。
Distutils可以進行擴充套件,比如增加新的命令、修改現有的命令。可參閱https://docs.python.org/2/distutils/extending.html
Distutils的API參閱https://docs.python.org/2/distutils/apiref.html