2. 编写安装脚本 — Python 文档

来自菜鸟教程
Python/docs/3.9/distutils/setupscript
跳转至:导航、​搜索

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__.pylib/bar/__init__.py。 (请记住,尽管 package_dir 以递归方式应用,但您必须明确列出 packages 中的所有包:Distutils 将 递归扫描您的源代码树,寻找任何带有__init__.py 文件。)


2.2. 列出单个模块

对于小模块分发,您可能更喜欢列出所有模块而不是列出包——尤其是在“根包”中的单个模块的情况下(即,根本没有包)。 这个最简单的例子在一个简单的例子部分中展示; 这是一个稍微复杂的例子:

py_modules = ['mod1', 'pkg.mod2']

这描述了两个模块,其中一个在“root”包中,另一个在 pkg 包中。 同样,默认包/目录布局意味着这两个模块可以在 mod1.pypkg/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.coresetup() 导入。 因此,仅包含这个扩展而没有其他任何内容的模块分发的安装脚本可能是:

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_dirsdefine_macrosundef_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_macrosundef_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_argsextra_link_args 可用于为相应的编译器和链接器命令行指定额外的命令行选项。

export_symbols 仅在 Windows 上有用。 它可以包含要导出的符号(函数或变量)列表。 构建编译扩展时不需要此选项:Distutils 会自动将 initmodule 添加到导出的符号列表中。

depends 选项是扩展所依赖的文件列表(例如头文件)。 如果此文件上的任何内容自上次构建以来已被修改,则构建命令将调用源代码上的编译器以重建扩展。


2.4. 发行版和包之间的关系

发行版可能以三种特定方式与包相关:

  1. 它可能需要包或模块。
  2. 它可以提供包或模块。
  3. 它可以淘汰包或模块。

可以使用 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)

笔记:

  1. 这些字段是必需的。
  2. 建议版本采用 major.minor[.patch[.sub]] 的形式。
  3. 必须确定作者或维护者。 如果提供了维护者,distutils 在 PKG-INFO 中将其列为作者。
  4. PyPI 在发布包时使用 long_description 字段来构建其项目页面。
  5. license 字段是一个文本,指示覆盖包的许可证,其中许可证不是从“许可证”Trove 分类器中选择的。 请参阅 Classifier 字段。 请注意,有一个 licence 分发选项已被弃用,但仍充当 license 的别名。
  6. 此字段必须是列表。
  7. PyPI 上列出了有效的分类器。
  8. 为了保持向后兼容性,该字段还接受一个字符串。 如果传入逗号分隔的字符串'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 现在在 classifierskeywordsplatforms 字段未指定为列表或列表时发出警告细绳。


2.9. 调试安装脚本

有时事情会出错,并且设置脚本不会按照开发人员的意愿行事。

Distutils 在运行安装脚本时捕获任何异常,并在脚本终止之前打印一条简单的错误消息。 这种行为的动机是不要混淆对 Python 不太了解并试图安装软件包的管理员。 如果他们从 Distutils 的内部深处得到一个很长的回溯,他们可能会认为包或 Python 安装已损坏,因为他们没有从头到尾阅读并发现这是一个权限问题。

另一方面,这无助于开发人员找到失败的原因。 为此,可以将 DISTUTILS_DEBUG 环境变量设置为空字符串以外的任何内容,并且 distutils 现在将打印有关其正在执行的操作的详细信息,在发生异常时转储完整的回溯,并在外部程序(如 C 编译器)失败时打印整个命令行。