2. 编写安装脚本

设置脚本是使用 Distutils 构建,分发和安装模块的所有活动的中心。设置脚本的主要目的是描述模块在 Distutils 上的分布,以便对模块进行操作的各种命令都能正确执行。正如我们在上面的一个简单的例子部分中所看到的,安装脚本主要包括对setup()的调用,并且模块开发人员提供给 Distutils 的大多数信息都作为setup()的关键字参数提供。

这是一个涉及程度稍高的示例,在接下来的两节中将 continue 进行: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='[email protected]',
      url='https://www.python.org/sigs/distutils-sig/',
      packages=['distutils', 'distutils.command'],
     )

此文件与第一个简单的例子节中介绍的单文件分发之间只有两个区别:更多元数据,以及按软件包而不是按模块指定纯 Python 模块。这一点很重要,因为 Distutils 由几十个模块组成,这些模块分为(到目前为止)两个软件包。每个模块的明确列表将难以生成且难以维护。有关其他元数据的更多信息,请参见Additional meta-data部分。

请注意,安装脚本中提供的任何路径名(文件或目录)都应使用 Unix 约定写成,即以斜杠分隔。在实际使用路径名之前,Distutils 将负责将此与平台无关的表示形式转换为当前平台上合适的形式。这使您的安装脚本可跨 os 移植,这当然是 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条目隐式适用于* package 以下的所有软件包,因此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 出去寻找正确的文件还不够。您必须指定 extensions,源文件以及所有编译/链接要求(包括目录,要链接的库等)。

所有这些都是passext_modules选项setup()的另一个关键字参数完成的。 ext_modules只是Extension个实例的列表,每个实例描述一个扩展模块。假设您的发行版包括一个名为foo并由foo.c实现的扩展。如果不需要对编译器/链接器的附加说明,则描述此扩展非常简单:

Extension('foo', ['foo.c'])

Extension类可以与setup()一起从distutils.core导入。因此,仅包含此 extensions 而没有其他内容的模块分发的安装脚本可能是:

from distutils.core import setup, Extension
setup(name='foo',
      version='1.0',
      ext_modules=[Extension('foo', ['foo.c'])],
      )

Extension类(实际上是由 build_ext 命令实现的底层扩展构建机制)在描述 Python 扩展方面具有很大的灵 Active,以下各节对此进行了说明。

2.3.1. extensions 和软件包

Extension构造函数的第一个参数始终是 extensions,包括所有程序包名称。例如,

Extension('foo', ['src/foo1.c', 'src/foo2.c'])

描述了位于根包中的扩展,而

Extension('pkg.foo', ['src/foo1.c', 'src/foo2.c'])

描述pkg包中的相同 extensions。在这两种情况下,源文件和生成的目标代码都是相同的。唯一的区别是生成的 extensions 在文件系统中的位置(因此在 Python 的名称空间层次结构中的位置)。

如果您在同一程序包中(或全部在同一基本程序包中)都有许多扩展,请对setup()使用关键字ext_package。例如,

setup(...,
      ext_package='pkg',
      ext_modules=[Extension('foo', ['foo.c']),
                   Extension('subpkg.bar', ['bar.c'])],
     )

会将foo.c编译为 extensionspkg.foo,并将bar.c编译为pkg.subpkg.bar

2.3.2. 扩展源文件

Extension构造函数的第二个参数是源文件列表。由于 Distutils 当前仅支持 C,C 和 Objective-C 扩展,因此它们通常是 C/C/Objective-C 源文件。 (请确保使用适当的 extensions 来区分 C 源文件:Unix 和 Windows 编译器都可以识别.cc.cpp.)

但是,您也可以在列表中包括 SWIG 界面(.i)文件。 build_ext 命令知道如何处理 SWIGextensions:它将在接口文件上运行 SWIG,并将生成的 C/C 文件编译为 extensions。

尽管有此警告,但 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

例如,如果 extensions 需要分发根目录下include目录中的头文件,请使用include_dirs选项:

Extension('foo', ['foo.c'], include_dirs=['include'])

您可以在此处指定绝对目录。如果您知道 extensions 将仅在安装了/usr的 X11R6 的 Unix 系统上构建,则可以避免使用

Extension('foo', ['foo.c'], include_dirs=['/usr/include/X11'])

如果您打算分发代码,则应避免这种不可移植的用法:最好编写如下的 C 代码:

#include <X11/Xlib.h>

如果您需要包括来自其他 Python 扩展的头文件,则可以利用 Distutils install_headers 命令以一致的方式安装头文件这一事实。例如,将 Python 数字 Headers 文件(在标准 Unix 安装中)安装到/usr/local/include/python1.5/Numerical。 (确切的位置会根据您的平台和 Python 安装的不同而有所不同.)由于 Python 包含目录(在这种情况下为/usr/local/include/python1.5)在构建 Python 扩展时始终包含在搜索路径中,因此最好的方法是编写 C 代码,例如

#include <Numerical/arrayobject.h>

但是,如果必须将Numerical include 目录放到标题搜索路径中,则可以使用 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)Tuples 的列表,其中name是要定义的宏的名称(字符串),value是其值:字符串或None。 (将FOO定义为None相当于在 C 源代码中使用裸#define FOO:在大多数编译器中,这会将FOO设置为字符串1.)undef_macros只是要取消定义的宏列表。

For example:

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. 其他选择

还有一些其他选项可用于处理特殊情况。

extra_objects选项是要传递给链接器的目标文件的列表。这些文件不得具有 extensions,因为使用了编译器的默认 extensions。

extra_compile_argsextra_link_args可用于为相应的编译器和链接器命令行指定其他命令行选项。

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

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

2.4. 发行与包装之间的关系

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

  • 它可能需要包装或模块。

  • 它可以提供软件包或模块。

  • 它可能会淘汰软件包或模块。

可以使用distutils.core.setup()函数的关键字参数来指定这些关系。

可以pass向setup()提供* requires *关键字参数来指定对其他 Python 模块和软件包的依赖关系。该值必须是字符串列表。每个字符串指定一个必需的程序包,并可选地指定什么版本就足够了。

要指定需要任何版本的模块或软件包,字符串应完全由模块或软件包名称组成。示例包括'mymodule''xml.parsers.expat'

如果需要特定版本,则可以在括号中提供一系列限定符。每个限定符可以由比较运算符和版本号组成。可接受的比较运算符为:

<    >    ==
<=   >=   !=

这些可以pass使用多个用逗号(和可选的空格)分隔的限定符进行组合。在这种情况下,所有限定词都必须匹配;逻辑 AND 用于合并评估。

让我们看一堆示例:

Requires ExpressionExplanation
==1.0仅兼容版本1.0
>1.0, !=1.5.1, <2.01.0之后和2.0之前的任何版本都兼容,但1.5.1除外

现在我们可以指定依赖项,我们还需要能够指定其他发行版可能需要的内容。这可以pass使用setup()的* provides *关键字参数来完成。此关键字的值是一个字符串列表,每个字符串都命名一个 Python 模块或程序包,并可选地标识版本。如果未指定版本,则假定它与发行版匹配。

Some examples:

Provides ExpressionExplanation
mypkg使用发行版提供mypkg
mypkg (1.1)提供mypkg版本 1.1,与发行版本无关

软件包可以使用关键字关键字 obsoletes 语句已淘汰其他软件包。其值类似于 requires *关键字的值:提供模块或软件包说明符的字符串列表。每个说明符由一个模块或程序包名称组成,还可以选择后面跟一个或多个版本限定符。版本限定符在模块或软件包名称后的括号中给出。

限定符标识的版本是所描述的发行版本已过时的版本。如果没有给出限定符,则该命名模块或包的所有版本都将被视为已废弃。

2.5. 安装脚本

到目前为止,我们一直在处理纯的和非纯的 Python 模块,这些模块通常不是自己运行,而是pass脚本导入。

脚本是包含 Python 源代码的文件,旨在从命令行启动。脚本不需要 Distutils 做任何非常复杂的事情。唯一聪明的Function是,如果脚本的第一行以#!开头并包含单词“ python”,则 Distutils 将调整第一行以引用当前解释器的位置。默认情况下,它将替换为当前解释器位置。 --executable(或-e)选项将允许显式覆盖解释器路径。

scripts选项只是以这种方式处理的文件列表。从 PyXML 设置脚本中:

setup(...,
      scripts=['scripts/xmlproc_parse', 'scripts/xmlproc_val']
      )

在 2.7 版中进行了更改:如果未提供模板,所有脚本也将添加到MANIFEST文件中。参见指定要分发的文件

2.6. 安装包数据

通常,其他文件需要安装到软件包中。这些文件通常是与程序包的实现紧密相关的数据,或者是文本文件,其中包含使用程序包的程序员可能会感兴趣的文档。这些文件称为包装数据

可以使用setup()函数的package_data关键字参数将包数据添加到包中。该值必须是从程序包名称到应复制到程序包中的相对路径名列表的 Map。路径被解释为相对于包含包的目录(如果合适,使用package_dirMap 中的信息);也就是说,这些文件应该是源目录中软件包的一部分。它们也可能包含 glob 模式。

路径名可能包含目录部分;任何必要的目录都将在安装中创建。

例如,如果程序包应包含带有多个数据文件的子目录,则可以在源树中按以下方式排列文件:

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']},
      )

2.4 版的新Function。

在 2.7 版中进行了更改:如果未提供模板,则所有与package_data匹配的文件都将添加到MANIFEST文件中。参见指定要分发的文件

2.7. 安装其他文件

data_files选项可用于指定模块分发所需的其他文件:配置文件,消息目录,数据文件以及不属于先前类别的任何文件。

data_filespass以下方式指定(* directory files *)对的序列:

setup(...,
      data_files=[('bitmaps', ['bm/b1.gif', 'bm/b2.gif']),
                  ('config', ['cfg/data.cfg']),
     )

序列中的每对(* directory files *)对都指定安装目录和要在其中安装的文件。

  • files *中的每个文件名都相对于包源分发顶部的setup.py脚本进行解释。请注意,您可以指定将要安装数据文件的目录,但不能重命名数据文件本身。

目录应该是相对路径。相对于安装前缀(对于系统安装,Python 的sys.prefix;对于用户安装,site.USER_BASE)进行解释。 Distutils 允许* directory *为绝对安装路径,但是不建议使用该目录,因为它与车轮包装格式不兼容。 * files *中没有目录信息用于确定已安装文件的finally位置。仅使用文件名。

您可以将data_files选项指定为简单的文件序列,而无需指定目标目录,但是不建议这样做,在这种情况下, install 命令将显示警告。要直接在目标目录中安装数据文件,应在目录中提供一个空字符串。

在 2.7 版中进行了更改:如果未提供模板,则所有与data_files匹配的文件都将添加到MANIFEST文件中。参见指定要分发的文件

2.8. 附加元数据

安装脚本可能包含名称和版本以外的其他元数据。这些信息包括:

Meta-DataDescriptionValueNotes
name包装名称short string(1)
version此版本的版本short string(1)(2)
author软件包作者的姓名short string(3)
author_email包裹作者的电子邮件地址email address(3)
maintainer软件包维护者的名字short string(3)
maintainer_email软件包维护者的电子邮件地址email address(3)
url套餐首页URL(1)
description简短的包装摘要说明short string
long_description包装的详细说明long string(5)
download_url可以下载软件包的位置URL(4)
classifiers分类列表字符串列表(4)
platforms平台 Lists字符串列表
license包装许可证short string(6)

Notes:

  • 这些字段是必需的。

  • 建议版本采用* major.minor [.patch [.sub]] *的形式。

  • 必须确定作者或维护者。如果提供了维护者,则 distutils 在PKG-INFO中将其列为作者。

  • 如果您的软件包要与 2.2.3 或 2.3 之前的 Python 版本兼容,则不应使用这些字段。该列表可从PyPI website获得。

  • PyPI 在发布包时使用long_description字段来构建其项目页面。

  • license字段是指示许可证的文本,该许可证涵盖了软件包,其中许可证不是“许可证” Trove 分类器的选择。请参见Classifier字段。注意,有一个licence分发选项已弃用,但仍充当license的别名。

  • 'short string'

    • 一行 Literals,最多 200 个字符。
  • 'long string'

  • “字符串列表”

    • See below.

字符串值均不能为 Unicode。

编码版本信息本身就是一门艺术。 Python 软件包通常遵循* major.minor [.patch] [sub] *的版本格式。对于软件的初始实验版本,主要数字为 0.对于代表软件包中主要里程碑的发行版,它会增加。将重要的新Function添加到程序包时,次要编号会增加。发布错误修复程序时,补丁号会增加。有时会使用附加的尾随版本信息来指示子版本。这些是“ a1,a2,…,aN”(对于 Alpha 版本,Function和 API 可能会更改),“ b1,b2,…,bN”(对于 Beta 版本,仅修复错误)和“ pr1,pr2,…” ,prN”(用于finally的预发行版本测试)。一些例子:

  • 0.1.0

    • 首先,实验性发布软件包
  • 1.0.1a2

    • 1.0 的第一个修补程序版本的第二个 alpha 版本

classifiers在 Python 列表中指定:

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',
          ],
      )

如果您希望在setup.py文件中包含分类器,并且还希望与 2.2.3 之前的 Python 版本向后兼容,则可以在setup()调用之前将以下代码片段包含在setup.py中。

# patch distutils if it can't cope with the "classifiers" or
# "download_url" keywords
from sys import version
if version < '2.2.3':
    from distutils.dist import DistributionMetadata
    DistributionMetadata.classifiers = None
    DistributionMetadata.download_url = None

2.9. 调试安装脚本

有时情况会出错,并且安装脚本无法满足开发人员的要求。

Distutils 在运行安装脚本时会捕获任何异常,并在脚本终止之前显示一条简单的错误消息。这种行为的动机是不要混淆对 Python 不太了解并试图安装软件包的 Management 员。如果他们从 Distutils 的内心深处获得了长久的 traceback,他们可能会认为该软件包或 Python 安装已损坏,因为他们没有一路读到最底层并看到这是一个权限问题。

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