卷1:第14章 Python打包工具

2018-02-24 15:55 更新

作者:Tarek Ziadé,翻譯:張吉

原文:http://www.aosabook.org/en/packaging.html

14.1 簡介

對于如何安裝軟件,目前有兩種思想流派。第一種是說軟件應該自給自足,不依賴于其它任何部件,這點在Windows和Mac OS X系統(tǒng)中很流行。這種方式簡化了軟件的管理:每個軟件都有自己獨立的“領域”,安裝和卸載它們不會對操作系統(tǒng)產(chǎn)生影響。如果軟件依賴一項不常見的類庫,那么這個類庫一定是包含在軟件安裝包之中的。

第二種流派,主要在類Linux的操作系統(tǒng)中盛行,即軟件應該是由一個個獨立的、小型的軟件包組成的。類庫被包含在軟件包中,包與包之間可以有依賴關系。安裝軟件時需要查找和安裝它所依賴的其他特定版本的軟件包。這些依賴包通常是從一個包含所有軟件包的中央倉庫中獲取的。這種理念也催生了Linux發(fā)行版中那些復雜的依賴管理工具,如dpkgRPM。它們會跟蹤軟件包的依賴關系,并防止兩個軟件使用了版本相沖突的第三方包。

以上兩種流派各有優(yōu)劣。高度模塊化的系統(tǒng)可以使得更新和替換某個軟件包變的非常方便,因為每個類庫都只有一份,所有依賴于它的應用程序都能因此受益。比如,修復某個類庫的安全漏洞可以立刻應用到所有程序中,而如果應用程序使用了自帶的類庫,那安全更新就很難應用進去了,特別是在類庫版本不一致的情況下更難處理。

不過這種“模塊化”也被一些開發(fā)者視為缺點,因為他們無法控制應用程序的依賴關系。他們希望提供一個獨立和穩(wěn)定的軟件運行環(huán)境,這樣就不會在系統(tǒng)升級后遭遇各種依賴方面的問題。

在安裝程序中包含所有依賴包還有一個優(yōu)點:便于跨平臺。有些項目在這點上做到了極致,它們將所有和操作系統(tǒng)的交互都封裝了起來,在一個獨立的目錄中運行,甚至包括日志文件的記錄位置。

Python的打包系統(tǒng)使用的是第二種設計思想,并盡可能地方便開發(fā)者、管理員、用戶對軟件的管理。不幸的是,這種方式導致了種種問題:錯綜復雜的版本結構、混亂的數(shù)據(jù)文件、難以重新打包等等。三年前,我和其他一些Python開發(fā)者決定研究解決這個問題,我們自稱為“打包別動隊”,本文就是講述我們在這個問題上做出的努力和取得的成果。

術語

在Python中,??表示一個包含Python文件的目錄。Python文件被稱為?模塊?,這樣一來,使用“包”這個單詞就顯得有些模糊了,因為它常常用來表示某個項目的?發(fā)行版本?。

Python開發(fā)者有時也對此表示不能理解。為了更清晰地進行表述,我們用“Python包(package)”來表示一個包含Python文件的目錄,用“發(fā)行版本(release)”來表示某個項目的特定版本,用“發(fā)布包(distribution)”來表示某個發(fā)行版本的源碼或二進制文件,通常是Tar包或Zip文件的形式。

14.2 Python開發(fā)者的困境

大多數(shù)Python開發(fā)者希望自己的程序能夠在任何環(huán)境中運行。他們還希望自己的軟件既能使用標準的Python類庫,又能使用依賴于特定系統(tǒng)類型的類庫。但除非開發(fā)者使用現(xiàn)有的各種打包工具生成不同的軟件包,否則他們打出的軟件安裝包就必須在一個安裝有Python環(huán)境的系統(tǒng)中運行。這樣的軟件包還希望做到以下幾點:

  • 其他人可以針對不同的目標系統(tǒng)對這個軟件重新打包;
  • 軟件所依賴的包也能夠針對不同的目標系統(tǒng)進行重新打包;
  • 系統(tǒng)依賴項能夠被清晰地描述出來。

要做到以上幾點往往是不可能的。舉例來說,Plone這一功能全面的CMS系統(tǒng),使用了上百個純Python語言編寫的類庫,而這些類庫并不一定在所有的打包系統(tǒng)中提供。這就意味著Plone必須將它所依賴的軟件包都集成到自己的安裝包中。要做到這一點,他們選擇使用zc.buildout這一工具,它能夠?qū)⑺械囊蕾嚢际占饋恚梢粋€完整的應用程序文件,在獨立的目錄中運行。它事實上是一個二進制的軟件包,因為所有C語言代碼都已經(jīng)編譯好了。

這對開發(fā)者來說是福音:他們只需要描述好依賴關系,然后借助zc.buildout來發(fā)布自己的程序即可。但正如上文所言,這種發(fā)布方式在系統(tǒng)層面構筑了一層屏障,這讓大多數(shù)Linux系統(tǒng)管理員非常惱火。Windows管理員不會在乎這些,但CentOS和Debian管理員則會,因為按照他們的管理原則,系統(tǒng)中的所有文件都應該被注冊和歸類到現(xiàn)有的管理工具中。

這些管理員會想要將你的軟件按照他們自己的標準重新打包。問題在于:Python有沒有這樣的打包工具,能夠自動地按照新的標準重新打包?如果有,那么Python的任何軟件和類庫就能夠針對不同的目標系統(tǒng)進行打包,而不需要額外的工作。這里,“自動”一詞并不是說打包過程可以完全由腳本來完成——這點上RPMdpkg的使用者已經(jīng)證實是不可能的了,因為他們總會需要增加額外的信息來重新打包。他們還會告訴你,在重新打包的過程中會遇到一些開發(fā)者沒有遵守基本打包原則的情況。

我們來舉一個實際例子,如何通過使用現(xiàn)有的Python打包工具來惹惱那些想要重新打包的管理員:在發(fā)布一個名為“MathUtils”的軟件包時使用“Fumanchu”這樣的版本號名字。撰寫這個類庫的數(shù)學家想用自家貓咪的名字來作為版本號,但是管理員怎么可能知道“Fumanchu”是他家第二只貓的名字,第一只貓叫做“Phil”,所以“Fumanchu”版本要比“Phil”版本來得高?

可能這個例子有些極端,但是在現(xiàn)有的打包工具和規(guī)范中是可能發(fā)生的。最壞的情況是easy_installpip使用自己的一套標準來追蹤已安裝的文件,并使用字母順序來比較“Fumanchu”和“Phil”的版本高低。

另一個問題是如何處理數(shù)據(jù)文件。比如,如果你的軟件使用了SQLite數(shù)據(jù)庫,安裝時被放置在包目錄中,那么在程序運行時,系統(tǒng)會阻止你對其進行讀寫操作。這樣做還會破壞Linux系統(tǒng)的一項慣例,即/var目錄下的數(shù)據(jù)文件是需要進行備份的。

在現(xiàn)實環(huán)境中,系統(tǒng)管理員需要能夠?qū)⒛愕奈募胖玫剿麄兿胍牡胤?,并且不破壞程序的完整性,這就需要你來告訴他們各類文件都是做什么用的。讓我們換一種方式來表述剛才的問題:Python是否有這樣一種打包工具,它可以提供各類信息,足以讓第三方打包工具能據(jù)此重新進行打包,而不需要閱讀軟件的源碼?

14.3 現(xiàn)有的打包管理架構

Python標準庫中提供的Distutils打包工具充斥了上述的種種問題,但由于它是一種標準,所以人們要么繼續(xù)忍受并使用它,或者轉(zhuǎn)向更先進的工具Setuptools,它在Distutils之上提供了一些高級特性。另外還有Distribute,它是Setuptools的衍生版本。Pip則是一種更為高級的安裝工具,它依賴于Setuptools

但是,這些工具都源自于Distutils,并繼承了它的種種問題。有人也想過要改進Distutils本身,但是由于它的使用范圍已經(jīng)很廣很廣,任何小的改動都會對Python軟件包的整個生態(tài)系統(tǒng)造成沖擊。

所以,我們決定凍結Distutils的代碼,并開始研發(fā)Distutils2,不去考慮向前兼容的問題。為了解釋我們所做的改動,首先讓我們近距離觀察一下Distutils。

14.3.1 Distutils基礎及設計缺陷

Distutils由一些命令組成,每條命令都是一個包含了run方法的類,可以附加若干參數(shù)進行調(diào)用。Distutils還提供了一個名為Distribution的類,它包含了一些全局變量,可供其他命令使用。

當要使用Distutils時,Python開發(fā)者需要在項目中添加一個模塊,通常命名為setup.py。這個模塊會調(diào)用Distutils的入口函數(shù):setup。這個函數(shù)有很多參數(shù),這些參數(shù)會被Distribution實例保存起來,供后續(xù)使用。下面這個例子中我們指定了一些常用的參數(shù),如項目名稱和版本,它所包含的模塊等:

from distutils.core import setup

setup(name='MyProject', version='1.0', py_modules=['mycode.py'])

這個模塊可以用來執(zhí)行Distutils的各種命令,如sdist。這條命令會在dist目錄中創(chuàng)建一個源代碼發(fā)布包:

$ python setup.py sdist

這個模塊還可以執(zhí)行install命令:

$ python setup.py install

Distutils還提供了一些其他命令:

  • upload?將發(fā)布包上傳至在線倉庫
  • register?向在線倉庫注冊項目的基本信息,而不上傳發(fā)布包
  • bdist?創(chuàng)建二進制發(fā)布包
  • bdist_msi?創(chuàng)建.msi安裝包,供Windows系統(tǒng)使用

我們還可以使用其他一些命令來獲取項目的基本信息。

所以在安裝或獲取應用程序信息時都是通過這個文件調(diào)用Distutils實現(xiàn)的,如獲取項目名稱:

$ python setup.py --name
MyProject

setup.py是一個項目的入口,可以通過它對項目進行構建、打包、發(fā)布、安裝等操作。開發(fā)者通過這個函數(shù)的參數(shù)信息來描述自己的項目,并使用它進行各種打包任務。這個文件同樣用于在目標系統(tǒng)中安裝軟件。

圖14.2:PyPI倉庫

你可以通過Classifies(類別)來瀏覽,獲取項目作者的名字和主頁。同時,Requires可以用來定義Python模塊的依賴關系。requires選項可以向元信息文件的Requires字段添加信息:

from distutils.core import setup

setup(name='foo', version='1.0', requires=['ldap'])

這里聲明了對ldap模塊的依賴,這種依賴并沒有實際效力,因為沒有安裝工具會保證這個模塊真實存在。如果說Python代碼中會使用類似Perl的require關鍵字來定義依賴關系,那還有些作用,因為這時安裝工具會檢索PyPI上的信息并進行安裝,其實這也就是CPAN的做法。但是對于Python來說,ldap模塊可以存在于任何項目之中,因為Distutils是允許開發(fā)者發(fā)布一個包含多個模塊的軟件的,所以這里的元信息字段并無太大作用。

Metadata的另一個缺點是,因為它是由Python腳本創(chuàng)建的,所以會根據(jù)腳本執(zhí)行環(huán)境的不同而產(chǎn)生特定信息。比如,運行在Windows環(huán)境下的一個項目會在setup.py文件中有以下描述:

from distutils.core import setup

setup(name='foo', version='1.0', requires=['win32com'])

這樣配置相當于是默認該項目只會運行在Windows環(huán)境下,即使它可能提供了跨平臺的方案。一種解決方法是根據(jù)不同的平臺來指定requires參數(shù):

from distutils.core import setup
import sys

if sys.platform == 'win32':
    setup(name='foo', version='1.0', requires=['win32com'])
else:
    setup(name='foo', version='1.0')

但這種做法往往會讓事情更糟。要注意,這個腳本是用來將項目的源碼包發(fā)布到PyPI上的,這樣寫就說明它向PyPI上傳的Metadata文件會因為該腳本運行環(huán)境的不同而不同。換句話說,這使得我們無法在元信息文件中看出這個項目依賴于特定的平臺。

14.3.3 PyPI的架構設計

http://python.org/peps/pep-0214.html

  • PEP 314: Metadata for Python Software Packages 1.1: http://python.org/peps/pep-0314.html
  • PEP 345: Metadata for Python Software Packages 1.2: http://python.org/peps/pep-0345.html
  • PEP 376: Database of Installed Python Distributions: http://python.org/peps/pep-0376.html
  • PEP 381: Mirroring infrastructure for PyPI: http://python.org/peps/pep-0381.html
  • PEP 386: Changing the version comparison module in Distutils: http://python.org/peps/pep-0386.html
  • 在這里我想感謝所有為打包標準的制定做出貢獻的人們,你可以在PEP中找到他們的名字。我還要特別感謝“打包別動隊”的成員們。還要謝謝Alexis Metaireau、Toshio Kuratomi、Holger Krekel、以及Stefane Fermigier,感謝他們對本文提供的反饋。

    本章中討論的項目有:

    腳注

    1. 文中引用的Python改進提案(Python Enhancement Proposals,簡稱PEP)會在本文最后一節(jié)整理。
    2. 過去被命名為CheeseShop
    3. 即RFC 3280 SubjectPublishKeyInfo中定義的1.3.14.3.2.12算法。
    4. 即RFC 3279 Dsa-Sig-Value中定義的1.2.840.10040.4.3算法。
    以上內(nèi)容是否對您有幫助:
    在線筆記
    App下載
    App下載

    掃描二維碼

    下載編程獅App

    公眾號
    微信公眾號

    編程獅公眾號