2. 编写安装脚本 — Python 文档
2. 编写安装脚本
笔记
本文档仅保留到 https://setuptools.readthedocs.io/en/latest/setuptools.html 上的 setuptools
文档独立涵盖当前包含在此处的所有相关信息之前。
安装脚本是使用 Distutils 构建、分发和安装模块的所有活动的中心。 设置脚本的主要目的是向 Distutils 描述您的模块分发,以便在您的模块上运行的各种命令做正确的事情。 正如我们在上面的 简单示例 部分中看到的,设置脚本主要由对 setup()
的调用组成,并且模块开发人员提供给 Distutils 的大部分信息都作为关键字参数提供给 [ X218X]。
这是一个稍微复杂的示例,我们将在接下来的几节中遵循它:Distutils 自己的设置脚本。 (请记住,虽然 Distutils 包含在 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
选项告诉 Distutils 处理(构建、分发、安装等)在 packages
列表中提到的每个包中找到的所有纯 Python 模块。 当然,为了做到这一点,文件系统中的包名和目录之间必须有对应关系。 默认对应是最明显的,即 包 distutils 位于相对于发行版根目录的 distutils
目录中。 因此,当您在设置脚本中说 packages = ['foo']
时,您承诺 Distutils 会找到一个文件 foo/__init__.py
(在您的系统上可能拼写不同,但您明白了)相对于安装脚本所在的目录。 如果你违反了这个承诺,Distutils 会发出警告,但仍然会处理损坏的包。
如果您使用不同的约定来布置源目录,那没问题:您只需要提供 package_dir
选项来告诉 Distutils 您的约定。 例如,假设您将所有 Python 源代码保存在 lib
下,以便“根包”中的模块(即,根本不在任何包中)在 lib
中,foo
包在 lib/foo
中,依此类推。 然后你会放
package_dir = {'': 'lib'}
在您的安装脚本中。 这个字典的关键字是包名,一个空的包名代表根包。 这些值是相对于您的分发根目录的目录名称。 在这种情况下,当您说 packages = ['foo']
时,您承诺文件 lib/foo/__init__.py
存在。
另一种可能的约定是将 foo
包直接放在 lib
中,将 foo.bar
包放在 lib/bar
中,等等。 这将在安装脚本中编写为
package_dir = {'foo': 'lib'}
package_dir
字典中的 package: dir
条目隐式适用于 包 下的所有包,因此此处自动处理 foo.bar
情况。 在这个例子中,有 packages = ['foo', 'foo.bar']
告诉 Distutils 寻找 lib/__init__.py
和 lib/bar/__init__.py
。 (请记住,尽管 package_dir
以递归方式应用,但您必须明确列出 packages
中的所有包:Distutils 将 不 递归扫描您的源代码树,寻找任何带有__init__.py
文件。)
2.2. 列出单个模块
对于小模块分发,您可能更喜欢列出所有模块而不是列出包——尤其是在“根包”中的单个模块的情况下(即,根本没有包)。 这个最简单的例子在一个简单的例子部分中展示; 这是一个稍微复杂的例子:
py_modules = ['mod1', 'pkg.mod2']
这描述了两个模块,其中一个在“root”包中,另一个在 pkg
包中。 同样,默认包/目录布局意味着这两个模块可以在 mod1.py
和 pkg/mod2.py
中找到,并且 pkg/__init__.py
也存在。 同样,您可以使用 package_dir
选项覆盖包/目录对应关系。
2.3. 描述扩展模块
正如编写 Python 扩展模块比编写纯 Python 模块要复杂一些,向 Distutils 描述它们也有点复杂。 与纯模块不同,仅仅列出模块或包并期望 Distutils 出去找到正确的文件是不够的; 您必须指定扩展名、源文件和任何编译/链接要求(包括目录、要链接的库等)。
所有这些都是通过 setup()
的另一个关键字参数来完成的,即 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 命令实现的底层扩展构建机制)在描述 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 构造函数的第二个参数是源文件列表。 由于 Distutils 当前仅支持 C、C++ 和 Objective-C 扩展,因此这些通常是 C/C++/Objective-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"
在某些平台上,您可以包含由编译器处理并包含在扩展中的非源文件。 目前,这仅意味着 Visual C++ 的 Windows 消息文本 (.mc
) 文件和资源定义 (.rc
) 文件。 这些将被编译为二进制资源 (.res
) 文件并链接到可执行文件中。
2.3.3. 预处理器选项
如果您需要指定要搜索的包含目录或要定义/取消定义的预处理器宏,则 Extension 的三个可选参数将有所帮助:include_dirs
、define_macros
和 undef_macros
]。
例如,如果您的扩展需要在您的分发根目录下的 include
目录中的头文件,请使用 include_dirs
选项:
Extension('foo', ['foo.c'], include_dirs=['include'])
您可以在那里指定绝对目录; 如果您知道您的扩展程序将仅构建在 X11R6 安装到 /usr
的 Unix 系统上,您可以逃脱
Extension('foo', ['foo.c'], include_dirs=['/usr/include/X11'])
如果您打算分发您的代码,您应该避免这种不可移植的用法:编写 C 代码可能更好
#include <X11/Xlib.h>
如果您需要包含来自其他 Python 扩展的头文件,您可以利用 Distutils install_headers 命令以一致方式安装头文件这一事实。 例如,Numerical 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
相当于 C 源代码中的 #define FOO
:对于大多数编译器,这会将 FOO
设置为字符串 [ X154X].) 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()
提供 requires 关键字参数来指定对其他 Python 模块和包的依赖关系。 该值必须是字符串列表。 每个字符串指定一个所需的包,以及可选的版本是足够的。
要指定需要任何版本的模块或包,字符串应完全由模块或包名称组成。 示例包括 'mymodule'
和 'xml.parsers.expat'
。
如果需要特定版本,可以在括号中提供一系列限定符。 每个限定符可能由一个比较运算符和一个版本号组成。 接受的比较运算符是:
< > ==
<= >= !=
这些可以通过使用以逗号(和可选的空格)分隔的多个限定符来组合。 在这种情况下,必须匹配所有限定符; 逻辑 AND 用于组合评估。
让我们看一堆例子:
需要表达 | 解释 |
---|---|
==1.0
|
仅兼容版本 1.0
|
>1.0, !=1.5.1, <2.0
|
1.0 之后和2.0 之前的任何版本都兼容,除了1.5.1
|
既然我们可以指定依赖项,我们还需要能够指定我们提供的其他发行版可能需要的内容。 这是使用 setup()
的 provides 关键字参数完成的。 此关键字的值是一个字符串列表,每个字符串都命名一个 Python 模块或包,并可选择标识版本。 如果未指定版本,则假定与发行版的版本相匹配。
一些例子:
提供表达 | 解释 |
---|---|
mypkg
|
提供mypkg ,使用发行版
|
mypkg (1.1)
|
提供mypkg 1.1版,不分发行版
|
一个包可以使用 obsoletes 关键字参数声明它废弃其他包。 这个值类似于 requires 关键字:给出模块或包说明符的字符串列表。 每个说明符由一个模块或包名称组成,可选地后跟一个或多个版本限定符。 版本限定符在模块或包名称后的括号中给出。
限定符标识的版本是那些被描述的发行版过时的版本。 如果未给出限定符,则将所有版本的命名模块或包理解为已过时。
2.5. 安装脚本
到目前为止,我们一直在处理纯和非纯 Python 模块,它们通常不是由自己运行而是由脚本导入的。
脚本是包含 Python 源代码的文件,旨在从命令行启动。 脚本不需要 Distutils 做任何非常复杂的事情。 唯一巧妙的功能是,如果脚本的第一行以 #!
开头并包含“python”一词,则 Distutils 将调整第一行以引用当前解释器的位置。 默认情况下,它会替换为当前的解释器位置。 --executable
(或 -e
)选项将允许显式覆盖解释器路径。
scripts
选项只是要以这种方式处理的文件列表。 从 PyXML 安装脚本:
setup(...,
scripts=['scripts/xmlproc_parse', 'scripts/xmlproc_val']
)
3.1 更改: 如果没有提供模板,所有脚本也会添加到 MANIFEST
文件中。 请参阅 指定要分发的文件 。
2.6. 安装包数据
通常,需要将附加文件安装到包中。 这些文件通常是与包的实现密切相关的数据,或者是包含使用该包的程序员可能感兴趣的文档的文本文件。 这些文件被称为包数据。
可以使用 setup()
函数的 package_data
关键字参数将包数据添加到包中。 该值必须是从包名称到应复制到包中的相对路径名称列表的映射。 路径被解释为相对于包含包的目录(如果合适,使用来自 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
以下列方式指定一系列 (directory, files) 对:
setup(...,
data_files=[('bitmaps', ['bm/b1.gif', 'bm/b2.gif']),
('config', ['cfg/data.cfg'])],
)
序列中的每个 (directory, files) 对都指定了安装目录和要安装的文件。
files 中的每个文件名都相对于包源分发顶部的 setup.py
脚本进行解释。 请注意,您可以指定将安装数据文件的目录,但不能重命名数据文件本身。
directory 应该是一个相对路径。 它相对于安装前缀进行解释(Python 的 sys.prefix
用于系统安装;site.USER_BASE
用于用户安装)。 Distutils 允许 directory 作为绝对安装路径,但不鼓励这样做,因为它与车轮打包格式不兼容。 没有来自 files 的目录信息用于确定安装文件的最终位置; 仅使用文件名。
您可以将 data_files
选项指定为简单的文件序列而不指定目标目录,但不推荐这样做,在这种情况下,install 命令将打印警告。 要将数据文件直接安装在目标目录中,应提供一个空字符串作为目录。
3.1 版本更改: 匹配data_files
的所有文件,如果没有提供模板,将添加到MANIFEST
文件中。 请参阅 指定要分发的文件 。
2.8. 附加元数据
安装脚本可能包括名称和版本之外的其他元数据。 这些信息包括:
元数据 | 描述 | 价值 | 笔记 |
---|---|---|---|
name
|
包名 | 短字符串 | (1) |
version
|
此版本的版本 | 短字符串 | (1)(2) |
author
|
包作者的名字 | 短字符串 | (3) |
author_email
|
包作者的电子邮件地址 | 电子邮件地址 | (3) |
maintainer
|
包维护者的名字 | 短字符串 | (3) |
maintainer_email
|
包维护者的电子邮件地址 | 电子邮件地址 | (3) |
url
|
包的主页 | 网址 | (1) |
description
|
包的简短摘要说明 | 短字符串 | |
long_description
|
包的详细说明 | 长字符串 | (4) |
download_url
|
可以下载包的位置 | 网址 | |
classifiers
|
分类器列表 | 字符串列表 | (6)(7) |
platforms
|
平台列表 | 字符串列表 | (6)(8) |
keywords
|
关键字列表 | 字符串列表 | (6)(8) |
license
|
包的许可证 | 短字符串 | (5) |
笔记:
- 这些字段是必需的。
- 建议版本采用 major.minor[.patch[.sub]] 的形式。
- 必须确定作者或维护者。 如果提供了维护者,distutils 在
PKG-INFO
中将其列为作者。 - PyPI 在发布包时使用
long_description
字段来构建其项目页面。 license
字段是一个文本,指示覆盖包的许可证,其中许可证不是从“许可证”Trove 分类器中选择的。 请参阅Classifier
字段。 请注意,有一个licence
分发选项已被弃用,但仍充当license
的别名。- 此字段必须是列表。
- PyPI 上列出了有效的分类器。
- 为了保持向后兼容性,该字段还接受一个字符串。 如果传入逗号分隔的字符串
'foo, bar'
,则将其转换为['foo', 'bar']
,否则将转换为一个字符串的列表。
- '短字符串'
- 单行文字,不超过 200 个字符。
- '长字符串'
- 多行 reStructuredText 格式的纯文本(参见 http://docutils.sourceforge.net/)。
- '字符串列表'
- 见下文。
编码版本信息本身就是一门艺术。 Python 包通常遵循版本格式 major.minor[.patch][sub]。 对于软件的初始实验版本,主要编号为 0。 对于代表包中主要里程碑的版本,它会增加。 当重要的新功能添加到包中时,次要编号会增加。 当发布错误修复版本时,补丁号会增加。 附加的尾随版本信息有时用于指示子版本。 它们是“a1,a2,...,aN”(对于 alpha 版本,功能和 API 可能会改变)、“b1,b2,...,bN”(对于 beta 版本,仅修复错误)和“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 编译器)失败时打印整个命令行。