2. 编写安装脚本¶
注解
本文件仅保留至 setuptools
https://setuptools.readthedocs.io/en/latest/setuptools.html上的文档独立地涵盖了此处当前包含的所有相关信息。
安装脚本是使用distutils构建、分发和安装模块的所有活动的中心。安装脚本的主要目的是描述模块分发给distutils的情况,以便在模块上操作的各种命令都能正确地执行操作。正如我们在章节中看到的 一个简单的例子 上面的设置脚本主要由调用 setup()
,模块开发人员提供给distutils的大多数信息都作为关键字参数提供给 setup()
.
下面是一个稍微复杂一些的示例,我们将在接下来的几节中继续学习:distutils自己的安装脚本。(请记住,尽管distuils包含在python 1.6和更高版本中,但它们也有独立的存在,以便python 1.5.2用户可以使用它们安装其他模块分发版。distutils自己的安装脚本(如图所示)用于将包安装到python 1.5.2中。)::
#!/usr/bin/env python
from distutils.core import setup
setup(name='Distutils',
version='1.0',
description='Python Distribution Utilities',
author='Greg Ward',
author_email='gward@python.net',
url='https://www.python.org/sigs/distutils-sig/',
packages=['distutils', 'distutils.command'],
)
这和第节中介绍的普通的一个文件分发之间只有两个区别。 一个简单的例子 :更多的元数据,以及纯Python模块的规范(按包,而不是按模块)。这一点很重要,因为distutils由几十个模块组成,分为(到目前为止)两个包;每个模块的显式列表生成起来很繁琐,很难维护。有关附加元数据的更多信息,请参见第节 附加元数据 .
请注意,安装脚本中提供的任何路径名(文件或目录)都应该使用UNIX约定编写,即斜杠分隔。在实际使用路径名之前,distutils将负责将此平台中性表示转换为当前平台上合适的表示。这使得您的安装脚本可以跨操作系统移植,这当然是distutils的主要目标之一。本着这种精神,文档中的所有路径名都是斜线分隔的。
当然,这只适用于为distutils函数指定的路径名。例如,如果您使用标准的python函数,例如 glob.glob()
或 os.listdir()
要指定文件,应注意编写可移植代码,而不是硬编码路径分隔符:
glob.glob(os.path.join('mydir', 'subdir', '*.html'))
os.listdir(os.path.join('mydir', 'subdir'))
2.1. 列出整个包¶
这个 packages
选项告诉distuils处理(构建、分发、安装等)中提到的每个包中的所有纯python模块。 packages
列表。当然,为了做到这一点,文件系统中的包名和目录之间必须有对应关系。默认的对应关系是最明显的,即包 distutils
在目录中找到 distutils
相对于分布根。因此,当你说 packages = ['foo']
在安装脚本中,您承诺distutils将找到一个文件 foo/__init__.py
(在您的系统中可能拼写不同,但您得到了这个想法)相对于安装脚本所在的目录。如果违反此承诺,distutils将发出警告,但仍然会处理损坏的包。
如果您使用不同的约定来布局源目录,那就没问题了:您只需提供 package_dir
告诉distuils您的约定的选项。例如,假设将所有python源代码保存在 lib
使“根包”(即根本不在任何包中)中的模块 lib
,中的模块 foo
包在 lib/foo
等等。然后你会说:
package_dir = {'': 'lib'}
在安装脚本中。这个字典的键是包名,空包名代表根包。这些值是相对于分布根目录的目录名。在这种情况下,当你说 packages = ['foo']
,你保证文件 lib/foo/__init__.py
存在。
另一个可能的惯例是 foo
包裹就在里面 lib
, the foo.bar
包内 lib/bar
等。这将在安装脚本中编写为:
package_dir = {'foo': 'lib'}
A package: dir
进入 package_dir
字典隐式应用于以下所有包 包裹 所以 foo.bar
案例在这里自动处理。在本例中,具有 packages = ['foo', 'foo.bar']
告诉distutils查找 lib/__init__.py
和 lib/bar/__init__.py
. (记住,尽管 package_dir
递归应用,必须显式列出 packages
:distutils将 not 递归扫描源目录树,查找具有 __init__.py
文件。
2.2. 列出各个模块¶
对于小型模块分发,您可能更类似于列出所有模块,而不是列出包——尤其是在“根包”(即根本没有包)中的单个模块的情况下。这一最简单的案例在第节中给出。 一个简单的例子 ;下面是一个稍微复杂一些的示例:
py_modules = ['mod1', 'pkg.mod2']
这描述了两个模块,一个在“根”包中,另一个在 pkg
包裹。同样,默认的包/目录布局意味着这两个模块可以在 mod1.py
和 pkg/mod2.py
那 pkg/__init__.py
也存在。同样,您可以使用 package_dir
选择权。
2.3. 描述扩展模块¶
正如编写python扩展模块比编写纯python模块要复杂一些一样,向distuils描述它们也要复杂一些。与纯模块不同,仅仅列出模块或包并期望distuils出去查找正确的文件是不够的;您必须指定扩展名、源文件和任何编译/链接要求(包括目录、要链接的库等)。
所有这些都是通过另一个关键字参数 setup()
, the ext_modules
选择权。 ext_modules
只是一个列表 Extension
实例,每个实例描述一个扩展模块。假设您的分布包含一个扩展名,称为 foo
并由执行 foo.c
. 如果不需要编译器/链接器的附加指令,则描述此扩展非常简单:
Extension('foo', ['foo.c'])
这个 Extension
类可以从导入 distutils.core
随着 setup()
. 因此,仅包含此扩展名而不包含其他扩展名的模块分发的安装脚本可能是:
from distutils.core import setup, Extension
setup(name='foo',
version='1.0',
ext_modules=[Extension('foo', ['foo.c'])],
)
这个 Extension
类(实际上,由 build_ext command)在描述Python扩展时支持很大的灵活性,这将在下面的部分中解释。
2.3.1. 扩展名和包¶
第一个参数 Extension
构造函数始终是扩展名的名称,包括任何包名称。例如:
Extension('foo', ['src/foo1.c', 'src/foo2.c'])
描述位于根包中的扩展,而:
Extension('pkg.foo', ['src/foo1.c', 'src/foo2.c'])
在中描述相同的扩展名 pkg
包裹。在这两种情况下,源文件和产生的对象代码是相同的;唯一的区别是文件系统中(因此在Python的名称空间层次结构中)产生的扩展的生存位置。
如果在同一个包中(或在同一个基包下)有多个扩展名,请使用 ext_package
关键字参数 setup()
. 例如:
setup(...,
ext_package='pkg',
ext_modules=[Extension('foo', ['foo.c']),
Extension('subpkg.bar', ['bar.c'])],
)
将编译 foo.c
延伸部分 pkg.foo
和 bar.c
到 pkg.subpkg.bar
.
2.3.2. 扩展源文件¶
第二个参数 Extension
构造函数是源文件的列表。由于ditudil目前只支持C、C++和Objto-C扩展,这些通常是C/C++ + Objto- C源文件。(确保使用适当的扩展来区分C++源文件: .cc
和 .cpp
似乎被Unix和Windows编译器识别。)
但是,您也可以包括swig接口 (.i
)列表中的文件; build_ext 命令知道如何处理SWIG扩展:它将在接口文件上运行SWIG,并将生成的C/C++文件编译到扩展名中。
尽管存在此警告,但当前可以像这样传递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消息文本 (.mc
)文件和资源定义 (.rc
)VisualC++的文件。这些将被编译成二进制资源 (.res
)文件并链接到可执行文件中。
2.3.3. 预处理器选项¶
三个可选参数 Extension
如果需要指定要搜索的包含目录或要定义/取消定义的预处理器宏,将有帮助: include_dirs
, define_macros
和 undef_macros
.
例如,如果扩展名需要 include
在您的分发根目录下,使用 include_dirs
选项:
Extension('foo', ['foo.c'], include_dirs=['include'])
您可以在那里指定绝对目录;如果您知道扩展将仅在安装了x11r6的UNIX系统上构建 /usr
,你可以离开:
Extension('foo', ['foo.c'], include_dirs=['/usr/include/X11'])
如果计划分发代码,应该避免这种不可移植的用法:最好编写C代码,如:
#include <X11/Xlib.h>
如果需要包含来自其他Python扩展的头文件,可以利用distutils以一致的方式安装头文件这一事实。 install_headers 命令。例如,数字python头文件(在标准的unix安装上)安装到 /usr/local/include/python1.5/Numerical
. (具体位置根据您的平台和python安装而不同。)因为python包含目录---/usr/local/include/python1.5
在这种情况下---在构建Python扩展时总是包含在搜索路径中,最好的方法是编写如下C代码:
#include <Numerical/arrayobject.h>
如果你必须把 Numerical
但是,可以使用distutils在头搜索路径中包含目录 distutils.sysconfig
模块:
from distutils.sysconfig import get_python_inc
incdir = os.path.join(get_python_inc(plat_specific=1), 'Numerical')
setup(...,
Extension(..., include_dirs=[incdir]),
)
尽管这是非常可移植的——无论平台如何,它都可以在任何Python安装上工作——以合理的方式编写C代码可能更容易。
可以使用定义和取消定义预处理宏 define_macros
和 undef_macros
选项。 define_macros
列出一个 (name, value)
元组,在哪里 name
是要定义的宏的名称(字符串),并且 value
其值为:字符串或 None
. (定义宏 FOO
到 None
相当于裸的 #define FOO
在C源代码中:对于大多数编译器,此集合 FOO
弦 1
) undef_macros
只是要取消定义的宏列表。
例如::
Extension(...,
define_macros=[('NDEBUG', '1'),
('HAVE_STRFTIME', None)],
undef_macros=['HAVE_FOO', 'HAVE_BAR'])
是否等同于在每个C源文件的顶部都具有此项::
#define NDEBUG 1
#define HAVE_STRFTIME
#undef HAVE_FOO
#undef HAVE_BAR
2.3.4. 库选项¶
您还可以指定在构建扩展时要链接的库,以及要搜索这些库的目录。这个 libraries
选项是要链接的库列表, library_dirs
是链接时要搜索库的目录列表,以及 runtime_library_dirs
是在运行时搜索共享(动态加载)库的目录列表。
例如,如果需要链接到目标系统上标准库搜索路径中已知的库:
Extension(...,
libraries=['gdbm', 'readline'])
如果需要在非标准位置链接库,则必须将该位置包含在 library_dirs
::
Extension(...,
library_dirs=['/usr/X11R6/lib'],
libraries=['X11', 'Xt'])
(同样,如果您打算分发代码,应该避免这种不可移植的构造。)
2.3.5. 其他选项¶
还有一些其他选项可用于处理特殊情况。
这个 optional
选项是布尔值;如果为真,则扩展中的生成失败将不会中止生成过程,而是简单地不安装失败的扩展。
这个 extra_objects
选项是要传递给链接器的对象文件列表。这些文件不能有扩展名,因为使用了编译器的默认扩展名。
extra_compile_args
和 extra_link_args
可用于为各自的编译器和链接器命令行指定其他命令行选项。
export_symbols
仅在Windows上有用。它可以包含要导出的符号(函数或变量)列表。构建编译扩展时不需要此选项:distutils将自动添加 initmodule
导出符号列表。
这个 depends
选项是扩展依赖的文件列表(例如头文件)。生成命令将调用源上的编译器来重新生成扩展名(如果自上一次生成以来已修改了此文件上的任何扩展名)。
2.4. 分发和包之间的关系¶
分发可能以三种特定方式与包相关:
它可能需要软件包或模块。
它可以提供包或模块。
它可以废弃软件包或模块。
可以使用关键字参数指定这些关系 distutils.core.setup()
功能。
通过提供 要求 关键字参数 setup()
. 该值必须是字符串列表。每个字符串指定一个所需的包,并且可以选择哪些版本足够。
要指定需要模块或包的任何版本,字符串应完全由模块或包名称组成。示例包括 'mymodule'
和 'xml.parsers.expat'
.
如果需要特定的版本,可以在括号中提供一系列限定符。每个限定符可以由比较运算符和版本号组成。可接受的比较运算符为:
< > ==
<= >= !=
这些可以通过使用多个用逗号(和可选空格)分隔的限定符来组合。在这种情况下,所有限定符都必须匹配;逻辑AND用于组合计算。
让我们来看一些例子:
需要表达式 |
解释 |
---|---|
|
唯一版本 |
|
之后的任何版本 |
既然我们可以指定依赖项,那么我们还需要能够指定我们提供的其他发行版可能需要的内容。这是用 提供 关键字参数 setup()
. 这个关键字的值是一个字符串列表,每个字符串命名一个python模块或包,还可以选择标识版本。如果未指定版本,则假定该版本与分发版的版本匹配。
一些例子:
提供表达式 |
解释 |
---|---|
|
提供 |
|
提供 |
一个包可以声明它使用 废弃的 关键字参数。它的值与 要求 关键字:给出模块或包说明符的字符串列表。每个说明符由一个模块或包名称组成,可选地后跟一个或多个版本限定符。版本限定符在模块或包名称后面的括号中给出。
限定符标识的版本是那些被描述的发行版废弃的版本。如果没有给定限定符,则将理解命名模块或包的所有版本都已过时。
2.5. 正在安装脚本¶
到目前为止,我们一直在处理纯的和非纯的Python模块,这些模块通常不是由它们自己运行的,而是由脚本导入的。
脚本是包含python源代码的文件,打算从命令行启动。脚本不需要distuils执行任何非常复杂的操作。唯一聪明的地方是,如果脚本的第一行以 #!
并且包含单词“python”,distuils将调整第一行以引用当前解释器位置。默认情况下,它将替换为当前解释器位置。这个 --executable
(或) -e
)选项将允许显式重写解释器路径。
这个 scripts
选项只是要以这种方式处理的文件列表。从pyxml安装脚本:
setup(...,
scripts=['scripts/xmlproc_parse', 'scripts/xmlproc_val']
)
在 3.1 版更改: 所有脚本也将添加到 MANIFEST
文件(如果未提供模板)。见 指定要分发的文件 .
2.6. 正在安装包数据¶
通常,需要将其他文件安装到包中。这些文件通常是与包的实现密切相关的数据,或者包含使用包的程序员可能感兴趣的文档的文本文件。这些文件称为 package data .
可以使用将包数据添加到包中 package_data
关键字参数 setup()
功能。该值必须是从包名称到应复制到包中的相对路径名称列表的映射。路径被解释为相对于包含包的目录(来自 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']},
)
在 3.1 版更改: 所有匹配的文件 package_data
将添加到 MANIFEST
文件(如果未提供模板)。见 指定要分发的文件 .
2.7. 安装其他文件¶
这个 data_files
选项可用于指定模块分发所需的其他文件:配置文件、消息目录、数据文件,以及不适用于以前类别的任何文件。
data_files
指定一个序列( 目录 , 文件夹 )按以下方式配对:
setup(...,
data_files=[('bitmaps', ['bm/b1.gif', 'bm/b2.gif']),
('config', ['cfg/data.cfg'])],
)
每一个( 目录 , 文件夹 )序列中的pair指定安装目录和要在其中安装的文件。
中的每个文件名 文件夹 是相对于 setup.py
在包源分发的顶部编写脚本。请注意,您可以指定安装数据文件的目录,但不能重命名数据文件本身。
这个 目录 应该是相对路径。它是相对于安装前缀(python的 sys.prefix
系统安装; site.USER_BASE
用于用户安装)。distutils允许 目录 是绝对安装路径,但不鼓励这样做,因为它与wheel封装格式不兼容。没有来自的目录信息 文件夹 用于确定已安装文件的最终位置;仅使用文件名。
您可以指定 data_files
选项作为一个简单的文件序列而不指定目标目录,但不建议这样做,并且 install 在这种情况下,命令将打印警告。若要直接在目标目录中安装数据文件,应提供一个空字符串作为目录。
在 3.1 版更改: 所有匹配的文件 data_files
将添加到 MANIFEST
文件(如果未提供模板)。见 指定要分发的文件 .
2.8. 附加元数据¶
安装脚本可能包含名称和版本之外的其他元数据。这些信息包括:
本地资源 |
描述 |
价值 |
笔记 |
---|---|---|---|
|
包的名称 |
短串 |
(1) |
|
此版本 |
短串 |
(1)(2) |
|
包作者姓名 |
短串 |
(3) |
|
包作者的电子邮件地址 |
电子邮件地址 |
(3) |
|
包维护者姓名 |
短串 |
(3) |
|
包维护者的电子邮件地址 |
电子邮件地址 |
(3) |
|
包的主页 |
URL |
(1) |
|
包的简要说明 |
短串 |
|
|
包的较长描述 |
长串 |
(4) |
|
可下载包的位置 |
URL |
|
|
分类器列表 |
字符串表 |
(6)(7) |
|
平台列表 |
字符串表 |
(6)(8) |
|
关键字列表 |
字符串表 |
(6)(8) |
|
包的许可证 |
短串 |
(5) |
笔记:
这些字段是必需的。
建议采用以下形式 [major.minor[.patch[.sub]]] .
必须标识作者或维护者。如果提供了Maintainer,distutils将其列为
PKG-INFO
.这个
long_description
当发布包时,pypi使用字段来构建其项目页。这个
license
字段是一个文本,指示包含软件包的许可证,其中许可证不是从“许可证”trove分类器中选择的。见Classifier
字段。注意有一个licence
分发选项已弃用,但仍用作的别名license
.此字段必须是列表。
有效的分类器列在 PyPI .
为了保持向后兼容性,此字段还接受一个字符串。如果传递逗号分隔的字符串
'foo, bar'
,将转换为['foo', 'bar']
否则,它将转换为一个字符串列表。
- '短字符串'
一行文字,不超过200个字符。
- “长弦”
重构文本格式的多行纯文本(请参阅http://docutils.sourceforge.net/)。
- '字符串列表'
见下文。
编码版本信息本身就是一门艺术。python包通常遵循版本格式 [major.minor[.patch][sub]] . 对于软件的初始实验版本,主要数字是0。对于表示包中主要里程碑的版本,它是递增的。当重要的新功能添加到包中时,次要数字将递增。当发布bug修复程序时,补丁号将增加。附加的尾随版本信息有时用于指示子版本。这些是“a1,a2,…,an”(对于alpha版本,功能和api可能会改变),“b1,b2,…,bn”(对于beta版本,只修复bug)和“pr1,pr2,…,prn”(对于最终的预发布测试)。一些例子:
- 0.1.0
包的第一次实验释放
- 1.0.1a2
第一个补丁版本1.0的第二个alpha版本
classifiers
必须在列表中指定::
setup(...,
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Console',
'Environment :: Web Environment',
'Intended Audience :: End Users/Desktop',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: Python Software Foundation License',
'Operating System :: MacOS :: MacOS X',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX',
'Programming Language :: Python',
'Topic :: Communications :: Email',
'Topic :: Office/Business',
'Topic :: Software Development :: Bug Tracking',
],
)
在 3.7 版更改: setup
现在警告什么时候 classifiers
, keywords
或 platforms
字段未指定为列表或字符串。
2.9. 调试安装脚本¶
有时事情会出错,安装脚本不会按开发人员的要求执行。
distutils在运行安装脚本时捕获任何异常,并在脚本终止之前打印一条简单的错误消息。这种行为的动机是不要让不太了解Python并试图安装软件包的管理员感到困惑。如果他们从distutils的内部深处得到了一个很长的回溯,他们可能会认为包或python安装被破坏了,因为他们不会一直往下读,并且会发现这是一个权限问题。
另一方面,这并不能帮助开发人员找到失败的原因。为此目的, DISTUTILS_DEBUG
环境变量可以设置为除空字符串之外的任何内容,distutils现在将打印有关它正在执行的操作的详细信息,在发生异常时转储完整的回溯,并在外部程序(如C编译器)失败时打印整个命令行。