在Python的setuptools框架下生成egg的教程
本文介绍了setuptools框架的内容,它是PEAK的一个副项目,它提供了比distutils更加简单的包管理和发行功能。
开始
setuptools模块很会“规避”。例如,如果我们下载一个使用setuptools而不是使用distutils构建的包,那么安装就应该可以像我们期望的一样工作:通常使用pythonsetup.pyinstall就可以。为了实现这种功能,使用setuptools绑定在一起的包就会在归档文件中包含一个很小的引导模块ez_setup.py。此处惟一需要注意的是ez_setup.py试图在后台下载并安装所需要的setuptools——当然,这需要有一个连接网络的机器。如果setuptools早已在本地机器上安装了,那么这个后台步骤就不再需要执行;但是如果它需要手工进行安装,那么很多透明性就都丢失了。不过,大部分系统现在都有一个Internet连接了;为没有连接网络的机器多执行几个特殊步骤也并非特别麻烦。
setuptools的真正优点并不在于实现distutils所能实现的功能——尽管它的确增强了distutils的功能并简化了setup.py脚本中的内容。setuptools最大的优势是它在包管理能力方面的增强。它可以使用一种更加透明的方法来查找、下载并安装依赖包;并可以在一个包的多个版本中自由进行切换,这些版本都安装在同一个系统上;也可以声明对某个包的特定版本的需求;还可以只使用一个简单的命令就能更新到某个包的最新版本。给人印象最为深刻的是,即使有些包的开发人员可能还从未考虑过任何setuptools兼容性问题,我们依然可以使用这些包。
下面让我们详细探讨一下。
引导
工具ez_setup.py是一个简单的脚本,它可以引导setuptools中其余部分。有点让人困惑的是,完整setuptools包中所提供的easy_install脚本与ez_setup.py所实现的功能是相同的。不过前者假设setuptools早已安装了,因此它会跳过幕后的安装过程。这两个版本都可以接受相同的参数和开关。
这个过程中的第一个步骤是下载一个小脚本ez_setup.py:
清单1.下载引导脚本
%wget-qhttp://peak.telecommunity.com/dist/ez_setup.py
然后,就可以不带任何参数运行脚本来安装setuptools中其余部分了(如果不作为一个单独的步骤来执行这个步骤,在首次安装其他包时,它还是会被完成)。会看到类似于下面的内容(当然,这要取决于所使用的版本):
清单2.引导setuptools
%pythonez_setup.py Downloadinghttp://cheeseshop.python.org/packages/2.4/s/ setuptools/setuptools-0.6b1-py2.4.egg#md5=b79a8a403e4502fbb85ee3f1941735cb Processingsetuptools-0.6b1-py2.4.egg creating/sw/lib/python2.4/site-packages/setuptools-0.6b1-py2.4.egg Extractingsetuptools-0.6b1-py2.4.eggto/sw/lib/python2.4/site-packages Removingsetuptools0.6a11fromeasy-install.pthfile Addingsetuptools0.6b1toeasy-install.pthfile Installingeasy_installscriptto/sw/bin Installingeasy_install-2.4scriptto/sw/bin Installed/sw/lib/python2.4/site-packages/setuptools-0.6b1-py2.4.egg Processingdependenciesforsetuptools
完毕。这就是我们需要确保在系统上安装setuptools而需要做的工作。
安装包
对于很多Python包来说,要安装这些包,需要做的就是将这些包的名字作为一个参数传递给ez_setup.py或easy_install。既然目前已经使用引导脚本加载了setuptools,那就可以使用内部更加简化的easy_install(实际上它与我们选择的版本的区别很小)了。
例如,假设希望安装SQLObject包。过程非常简单,如清单3所示。注意消息中说SQLObject依赖于一个名为FormEncode的包;所幸的是,这会被很好地解决:
清单3.安装一个典型的包
%easy_installSQLObject SearchingforSQLObject Readinghttp://www.python.org/pypi/SQLObject/ Readinghttp://sqlobject.org Bestmatch:SQLObject0.7.0 Downloadinghttp://cheeseshop.python.org/packages/2.4/S/ SQLObject/SQLObject-0.7.0-py2.4.egg#md5=71830b26083afc6ea7c53b99478e1b6a ProcessingSQLObject-0.7.0-py2.4.egg creating/sw/lib/python2.4/site-packages/SQLObject-0.7.0-py2.4.egg ExtractingSQLObject-0.7.0-py2.4.eggto/sw/lib/python2.4/site-packages AddingSQLObject0.7.0toeasy-install.pthfile Installingsqlobject-adminscriptto/sw/bin Installed/sw/lib/python2.4/site-packages/SQLObject-0.7.0-py2.4.egg ProcessingdependenciesforSQLObject SearchingforFormEncode>=0.2.2 Readinghttp://www.python.org/pypi/FormEncode/ Readinghttp://formencode.org Bestmatch:FormEncode0.5.1 Downloadinghttp://cheeseshop.python.org/packages/2.4/F/ FormEncode/FormEncode-0.5.1-py2.4.egg#md5=f8a19cbe95d0ed1b9d1759b033b7760d ProcessingFormEncode-0.5.1-py2.4.egg creating/sw/lib/python2.4/site-packages/FormEncode-0.5.1-py2.4.egg ExtractingFormEncode-0.5.1-py2.4.eggto/sw/lib/python2.4/site-packages AddingFormEncode0.5.1toeasy-install.pthfile Installed/sw/lib/python2.4/site-packages/FormEncode-0.5.1-py2.4.egg
正如可以从这些消息中看到的一样,easy_install要在www.python.org/pypi/上查找有关这个包的信息,然后查找真正可以下载它的地方(此处egg包就在cheeseshop.python.org上;后面将介绍有关egg的更多内容)。
现在不仅仅可以安装某个包的最新版本(这是默认操作)。如果愿意,还可以为easy_install提供一个特定的版本需求。现在让我们尝试安装SQLObject的一个post-beta版本。
清单4.安装某个包的最小版本
%easy_install'SQLObject>=1.0' SearchingforSQLObject>=1.0 Readinghttp://www.python.org/pypi/SQLObject/ Readinghttp://sqlobject.org NolocalpackagesordownloadlinksfoundforSQLObject>=1.0 error:Couldnotfindsuitabledistributionfor Requirement.parse('SQLObject>=1.0')
如果(在本文编写时情况就是如此)SQLObject的最新版本小于1.0,那么这会什么也不安装。
安装“naive”包
SQLObject是可以识别setuptools的;但是如果要安装一个尚未兼容setuptools的包又该如何呢?例如,在本文之前,我从没有对自己的“GnosisUtilities”使用过setuptools。不过,现在让我们来尝试安装一下这个包,已知的只有它所在的HTTP(或FTP、SVN、CVS)位置(setuptools可以理解所有这些协议)。我的下载Web站点上有各个GnosisUtilities的版本,它们的命名采用了常见的版本风格:
清单5.安装不识别setuptools的包
%easy_install-fhttp://gnosis.cx/download/Gnosis_Utils.More/Gnosis_Utils SearchingforGnosis-Utils Readinghttp://gnosis.cx/download/Gnosis_Utils.More/ Bestmatch:Gnosis-Utils1.2.1 Downloadinghttp://gnosis.cx/download/Gnosis_Utils.More/ Gnosis_Utils-1.2.1.zip ProcessingGnosis_Utils-1.2.1.zip RunningGnosis_Utils-1.2.1/setup.py-qbdist_egg--dist-dir /tmp/easy_install-CCrXEs/Gnosis_Utils-1.2.1/egg-dist-tmp-Sh4DW1 zip_safeflagnotset;analyzingarchivecontents... gnosis.__init__:modulereferences__file__ gnosis.magic.__init__:modulereferences__file__ gnosis.xml.objectify.doc.__init__:modulereferences__file__ gnosis.xml.pickle.doc.__init__:modulereferences__file__ gnosis.xml.pickle.test.test_zdump:modulereferences__file__ AddingGnosis-Utils1.2.1toeasy-install.pthfile Installed/sw/lib/python2.4/site-packages/Gnosis_Utils-1.2.1-py2.4.egg ProcessingdependenciesforGnosis-Utils
所幸的是easy_install可以把这一切都完成得很好。它会查看给定的下载目录,识别出可用的最高版本,展开这个包,然后将其重新打包为“egg”格式,后者就可以用来进行安装了。导入gnosis现在可以在一个脚本中运行。但是假设现在需要对GnosisUtilities之前的某个特定版本来测试一个脚本又该怎么做呢?这也非常简单:
清单6.安装一个“naive”包的特定版本
%easy_install-fhttp://gnosis.cx/download/Gnosis_Utils.More/ "Gnosis_Utils==1.2.0" SearchingforGnosis-Utils==1.2.0 Readinghttp://gnosis.cx/download/Gnosis_Utils.More/ Bestmatch:Gnosis-Utils1.2.0 Downloadinghttp://gnosis.cx/download/Gnosis_Utils.More/ Gnosis_Utils-1.2.0.zip [...] RemovingGnosis-Utils1.2.1fromeasy-install.pthfile AddingGnosis-Utils1.2.0toeasy-install.pthfile Installed/sw/lib/python2.4/site-packages/Gnosis_Utils-1.2.0-py2.4.egg ProcessingdependenciesforGnosis-Utils==1.2.0
现在通常已经安装了两个版本的GnosisUtilities,当前活动版本是1.2.0。将活动版本切换回1.2.1也非常简单:
清单7.在系统范围修改“活动”版本
%easy_install"Gnosis_Utils==1.2.1" SearchingforGnosis-Utils==1.2.1 Bestmatch:Gnosis-Utils1.2.1 ProcessingGnosis_Utils-1.2.1-py2.4.egg RemovingGnosis-Utils1.2.0fromeasy-install.pthfile AddingGnosis-Utils1.2.1toeasy-install.pthfile Using/sw/lib/python2.4/site-packages/Gnosis_Utils-1.2.1-py2.4.egg ProcessingdependenciesforGnosis-Utils==1.2.1
当然,这一次只能使一个版本是活动的。不过通过在各个脚本上面放上这样两行类似内容,就可以让脚本选择自己希望使用的版本:
清单8.在脚本中使用某个版本的包
frompkg_resourcesimportrequire require("Gnosis_Utils==1.2.0")
通过使用上述要求,setuptools就可以在运行import语句时添加一个特定的版本(如果指定了大于比较,就是最新的可用版本)。
让包可以识别setuptools
我会更希望让用户不需要知道GnosisUtilities的下载目录就可以安装它。这通常都可以工作,因为GnosisUtilities在PythonCheeseshop上有一个信息清单。不幸的是,因为没有考虑setuptools,所以我在python.org上为我的GnosisUtilities建立了一个“不匹配”的入口http://www.python.org/pypi/Gnosis%20Utilities/1.2.1。具体地说,这个归档文件是根据类似于Gnosis_Utils-N.N.N.tar.gz的模式进行命名的(这些工具也打包成了.zip和.tar.bz2文件,最新的几个版本还打包成了win32.exe的安装程序,所有这些文件setuptools都可以很好地处理)。不过Cheeseshop上的项目名的拼写与“GnosisUtilities”稍微有点不同。实际上,在Cheeseshop的一个很小的管理版本的更改就会将http://www.python.org/pypi/Gnosis_Utils/1.2.1-a创建为一个发布后版本。发行版归档文件本身并没有什么变化,不过是在Cheeseshop里增加了一点元数据。只需要少量努力,就可以使用更加简单的安装程序(注意,出于测试目的,我运行了一个easy_install-m来删除所安装的包)。
清单9.简单增加对setuptools的识别
%easy_installGnosis_Utils SearchingforGnosis-Utils Readinghttp://www.python.org/pypi/Gnosis_Utils/ Readinghttp://www.gnosis.cx/download/Gnosis_Utils.ANNOUNCE Readinghttp://gnosis.cx/download/Gnosis_Utils.More/ Bestmatch:Gnosis-Utils1.2.1 Downloading[...]
我把这个过程剩余的部分忽略掉了,因为这与您前面看到的内容没什么两样。惟一的区别在于easy_install要在Cheeseshop(换言之www.python.org/pypi/)上寻找可以匹配指定名字的元数据,并使用这些信息来查找真正的下载位置。在这种情况中,所列出的.ANNOUNCE文件没有包含任何有帮助的内容,不过easy_install还会继续查看另一个所列的URL,这会证明它是一个下载目录。
关于egg
egg是一个包含所有包数据的文件包。在理想情况中,egg是一个使用zip压缩的文件,其中包括了所有需要的包文件。但是在某些情况下,setuptools会决定(或被开关告知)包不应该是zip压缩的。在这些情况下,egg只是一个简单的未曾压缩的子目录,但是里面的内容是相同的。使用单一的版本可以方便地进行转换,并可以节省一点磁盘空间,但是egg目录从功能和组织结构上来说都是相同的。一直使用JAR文件的Java?技术的用户会发现egg非常熟悉。
由于最新的Python版本中(需要2.3.5+或2.4)导入挂钩的更改,可以简单地通过设置PYTHONPATH或sys.path并像往常一样导入相应的包来使用egg。如果希望采用这种方法,就不需要使用setuptools或ez_setup.py了。例如,在本文使用的工作目录中,我就为PyYAML包放入了一个egg。现在我就可以使用这个包了,方法如下:
清单10.PYTHONPATH上的egg
%exportPYTHONPATH=~/work/dW/PyYAML-3.01-py2.4.egg %python-c'importyaml;printyaml.dump({"foo":"bar",1:[2,3]})' 1:[2,3] foo:bar
不过,PYTHONPATH的(或者脚本或Pythonshell会话内的sys.path的)这种操作有些脆弱。egg的发现最好是在新一点的.pth文件中进行。在site-packages/或PYTHONPATH中的任何.pth文件都会进行解析来执行其他导入操作,其方法类似于检查可能包含包的那些目录位置一样。如果使用setuptools来处理包的管理功能,那么在安装、更新、删除包时,就需要修改一个名为easy-install.pth的文件。而且可以按照自己喜欢的方式对这个.pth进行命名(只要其扩展名是.pth即可)。例如,下面是我的easy-install.pth文件的内容:
清单11.用作egg位置配置的.pth文件
%cat/sw/lib/python2.4/site-packages/easy-install.pth importsys;sys.__plen=len(sys.path) setuptools-0.6b1-py2.4.egg SQLObject-0.7.0-py2.4.egg FormEncode-0.5.1-py2.4.egg Gnosis_Utils-1.2.1-py2.4.egg importsys;new=sys.path[sys.__plen:];delsys.path[sys.__plen:]; p=getattr(sys,'__egginsert',0);sys.path[p:p]=new; sys.__egginsert=p+len(new)
这种格式有点特殊:它近似于一个Python脚本,但却不完全是。需要说明的是,可以在那里添加额外列出的egg;更好的情况是,easy_install会在运行时实现这种功能。也可以在site-packages/下创建任意多个.pth文件;每个都可以列出有哪些egg是可用的。
增强安装脚本
上面所述的这种安装setuptoolsnaive包的能力(请参阅清单6)只部分有效。也就是说,包Gnosis_Utils的确安装上了,但是并不完整。所有常见的功能都可以工作,但是在自动生成egg时却忽略了很多支持文件——大部分是扩展名为.txt的文档和扩展名为.xml的测试文件(还有一些其他的README、.rnc、.rng、.xsl和围绕子包的文件)。在安装时,所有这些支持文件都“最好要有”,而没有严格要求一定要有。不过,我们仍然希望能够包含所有的支持文件。
Gnosis_Utils使用的setup.py脚本实际上非常复杂。除了列出基本的元数据之外,在第467行代码中,它还对Python版本的功能和bug进行完整测试;解决旧版本的distutils中的一些故障;回溯跳过对不支持部分的安装(例如,如果pyexpat在Python发行版中并没有包括);处理OS行结束符的转换;创建多个归档/安装程序类型;根据测试结果重新构建MANIFEST文件。能够实现处理这些工作的能力要感谢此包的另外一个维护人员FrankMcIngvale;这些能力可以让Gnosis_Utils能成功安装回Python1.5.1的版本,当然前提是需要这么做(早期版本中的功能没有这么丰富)。不过此处我要向大家展示的脚本并没有像distutils脚本一样做这么复杂的事情:它只是简单地假设系统中已经安装了一个“普通的”最新版本的Python。即使这么讲,setuptools能让安装脚本变得如此简单还是非常吸引人。
在第一次尝试时,让我们来创建一个setup.py脚本,它是从setuptools手册中借用的,并试图使用它来创建一个egg:
清单12.setuptoolssetup.py脚本
%catsetup.py fromsetuptoolsimportsetup,find_packages setup( name="Gnosis_Utils", version="1.2.2", packages=find_packages(), ) %pythonsetup.py-qbdist_egg zip_safeflagnotset;analyzingarchivecontents... gnosis.__init__:modulereferences__file__ gnosis.doc.__init__:modulereferences__file__ gnosis.magic.__init__:modulereferences__file__ gnosis.xml.objectify.doc.__init__:modulereferences__file__ gnosis.xml.pickle.doc.__init__:modulereferences__file__ gnosis.xml.pickle.test.test_zdump:modulereferences__file__
这点努力就已经可以起作用;至少可以部分地起作用。使用这几行内容的确可以创建一个egg,不过这个egg与使用easy_install创建的egg有一些相似的缺点:缺乏对不使用.py命名的文件的支持。因此让我们再试一次,只是需要稍微再努力一点:
清单13.添加缺少的package_data
fromsetuptoolsimportsetup,find_packages setup( name="Gnosis_Utils", version="1.2.2", package_data={'':['*.*']}, packages=find_packages(), )
这就是需要做的所有操作。当然,根据实际情况,通常希望对它进行一些调整。例如,它可能会列出下面的内容:
清单14.打包特定类型文件类型
package_data={'doc':['*.txt'],'xml':['*.xml','relax/*.rnc']}
这段内容翻译一下就是:将.txt文件包括在doc/子包中,将.xml文件包括在xml/子包中,将所有.rnc文件包括在xml/relax/子包中。
结束语
本文实际上只介绍了用支持setuptools的发行版可以执行的定制操作的表层的知识。例如,假设您现在有一个发行版(可以是首选的egg格式或另外一种归档类型),您就可以使用一个命令将这个归档文件和元数据上载到Cheeseshop上。显然,完整的setup.py脚本应该包含旧版本distutils脚本中所包含的同样详细的元数据;为了简单起见,本文跳过了这些内容,但是其参数名与distutils是兼容的。
尽管要完全适应setuptools所提供的巨大功能需要一些时间,但是实际上它确实可以让维护您自己的包和安装外来包都要比distutils更加简单。如果您所关心的内容仅仅是安装包,那么您所需要了解的内容在本文的介绍中已经全部包括了;只是您在描述您自己的包时可能会发现一些复杂性,不过仍然没有使用distutils那么复杂。