setuptools 和 setup.py
Setuptools
和 distutils
都是用於編譯、分發和安裝 python 包的一個工具,特別是在包依賴問題場景下非常有用,它是一個強大的包管理工具。Setuptools 是 distutils 的加強版。編譯、分發和安裝 python 包的一個關鍵的事就是編寫 setup 腳本。setup 腳本的主要作用在於向包管理工具 Setuptools
或 distutils
說明你的模塊分發細節,所以 Setuptools
支持大量的命令以操作你的包。setup 腳本主要調用一個 setup()
方法,許多提供給 Setuptools
的信息都以 keyword arguments
的參數形式提供給 setup()
方法。
你所需要做的事 & 一些概念
對於包開發者和使用者,所需要做的事:
- 編寫 setup.py 腳本,用於處理你的包
- (可選)編寫 setup 配置文件
- 創建源碼分發文件,
python setup.py sdist
, - (可選)創建二進制分發文件,
python setup.py bdist
對於包使用者,只需要 python setup.py install
,便可以成功安裝 python 包。
基礎概念
- module 模塊:module 是 python 中代碼重用的基本單元,一個 module 可以通過
import
語句導入到另一個 module;module 分為:pure python module
(純 python 模塊)、extension module
(擴展模塊)和package
(包) - pure python module:純 python 模塊是用純 python 語言編寫的模塊,單一的
.py
文件作為一個模塊使用,也就是一個.py
可以稱為模塊了 - extension module:擴展模塊是用底層的 C/C++、Objective-C或 Java 編寫的模塊,通常包含了一個動態鏈接庫,比如 so、dll 或 Java,目前
distutils
只支持 C/C++ 和 Objective-C,不支持 Java 編寫擴展模塊;但是 python 提供了一個JCC
這樣一個用於生成訪問 Java 類接口的 C++ 代碼的膠水模塊,應該也是可以使用 Java 編寫模塊的。 - package:包是一個帶有
__init__.py
文件的文件夾,用於包含其他模塊 - root package:root package 是包的最頂層,它不是實質性的包,因為它不包含
__init__.py
文件。大量的標准庫位於root package
,因為它們不屬於一個任何更大的模塊集合了。實際上,每一個sys.path
列舉出來的文件夾都是root package
,你可以在這些文件夾中找到大量的模塊。 - distribution:模塊分發,一個歸檔在一起的 python 模塊集合,它作為一個可下載安裝的資源,方便用戶使用,作為開發者便需要努力創建一個易於使用的
distribution
。 - distribution root:源代碼樹的最頂層,也就是
setup.py
所在的位置。
關於源碼分發文件和二進制分發文件
源碼分發文件是將包分享給其他人更為推薦的一種形式,因為源碼分發文件比二進制分發文件更適合跨平台,這樣使用者可以在自己的機器上通過編譯得到自己的機器相關的包代碼並且進行安裝。
示例和分發選擇
- 如果你只是發布幾個腳本文件而已,特別是它們邏輯上不屬於同一個包,你可以使用
py_modules
選項一個一個地指定; - 如果你需要發布的模塊文件太多,使用
py_modules
一個一個指定比較麻煩,特別是模塊位於多個包中,那么你可以使用packages
指定整個包,另外只需要另外指定package_dir
,位於 distribution root 下的模塊文件也可以被處理; - setuptools 幫助文檔聲明,
package_dir
和py_modules
也可以支持分發任何沒有包含__init__.py
文件夾下的模塊,經測試安裝過程沒有報錯,但是沒有包含__init__.py
的文件夾下的模塊是沒有被正確安裝的!因此,如果 python 模塊分布在不同的文件夾,最好是在該文件夾下創建一個__init__.py
文件,以表示它是一個包。
pure python module
舉個簡單的例子,你需要發布兩個模塊 foo 以及 bar.bar,以供別人使用(import foo 和 import bar.bar)。
其目錄樹如下:
pure_module
├── bar
│ └── bar.py
│ └── __init__.py
├── foo.py
└── setup.py
如上圖所示,pure_module 目錄下包含了一個 foo 模塊以及一個 bar 包,同時在 bar 包下還包含了一個 bar 模塊。
一個僅使用 py_modules
的 setup 腳本可以這樣寫:
from setuptools import setup
NAME = 'foo'
VERSION = '1.0'
PY_MODULES = ['foo', 'bar.bar']
setup(name = NAME
, version = VERSION
, py_modules = PY_MODULES)
py_modules 指定了 foo 模塊以及 bar.bar 模塊。
通過 python setup.py install --user --prefix=
進行安裝后,便可以直接通過 import foo 和 import bar.bar
直接使用了。
- 注意:經測試,如果
.py
文件位於其他文件夾,該文件夾需要創建一個允許為空的__init__.py
文件,表示為一個 package,否則安裝后不能正常使用其他文件夾的模塊。
package
上一節的例子中,bar.bar 屬於 bar 包,foo 位於 distribution root,安裝后屬於 root package,在 Setuptools 中 "" 可以用於表示 root package。所以下面展示兩種 setup 腳本的寫法:
pure_module
├── bar
│ └── bar.py
│ └── __init__.py
├── foo.py
└── setup.py
僅使用 packages
的 setup.py 文件如下:
from setuptools import setup
NAME = 'foo'
VERSION = '1.0'
PACKAGES = ['', 'bar']
setup(name = NAME
, version = VERSION
, packages = PACKAGES
)
如上,packages 包含了 package 的列表 root package 以及 bar,這樣便能輕松覆蓋到 distribution root 下的 foo.py 和 bar 文件夾下的 bar.py 了,在存在大量模塊的情況下,省去像 py_modules
一樣窮舉模塊的麻煩。在 python 中,默認的情況下,包的名字和目錄的名字是一致的,比如 bar 包對應了 bar 目錄,且包的路徑表示是相對於 distribution root 的(也就是 setup.py 所在目錄)。比如 packages = ['foo']
,Setuptools 會在 setup.py 所在目錄下尋找 foo/__init__.py
,並將 foo/ 下的所有模塊包含進去。
另外一個關鍵字是 package_dir
,它的作用是將 package 映射到其他目錄,這樣的一個好處是方便將 package 移到其他目錄而不用修改 packages
的參數值。舉個例子,假設我們現在需要把 bar 移到 foobar 目錄下,按照原來的腳本,Setuptools 是無法成功找到 bar 包的。
package_dir
├── foobar
│ ├── bar.py
│ └── __init__.py
├── foo.py
└── setup.py
通過 package_dir = = {'bar':'foobar'}
,將原來的 bar package 映射到 foobar 下。完整的腳本如下:
from setuptools import setup
NAME = 'foo'
VERSION = '1.0'
PACKAGE_DIR = {'bar':'foobar'}
PACKAGES = ['', 'bar']
setup(name = NAME
, version = VERSION
, package_dir = PACKAGE_DIR
, packages = PACKAGES
)
package_dir 是一個字典,它的 key 是 package 名("" 表示 root package),value 是相對於 distribution root 的目錄名。在上面的例子中, package_dir = = {'bar':'foobar'}
改變了 packages
中 package 對應的目錄位置,這樣當 Setuptools 在找 bar package 時,會在 foobar 目錄下找相應的 __init__.py
文件。
- 注意,package_dir 會影響 packages 下列出的所有 package,比如,
packages =['bar', 'bar.lib']
,package_dir 不僅會影響所有和 bar 有關的 package,bar.lib
也會相應被映射到foorbar.lib
。
extension module
擴展模塊需要使用 ext_modules
參數。上面所說的 package_dir
和 packages
都是針對純 python 模塊,而 ext_modules
針對的是使用 C/C++ 底層語言所寫的模塊。下面舉個最簡單的例子,擴展模塊僅包含一個 foo.cpp 文件,其中定義了可供 python 調用的 myPrint 函數。
#include <iostream>
#include <string>
using namespace std;
void myPrint(string text)
{
cout << text << endl;
}
.
├── setup.py
└── src
├── foo.cpp
├── foo.h
└── PythonWrapAPI.cpp
現在,我們想要發布擴展模塊供別人使用我們的 myPrint 方法。想要 python 中成功導入你的包,需要利用額外的代碼封裝將被調用的方法。這里的 PythonWrapAPI.cpp 的作用就是使用 Python 提供的庫封裝你所寫的接口,它是處在 python 和 C++ 間的膠水庫,當 python 調用你的 C++ 方法時,由於語言類型的差別,需要做轉換。
PythonWrapAPI.cpp 如下:
#include "foo.h"
#include <string>
#include <python2.6/Python.h>
using namespace std;
/*
Notice:Python Interface Wrap
*/
static PyObject *_myPrint(PyObject *self, PyObject *args)
{
char *text;
// 解析 Python 傳過來的參數
if (!PyArg_ParseTuple(args, "s", &text))
return NULL;
myPrint(text);
return Py_None;
}
static PyMethodDef ExtestMethods[] =
{
{ "myPrint", _myPrint, METH_VARARGS },
{ NULL, NULL },
};
PyMODINIT_FUNC initmyprint(void) {
(void)Py_InitModule("myprint", ExtestMethods);
}
- wrapper 函數 _myPrint。它負責將 Python 的參數轉化為C/C++的參數(PyArg_ParseTuple),然后調用實際的 myPrint,並處理 myPrint 的返回值,最終返回給Python環境。需要注意的是,C/C++ 中無返回值時,不能直接返回 NULL,而是需要返回 Py_None。
- 導出表 ExtestMethods。它負責告訴Python這個模塊里有哪些函數可以被 Python 調用。導出表的名字可以隨便起,每一項有4個參數:第一個參數是提供給 Python 環境的函數名稱,第二個參數是_myPrint,即 wrapper 函數。第三個參數的含義是參數變長,第四個參數是一個說明性的字符串。導出表總是以{NULL, NULL, 0, NULL}結束。說明,第 3 和 4 個參數可以省略。
- 導出函數 initmyprint。這個的名字不是任取的,是你的 module 名稱添加前綴init。導出函數中將模塊名稱與導出表進行連接。
擴展模塊和純 python 模塊有點不太一樣,我們導入的 package 名字與 setup() 的 packages
或者 package_dir
參數是一致的,但是擴展模塊的名字是由 Extension
實例的 name
參數決定的,且需要和導出函數對應的initxxx 名字以及 Py_InitModule
方法對應的第一個參數相同。
最后,我們需要編寫 setup 腳本編譯我們的 cpp 文件為 so 動態鏈接庫,並進行相應的封裝。運行 python setup.py install
,setuptools 會幫我們自動編譯。
from setuptools import setup, Extension, find_packages
# package name, import NAME
NAME = "foo"
VERSION = '1.0.0'
# an Extension instance list
EXT_MODULES = [
Extension(
name = 'myprint'
, sources=['src/foo.cpp','src/PythonWrapAPI.cpp']
, include_dirs = ['src']
)
]
setup(name = NAME
, version = VERSION
, ext_modules = EXT_MODULES
, )
ext_modules
是一個 Extension
實例列表,Extension 的參數 sources 用於指定所有源文件位置,include_dirs 指定頭文件位置,同時還可以使用 library_dirs 和 libraries 指定外部鏈接庫,以及 extra_compile_args 指定額外的編譯參數。
package 元信息參數
在編寫一個 package 的時候,盡量提供更多的元信息,這樣使用者更加能夠了解到 package 的相關信息,並且有些信息會被 PyPi 使用。
元信息 | 類型 | 描述 | 說明 |
---|---|---|---|
name | short string | package 名字, 這里用於 pypi 的顯示, 而不是用於 import, import 的包名與 packages 和 package_dir 參數一致 | 1 |
version | short string | package 的發布版本, 建議:major.minor[.patch[.sub]] | 1 |
author | short string | package 作者名字 | 3 |
author_email | short string | package 作者郵箱 | 3 |
maintainer | short string | package 維護者名字 | 3 |
maintainer_email | short string | package 維護者郵箱 | 3 |
url | short string | package 項目地址 | 1 |
description | short string | 簡介 | |
long_description | long string | 顯示於 pypi 的介紹 | |
download_url | short string | package 下載地址 | 2 |
classifiers | strings list | 分類符, 這樣便於 pypi 索引, 由 pypi 固定提供, https://pypi.python.org/pypi?%3Aaction=list_classifiers | 2 |
platforms | strings list | 支持的平台列表 | |
license | short string | 授權協議 |
- 必填
- 如果為了兼容 2.2.3 或 2.3 版本,不建議使用此字段
- 如果提供了 maintainer,那么 distutils 會將其加入 PKG-INFO
package 內容參數
py_modules 列舉每個模塊
py_modules 是一個字符串列表,用於指定所有的模塊,即 py 文件模塊。 如果你只是發布幾個腳本文件而已,特別是它們邏輯上不屬於同一個 package。
比如,py_modules = ['mod1', 'pkg.mod2']
。這指定了兩個模塊,一個位於 root package
,而另一個位於 pkg package
。如果沒有使用 package_dir
重新映射 package 和目錄的關系的話,那么這兩個模塊分別對應了 mod1.py
以及 pkg/mod2.py
文件,並且在 pkg 文件夾下還存在 __init__.py
文件。
package 列舉每個包
如果你需要發布的模塊文件太多,使用 py_modules
一個一個指定比較麻煩,特別是模塊位於多個包中,那么你可以使用 packages
指定整個包。
packages 是一個包名列表,packages 參數告訴 Setuptools 處理列舉出的 package 下所有純 python 模塊。在文件系統中,默認地,package 的名字與目錄是一一對應的,也就是說,packages = ['foo']
,Setuptools 會去查找 foo/__init__.py
文件。
package_dir 重新映射 package 和目錄的關系
當你想要重命名你的 package 所在的文件夾,或者想要移動整個 package 到其他目錄下,一般情況下,一旦你的源代碼布局改變,你需要重新修改 packages。但是 package_dir 可以重新映射 package 和目錄的關系。比如你將 root package 下的模塊和 package 移到 lib 目錄下,那么你只需要在 package_dir 中將 root package 映射到 lib 下。比如 package_dir = {'': 'lib'}
再舉個例子,比如一下目錄結果,當我使用 package_dir= {'bar':'foobar'}
和 packages= ['bar']時,Setuptools 根據 packages
參數查找 bar package 時,會在 foobar 文件夾下找相應的 __init__.py
文件。
package_dir
├── foobar
│ ├── bar.py
│ └── __init__.py
└── setup.py
- 注意,package_dir 會影響 packages 下列出的所有 package,比如,
packages =['bar', 'bar.lib']
,package_dir 不僅會影響所有和 bar 有關的 package,bar.lib
也會相應被映射到foorbar.lib
。
install_requires 和 dependency_links 安裝依賴模塊
install_requires
可以聲明 package 安裝時所需的依賴模塊及其版本。安裝 package 時,Setuptools 便能夠從 PyPi 上自動下載其所依賴的模塊,並且將依賴信息包含進 Python Eggs 中。
比如我們在自己的 package 中用到了一個 python 非標准庫 pycurl 和 xmltodict,當我們的 package 在別的機器上使用時便會報錯。為了解決這個問題,我們可以使用 install_requires = ['pycurl', 'xmltodict']
將 pycurl 和 xmltodict 加入 package 依賴。
install_requires
可以是 string 或 string list,指明所需要依賴的模塊。當 install_requires
為 string 類型,並且依賴多於 1 個時,每個依賴的聲明都要另起一行。
最新版本的 Setuptools 的install_requires
有另外兩個作用:
- 在運行時,任何腳本都會檢查其依賴模塊的正確性,並且確保正確的依賴版本都加入到 sys.path 中(假如有多個版本的話)。
- Python Egg distributions 將會包含依賴相關的元信息。
前面說到,Setuptools 便能夠從 PyPi 上自動下載其所依賴的模塊,但是在某些環境下無法正常訪問 Pypi 下,我們也可以通過 dependency_links
參數 指定到自己的 python 源,這樣便可以解決下載問題。
比如,dependency_links = ['http://xxx/xmltodict', 'http://xxx/pycurl']
。
dependency_links 是一個字符串列表,包含了依賴的下載 URL。
Setuptools 對鏈接的支持比較強大!
下載的資源可以滿足以下條件:
- 通過 python setup.py sdist 進行分發的壓縮文件,默認情況下在 linux 為 .tar.gz,在 windows 為 zip
- 單一的 py 文件
- VCS 倉庫(Subversion, Mercurial, Git)
URL 鏈接可以是:
- 可以直接下載的 URL
- 包含資源下載鏈接的網頁 URL
- 倉庫 URL
當包含資源下載鏈接的網頁 URL 中存在多個版本時,Setuptools 會根據版本要求下載合適的版本。
一般,比較好的方式是網頁 URL 方式。我們也可以使用 SourceForge 的 showfiles.php 鏈接來下載我們所依賴的模塊。
如果依賴的模塊是一個 py 文件時,你必須在 URL 添加 "#egg=project-version"
后綴,以指出模塊的名字和版本,另外需要確保將模塊名和版本中出現的 -
替換為 _
。EasyInstall 將會識別這個后綴並且自動創建一個 setup.py 腳本,將我們的 py 文件包裝為 egg 文件。如果為 VCS,將會 checkout 對應的版本,創建一個臨時文件夾並執行 setup.py bdist_egg,安裝所需的依賴。
在使用 VCS 的情況下,你也可以使用 #egg=project-version
指定要使用的版本。你可以通過在 #egg=project-version
前加入 @REV
來指定要 checkout 的版本。另外你也可以通過在 URL 前加上以下標識顯式聲明 URL 使用 的是 VCS:
- Subversion:
svn+URL
- Git:
git+URL
- Mercurial:
hg+URL
因此使用 VCS 更復雜的一個示例為: vcs+proto://host/path@revision#egg=project-version
ext_module Python 調用 C/C++
Python 的可擴展性特別強,不僅支持 python 語言的擴展模塊,而且支持其他語言的擴展。
Python 調用 C++ 的詳細文檔可以查看 https://docs.python.org/2/extending/building.html
這里假設已經懂得怎么調用 C++ 方法了,接下來只需要使用 ext_module 參數,使 Setuptools 能夠編譯和安裝擴展模塊了。
ext_module
參數是一個 Extension 實例列表,Extension 類似於 gcc/g++ 的所需參數,包含了指定源文件、頭文件、依賴的靜態庫或動態庫、額外的編譯參數、宏定義等功能。
name 擴展模塊名字
name
是一個字符串,用於指定擴展模塊的名字。
packages
和 package_dir
用於支持 python 語言編寫模塊,其 import 語句使用的包名與 packages
和 package_dir
中所指定的名字是一致的。但是擴展模塊的名字是由 Extension
實例的 name
參數決定的,且需要和導出函數對應的initxxx 名字以及 Py_InitModule
方法對應的第一個參數相同。定義好模塊的名字 xxx 后,我們便可以使用 import xxx
使用我們自己的模塊了。
sources 和 include_dirs
sources
為用於指定要編譯源文件的字符串列表,比如,sources=['foo/foo.cpp', 'bar/bar.cpp']
,Setuptools 支持 C/C++ 以及 Objective-C。
include_dirs
為用於指定編譯需要的頭文件目錄的字符串列表,比如,include_dirs=['foo/include', 'bar/include']
。如果頭文件位於 distribution root 目錄,需要使用 '.'
表示頭文件位於當前目錄,不能為 ''
,否則將找不到頭文件。
另外還支持 extra_objects
向鏈接程序傳遞 object 文件,比如 .o
文件。
define_macros 和 undef_macros
gcc 支持在編譯的時候定義新的宏變量和取消某個宏變量的定義,具體的選項 [-Dmacro[=defn]...] [-Umacro]
。Extension 也支持這樣的選項。
你可以使用 define_macros
和 undef_macros
定義新的宏變量和取消某個宏變量的定義。
define_macros
是一個 (name, value)
元組列表,其中的 name 為宏變量名字符串,value 為對應的值,可以為字符串、數字或為 None 類型(說明:官方文檔沒有聲明 value
可以為數字,但是經過測試,只要是 python 支持的數字類型都可以用於 value
,但是最好還是使用字符串的形式,這樣腳本的兼容性會更好).
比如,define_macros=[('DEBUG', None), ('FOO', '1'), ('BAR', 2), ('FOOBAR', '"abc"')]
,gcc 對應的編譯選項結果為 -DDEBUG -DFOO=1 -DBAR=2 -DFOOBAR="abc"
。
undef_macros
比 define_macros
簡單得多,它就是一個宏變量字符串列表,舉個例子,我們想要取消以上定義的宏變量,對應的 undef_macros 值為 undef_macros=['DEBUG', 'FOO', 'BAR', 'FOOBAR']
。
libraries 和 library_dirs
Setuptools 對 C/C++ 庫的引用方法和 gcc 一樣,具體的規則可以參考 gcc。
libraries 為要添加的庫的名字字符串列表,而 library_dirs 為要添加的庫所在的目錄,舉個例子:
.
├── setup.py
└── curl
├── include
├── curl.h
├── test.h
├── lib
├── libcurl.a
├── libtest.a
其對應的參數為 libraries=['curl', 'test']
、library_dirs=['curl/lib']
、include_dirs=[‘curl/include’]
注意:在實際的使用過程中碰到過一個鏈接錯誤的坑,Setuptools 在編譯的時候報錯:
libcurl.a : relocation against .rodata can not be used when making a shared object:recompile with -fPIC
libcurl.a : could not read symbols:Bad value
前面提到,python 在創建擴展模塊時會將源文件編譯為動態鏈接庫,動態鏈接庫在加載的時候,內存位置是不固定的,所以我們鏈接的外部庫代碼也需要全部使用相對地址,這樣代碼便可以加載到內存的任意位置。因為有的庫沒有使用 -fPIC
選項進行編譯,導致庫最終在鏈接到 so 文件時報錯。
解決方案是使用 -fPIC
重新編譯 libcurl.a
庫。
extra_compile_args
在編譯擴展模塊時,Setuptools 會自動指定編譯參數,比如下面一個模塊的編譯:
gcc -pthread -fno-strict-aliasing -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -DNDEBUG -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -fPIC -DDEBUG -DFOO=1 -DBAR=2 -DFOOBAR="abc" -Isrc -I/usr/include/python2.6 -c src/foo.cpp -o build/temp.linux-x86_64-2.6/src/foo.o
gcc -pthread -fno-strict-aliasing -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -DNDEBUG -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -fPIC -DDEBUG -DFOO=1 -DBAR=2 -DFOOBAR="abc" -Isrc -I/usr/include/python2.6 -c src/PythonWrapAPI.cpp -o build/temp.linux-x86_64-2.6/src/PythonWrapAPI.o
g++ -pthread -shared build/temp.linux-x86_64-2.6/src/foo.o build/temp.linux-x86_64-2.6/src/PythonWrapAPI.o -L/usr/lib64 -lpython2.6 -o build/lib.linux-x86_64-2.6/myprint.so
這么多的編譯參數絕大部分是 Setuptools 自動指定的,但是如果我們還想要在每個文件的編譯再加上額外的編譯選項,可以使用 extra_compile_args
和 extra_link_args
,其中 extra_link_args
選項用於鏈接。
extra_compile_args
是一個編譯選項字符串列表,每個編譯選項都要單獨作為一個字符串,不能並在一起,否則會報錯。
創建源碼分發
建議使用源碼分發的形式發布你的包,而不是二進制發布形式,這樣包將更方便跨平台。
sdist 命令
創建源碼分發的命令為:python setup.py sdist
,命令執行后會創建 dist 目錄,收集一些必要的文件以及 setup 腳本,生成一個壓縮文件,用戶安裝時,只需要解壓,然后執行 python setup.py install
命令,將進行編譯和安裝,將相應的文件存放到 python 第三方庫目錄下。
sdist 比較常用的一個選項是 --format
,選擇壓縮的格式。比如,使用 zip 進行壓縮,python setup.py sdist --format=zip
。
格式 | 后綴 |
---|---|
zip | .zip |
gztar | .tar.gz |
bztar | .tar.bz2 |
ztar | .tar.Z |
tar | .tar |
說明:python setup.py sdist --format=zip, tar
,Setuptools 會分別使用 zip 和 tar 進行壓縮,將同時產生兩個壓縮文件。
setuptools 和 distutils 對於文件查找的算法是一樣的:
- 所有在
py_modules
和packages
指定的對應模塊文件 - 所有在
ext_modules
和libraries
選項指定的源文件和庫 scripts
選項指定的腳本文件- 所有類似測試腳本的文件,比如:test/test*.py (低版本的包管理工具可能不支持)
- README.txt(或 README),setup.py 以及 setup.cfg(README 文件目前無法支持更多的后綴格式)
package_data
選項指定的文件data_files
選項指定的文件
另外在使用過程中,遇到 Setuptools 的一個巨坑,確實可以包含文件,但是它並不總能包含文件,這是有前提的。
bdist是發布二進制文件,sdist是發布源文件。而在舊版本的 python 中(2.7 以前), package_data只有在使用 bdist 時候才有用,也就是如果使用 sdist,是無法正確包含文件的。而在新版本中,會自動把package_data 里面的內容添加到 MANIFEST 文件中。
MANIFEST.in 模版文件
當我們使用 sdist 進行分發包時,如果需要包含額外的文件,可以使用 MANIFEST.in
文件,在該文件中列舉出需要包含的文件。當我們執行 sdist 時,將會對 MANIFEST.in
文件進行檢查,讀取解釋並生成 MANIFEST 文件,該文件列舉了所有需要包含進包的文件。位於 distribution root 下的 MANIFEST.in 文件每行對應一條包含一系列文件的命令。