5. 创建构建分发版
*****************

注解:

  这篇文档是历史遗留文档，在
  https://setuptools.readthedocs.io/en/latest/setuptools.html 上的
  "setuptools" 文档独立涵盖此处包含的所有相关信息之后，将不再单独作为
  正式文档保留。

“构建分发版”即你通常所认为的“二进制包”或“安装程序”（取决于你的技术背景
）。 但它并不一定是二进制的，因为它可能只包含 Python 源代码和/或字节码
；并且我们也不将其称为包，因为这个词在 Python 中已经被使用。 （而“安装
程序”是主流桌面系统领域的一个专有术语。）

分发版可以让你的模块分发安装器尽可能地方便易用：对于基于 RPM 的 Linux
系统的用户，它将为二进制 RPM；对于 Windows 的用户，它将为可执行文件安
装器；对于基于 Debian 的 Linux 用户，它将为 Debian 安装包；诸如此类。
显然，没有人能为世界上所有的系统平台都构建分发版，因此 Distutils 被设
计用来让模块开发者能够专注于他们的专长 --- 编写代码并创建源代码分发版
--- 而让另一群作为中介的 *打包者* 负责在尽可能多的受支持系统平台上将源
代码分发版转换为构建分发版。

当然，模块开发者可以是自己模块的打包者；或者打包者也可以是住在别处的“
某一位”能够接触到原始开发者接触不到的特定系统平台的志愿者；或者还可以
是一个定期抓取新的源代码分发版并在尽可能多的受支持系统平台上将其转换为
构建分发版的软件。 无论他们属于哪一种，打包者都会使用设置脚本和
**bdist** 命令族来生成构建分发版。

作为一个简单示例，如果我在 Distutils 源代码树中运行以下命令:

   python setup.py bdist

则 Distutils 将构建我的模块分发版（在此情况下即 Distutils 本身），执行
“模拟”安装（同样是在 "build" 目录中），并为我的系统平台创建默认类型的
构建分发版。 构建分发版的默认格式在 Unix 上是一个“非自动” tar 文件，而
在 Windows 上则是一个简单的可执行文件安装器。 （此 tar 文件被称为“非自
动”是因为它必须在指定的位置上解包方可使用。）

这样，上述命令在 Unix 系统上将会创建 "Distutils-1.0.*plat*.tar.gz"；在
正确的位置上解包这个 tar 文件将会装好 Distutils，就像是你下载了源代码
分发版并运行 "python setup.py install" 一样。 （这个“正确的位置”可能是
文件系统根目录或 Python 的 "*prefix*" 目录，具体取决于提供给
**bdist_dumb** 命令的选项；在默认情况下是相对于 "*prefix*" 创建非自动
颁发版。）

显然，对于纯粹的 Python 分发版来说，这并不比运行 "python setup.py
install" 更简单 --- 但是对于包括需要被编译的扩展的非纯粹的分发版来说，
就可能是有人能使用你的扩展而有人不能使用的差别。 而创建“自动”构建分发
版，例如 RPM 包或 Windows 的可执行文件安装器，对于用户来说就会更加方便
，即使你的分发版不包括任何扩展。

**bdist** 命令有一个 "--formats" 选项，与 **sdist** 命令类似，你可以用
该选项来选择要生成的构建分发版类型：例如，:

   python setup.py bdist --format=zip

当在 Unix 系统上运行时，会再次创建 "Distutils-1.0.*plat*.zip"，这个归
档文件将从根目录被解包以安装 Distutils。

构建分发版的可用格式有:

+---------------+--------------------------------+-----------+
| 格式          | 描述                           | 备注      |
|===============|================================|===========|
| "gztar"       | gzipped tar 文件 (".tar.gz")   | (1)       |
+---------------+--------------------------------+-----------+
| "bztar"       | bzipped tar 文件 (".tar.bz2")  |           |
+---------------+--------------------------------+-----------+
| "xztar"       | xzipped tar 文件 (".tar.xz")   |           |
+---------------+--------------------------------+-----------+
| "ztar"        | 带压缩的 tar 文件 (".tar.Z")   | (3)       |
+---------------+--------------------------------+-----------+
| "tar"         | tar 文件 (".tar")              |           |
+---------------+--------------------------------+-----------+
| "zip"         | zip 文件 (".zip")              | (2),(4)   |
+---------------+--------------------------------+-----------+
| "rpm"         | RPM                            | (5)       |
+---------------+--------------------------------+-----------+
| "pkgtool"     | Solaris **pkgtool**            |           |
+---------------+--------------------------------+-----------+
| "sdux"        | HP-UX **swinstall**            |           |
+---------------+--------------------------------+-----------+
| "msi"         | Microsoft安装程序。            |           |
+---------------+--------------------------------+-----------+

在 3.5 版更改: 添加了对 "xztar" 格式的支持

注释：

1. 默认 Unix

2. 默认Windows

3. 需要外部 **compress** 工具。

4. 需要有外部 **zip** 工具或 "zipfile" 模块（自 Python 1.6 起是标准
   Python 库的一部分）

5. 需要有外部 **rpm** 工具，版本号为 3.0.4 或以上（可使用 "rpm
   --version" 查看你所使用的版本）

你不必附带 "--formats" 来使用 **bdist** 命令；你还可以使用直接实现了你
想要的特定格式的命令。 某些这样的 **bdist** "子命令" 实际上会生成几种
相似的格式；例如，**bdist_dumb** 命令将生成所有 "非自动" 归档格式
("tar", "gztar", "bztar", "xztar", "ztar" 和 "zip")，而 **bdist_rpm**
将同时生成二进制和源代码 RPM。 **bdist** 子命令以及每个子命令所生成的
格式如下:

+----------------------------+---------------------------------------+
| 命令                       | 格式                                  |
|============================|=======================================|
| **bdist_dumb**             | tar, gztar, bztar, xztar, ztar, zip   |
+----------------------------+---------------------------------------+
| **bdist_rpm**              | rpm, srpm                             |
+----------------------------+---------------------------------------+
| **bdist_msi**              | msi                                   |
+----------------------------+---------------------------------------+

注解:

  bdist_msi 从 Python 3.9 起被弃用。

以下小节提供了每个 **bdist_*** 命令的详情。


5.1. 创建RPM软件包
==================

RPM 格式被许多流行的 Linux 发行版所使用，包括 Red Hat, SuSE 和
Mandrake。 如果其中（或任何其他基于 RPM 的 Linux 发行版）的某一个是你
的常用环境，那么为相同发行版的其他用户创建 RPM 包是很容易的。 根据你的
模块分发版的复杂度以及 Linux 发行版之间的差异性，你还可能创建适用于多
个基于 RPM 的发行版的 RPM 包。

为你的模块分发版创建 RPM 的通常方式是运行 **bdist_rpm** 命令:

   python setup.py bdist_rpm

或者 **bdist** 命令附带 "--format" 选项:

   python setup.py bdist --formats=rpm

前者允许你指定 RPM 专属的选项；后者允许你方便地一次性指定多种格式。 如
果这两样你全都要，你可以显式地指定多个 **bdist_*** 命令及其选项:

   python setup.py bdist_rpm --packager="John Doe <jdoe@example.org>"

创建 RPM 包的操作是由 ".spec" 文件驱动的，就像 Distutils 的使用是由
setup 脚本驱动的一样。 为了让你更方便，**bdist_rpm** 命令通常会根据你
在 setup 脚本、命令行和任意 Distutils 配置文件中提供的信息创建一个
".spec" 文件。 ".spec" 文件中的各种选项和小节从 setup 脚本中派生的情况
如下:

+--------------------------------------------+------------------------------------------------+
| RPM ".spec" 文件配置或选项                 | Distutils安装脚本选项                          |
|============================================|================================================|
| 名称                                       | "name"                                         |
+--------------------------------------------+------------------------------------------------+
| 摘要（在序言中）                           | "description"                                  |
+--------------------------------------------+------------------------------------------------+
| 版本                                       | "version"                                      |
+--------------------------------------------+------------------------------------------------+
| 供应商                                     | "author" 和 "author_email", 或 --- &           |
|                                            | "maintainer" 和 "maintainer_email"             |
+--------------------------------------------+------------------------------------------------+
| 版权所有                                   | "license"                                      |
+--------------------------------------------+------------------------------------------------+
| Url                                        | "url"                                          |
+--------------------------------------------+------------------------------------------------+
| %d描述（部分）                             | "long_description"                             |
+--------------------------------------------+------------------------------------------------+

此外，在 ".spec" 文件中还有许多选项在 setup 脚本中没有对应的选项。 这
些选项大多是通过传给 **bdist_rpm** 命令的选项来处理的，如下所示:

+---------------------------------+-------------------------------+---------------------------+
| RPM ".spec" 文件配置或选项      | **bdist_rpm** 选项            | 默认值                    |
|=================================|===============================|===========================|
| 发布版本                        | "release"                     | "1"                       |
+---------------------------------+-------------------------------+---------------------------+
| 组织                            | "group"                       | "Development/Libraries"   |
+---------------------------------+-------------------------------+---------------------------+
| 供应商                          | "vendor"                      | （同上）                  |
+---------------------------------+-------------------------------+---------------------------+
| 打包                            | "packager"                    | (none)                    |
+---------------------------------+-------------------------------+---------------------------+
| 提供                            | "provides"                    | (none)                    |
+---------------------------------+-------------------------------+---------------------------+
| 需求                            | "requires"                    | (none)                    |
+---------------------------------+-------------------------------+---------------------------+
| 冲突                            | "conflicts"                   | (none)                    |
+---------------------------------+-------------------------------+---------------------------+
| 淘汰                            | "obsoletes"                   | (none)                    |
+---------------------------------+-------------------------------+---------------------------+
| Distribution                    | "distribution_name"           | (none)                    |
+---------------------------------+-------------------------------+---------------------------+
| 构建要求                        | "build_requires"              | (none)                    |
+---------------------------------+-------------------------------+---------------------------+
| Icon                            | "icon"                        | (none)                    |
+---------------------------------+-------------------------------+---------------------------+

显然，即使是在命令行中提供少量的此类选项也是很繁琐易出错的，因此通常最
好是将它们放在 setup 配置文件 "setup.cfg" 中，参见 编写设置脚本的配置
文件 一节。 如果你要分发或打包许多 Python 模块分发版，你可能会需要将适
用于所有这些分发版的选项放在你私人的 Distutils 配置文件中
("~/.pydistutils.cfg")。 如果你想要临时禁用此文件，你可以将 "--no-
user-cfg" 选项传给 "setup.py"。

构建一个二进制 RPM 包有三个步骤，它们全都是由 Distutils 自动处理的:

1. 创建一个 ".spec" 文件，该文件对包进行了描述（类似于 Distutils setup
   脚本；实际上 setup 脚本中的许多信息都会出现在 ".spec" 文件中）

2. 创建源 RPM

3. 创建“二进制”RPM（其中可能包含二进制代码也可能不包含，具体取决于你的
   模块分发版是否包含 Python 扩展）

通常，RPM 会将后两个步骤捆绑在一起；当你使用 Distutils 时，三个步骤通
常都会捆绑在一起。

如果你愿意，你也可以将这三个步骤分开。 你可以使用 "--spec-only" 选项来
让 **bdist_rpm** 只创建 ".spec" 文件并退出；在这种情况下，".spec" 文件
将被写到“分发目录” --- 通常为 "dist/"，但可通过 "--dist-dir" 选项来自
定义。 （通常，".spec" 文件会位于“构建树”的深处，在 **bdist_rpm** 所创
建的一个临时目录中。）


5.2. 在 Windows 上的交叉编译
============================

从 Python 2.6 开始，distutils 能够在不同 Windows 平台之间执行交叉编译
。 实际上，这意味着只要安装了正确的工具，你可以使用 32 位版 Windows 来
创建 64 位的扩展或是反向操作。

要针对替代平台进行编译，请为构建命令指定 "--plat-name" 选项。 目前的有
效值为 'win32' 和d  'win-amd64'。 例如，在 32 位版 Windows 上，你可以
执行:

   python setup.py build --plat-name=win-amd64

来构建你的扩展的 64 位版。

将在你的 32 位版 Windows 上创建一个 64 位版的安装程序可执行文件。

要进行交叉编译，你必须下载 Python 源代码并针对你的目标平台交叉编译
Python 本身 —— 使用 Python 的二进制安装版是无法做到的（因为其中不包括
针对其他平台的 .lib 等文件。） 实际上，这意味着 32 位操作系统的用户将
需要使用 Visual Studio 2008 来打开 Python 源代码目录树下的
"PCbuild/PCbuild.sln" 解决方案并构建 'pythoncore' 项目的 "x64" 配置之
后才能进行扩展的交叉编译。

请注意在默认情况下，Visual Studio 2008 并不会安装 64 位编译器或工具。
你可能需要重新执行 Visual Studio 安装过程并选择这些工具（使用控制面板
-> [添加/移除] 程序是检查或修改你的现有安装的一个便捷方式。）


5.2.1. 安装后脚本
-----------------

从 Python 2.3 开始，可以通过 "--install-script" 选项指定一个安装后脚本
。 必须要指定脚本的主文件名，并且该脚本文件名还必须在传给 setup 函数的
参数中列出。

在安装时当所有文件拷贝完毕后该脚本将会在目标系统上运行，并将 "argv[1]"
设为 "-install"，而在卸载时当文件被移除前会再次运行并将 "argv[1]" 设为
"-remove"。

安装脚本嵌入在 Windows 安装程序中运行，每个输出 ("sys.stdout",
"sys.stderr") 都会被重定向到一个缓冲区并将在脚本完成后显示到 GUI 中。

一些在此上下文中特别有用的功能在安装脚本中作为附加内置函数被提供。

directory_created(path)
file_created(path)

   这些函数应当在安装时的安装后脚本创建某个目录或文件时被调用。 它会将
   *path* 注册到卸载程序，这样当分发版被卸载时它将被移除。 安全起见，
   目录只有在为空时才会被移除。

get_special_folder_path(csidl_string)

   此函数可被用来获取 Windows 中的特殊文件夹位置如 Start Menu 或
   Desktop。 它将返回相应文件夹的完整路径。 *csidl_string* 必须为下列
   字符串之一:

      "CSIDL_APPDATA"

      "CSIDL_COMMON_STARTMENU"
      "CSIDL_STARTMENU"

      "CSIDL_COMMON_DESKTOPDIRECTORY"
      "CSIDL_DESKTOPDIRECTORY"

      "CSIDL_COMMON_STARTUP"
      "CSIDL_STARTUP"

      "CSIDL_COMMON_PROGRAMS"
      "CSIDL_PROGRAMS"

      "CSIDL_FONTS"

   如果文件夹不能被检索到，会触发 "OSError" 。

   有哪些可用的文件夹取决于的 Windows 的具体版本，并可能受特定配置的影
   响。 更多细节请参考 Microsoft 有关 "SHGetSpecialFolderPath()" 函数
   的文档。

create_shortcut(target, description, filename[, arguments[, workdir[, iconpath[, iconindex]]]])

   此函数会创建一个快捷方式。 *target* 是此快捷方式要启动的程序的路径
   。 *description* 是快捷方式的描述文本。 *filename* 是用户将看到的快
   捷方式的标题。 *arguments* 指定命令行参数，如果有的话。 *workdir*
   是程序的工作目录。 *iconpath* 是包含快捷方式的图标的文件，而
   *iconindex* 是 *iconpath* 文件中图标的索引号。 同样地，更多细节请参
   考 Microsoft 有关 "IShellLink" 接口的文档。
