为Windows编译Python安装包

Author Avatar
Lussac 7月3日 ; Views: 513
  • 在其它设备中阅读本文章

Build Python binary installers for Windows.

本文探索了如何编译适用于 Windows 的 Python 二进制安装包。

前言

Python 官方对于 安全修复版本 (security fix release) 一般不提供 二进制安装包 (binary installer) 。

例如 Python 3.7.9Python 3.7.x 系列中最后一个为 Windows 和 macOS 提供二进制安装包的版本, Python 3.7.10 及之后的 Python 3.7.x 系列版本则只提供 源代码 (source-only release) 。

那么如何在 Windows 上安装 当前 (2021-06-28) 最新的 Python 3.7.xPython 3.7.11 呢?

文末 附下载。


准备工作

官方文档

查阅 官方文档Python Setup and Usage 一节可以找到题为 Compiling Python on Windows 的说明:

If you want to compile CPython yourself, first thing you should do is get the source. You can download either the latest release’s source or just grab a fresh checkout.

The source tree contains a build solution and project files for Microsoft Visual Studio 2015, which is the compiler used to build the official Python releases. These files are in the PCbuild directory.

Check PCbuild/readme.txt for general information on the build process.

下载 Python 3.7.11 的 源代码 [1] [2] 并解压,找到 PCbuild/readme.txt

  • Install Microsoft Visual Studio 2017 with Python workload and Python native development component.
  • ... requires an installation of Microsoft Visual Studio 2017 (MSVC 14.1) with the Python workload and its optional Python native development component selected. (For command-line builds, Visual Studio 2015 may also be used.)
  • To build an installer package, refer to the README in the Tools/msi folder.

再找到 Tools/msi/README.txt

For testing, the installer should be built with the Tools/msi/build.bat script:

build.bat [-x86] [-x64] [--doc]

For an official release, the installer should be built with the Tools/msi/buildrelease.bat script and environment variables:

set PYTHON=<path to Python 2.7 or 3.4>
set SPHINXBUILD=<path to sphinx-build.exe>
set PATH=<path to Mercurial (hg.exe)>;
         <path to HTML Help Compiler (hhc.exe);%PATH%

buildrelease.bat [-x86] [-x64] [-D] [-B]
    [-o <output directory>] [-c <certificate name>]

编译环境

本文在全新的 Windows_7_SP1_64-bit 虚拟机上进行编译;宿主机为 Windows_10_20H2_64-bit ;目标 Python 版本为 3.7.11

所需软件

Visual Studio 2017

虽然 文档 [3] 中说 VS2015 可能也行,但经过我的实际测试,VS2015 会在编译开始时的某一步报错,因此仍建议选择安装 VS2017 。

选择 ProfessionalEnterprise 等付费版本(30 天试用期),可以在最后一步 "编译发行版 " 时启用 PGO 优化 [4]
但经过实际测试,Community 版本也可以正常地启用 PGO 优化。

Visual Studio 2017 的安装包可以从 此处 下载,但需要注册并登录 Microsoft Azure DevOps 。当前有效的几个下载直链分别是:

该版本的详细信息为 Visual Studio 2017 (version 15.9.36) (MSVC 14.16)。

Python 3.6+

编译 Python 安装包需要先安装 Python 3.6 或之后的版本。本文采用 python-3.7.9-amd64.exe
简洁起见,我将 Python 的安装目录设置为 C:\Python\Python37 。你当然可以使用默认设置,但建议安装路径中不要含有空格等特殊字符。另外,记得勾选 Add Python 3.x to PATH

Git for Windows 2.x

Git 的版本并不关键,本文采用 Git-2.32.0-64-bit.exe


安装 VS2017

安装流程

双击运行 vs_Community.exe 等,即开始自动安装 Visual Studio Installer 。然后选择 工作负载 中的 "Python 开发 (Python development workload)" 及其可选子项 "Python 本机开发工具 (Python native development tools component)" ,要求的总空间约为 11 GB 。本文使用默认安装路径。

vs-install-workload

耐心等待安装完成。

注意,VS2017 安装完成后无需打开,之后的编译全过程可以在 CMD 命令行中完成。

安装指北

在全新的 Windows 7 系统上安装 Visual Studio 2017 时可能遇到问题,你可能需要先安装以下两项依赖。

.NET Framework 4.6

打开 vs_Community.exe 等需要 .NET Framework 4.6 运行时。

Windows Updates

VS2017 在线安装时可能卡在 "稍等片刻... 正在提取文件" ,有两种可行的解决方案 [5]

vs-install-download

  • 修改 DNS 地址 为 114.114.114.1148.8.8.8
  • (win7) 下载安装系统更新补丁 KB4490628KB4474419

编译

必要依赖

下载 Python 3.7.11 的 源代码 [1] [2] 并解压。

# 尝试首次构建 CPython
$ PCbuild\build.bat

脚本会首先下载并构建外部依赖,包含 bzip2 , sqlite-3 , xz , zlib , openssl , tcltk 等。

你可能会遇到 SSL 相关问题,只需修改 PCbuild/get_external.py 即可;
另外 get_external.py 将从 GitHub 下载外部依赖的源码,因此你可能还需要为 urllib.request.urlretrieve() 函数指定代理。

from urllib.request import urlretrieve

# 添加以下两行语句以禁用 SSL
import ssl
ssl._create_default_https_context = ssl._create_unverified_context

# 添加以下三行语句以指定代理
from urllib import request
proxy = request.ProxyHandler({'https': 'http://192.168.1.200:10809'})
opener = request.build_opener(proxy)


def fetch_zip(commit_hash, zip_dir, *, org='python', binary=False, verbose):
    repo = f'cpython-{"bin" if binary else "source"}-deps'
    url = f'https://github.com/{org}/{repo}/archive/{commit_hash}.zip'
    reporthook = None
    if verbose:
        reporthook = print
    zip_dir.mkdir(parents=True, exist_ok=True)
    
    # 添加此句以应用代理(需与指定代理的三行语句同时添加)
    request.install_opener(opener)
    
    filename, headers = urlretrieve(
        url,
        zip_dir / f'{commit_hash}.zip',
        reporthook=reporthook,
    )
    return filename

编译 CPython

此时 PCbuild/build.bat 应当能正确执行。编译需要一段时间,若能正常编译,则完成后的输出位于 PCbuild/win32 下。双击其中的 python.exe 即可打开 Python 的 REPL 交互式命令行。

python-repl

这一步已经生成了能正常使用的 Python 解释器。如果你不需要编译 Python 的安装包,你可以不进行之后的步骤。

编译 MSI 安装包

编译 MSI 安装包 需要用到 Tools/msi/build.bat

# 尝试首次构建 MSI installer
$ Tools\msi\build.bat -x86

脚本会首先下载二进制依赖到 externals/windows-installer 下,包含 binutils , gpg , htmlhelp , nuget , redist-1 , wix 等。

编译需要一段时间,若能正常编译,则完成后的输出位于 PCbuild/win32/en-us 下。其中包含一系列的 .msi 文件及其对应的 .wixpdb 文件,和最重要的安装程序入口文件 python-3.7.11.7488.exe ,双击打开即可执行 Python 安装流程。

python-installer-msi

尽管 Tools/msi/build.bat 中提到可以使用 --pack 参数来将 msi 文件打包进 exe 安装程序 [6] [7]

# 尝试构建独立的 installer
$ Tools\msi\build.bat -x86 --pack

但经过我的实际测试发现并不能。可能是由于编译过程中出现了 "3~4 个警告" ?

python-installer-msi-warnings

编译文档

在编译发行版前不妨先编译好 python3711.chm 文档,否则你可能在下一步中遇到错误而无法打包。

Specify --doc to build the documentation (.chm) file. If the file is not available, it will simply be excluded from the installer. Ensure %PYTHON% and %SPHINXBUILD% are set when passing this option. You may also set %HTMLHELP% to the Html Help Compiler (hhc.exe), or put HHC on your PATH or in externals/.

而查看 Tools/msi/build.bat 发现编译文档所使用的命令为 [8]

Doc\make.bat htmlhelp

因此需要先安装 sphinx-build ,根据我的尝试,可用的版本为 sphinx 1.8 [9] [10]

pip install "sphinx==1.8"

这里提供一个批处理脚本,可以在此后的步骤中使用到,建议保存为 env.bat 。其中的内容需要你根据情况自行修改。

@echo off

:: 指向之前 Python 3.7.9 安装目录中的 `python.exe`
set PYTHON=C:\Python\Python37\python.exe

:: 指向 `sphinx-build.exe` 的位置,可以通过 `where sphinx` 获得
set SPHINXBUILD=C:\Python\Python37\Scripts\sphinx-build.exe
:: 若文件路径含有空格等特殊字符,则使用引号包括
:: set SPHINXBUILD="C:\Program Files\Python37\Scripts\sphinx-build.exe"

:: 指向 Python 3.7.11 源代码解压目录中的 `hhc.exe`
set HTMLHELP=C:\Users\Lussac\Desktop\Python-3.7.11\externals\windows-installer\htmlhelp\hhc.exe

:: 将 "Python 3.7.9 安装目录中的 Scripts 目录" 和 "hhc.exe 所在目录" 加入 %PATH%
set "PATH=C:\Python\Python37\Scripts\;C:\Users\Lussac\Desktop\Python-3.7.11\externals\windows-installer\htmlhelp\;%PATH%"

重新打开一个 CMD 窗口,执行以下命令以构建 chm 文档:

# 将 env.bat 拖动到 CMD 窗口中即可自动填充路径
$ C:\Users\Lussac\Desktop\env.bat

$ cd Doc
$ mkdir build\htmlhelp
$ make.bat htmlhelp

编译需要一段时间,若能正常编译,则完成后的输出为 Doc/build/htmlhelp/python3711.chm 。双击打开即可阅读 Python 3.7.11 文档。

python-doc

编译发行版

构建 Python 安装包的发行版需要使用 Tools/msi/buildrelease.bat 而不是 Tools/msi/build.bat

For an official release, the installer should be built with the Tools/msi/buildrelease.bat script and environment variables:

set PYTHON=<path to Python 2.7 or 3.4>
set SPHINXBUILD=<path to sphinx-build.exe>
set PATH=<path to Mercurial (hg.exe)>;
         <path to HTML Help Compiler (hhc.exe);%PATH%

buildrelease.bat [-x86] [-x64] [-D] [-B]
    [-o <output directory>] [-c <certificate name>]

因此需要先安装 Mercurial ,根据我的尝试,对版本应该没有特殊要求,这里选择最新版 5.8

$ pip install -U mercurial
# 或
$ pip install "mercurial==5.8"

根据 Tools/msi/buildrelease.bat 帮助说明 ,并结合 Tools/msi/README.txt ,构建 32 位 Python 安装包 所使用的命令为:

$ C:\Users\Lussac\Desktop\env.bat

$ cd Tools\msi

$ buildrelease.bat -x86 --skip-nuget -o C:\Users\Lussac\Desktop\outputs
# 或
$ buildrelease.bat -x86 -D -B --skip-nuget -o C:\Users\Lussac\Desktop\outputs

其中各个参数的意义为:

  • -x86 : 构建 win32 版本的安装包。替换为 -x64 可构建 amd64 版本的安装包;省略此参数则构建两个版本的安装包。
  • -D : 跳过文档的重新构建。因为上一步已经构建完成了。但若文档不存在会导致构建失败。
  • -B : 跳过 CPython 的重新构建。当你不是第一次执行 buildrelease.bat 时可以添加此参数。
  • -o : 指定输出目录。便于找到编译产物。
  • --skip-nuget : 跳过 Nuget 包的构建。因为这一步经常卡住,且其对最后生成的 Python 安装包没有影响。

编译需要一段时间,若能正常编译,则完成后的输出位于 PCbuild/{win32|amd64}/en-us 和 由 -o 参数指定的目录下。其中包含 python-3.7.11.exe , python-3.7.11-embed-win32.zip , python-3.7.11-webinstall.exe 等。双击打开 python-3.7.11.exe 即可正常执行 Python 3.7.11 的安装流程。

python-installer-exe

若想使自己编译的 python-3.x.x-webinstall.exe 能够正常使用,则需要指定 --download 参数并自行使用服务器提供其他 .msi 文件下载。总之不建议使用。

启用 PGO 优化

简洁起见,下文将刻意省略 -o 参数,记得自行指定。

构建 64 位 Python 安装包 所使用的命令为:

buildrelease.bat -x64 --skip-pgo --skip-nuget

其中 --skip-pgo 参数为 "构建 64 位安装包时不使用 PGO" 。

若需启用 PGO 优化,经过测试,须先将 pgort140.dll 复制到 C:\Windows\System32 目录下。

pgort140.dll 可以在 VS2017 的安装目录下找到。

以本文采用的默认安装路径为例,其位于 C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\bin\Hostx64\x64\pgort140.dll ,其中 Community , 14.16.27023 , Hostx64 等取决于你所安装的 Visual Studio 版本。其大小约为 55 KB 。

对应的命令为:

buildrelease.bat -x64 --pgo -V --skip-nuget

注意上述命令中的 -V 不是传递给 buildrelease.bat 的参数,而是为 --pgo 参数设置的值,可以看作 --pgo="-V"
其中 --pgo 参数为 "The job to use for PGO training" ,其值将传递给 PCbuild/build.bat--pgo-job 参数 [11] [12] 。其默认值为 -m test --pgo ,相当于 CPython-x64 编译完成后执行 python -m test --pgo 的测试任务。
这会导致构建时间极大地延长,或卡在某一 test 阶段。因此不妨将其值设置为 -V,即简单地打印版本号信息 ( python -V ) 后即可继续编译 msi 文件。

若你不想跳过上述的测试阶段,则可以不使用 --pgo 参数以保持默认值:

buildrelease.bat -x64 --skip-nuget

至此,编译完成。


其他

无法打开输入文件 "libffi-7.lib"

cpython-bin-deps 于 2021-08-27 发布了 libffi-3.4.2 ,其中 libffi-X.dll 的文件名由 libffi-7.lib 改为了 libffi-8.lib 。而 PCbuild/get_external.bat 中的 libffi 并未指定版本号 ,即始终下载最新版。于是自 2021-08-27 开始,编译 Cpython 3.7.12 / 3.8.12 等版本时将出现以下错误:

  winsound.vcxproj -> C:\Users\Lussac\Desktop\Python-3.8.12\Tools\msi\..\..\PCbuild\\win32\winsound.pyd
  _ctypes.c
  callbacks.c
  callproc.c
  cfield.c
  malloc_closure.c
  stgdict.c
LINK : fatal error LNK1181: 无法打开输入文件“libffi-7.lib” [C:\Users\Lussac\Desktop\Python-3.8.12\PCbuild\_ctypes.vcxproj]

生成失败。

解决方法:

:: 修改 `PCbuild\get_external.bat` ,为 libffi 指定版本号
if NOT "%IncludeLibffi%"=="false"  set binaries=%binaries% libffi-3.3.0

:: 删除 `externals\libffi\` 文件夹并重新下载
$ PCbuild\get_externals.bat

:: 还原 `PCbuild\get_external.bat` ,或丢弃所有修改
$ git checkout .

:: 重命名 `externals\libffi-3.3.0\` 文件夹为 `externals\libffi\`
$ ren externals\libffi-3.3.0 libffi

:: 尝试重新编译 Cpython
$ PCbuild\build.bat

或者你可以选择更新 VS 项目的构建配置文件,以指定 libffi 版本为 8 。但我暂未尝试,如果你感兴趣,可以参照以下提交来修改:

在 Windows 10 上编译

在 Windows 10 上可以正常地编译 CPython 。但在编译 MSI 安装包前,需要通过 "控制面板 -> 程序 -> 启用或关闭 Windows 功能" 来启用 .NET Framework 3.5 ,以使 Wix 能正常运行 [13]

基于相同原因,你可以在 Windows 10 Sandbox 中编译 CPython ,但无法编译 MSI 安装包。因为你无法在 Windows Sandbox 中通过控制面板来启用 .NET Framework 3.5(至少我暂未找到方法) [14]

通过 Build Tools for Visual Studio 编译

你甚至可以抛弃 Visual Studio IDE ,使用 Build Tools for Visual Studio 2017 进行编译。

下载 vs_BuildTools.exe 并运行。选择 工作负载 中的 "Visual C++ 生成工具 " ,并保持其默认的三个可选子项。安装后即可正常地编译 CPython 。

buildtools-install

若需编译 MSI 安装包,则还须额外勾选子项 "适用于桌面的 VC++ 2015.3 v14.00 (v140) 工具集 "。

否则你将遇到错误:error MSB8020: 无法找到 v140 的生成工具(平台工具集 =“v140”)

buildtools-msi-errors


下载

这里提供按以上方法自行编译的 Python 安装包的 下载 (访问密码为 lussac ) 。请勿在生产环境中使用。

  • 编译环境:
    • Visual Studio Professional 2017 (version 15.9.36) (MSVC 14.16)
    • Windows 7, Service Pack 1, 7601, 64-bit (虚拟机)
  • 编译时 实际使用的命令 为:

    # 使用 git 获取源码(以在 Python REPL 中正确地显示 tag 和 commit-hash)
    $ git clone -b v3.7.11 --depth 1 https://github.com/python/cpython.git Python-3.7.11
    
    # 编译时跳过 PGO 测试和 Nuget 包的构建。
    $ Tools\msi\buildrelease.bat --pgo -V --skip-nuget -o C:\Users\Lussac\Desktop\outputs > C:\Users\Lussac\Desktop\build.log 2>&1

在宿主机的 Windows 沙盒中重新安装 python-3.7.11-amd64.exe 并执行测试:

$ python -m test
...
== Tests result: FAILURE ==

367 tests OK.

6 tests failed:
    test__locale test_distutils test_locale test_os test_webbrowser
    test_winconsoleio

43 tests skipped:
    test_asdl_parser test_clinic test_crypt test_curses test_dbm_gnu
    test_dbm_ndbm test_devpoll test_epoll test_fcntl test_fork1
    test_gdb test_grp test_ioctl test_kqueue test_multiprocessing_fork
    test_multiprocessing_forkserver test_nis test_openpty
    test_ossaudiodev test_pipes test_poll test_posix test_pty test_pwd
    test_readline test_resource test_smtpnet test_socketserver
    test_spwd test_syslog test_threadsignals test_timeout test_tix
    test_tk test_ttk_guionly test_urllib2net test_urllibnet test_wait3
    test_wait4 test_winsound test_xmlrpc_net test_xxtestfuzz
    test_zipfile64

Total duration: 25 min 28 sec
Tests result: FAILURE

再对比沙盒中 自编译版 Python 3.7.11 和宿主机中 官方版 Python 3.7.9 的测试结果:

python -m test --pgo -q >log.txt 2>&1
  • 两者都未通过的:
    • test_locale
    • test_ssl
    • test_winconsoleio
  • 自编译版通过而官方版未通过的;
    • test_strptime
    • test_time
    • test_uuid
  • 官方版通过而自编译版未通过的:

参考资料

  1. Python 3.7.11 documentation
  2. Compile CPython on Windows — Python Development 1.0 documentation
  3. Build CPython on Windows — Tutorial to contribute to the CPython project
  4. Getting Started - Python Developer's Guide
  5. GitHub - python/cpython
  6. Python3.7 源码在 windows(VS2015)下的编译和安装 - CSDN
  7. What does --enable-optimizations do while compiling python? - Stack Overflow
  8. Visual Studio 的离线安装 :
  9. pgort140.dll not found - Visual Studio Feedback
  10. cannot open file 'python37.lib' :
  11. 彻底解决 error: Unable to find vcvarsall.bat - CSDN
  12. How to build embeddable Python - Stack Overflow