# 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 |
`wininst` | Windows的自解压ZIP文件 | (4)
`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_wininst | wininst
bdist_msi | msi

注解

从Python 3.8开始不推荐使用 bdist_wininst

注解

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>" \
                    bdist_wininst --target-version="2.0"

创建 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. Creating Windows Installers

警告

从Python 3.8开始不推荐使用 bdist_wininst

警告

bdist_msi 从 Python 3.9 起被弃用。

Executable installers are the natural format for binary distributions on Windows. They display a nice graphical user interface, display some information about the module distribution to be installed taken from the metadata in the setup script, let the user select a few options, and start or cancel the installation.

Since the metadata is taken from the setup script, creating Windows installers is usually as easy as running:

    python setup.py bdist_wininst

or the bdist command with the `--formats` option:

    python setup.py bdist --formats=wininst

If you have a pure module distribution (only containing pure Python modules and packages), the resulting installer will be version independent and have a name like `foo-1.0.win32.exe`. Note that creating `wininst` binary distributions in only supported on Windows systems.

If you have a non-pure distribution, the extensions can only be created on a Windows platform, and will be Python version dependent. The installer filename will reflect this and now has the form `foo-1.0.win32-py2.0.exe`. You have to create a separate installer for every Python version you want to support.

The installer will try to compile pure modules into bytecode after installation on the target system in normal and optimizing mode. If you don't want this to happen for some reason, you can run the bdist_wininst command with the `--no-target-compile` and/or the `--no-target-optimize` option.

By default the installer will display the cool "Python Powered" logo when it is run, but you can also supply your own 152x261 bitmap which must be a Windows `.bmp` file with the `--bitmap` option.

The installer will also display a large title on the desktop background window when it is run, which is constructed from the name of your distribution and the version number. This can be changed to another text by using the `--title` option.

The installer file will be written to the "distribution directory" --- normally `dist/`, but customizable with the `--dist-dir` option.

## 5.3. 在 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

to build a 64bit version of your extension. The Windows Installers also support this option, so the command:

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

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

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

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

### 5.3.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` 接口的文档。

## 5.4. Vista User Access Control (UAC)

Starting with Python 2.6, bdist_wininst supports a `--user-access-control` option. The default is 'none' (meaning no UAC handling is done), and other valid values are 'auto' (meaning prompt for UAC elevation if Python was installed for all users) and 'force' (meaning always prompt for elevation).

注解

从Python 3.8开始不推荐使用 bdist_wininst

注解

bdist_msi 从 Python 3.9 起被弃用。