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__.pylib/bar/__init__.py . (记住,尽管 package_dir 递归应用,必须显式列出 packages :distutils将 not 递归扫描源目录树,查找具有 __init__.py 文件。

2.2. 列出各个模块

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

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

这描述了两个模块,一个在“根”包中,另一个在 pkg 包裹。同样,默认的包/目录布局意味着这两个模块可以在 mod1.pypkg/mod2.pypkg/__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.foobar.cpkg.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_dirsdefine_macrosundef_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_macrosundef_macros 选项。 define_macros 列出一个 (name, value) 元组,在哪里 name 是要定义的宏的名称(字符串),并且 value 其值为:字符串或 None . (定义宏 FOONone 相当于裸的 #define FOO 在C源代码中:对于大多数编译器,此集合 FOO1undef_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() . 该值必须是字符串列表。每个字符串指定一个所需的包,并且可以选择哪些版本足够。

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

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

<    >    ==
<=   >=   !=

这些可以通过使用多个用逗号(和可选空格)分隔的限定符来组合。在这种情况下,所有限定符都必须匹配;逻辑AND用于组合计算。

让我们来看一些例子:

需要表达式

解释

==1.0

唯一版本 1.0 兼容

>1.0, !=1.5.1, <2.0

之后的任何版本 1.0 以前 2.0 兼容,除非 1.5.1

既然我们可以指定依赖项,那么我们还需要能够指定我们提供的其他发行版可能需要的内容。这是用 提供 关键字参数 setup() . 这个关键字的值是一个字符串列表,每个字符串命名一个python模块或包,还可以选择标识版本。如果未指定版本,则假定该版本与分发版的版本匹配。

一些例子:

提供表达式

解释

mypkg

提供 mypkg ,使用分发版本

mypkg (1.1)

提供 mypkg 1.1版,不考虑发行版

一个包可以声明它使用 废弃的 关键字参数。它的值与 要求 关键字:给出模块或包说明符的字符串列表。每个说明符由一个模块或包名称组成,可选地后跟一个或多个版本限定符。版本限定符在模块或包名称后面的括号中给出。

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

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. 附加元数据

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

本地资源

描述

价值

笔记

name

包的名称

短串

(1)

version

此版本

短串

(1)(2)

author

包作者姓名

短串

(3)

author_email

包作者的电子邮件地址

电子邮件地址

(3)

maintainer

包维护者姓名

短串

(3)

maintainer_email

包维护者的电子邮件地址

电子邮件地址

(3)

url

包的主页

URL

(1)

description

包的简要说明

短串

long_description

包的较长描述

长串

(4)

download_url

可下载包的位置

URL

classifiers

分类器列表

字符串表

(6)(7)

platforms

平台列表

字符串表

(6)(8)

keywords

关键字列表

字符串表

(6)(8)

license

包的许可证

短串

(5)

笔记:

  1. 这些字段是必需的。

  2. 建议采用以下形式 [major.minor[.patch[.sub]]] .

  3. 必须标识作者或维护者。如果提供了Maintainer,distutils将其列为 PKG-INFO .

  4. 这个 long_description 当发布包时,pypi使用字段来构建其项目页。

  5. 这个 license 字段是一个文本,指示包含软件包的许可证,其中许可证不是从“许可证”trove分类器中选择的。见 Classifier 字段。注意有一个 licence 分发选项已弃用,但仍用作的别名 license .

  6. 此字段必须是列表。

  7. 有效的分类器列在 PyPI .

  8. 为了保持向后兼容性,此字段还接受一个字符串。如果传递逗号分隔的字符串 '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 现在警告什么时候 classifierskeywordsplatforms 字段未指定为列表或字符串。

2.9. 调试安装脚本

有时事情会出错,安装脚本不会按开发人员的要求执行。

distutils在运行安装脚本时捕获任何异常,并在脚本终止之前打印一条简单的错误消息。这种行为的动机是不要让不太了解Python并试图安装软件包的管理员感到困惑。如果他们从distutils的内部深处得到了一个很长的回溯,他们可能会认为包或python安装被破坏了,因为他们不会一直往下读,并且会发现这是一个权限问题。

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