【python打包】Pyinstaller-完整指南
PyInstaller 打包指南
0.基本信息
本文是PyInstaller 文档 的非官方简体中文翻译,基于 v6.3.0。
翻译声明
- **在 **muzing 翻译的基础(此仓库)上,少量适当修改,感谢原译者的工作,受益颇多。
- 本翻译仅供参考,由于双语间固有表达方式差异、专业术语习惯性翻译差异以及译者水平有限,不保证内容绝对正确。
- 原作者:David Cortesi, based on structure by Giovanni Bajo & William Caban, based on Gordon McMillan’s manual
- 版权信息:This document has been placed in the public domain.
翻译信息
- **翻译自: **PyInstaller 文档 v6.3.0 - PyInstaller Manual
- 原译者:muzing <muzi2001@foxmail.com>
- 文档源码仓库:https://github.com/muziing/pyinstaller-docs-zh-cn
译注
目前中文社区习惯性将 PyInstaller「整合代码、解释器、依赖项等构建可执行文件」这一过程翻译为「打包」。但其实英文官方文档中称之为捆绑(bundle)或冻结(freeze)。至于构建产物,英文文档中将其称为捆绑包或冻结应用程序。而「包」(package),在 Python 中更多时候是专指具有 path 属性的 Python 模块。综上所述,在本套 PyInstaller 文档译文中,暂统一译做「捆绑」与「捆绑包」。
1.PyInstaller 简介
键 | 值 |
---|---|
版本 | PyInstaller 6.3.0 |
主页 | https://pyinstaller.org/ |
联系 | pyinstaller@googlegroups.com |
作者 | David Cortesi, based on structure by Giovanni Bajo & William Caban, based on Gordon McMillan’s manual |
版权 | 本文档已置于公共域。 |
PyInstaller 将 Python 应用程序及其所有依赖项捆绑到单个软件包中。用户无需安装 Python 解释器或任何模块,即可运行打包后的应用程序。PyInstaller 支持 Python 3.8 及其更新版本,并能正确捆绑 numpy、matplotlib、PyQt、wxPython 等许多主流 Python 包。
PyInstaller 已经过 Windows、MacOS X 和 Linux 测试。不过,它并不是一个交叉编译器;要制作 Windows 应用程序就需要在 Windows 上运行 PyInstaller,要制作 Linux 应用程序就需要在 Linux 上运行它,依此类推。PyInstaller 已经成功地在 AIX、Solaris、FreeBSD 和 OpenBSD 上使用,但针对这些平台的测试并不是我们持续集成测试的一部分,开发团队也不能保证(这些平台的所有代码都来自外部贡献)PyInstaller 将能够在这些平台上运行,或得到持续支持。
快速入门
确保你的系统符合要求,然后从 PyPI 安装 PyInstaller:
pip install -U pyinstaller
打开命令提示符/shell 窗口,导航到你的 .py 文件所在目录,然后用如下命令创建应用程序:
pyinstaller your_program.py
现在应该可以在 dist 文件夹中找到你的捆绑应用程序了。
目录
PyInstaller 打包指南0.基本信息翻译声明翻译信息译注1.PyInstaller 简介快速入门目录2.系统要求WindowsmacOSGNU/LinuxAIX、Solaris、FreeBSD 与 OpenBSD3.许可证4.贡献指南你可以提供的一些帮助5.如何安装 PyInstaller从源码压缩包中安装验证安装已安装的命令6.PyInstaller 的功能和原理分析:查找项目所需的文件捆绑至单个文件夹单文件夹程序如何工作捆绑至单个文件单文件程序如何工作使用控制台窗口隐藏源代码7.使用 PyInstaller命令选项位置参数选项生成什么捆绑什么,在何处搜索如何生成Windows 与 macOS 专用选项Windows 专用选项macOS 专用选项罕用的特殊选项缩短命令从 Python 代码中运行 PyInstaller使用 UPX从 UPX 处理中排除有问题的文件闪屏(实验性功能)pyi_splash 模块定义提取位置多平台支持支持多种 Python 环境支持多种操作系统捕获 Windows 版本数据构建 macOS 应用程序捆绑包特定平台注意事项GNU/Linux让 GNU/Linux 应用程序向前兼容WindowsmacOS让 macOS 应用程序向前兼容在 macOS 上构建 32-bit 应用程序获取已打开的文档名称AIX8.运行时信息使用 __file__
将数据文件放在捆绑包内的预期位置使用 sys.executable
与 sys.argv[0]
对 LD_LIBRARY_PATH / LIBPATH 的考量9.使用 Spec 文件Spec 文件选项向软件包添加文件添加数据文件使用模块中的数据文件添加二进制文件添加文件的高级方法指定 Python 解释器选项用于 macOS 捆绑的 spec 文件选项POSIX 专有选项Splash
目标多软件包捆绑单文件夹应用程序中的多软件包捆绑单文件应用程序中的多软件包捆绑示例 MERGE spec 文件Spec 文件中可用的全局变量在 spec 文件中添加参数10.特定功能注意事项11.当发生错误时针对具体问题的配方和示例找出问题所在构建时信息构建时依赖图构建时 Python 错误获取调试信息获取 Python 的详尽导入信息找出 GUI 应用程序无法启动的原因Operation not permitted 错误帮助 PyInstaller 查找模块扩展路径列出隐式导入扩展一个包的 __path__
改变运行时行为获取最新版本向他人寻求帮助12.进阶话题Bootstrap 流程详解Bootloader捆绑应用程序中的 Python 导入闪屏启动pyi_splash
模块(详细)函数目录(TOC)和 Tree 类目录(TOC)列表Tree 类检视归档ZlibArchiveCArchive使用 pyi-archive_viewer检视可执行文件创建可重复的构建13.钩子配置选项14.构建 Bootloader15.pyi-makespec 命令16.更新日志6.11.1 (2024-11-10)更新日志6.10.0 (2024-08-10)更新日志6.9.0 (2024-07-06)更新日志6.8.0 (2024-06-08)更新日志6.7.0 (2024-05-21)更新日志6.6.0 (2024-04-13)更新日志6.5.0 (2024-03-09) 更新日志6.4.0 (2024-02-10) 更新日志
2.系统要求
Windows
PyInstaller 可在 Windows 8 及更新版本中运行。它可以创建图形窗口应用程序(不需要命令窗口的应用程序)。
macOS
**PyInstaller 可在 macOS 10.15 (Catalina) 或更新版本上运行。它可以构建图形窗口应用程序(不需要使用终端窗口的应用程序)。PyInstaller 构建的应用程序与运行它的 macOS 版本及后续版本兼容。它可以在任一架构的 macOS 机器上构建 **x86_64
、arm64
或混合 universal2 二进制文件。详情参阅 macOS multi-arch support。
GNU/Linux
**PyInstaller 需要 **ldd
终端应用程序来发现每个程序或共享库所需的共享库。它通常存在于发行包 glibc
或 libc-bin
中。
**它还需要 **objdump
终端应用程序从对象文件中提取信息,以及 objcopy
终端应用程序向 bootloader 添加数据。这些通常可以在发行包 binutils
中找到。
AIX、Solaris、FreeBSD 与 OpenBSD
**有用户报告称在这些平台上成功运行了 PyInstaller,但官方没有在这些平台上进行测试。需要可以使用 **ldd
和 objdump
命令。
**每个捆绑应用程序都包含一个 bootloader 的副本,这是一个用于设置应用程序并启动它的程序(参阅 **Bootstrap 流程详解)。
**使用 **pip 安装 PyInstaller 时,安装程序会尝试为该平台构建 bootloader。如果成功,安装将继续,PyInstaller 就可以使用了。
**如果 pip 安装失败或没有使用 pip 安装,则必须手动编译 bootloader。具体过程参阅 **构建 Bootloader。
3.许可证
**翻译自 **PyInstaller 文档 v6.3.0 - License
**PyInstaller 基于双重许可方式发布,既使用 GPL 2.0 许可,有一种允许用它来构建商业产品的例外——如下所示——又对一些特定文件使用 Apache 2.0 许可。要查看 Apache 许可证适用于哪些文件、GPL 许可证适用于哪些文件,参阅 PyInstaller 源代码库根目录下的 **COPYING.txt 文件。
GPL 许可证例外情况简述:
- 你可以使用 PyInstaller 来从源代码捆绑商业应用程序。
- 从你的源代码使用 PyInstaller 生成的可执行捆绑包可以使用你想要的任何许可证,只要它符合你依赖项的许可证要求即可。
- 你可以根据自己的需要修改 PyInstaller,但对 PyInstaller 源代码的修改必须遵守 GPL 许可证的条款。也就是说,如果你想发布你的修改,则必须按照 GPL 条款发布它们。
4.贡献指南
**翻译自 **PyInstaller 文档 v6.3.0 - How To Contribute
**对简体中文翻译的勘误或其他贡献,请前往 **https://github.com/muziing/pyinstaller-docs-zh-cn 并提交 issue。
非常欢迎你的贡献! PyInstaller 由一群志愿者维护。欢迎一切贡献,如社区支持、bug 汇报、bug 修复、文档改进、增强功能和想法。
PyInstaller 是一个由志愿者创建和维护的自由软件项目。它的生死取决于其他人对它的支持,考虑为 PyInstaller 做出贡献是非常慷慨的。
由于目前所有的核心开发者都是在业余时间开发 PyInstaller,因此如果你能遵守一些简单的准则,就能为我们(和项目)提供最大的帮助。你的贡献质量越高,我们纳入它们的工作量就越少,就能越早地纳入 :-)
如果你在任何时候遇到问题,可以在 GitHub 上创建一条新的 issue。
关于我们的开发流程和方法的更多信息,参阅开发指南。
你可以提供的一些帮助
你可以提供的一些帮助:
- 回答支持单:通常情况下,只需告诉用户该去文档手册的哪一节找到对应的信息。
- 分流开放的 issue,这意味着:阅读报告;要求 issue 请求者提供缺失的信息和尝试使用最新的开发版;确保有最小的示例;确保 issue 请求者遵守了当发生错误时中的所有步骤。如果你能复现问题并追踪 bug,将对核心开发者有巨大的帮助。
- 帮助改进文档: 有一个文档 issues 列表,可以从中挑选一个。请为你的修改提供一个 pull-request。 详细内容 »»
- 选择一个需要 pull-request 的 issue 并提供一个 pull-request。
- 审核 pull requests:提交信息是否符合提交信息指南;所有新文件是否都有版权标题(尤其是钩子文件经常缺少);代码是否正确等。
- 扫描尚未解决的 issue 列表并挑选一些任务 :-)
非常感谢!
如果你计划经常提交贡献,只需申请主 git 仓库的写入权限即可。我们非常欢迎你加入团队!
5.如何安装 PyInstaller
**PyInstaller 可作为普通 Python 软件包获取。已发布版本的源代码压缩包可从 **PyPi 获取,但使用 pip 安装最新版本更为方便:
pip install pyinstaller
要将已有的 PyInstaller 安装升级到最新版本,使用:
pip install --upgrade pyinstaller
要安装当前的开发版本,使用:
pip install https://github.com/pyinstaller/pyinstaller/tarball/develop
要直接使用 pip 内置的 git 签出支持进行安装,使用:
pip install git+https://github.com/pyinstaller/pyinstaller
或安装特定分支(比如,develop
):
pip install git+https://github.com/pyinstaller/pyinstaller@develop
从源码压缩包中安装
**PyInstaller 已发布版本的源代码压缩包可在 **PyPI 和 PyInstaller Downloads 页面获取。
Note
**尽管源代码压缩包提供了 **
setup.py
脚本,但通过python setup.py install
进行安装已被弃用,不应再使用。取而代之的是在解压后的源代码目录下运行pip install .
,如下所述。
安装步骤如下:
- 解压源码压缩包。
- 进入解压后的源码目录。
- **在解压后的源代码目录运行 **
pip install .
。如果要安装到系统级的 Python 中,则需要管理员权限。
同样的步骤也适用于从手动 git 签出安装:
git clone https://github.com/pyinstaller/pyinstaller
cd pyinstaller
pip install .
如果你打算对源代码进行修改,并且希望修改立刻生效而无需每次都重新安装软件包,则可以在可编辑模式下安装:
pip install -e .
**对于除 Windows、GNU/Linux 和 macOS 之外的平台,必须首先为该平台构建 bootloader(引导加载程序):参阅 **构建 Bootloader。在构建 bootloader 之后,使用 pip install .
命令来完成安装。
验证安装
在所有平台上,pyinstaller
命令现在都应存在于可执行路径中。要验证这一点,输入命令:
pyinstaller --version
**结果应类似于发布版本的 **6.n
,以及开发分支的 6.n.dev0-xxxxxx
。
如果找不到命令,请确保执行路径(环境变量)包含正确的目录:
- **Windows: **
C:\PythonXY\Scripts
其中 XY 表示 Python 的主要和次要版本号,例如C:\Python38\Scripts
对应 Python 3.8 - **GNU/Linux: **
/usr/bin/
- **macOS (使用苹果提供的默认 Python): **
/usr/bin
- **macOS (使用通过 homebrew 安装的 Python): **
/usr/local/bin
- **macOS (使用通过 macports 安装的 Python): **
/opt/local/bin
**在 Windows 系统中显示当前 PATH 的命令是 **echo %path%
,而在其他系统中则是 echo $PATH
。
Note
**如果由于脚本目录不在 **
PATH
中而无法使用pyinstaller
命令,可以通过运行python -m PyInstaller
(注意模块名区分大小写)来调用PyInstaller
模块。当你在多个 Python 环境中安装了 PyInstaller,而不能确定将从哪个安装中运行pyinstaller
命令时,这种调用方式也很有用。
已安装的命令
安装完成后,这些命令就会进入可执行路径:
pyinstaller
是构建捆绑应用程序的主要命令。参阅使用 PyInstaller。pyi-makespec
用于创建 spec 文件。参阅使用 Spec 文件。pyi-archive_viewer
用于检查捆绑应用程序。参阅 检视归档。pyi-bindepend
用于显示可执行文件的依赖关系。参阅 检视可执行文件。pyi-grab_version
用于从 Windows 可执行文件中提取版本资源。参阅捕获 Windows 版本数据。pyi-set_version
可用于将先前提取的版本资源应用于现有的 Windows 可执行文件。
6.PyInstaller 的功能和原理
**翻译自 **PyInstaller 文档 v6.3.0 - What PyInstaller Does and How It Does It
本节介绍 PyInstaller 的基本思想。这些思想适用于所有平台。选项和特殊情况将在使用PyInstaller 中介绍。
PyInstaller 读取你编写的 Python 脚本。它分析你的代码,找到脚本执行所需的其他模块和库。然后搜集所有这些文件的副本——包括活动的 Python 解释器!——并把它们与你的脚本放在一个文件夹中,或可选地放在一个可执行文件中。
对于绝大多数程序,只需一条简短的命令即可完成:
pyinstaller myscript.py
或者增加一些选项,例如把窗口化应用程序作为单文件的可执行文件:
pyinstaller --onefile --windowed myscript.py
你可以将捆绑包以文件夹或文件形式分发给其他人,他们就可以执行你的程序。对你的用户来说,应用程序是独立的,他们不需要安装任何特定版本的 Python 或其他模块。实际上,他们根本不需要安装 Python。
Note
PyInstaller 的输出是针对当前操作系统和当前 Python 版本的。这意味着,要为
- 不同的操作系统
- 不同的 Python 版本
- 32位或64位的操作系统
准备发行版,需要在该操作系统的该版本 Python 下运行 PyInstaller。执行 PyInstaller 的 Python 解释器是捆绑包的一部分,它与操作系统和字的大小(word size)有关。
分析:查找项目所需的文件
你的脚本还需要哪些模块和库才能运行?(这些有时被称为 "依赖项")。
**PyInstaller 会查找脚本中的所有 **import
语句来查明。它找到已导入的模块,并在其中查找 import
语句,如此递归,直到获得脚本可能使用的模块的完整列表。
PyInstaller 能够理解 Python 软件包常用的 "egg" 发布格式。如果你的脚本从 "egg" 中导入了一个模块,PyInstaller 就会将该模块及其依赖项添加到需求文件集中。
**PyInstaller 还了解许多主流 Python 包,包括 GUI 包 **Qt(通过 PyQt 或 PySide 导入)、WxPython、TkInter、matplotlib,以及其他主要软件包。如需完整列表,参阅 Supported Packages。
**有些 Python 脚本导入模块的方式是 PyInstaller 无法检测到的:例如,使用带有变量数据的 **__import__
函数、使用 importlib.import_module
,或在运行时操作 sys.path
值。如果你的脚本需要 PyInstaller 不知道的文件,那么你必须提供额外帮助:
- **你可以在 **
pyinstaller
命令行中添加额外文件。 - 你可以在命令行中提供额外导入路径。
- **你可以编辑 **
myscript.spec
文件,PyInstaller 会在第一次为脚本运行时写入该文件。在 spec 文件中,你可以告知 PyInstaller 你的脚本所特有的代码模块。 - 你可以编写 "hook" 钩子文件,告知 PyInstaller 隐式导入信息。如果你为一个软件包创建了一个 "hook" 钩子,而其他用户可能也会使用这个软件包,那么可以将你的钩子文件贡献给 PyInstaller。
如果你的程序依赖于对某些数据文件的访问,你可以要求 PyInstaller 将它们也包含在捆绑包中。可以通过修改 spec 文件来实现这一点,这是使用 Spec 文件中涉及的一个高级主题。
为了在运行时找到包含的文件,无论是否从捆绑包中运行,程序都需要能够在运行时了解其路径。这将在运行时信息 中介绍。
**PyInstaller **不会包含当前操作系统的所有安装中都应该存在的库。例如在 GNU/Linux 中,它不会捆绑 /lib
或 /usr/lib
中的任何文件,因为它认为这些文件在每个系统中都能找到。
捆绑至单个文件夹
**对 **myscript.py
使用 PyInstaller 时,默认结果是一个名为 myscript
的文件夹。该文件夹包含脚本的所有依赖项,以及一个同样名为 myscript
的可执行文件(Windows 中为 myscript.exe
)。
**你可以把文件夹压缩成 **myscript.zip
并发送给用户。用户只需解压缩即可安装程序,打开文件夹,启动其中的 myscript
可执行文件,即可运行应用程序。
使用单文件夹模式时,很容易调试构建应用程序时出现的问题。你可以看到 PyInstaller 究竟收集了哪些文件到文件夹中。
单文件夹捆绑的另一个优点是,当你修改你的代码时,只要导入的依赖集完全相同,就可以只发送更新后的 myscript
可执行文件。这通常比整个文件夹要小得多。(如果你修改了脚本,而且导入了更多或不同的依赖项,或依赖项版本升级了,你就必须重新发布整个捆绑包。)
单文件夹程序如何工作
**捆绑程序总是在 PyInstaller bootloader(引导加载程序)中开始执行。这是文件夹中 **myscript
可执行文件的核心。
**PyInstaller bootloader 是当前活动平台(Windows、GNU/Linux、macOS 等)下的二进制可执行程序。当用户启动你的程序时,其实就是在运行 bootloader。引导程序会创建一个临时 Python 环境,该 Python 解释器能在 **myscript
文件夹中找到所有导入的模块和库。
Bootloader 会启动一个 Python 解释器的拷贝来执行你的脚本。只要包含了所有必须的支持文件,一切都会正常进行。
**(这只是一个概述。更多详情,参阅 **Bootstrap 流程详解。)
捆绑至单个文件
**PyInstaller 能够把你的脚本及其依赖项都捆绑至一个名为 **myscript
(Windows 下为 myscript.exe
)的可执行文件中。
这样做的好处是,用户可以得到简单易理解的东西——只需启动一个可执行文件。缺点是所有相关文件(比如 README)都必须分别发布。此外,单个可执行文件的启动速度也比单文件夹捆绑稍慢一些。
在尝试捆绑至单个文件之前,确保你的应用程序在捆绑至单个文件夹时工作正常。在单文件夹模式下,诊断问题要容易得多。
单文件程序如何工作
**Bootloader 也是单文件捆绑包的核心。启动时,它会在该操作系统的临时文件夹位置创建一个名为 **_MEIxxxxxx
的临时文件夹。其中 xxxxxx 是一串随机数。
**单可执行文件中包含了脚本使用的所有 Python 模块的嵌入式归档,以及所有非 Python 支持文件(比如 **.so
文件)的压缩副本。Bootloader 会解压缩支持文件并将其副本写入临时文件夹。这个过程需要一点时间,这也正是单文件应用程序比单文件夹应用程序启动速度稍慢的原因。
Note
**PyInstaller 目前不会保留文件属性。参阅 **#3926。
创建临时文件夹后,bootloader 将在临时文件夹中继续执行与单文件夹捆绑完全相同的程序。当捆绑代码运行结束时,bootloader 会删除临时文件夹。
**(在 GNU/Linux 和相关系统中,挂载 **/tmp
目录时可以选择 "no-execution" 选项。该选项与 PyInstaller 的单文件捆绑不兼容,因为后者需要在 /tmp
中执行代码。如果你了解目标环境,或可以使用 --runtime-tmpdir
作为一种变通办法。)
由于程序会创建一个具有唯一名称的临时文件夹,因此你可以运行多个程序副本而不必担心相互干扰。不过,运行多个副本会消耗大量磁盘空间,因为没有任何东西是共享的。
如果程序崩溃或被杀死(在 Unix 上 kill -9、在 Windows 上被任务管理器杀死、在 macOS 上 "强制退出"),_MEIxxxxxx
文件夹不会被删除。因此,如果你的应用程序经常崩溃,你的用户就会因为多个 _MEIxxxxxx
临时文件夹而耗费磁盘空间。
**使用 **--runtime-tmpdir
命令行选项可以控制 _MEIxxxxxx
文件夹的位置。指定的路径将存储在可执行文件中,bootloader 将在指定位置创建 _MEIxxxxxx
文件夹。详情请参阅定义提取位置。
Note
不要给 Windows 上的单文件可执行文件赋予管理员权限("以管理员身份运行此程序")。恶意攻击者可能在 bootloader 准备临时文件夹中的一个共享库时破坏它。虽然这种可能性不大,但也并非绝无可能。一般来说,在分发特权程序时,应通过文件权限机制来防止共享库或可执行文件被篡改。否则,拥有这些文件写入权限的未提权进程可能会通过修改这些文件来提升自身权限。
Note
**使用 **
os.setuid()
的应用程序可能会遇到权限错误。调用setuid
后,运行捆绑应用程序的临时文件夹可能会无法读取。如果你的脚本需要调用setuid
,最好使用单文件夹模式,以便更好地控制其文件的权限。
使用控制台窗口
**默认情况下,bootloader 会创建一个命令行控制台(GNU/Linux 和 macOS 中的终端窗口,Windows 中的命令窗口)。它将此窗口提供给 Python 解释器用于标准输入和输出。你的脚本中使用的 **print()
和 input()
都会指向这里。Python 的错误信息和默认日志输出也会显示在控制台窗口中。
Windows 和 macOS 下的一个选项是告知 PyInstaller 不提供控制台窗口。bootloader 启动 Python 时不会为标准输出或输入提供目标。当你的脚本有图形界面供用户输入,并能正确报告自己的诊断结果时,可以使用此选项。
如 Python 教程附录中的可执行的Python脚本所述,对于 Windows 操作系统,扩展名为 .pyw 的文件会隐藏通常出现的控制台窗口。类似地,在 PyInstaller 中使用 myscript.pyw
脚本时,也不会出现控制台窗口。
隐藏源代码
捆绑应用程序不包含任何源代码。不过,PyInstaller 捆绑了编译后的 Python 脚本(.pyc
文件)。这些文件原则上可以反编译,显示代码的逻辑。
**如果想更彻底地隐藏源代码,一个可行的办法是用 **Cython 编译某些模块。使用 Cython,可以将 Python 模块转换为 C 语言,然后将 C 语言编译为机器语言。PyInstaller 可以跟踪引用 Cython C 对象模块的导入语句,并捆绑它们。
7.使用 PyInstaller
pyinstaller
命令的语法为:
pyinstaller
[options]** script [script ...] | **specfile
**在最简单的情况下,将当前目录设置为你的程序 **myscript.py
的位置,然后执行:
pyinstaller myscript.py
**PyInstaller 分析 **myscript.py
并:
- **在与脚本相同的文件夹中写入 **
myscript.spec
。 - **如果 **
build
文件夹不存在,则在与脚本相同的文件夹中创建该文件夹。 - **在 **
build
文件夹中写入一些日志文件和工作文件。 - **如果不存在 **
dist
文件夹,则在与脚本相同的文件夹中创建该文件夹。 - **在 **
dist
文件夹中写入myscript
可执行文件夹。
**在 **dist
文件夹中,你可以找到向用户分发的捆绑应用程序。
通常情况下,你只需在命令行中指定一个脚本。如果指定了更多脚本,则所有脚本都会被分析并包含在输出中。不过,指定的第一个脚本将提供 spec 文件和可执行文件夹或文件的名称。它的代码将在运行时首先执行。
**对于特定使用,你可以编辑 **myscript.spec
的内容(参见使用 Spec 文件)。这样做之后,你可以向 PyInstaller 指定 spec 文件替代脚本:
pyinstaller myscript.spec
myscript.spec
文件包含以脚本文件为参数运行 pyinstaller
(或 pyi-makespec
)时指定的选项所提供的大部分信息。使用 spec 文件运行 pyinstaller
时,通常不需要指定任何选项。从 spec 文件构建时,只有少数命令行选项会产生影响。
你可以给出脚本或 spec 文件的路径,例如
pyinstaller
options...~/myproject/source/myscript.py
或者,在 Windows 上,
pyinstaller "C:\Documents and Settings\project\myscript.spec"
命令选项
pyinstaller
命令选项的完整列表如下:
位置参数
scriptname
** 要处理的脚本文件名或一个 **.spec
文件名。如果指定了 .spec
文件,则大部分选项都是非必须的,会被忽略。
选项
-h, --help
** 显示此帮助信息并退出。**
-v, --version
** 显示程序版本信息并退出。**
--distpath DIR
** 捆绑应用程序的放置位置(默认值:**./dist
)。
--workpath WORKPATH
** 放置所有临时工作文件、**.log
、.pyz
等的位置(默认值:./build
)。
-y, --noconfirm
** 覆盖输出目录中的原有内容(默认值:**SPECPATH/dist/SPECNAME
),不请求确认。
--upx-dir UPX_DIR
** UPX 组件的位置(默认值:搜索可执行文件路径,即环境变量中的 **PATH
)。
--clean
** 在构建之前,清理 PyInstaller 缓存并删除临时文件。**
--log-level LEVEL
** 编译时控制台信息的详细程度。LEVEL 可以是 **TRACE
、DEBUG
、INFO
、WARN
、DEPRECATION
、ERROR
、FATAL
之一(默认值:INFO
)。也可以通过 PYI_LOG_LEVEL
环境变量进行覆盖设置。
生成什么
-D, --onedir
** 创建包含一个可执行文件的单文件夹捆绑包(默认值)。**
-F, --onefile
** 创建单文件捆绑的可执行文件。**
--specpath DIR
** 存储生成的 spec 文件的文件夹(默认值:当前目录)。**
-n NAME, --name NAME
** 为捆绑的应用程序和 spec 文件指定的名称(默认值:第一个脚本的名称)。**
--contents-directory CONTENTS_DIRECTORY
** 仅适用于单文件夹构建。指定存放所有支持文件(即除可执行文件本身外的所有文件)的目录名称。使用 **.
来重新启用旧的 onedir 布局,但不包含内容目录。
捆绑什么,在何处搜索
--add-data SOURCE:DEST
** 要添加到应用程序中的附加数据文件或包含数据文件的目录。参数值的形式应为 "source:dest_dir",其中 source 是要收集的文件(或目录)的路径,dest_dir 是相对于应用程序顶层目录的目标目录,两个路径之间用冒号(**:
)分隔。要将文件放入应用程序顶层目录,使用 .
作为 dest_dir。该选项可多次使用。
--add-binary SOURCE:DEST
** 要添加到可执行文件中的其他二进制文件。格式参考 **--add-data
选项。该选项可多次使用。
-p DIR, --paths DIR
** 搜索导入的路径(如使用 PYTHONPATH)。允许使用多个路径,以 **:
分隔,或多次使用该选项。相当于在 spec 文件中提供 pathex 参数。
--hidden-import MODULENAME, --hiddenimport MODULENAME
** 指明脚本中不可见的导入关系。该选项可多次使用。**
--collect-submodules MODULENAME
** 收集指定软件包或模块的所有子模块。该选项可多次使用。**
--collect-data MODULENAME, --collect-datas MODULENAME
** 收集指定软件包或模块的所有数据。该选项可多次使用。**
--collect-binaries MODULENAME
** 收集指定软件包或模块的所有二进制文件。该选项可多次使用。**
--collect-all MODULENAME
** 收集指定软件包或模块的所有子模块、数据文件和二进制文件。该选项可多次使用。**
--copy-metadata PACKAGENAME
** 复制指定包的元数据。该选项可多次使用。**
--recursive-copy-metadata PACKAGENAME
** 复制指定包及其所有依赖项的元数据。该选项可多次使用。**
--additional-hooks-dir HOOKSPATH
** 用于搜索钩子的附加路径。该选项可多次使用。**
--runtime-hook RUNTIME_HOOKS
** 自定义运行时钩子文件的路径。运行时钩子是与可执行文件捆绑在一起的代码,在其他代码或模块之前执行,以设置运行时环境的特殊功能。该选项可多次使用。**
--exclude-module EXCLUDES
** 将被忽略(就像没有被找到一样)的可选模块或软件包(Python 名称,不是路径名)。该选项可多次使用。**
--splash IMAGE_FILE
** (实验性功能)为应用程序添加一个带有 IMAGE_FILE 图像的闪屏。闪屏可以在解压缩时显示进度更新。**
如何生成
-d {all,imports,bootloader,noarchive}, --debug {all,imports,bootloader,noarchive}
** 辅助调试冻结应用程序。该参数可以提供多次以选择下面多个选项。- **all
:以下所有三个选项。- imports
:向底层 Python 解释器指定 -v
选项,使其在每次初始化模块时打印一条信息,显示模块的加载位置(文件名或内置模块)。参考命令行选项 -v。- bootloader
:告知 bootloader 在初始化和启动捆绑应用程序时发布进度消息,用于诊断导入丢失的问题。- noarchive
:不将所有冻结的 Python 源文件作为压缩归档存储在生成的可执行文件中,而是将它们作为文件存储在生成的输出目录中。
--python-option PYTHON_OPTION
** 指定运行时传递给 Python 解释器的命令行选项。目前支持 “v”(相当于 “–debug imports”)、“u”、“W **<**warning control>”、“X **<xoption>” 与 “hash_seed=<value>”。详情参阅指定 Python 解释器选项。
-s, --strip
** 对可执行文件和共享库应用符号条带表(symbol-table strip)。不建议在 Windows 环境下使用。**
--noupx
** 即使有可用的 UPX 也不要使用。在 Windows 和 ***nix 下效果有所不同。
--upx-exclude FILE
** 防止二进制文件在使用 upx 时被压缩。如果 upx 在压缩过程中破坏了某些二进制文件,通常可以使用此功能。FILE 是二进制文件不含路径的文件名。该选项可多次使用。**
Windows 与 macOS 专用选项
-c, --console, --nowindowed
** 为标准 i/o 打开一个控制台窗口(默认选项)。在 Windows 中,如果第一个脚本是 ‘.pyw’ 文件,则此选项无效。**
-w, --windowed, --noconsole
** 不提供用于标准 i/o 的控制台窗口。在 macOS 上,这也会触发构建一个 .app 捆绑程序。在 Windows 系统中,如果第一个脚本是 ‘.pyw’ 文件,则会自动设置该选项。在 ***NIX 系统上,该选项将被忽略。
-i <FILE.ico or FILE.exe,ID or FILE.icns or Image or "NONE">, --icon <FILE.ico or FILE.exe,ID or FILE.icns or Image or "NONE">
** FILE.ico:将图标应用于 Windows 可执行文件。FILE.exe,ID:从一个 exe 文件中提取带有 ID 的图标。FILE.icns:将图标应用到 macOS 的 .app 捆绑程序中。如果输入的图像文件不是对应平台的格式(Windows 为 ico,Mac 为 icns),PyInstaller 会尝试使用 Pillow 将图标翻译成正确的格式(如果安装了 Pillow)。使用 “NONE” 不应用任何图标,从而使操作系统显示默认图标(默认值:使用 PyInstaller 的图标)。该选项可多次使用。**
--disable-windowed-traceback
** 禁用窗口(noconsole)模式下未处理异常的回溯转储,并显示禁用此功能的信息。**
Windows 专用选项
--version-file FILE
** 将 FILE 中的版本资源添加到 exe 中。**
-m <FILE or XML>, --manifest <FILE or XML>
** 将 manifest FILE 或 XML 添加到 exe 中。**
-r RESOURCE, --resource RESOURCE
** 为 Windows 可执行文件添加或更新资源。RESOURCE 包含一到四个条目:FILE[,TYPE[,NAME[,LANGUAGE]]]。FILE 可以是数据文件或 exe/dll。对于数据文件,则必须指定 TYPE 和 NAME。LANGUAGE 默认为 0,也可以指定为通配符 ***
,以更新给定 TYPE 和 NAME 的所有资源。对于 exe/dll 文件,如果省略 TYPE、NAME 和 LANGUAGE 或将其指定为通配符 *
,则 FILE 中的所有资源都将添加/更新到最终可执行文件中。该选项可多次使用。
--uac-admin
** 使用该选项可创建一个 Manifest,在应用程序启动时请求提升权限。**
--uac-uiaccess
** 使用此选项,可让提升后的应用程序与远程桌面协同工作。**
--hide-console {hide-late,minimize-late,hide-early,minimize-early}
** 在启用控制台的可执行文件中,如果程序有控制台窗口(即,不是从一个现有的控制台窗口启动的),bootloader 会自动隐藏或最小化控制台窗口。**
macOS 专用选项
--argv-emulation
** 启用 macOS 应用程序捆绑包的 argv 仿真。如果启用,初始打开文档/URL 事件将由 bootloader 处理,并将传递的文件路径或 URL 附加到 sys.argv。**
--osx-bundle-identifier BUNDLE_IDENTIFIER
** macOS **.app
捆绑标识符用于代码签名的唯一程序名称。通常的形式是以反向 DNS 记法表示的分层名称。例如:com.mycompany.department.appname。(默认值:第一个脚本的名称)
--target-architecture ARCH, --target-arch ARCH
** 目标架构。有效值:**x86_64
、arm64
、universal2
。启用冻结应用程序在 universal2 和 single-arch version 之间的切换(前提是 Python 安装支持目标架构)。如果为指定目标架构,则以当前运行的架构为目标。
--codesign-identity IDENTITY
** 代码签名身份。使用提供的身份对收集的二进制文件和生成的可执行文件进行签名。如果未提供签名标识,则会执行临时签名。**
--osx-entitlements-file FILENAME
** 在对收集的二进制文件进行代码签名时使用的权限文件。**
罕用的特殊选项
--runtime-tmpdir PATH
** 在单文件模式下提取库和支持文件的位置。如果给定此选项,bootloader 将忽略运行时操作系统定义的任何临时文件夹位置。将在此处创建 **_MEIxxxxxx
文件夹。请仅在你知道自己在做什么的情况下使用该选项。
--bootloader-ignore-signals
** 告知 bootloader 忽略信号,而不是将其转发给子进程。例如,在监督进程同时向 bootloader 和子进程发出信号(如,通过进程组)以避免向子进程发出两次信号的情况下就很有用。**
缩短命令
**由于选项众多,一条完整的 **pyinstaller
命令可能会变得非常长。在开发脚本的过程中,你会一遍又一遍地运行相同的命令。可以将该命令放在 shell 脚本或批处理文件中,使用续行符使其可读。例如,在 GNU/Linux 中:
pyinstaller --noconfirm --log-level=WARN \
--onefile --nowindow \
--add-data="README:." \
--add-data="image1.png:img" \
--add-binary="libfoo.so:lib" \
--hidden-import=secret1 \
--hidden-import=secret2 \
--upx-dir=/usr/local/share/ \
myscript.spec
或者在 Windows 中,使用鲜为人知的 BAT 文件续行符(PowerShell 的续行符则是 `):
pyinstaller --noconfirm --log-level=WARN ^
--onefile --nowindow ^
--add-data="README:." ^
--add-data="image1.png:img" ^
--add-binary="libfoo.so:lib" ^
--hidden-import=secret1 ^
--hidden-import=secret2 ^
--icon=..\MLNMFLCN.ICO ^
myscript.spec
从 Python 代码中运行 PyInstaller
**如果想从 Python 代码中运行 PyInstaller,可以使用 **PyInstaller.__main__
中定义的 run
函数。例如,下面的代码:
import PyInstaller.__main__
PyInstaller.__main__.run([
'my_script.py',
'--onefile',
'--windowed'
])
等同于:
pyinstaller my_script.py --onefile --windowed
使用 UPX
UPX 是一款用于压缩可执行文件和库的免费工具。它适用于大多数操作系统,可用压缩大量可执行文件格式。关于下载和支持的文件格式列表,请参阅 UPX 主页。
当 UPX 可用时,PyInstaller 会用它来单独压缩每个收集的二进制文件(可执行文件、共享库或 Python 扩展)以减小冻结应用程序(单文件夹捆绑的目录或单文件可执行文件)的整体大小。冻结应用程序的可执行文件本身并没有经过 UPX 压缩(不管是单文件夹模式还是单文件模式),因为其文件大小中的大部分都已经是由包含单独压缩文件的嵌入式压缩包构成。
**PyInstaller 会在标准可执行路径(由 **PATH
环境变量定义)或通过 --upx-dir
命令行选项指定的路径中查找 UPX。如果找到,就会自动使用。使用 --noupx
命令行选项可用完全禁用 UPX。
Note
**UPX 目前仅用于 Windows 系统。在其他操作系统上,即使找到 UPX,也无法处理收集到的二进制文件。现代 Linux 发行版上的共享库(比如 Python 共享库)在使用 UPX 处理时似乎会崩溃,导致应用程序捆绑包失效。在 macOS 上,UPX 目前无法处理 .dylib 共享库;此外,UPX 压缩文件无法通过 **
codesign
组件的验证检查,因此无法进行代码签名(这是苹果 M1 平台的一项要求)。
从 UPX 处理中排除有问题的文件
**使用 UPX 可能会导致已收集的共享库损坏。这种损坏的已知例子包括启用了 **Control Flow Guard (CFG) 的 Windows DLL,以及 Qt5 和 Qt6 插件。在这种情况下,可能需要使用 --upx-exclude
选项(或在 .spec 文件中使用 upx_exclude
参数)将个别文件排除在 UPX 处理之外。
在版本 4.2 中修改: PyInstaller 可检测启用 CFG 的 DLL,并自动将其排除在 UPX 处理之外。
在版本 4.3 中修改: PyInstaller 会自动将 Qt5 和 Qt6 插件排除在 UPX 处理之外。
尽管 PyInstaller 尝试自动检测并从 UPX 处理中排除一些有问题的文件,但在某些情况下,需要手动指定 UPX 排除项。例如,PySide2
软件包中的 32 位 Windows 二进制文件(Qt5 DLL 和 Python 扩展模块)据报告称会被 UPX 破坏。
在版本 5.0 中修改: 早期版本将提供的 UPX 排除项的名称与收集的二进制文件的名称进行比较(由于大小写规范化不完善,在 Windows 系统上要求提供的排除项名称必须小写)。现在的 UPX 排除模式匹配使用操作系统默认的大小写敏感性,并支持通配符(*
)。它还支持指定文件的(全部或部分)父路径。
所提供的 UPX 排除模式与所收集二进制文件的源(原始)路径相匹配,匹配从右向左进行。
**例如,要从 PySide2 包中排除 Qt5 DLL 文件,使用 **--upx-exclude "Qt*.dll"
;要从 PySide2 包中排除 Python 扩展,使用 --upx-exclude "PySide2\*.pyd"
。
闪屏(实验性功能)
Note
该功能与 macOS 不兼容。在当前的设计中,闪屏在二级线程中运行,而 macOS 上的 Tcl/Tk(或者说底层 GUI 工具包)不允许这样做。
有些应用程序可能需要在程序(bootloader)启动后立即显示闪屏(splash screen)。因为,特别在是单文件模式下,大型应用程序的提取/启动时间可能会很长,bootloader 在进行准备工作,但用户无法判断应用程序是否已经成功启动。
Bootloader 能够显示单幅(只有一幅图像的)闪屏,该闪屏在实际主提取程序启动前显示。闪屏支持非透明和硬切透明图像作为背景图像,因此也可以显示非矩形闪屏。
**该闪屏基于 **Tcl/Tk,与 Python 模块 tkinter 使用的库相同。PyInstaller 在编译时将 tcl 和 tk 的动态库捆绑到应用程序中。这些动态库在解压缩后(如果程序已被打包成单文件压缩包),会在应用程序启动时加载到 bootloader 中。由于必要的动态链接库的文件大小非常小,应用程序启动和闪屏之间几乎没有延迟。闪屏所需文件的压缩大小约为 1.5MB。
作为一种附加功能,还可以选择在闪屏上显示文本。这可以在 Python 中更改/更新。这为在 Python 程序较长的启动过程中(如等待网络响应或将大文件加载到内存中)显示闪屏提供了可能。你还可以在闪屏后面启动 GUI,只有在图形界面完全初始化后才关闭闪屏。可以选择设置文字的字体、颜色和大小。不过,因为字体没有捆绑,所以必须预先已经安装在用户系统上。如果字体不可用,则使用后备字体。
如果将闪屏配置为显示文本,它将自动(作为单文件压缩包时)显示当前正在解压缩的文件名与进度条。
pyi_splash 模块
闪屏由 Python 中的 pyi_splash
模块控制,该模块可以在运行时导入。该模块并不能通过软件包管理器安装,因为它是 PyInstaller 的一部分,在需要时会被包含进来。该模块必须在 Python 程序中导入。用法如下:
import pyi_splash
# 更新闪屏上的文字
pyi_splash.update_text("PyInstaller is a great software!")
pyi_splash.update_text("Second time's a charm!")
# 关闭闪屏。调用该函数的时间并不重要,在调用
# 该函数或 Python 程序结束之前,闪屏一直是打开的
pyi_splash.close()
**当然,导入应放在一个 **try ... except
块中,以防程序作为普通 Python 脚本被外部使用,而没有 bootloader。详细说明参见 pyi_splash 模块(详细)。
定义提取位置
在极少数情况下,当你捆绑至单个可执行文件(参见捆绑至单个文件 与单文件程序如何工作)时,你可能希望在编译时控制临时目录的位置。这可以使用 --runtime-tmpdir
选项来实现。如果给定了该选项,bootloader 将忽略运行时操作系统定义的任何临时文件夹位置。请仅在你知道自己在做什么的情况下使用该选项。
多平台支持
如果你只为一种操作系统和 Python 的组合发布应用程序,则只需像安装其他软件包一样安装 PyInstaller,并在正常开发设置中使用即可。
支持多种 Python 环境
**当你需要在一个操作系统中为不同版本的 Python 和支持库(例如,一个 Python 3.6 版本和一个 Python 3.7 版本;或一个使用 Qt4 的支持版本和一个使用 Qt5的开发版本)捆绑应用程序时,我们建议你使用 **venv。使用 venv,可以维护 Python 和已安装软件包的不同组合,并轻松地从一种组合切换到另一种组合。这些组合被称之为虚拟环境(virtual environments)。
- **使用 **venv 可以根据需要创建多个不同的开发环境,每个环境都有自己独特的 Python 和已安装软件包的组合。
- 在每个虚拟环境中安装 PyInstaller。
- 在每个虚拟环境中使用 PyInstaller 来构建你的应用程序。
**注意,当使用 **venv 时,PyInstaller 命令的路径为:
- Windows: ENV_ROOT\Scripts
- 其他平台:ENV_ROOT/bin
或者也可以通过激活虚拟环境来简化命令路径。
**参阅 **PEP 405 和关于虚拟环境和包的 Python 官方教程来了解更多信息。
支持多种操作系统
如果你需要在多个操作系统(例如 Windows 和 macOS)上发布应用程序,你必须在每个平台上安装 PyInstaller,并在每个平台上分别捆绑你的应用程序。
**你可以使用虚拟化技术在一台机器上完成这项工作。免费的 **VirtualBox 或付费的 VMWare 和 Parallels 允许你以"客户"身份运行另一个完整的操作系统。你可以为每个"客户"操作系统设置一个虚拟机,在其中安装 Python、应用程序所需的支持包和 PyInstaller。
**像 **NextCloud 或坚果云这样的文件同步与共享系统对虚拟机很有用。在每台虚拟机上安装同步客户端,所有虚拟机都链接到你的同步账户。在同步文件夹中保存一份脚本副本。这样,你就可以在任何虚拟机上运行 PyInstaller 了:
cd ~/NextCloud/project_folder/src # GNU/Linux, Mac -- Windows 类似
rm *.pyc # 删除由另一个 Python 编译的模块
pyinstaller --workpath=path-to-local-temp-folder \
--distpath=path-to-local-dist-folder \
...other options as required... \
./myscript.py
PyInstaller 从公共同步文件夹中读取脚本,但会将其工作文件夹和捆绑的应用程序写入虚拟机本地的文件夹中。
如果你在多个平台(例如 GNU/Linux 和 macOS)上共享同一个家目录(home),则需要在每个平台上将 PYINSTALLER_CONFIG_DIR 环境变量设置为不同的值,否则 PyInstaller 可能会缓存一个平台的文件,并在另一个平台上使用它们,因为默认情况下,它使用你的家目录的子目录作为缓存位置。
**据说可以使用免费的 **Wine 环境在 GNU/Linux 下为 Windows 进行交叉开发。需要更多细节详情,参阅贡献指南。
捕获 Windows 版本数据
**Windows 应用程序可能需要包含版本资源文件(Version resource file)。版本资源文件包含一组数据结构,其中一些包含二进制整数,一些包含字符串,用于描述可执行文件的属性。更多详细信息,参阅 Microsoft **Version Information Structures 页面。
**版本资源非常复杂,有些元素是可选的,有些则是必需的。当你查看属性对话框的版本选项卡时,显示的数据与资源的结构之间没有简单的关系。因此,PyInstaller 包含了 **pyi-grab_version
命令。调用该命令时,需要输入任何具有版本资源的 Windows 可执行文件的完整路径名:
pyi-grab_version
executable_with_version_resource
该命令将以可读形式,把表示版本资源的文本写入标准输出。你可以将其从控制台窗口复制,或重定向到文件。然后你可以编辑版本信息,使其适应你的程序。
版本文本文件使用 UTF-8 编码,可能包含非 ASCII 字符。(版本资源文件的字符串字段允许使用 Unicode 字符。)除非你确定文本文件只包含 ASCII 字符串值,否则一定要以 UTF-8 编码编辑和保存文本文件。
**编辑后的版本文本文件可通过 **--version-file
选项提供给 pyinstaller
或 pyi-makespec
。文本数据将被转换成版本资源并安装到捆绑的应用程序中。
版本资源中由两个 64 位二进制值,FileVersion
与 ProductVersion
。在版本文本文件中,这些值以四元素元组的形式给出,例如:
filevers=(2, 0, 4, 0),
prodvers=(2, 0, 4, 0),
**每个元组的元素代表从高位到低位的 16 位数值。例如,值 **(2, 0, 4, 0)
在十六进制中解析为 0002000000040000
。
**你也可以在创建捆绑应用程序后,使用 **pyi-set_version
命令从文本文件中安装版本资源:
pyi-set_version
version_text_file executable_file
pyi-set_version
组件读取由 pyi-grab_version
写入的版本文本文件,将其转换为版本资源,并将该资源安装到指定的 executable_file 中。
**对于高级用途,请检查由 **pyi-grab_version
编写的版本文本文件。你会发现它是创建一个 VSVersionInfo
对象的 Python 代码。VSVersionInfo
类的定义可以在 PyInstaller 发行文件夹中的 utils/win32/versioninfo.py
中找到。你可以编写一个导入 versioninfo
的程序。在该程序中,可以对版本信息文本文件的内容使用 eval
来生成一个 VSVersionInfo
对象。你可以使用该对象的 .toRaw()
方法生成二进制形式的版本资源。或者对该对象使用 unicode()
函数来重新生成版本文本文件。
构建 macOS 应用程序捆绑包
**在 macOS 下,PyInstaller 总是在 **dist
中构建一个 UNIX 可执行文件。如果指定 --onedir
,输出将是名为 myscript
的文件夹,其中包含支持文件和名为 myscript
的可执行文件。如果指定 --onefile
,输出将是一个名为 myscript
的 UNIX 可执行文件。这两个可执行文件都可以通过终端命令行启动。标准输入和输出通过终端窗口正常工作。
**如果你在这二者中的任一模式中指定了 **--windowed
,dist
文件夹中还会包含一个名为 myscript.app
的 macOS 应用程序。
**你可能知道,应用程序是一种特殊类型的文件夹。PyInstaller 构建的应用程序包含一个始终名为 **Contents
的文件夹,其中包含:
- **一个空的 **
Frameworks
文件夹。 - **包含图标文件的文件夹 **
Resources
。 - **描述应用程序的文件 **
Info.plist
。 - **一个 **
MacOS
文件夹,包含可执行文件和支持文件,与--onedir
文件夹一样。
**使用 **--icon
参数来为应用程序指定一个自定义图标。它将被复制到 Resources
文件夹中。(如果没有指定图标文件,PyInstaller 会提供一个带有 PyInstaller logo 的文件 icon-windowed.icns
)。
**使用 **--osx-bundle-identifier
参数来添加捆绑标识符。这将成为代码签名中使用的 CFBundleIdentifier
(详见 PyInstaller code signing recipe 和 Apple code signing overview 技术说明)。
**你可以通过编辑 spec 文件向 **Info.plist
添加其他项目;参阅用于 macOS 捆绑的 spec 文件选项。
特定平台注意事项
GNU/Linux
让 GNU/Linux 应用程序向前兼容
**在 GNU/Linux 下,PyInstaller 不会将 **libc
(C 标准库,通常是 glibc
,Gnu 版本)与应用程序捆绑在一起。取而代之的是,应用程序希望从其运行的本地操作系统动态链接到 libc
。任何应用程序与 libc
之间的接口都能向前兼容较新的版本,但不能向后兼容较旧的版本。
因此,如果在当前版本的 GNU/Linux 上捆绑应用程序,那么在旧版本的 GNU/Linux 上可能无法执行(通常会出现运行时动态链接错误)。
解决方法是始终在你打算支持的最旧版本的 GNU/Linux 上构建应用程序。在较新版本中,程序应能继续使用 libc
。
**GNU/Linux 标准库(比如 **glibc
)分为 64 位和 32 位两个版本,且二者并不兼容。因此,不能将应用程序捆绑到 32 位系统上,然后在 64 位系统上运行,反之亦然。你必须为支持的每种字长制作一个独特的应用程序版本。
注意,PyInstaller 会捆绑通过依赖关系分析找到的其他共享库,如 libstdc++.so.6、libfontconfig.so.1、libfreetype.so.6。如果系统中存在这些库的旧版本(因此不兼容),则可能需要这些库。另一方面,如果试图加载系统提供的共享库,而该共享库又与系统提供的较新版本的库链接,捆绑的库可能会导致问题。
例如,系统安装的 mesa DRI 驱动(如,radeonsi_dri.so)依赖于系统提供的 libstdc++.so.6 版本。如果冻结的应用程序捆绑了旧版本的 libstdc++.so.6(从构建系统中收集),很可能会导致符号缺失错误,并阻止 DRI 驱动加载。在这种情况下,应删除捆绑的 libstdc++.so.6。不过,在当前系统提供的 libstdc++.so.6 版本旧于构建系统提供的版本时,这可能不起作用;在这种情况下,应保留捆绑的版本,因为系统提供的版本可能缺少其他依赖于 libstdc++.so.6 的已收集二进制文件所需的符号。
Windows
**开发者需要特别注意包含 Visual C++ 运行时 .dll:Python 3.5+ 使用 Visual Studio 2015 运行时,它已更名为 **“Universal CRT“,并成为 Windows 10 的一部分。从 Windows Vista 到 Windows 8.1,都有 Windows 更新包,目标系统可能已安装,也可能未安装。因此,你有以下选择:
- **在 **Windows 7 上构建,据报告可以正常运行。
- 在应用程序的安装程序中包含一个 VCRedist 包(可重新发布的软件包文件)。这是Microsoft 推荐的方法,参阅上述链接中的 “Distributing Software that uses the Universal CRT“,第 2 和第 3 条。
- **安装 **Windows 软件开发工具包(SDK),并扩展 .spec 文件以包含所需的 DLL,参阅上述链接第 6 条中的 "Distributing Software that uses the Universal CRT"。
如果你认为 PyInstaller 应该自己完成这项工作,请帮助改进 PyInstaller。
macOS
让 macOS 应用程序向前兼容
在 macOS 上,某一版操作系统的系统组件通常与后续版本兼容,但可能无法与早期版本兼容。虽然 PyInstaller 不会收集操作系统的系统组件,但收集的第三方二进制文件(如 Python 扩展模块)是根据特定版本的操作系统构建的,可能支持也可能不支持旧版本的操作系统。
**因此,要确保冻结的应用程序支持旧版本操作系统,唯一的办法就是将其冻结在希望支持的最旧版本的操作系统上。这一点在使用 **Homebrew Python 构建时尤其适用,因为其二进制文件通常会明确针对正在运行的操作系统。
例如,为确保与 "Mojave" (10.14) 及更高版本的兼容性,应在 macOS 10.14 的副本中设置一个完整的环境(安装 Python、PyInstaller、你的应用程序代码及其所有依赖项),必要时可使用虚拟机。然后使用 PyInstaller 在该环境中冻结应用程序;生成的冻结应用程序应与该版本及后续版本的 macOS 兼容。
在 macOS 上构建 32-bit 应用程序
Note
由于 macOS 10.15 Catalina 取消了对 32 位应用程序的支持,本节内容已基本过时。关于现代版本 macOS 的64位多架构操作系统支持,请参阅此处。不过,PyInstaller 然支持构建 32 位的 bootloader,而且 python.org 上仍提供(部分)Python 3.7 的 32 位/64 位安装包。PyInstaller 从 v6.0 其结束了对 Python 3.7 的支持。
旧版本的 macOS 同时支持 32 位和 64位可执行文件。PyInstaller 使用用于执行它的 Python 解释器的字长构建应用程序。这通常是一个 64 位版本的 Python,从而产生一个 64 位可执行文件。要创建 32 位可执行文件,在 32 位 Python 下运行 PyInstaller。
要验证已安装的 Python 版本是否支持在 64 位或 32 位模式下执行,请在 Python 可执行文件上使用 file 命令:
$ file /usr/local/bin/python3
/usr/local/bin/python3: Mach-O universal binary with 2 architectures
/usr/local/bin/python3 (for architecture i386): Mach-O executable i386
/usr/local/bin/python3 (for architecture x86_64): Mach-O 64-bit executable x86_64
**操作系统会选择运行哪种架构,通常默认为 64 位。你可以使用 **arch
命令按名称强行使用任一架构:
$ /usr/local/bin/python3
Python 3.7.6 (v3.7.6:43364a7ae0, Dec 18 2019, 14:12:53)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys; sys.maxsize
9223372036854775807
$ arch -i386 /usr/local/bin/python3
Python 3.7.6 (v3.7.6:43364a7ae0, Dec 18 2019, 14:12:53)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys; sys.maxsize
2147483647
Note
**PyInstaller 不再为 macOS 提供预构建的 32 位 bootloader。为了在 32 位 Python 中使用 PyInstaller,你需要使用仍然支持编译 32 位的 XCode 版本,自己构建 bootloader。根据编译器/工具链的不同,你可能还需要向 **
waf
命令明确传递--target-arch=32bit
。
获取已打开的文档名称
当用户双击已在你的应用程序中注册的文档类型时,或当用户拖拽文档并将其放置到你的应用程序图标上时,macOS 会启动你的应用程序,并以 OpenDocument AppleEvent 的形式提供已打开文档的名称。
**这些事件通常通过应用程序中已安装的事件处理程序来处理(比如,通过 **ctypes
使用 Carbon
,或使用 tkinter
PyQt5
这些 UI 工具包提供的功能)。
**另外,PyInstaller 还支持将打开文档/URL 事件转换为参数,附加到 **sys.argv
中。这只适用于在应用程序启动期间接收到的事件,即,在你的冻结代码启动之前。要处理应用程序已运行时派发的事件,你需要设置相应的事件处理程序。
更多细节,参阅此节。
AIX
**根据 Python 是 32 位或 64 位可执行文件构建的,你可能需要设置或取消设置环境变量 **OBJECT_MODE
。要确定大小,可以使用以下命令:
$ python -c "import sys; print(sys.maxsize <= 2**32)"
True
**当答案为 **True
(如上)时,Python 将作为 32 位可执行文件构建。
处理 32 位 Python 可执行文件时,请按以下步骤操作:
unset OBJECT_MODE
pyinstaller <your arguments>
处理 64 位 Python 可执行文件时,请按以下步骤操作:
export OBJECT_MODE=64
pyinstaller <your arguments>
8.运行时信息
你的应用程序在捆绑包中运行时,行为应当和从源码运行时完全一样。不过,你可能希望在运行时了解应用程序是在正在源码中运行还是已被捆绑("frozen")。你可以使用下面的代码来检查 "在捆绑包里吗?":
import sys
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
print('running in a PyInstaller bundle')
else:
print('running in a normal Python process')
**当捆绑应用程序启动时,bootloader 会设置 **sys.frozen
属性,并在 sys._MEIPASS
中存储捆绑文件夹的绝对路径。对于单文件夹捆绑,这是指捆绑中 _internal
文件夹的路径;对于单文件捆绑,则是 bootloader 创建的临时文件夹的路径(参阅单文件程序如何工作)。
应用程序运行时,可能需要访问以下位置的数据文件:
- 与之捆绑的文件(参阅添加数据文件)。
- 用户添加的与捆绑包放在一起的文件,例如同一文件夹中的文件。
- 用户当前工作目录中的文件。
程序可以访问数个变量来实现这些用途。
使用 __file__
**当你的程序未被捆绑时,Python 变量 **__file__
指向它所包含于的模块的当前路径。从捆绑脚本导入模块时,PyInstaller bootloader 会将模块的 __file__
属性设置为相对于捆绑文件夹的正确路径。
**例如,如果你从一个捆绑脚本中导入 **mypackage.mymodule
,那么该模块的 __file__
属性将会是 sys._MEIPASS + 'mypackage/mymodule.pyc'
。因此,如果你在 mypackage/file.dat
处有一个数据文件,而又将它添加到了 mypackage/file.dat
处的捆绑包中,那么通过下面的代码就可以获取其路径(在捆绑和非捆绑的情况下均可):
from os import path
path_to_dat = path.abspath(path.join(path.dirname(__file__), 'file.dat'))
在主脚本(__main__
模块)中,__file__
变量包含脚本文件的路径。在 Python 3.8 及更早版本中,这个路径是绝对路径或相对路径(取决于脚本是如何传递给 python
解释器的);而在 Python 3.9 及更高版本中,它总是绝对路径。在捆绑脚本中,PyInstaller bootloader 总是将 __main__
模块中的 __file__
变量设置为捆绑目录中的绝对路径,就像字节编译的入口点脚本存在于那里一样。
**例如,如果你的入口点脚本名为 **program.py
,那么捆绑脚本中的 __file__
属性将指向 sys._MEIPASS + 'program.py'
。因此,可以直接使用 sys._MEIPASS
或通过主脚本内 __file__
的父路径来定位相对于主脚本的数据文件。
**下面的示例将获取文件 **other-file.dat
的路径,如果没有捆绑,则该文件位于主脚本旁边;如果捆绑来,则位于捆绑文件夹内:
from os import path
bundle_dir = path.abspath(path.dirname(__file__))
path_to_dat = path.join(bundle_dir, 'other-file.dat')
**或者,如果你更愿意使用 **pathlib:
from pathlib import Path
path_to_dat = Path(__file__).resolve().with_name("other-file.dat")
在版本 4.3 中更改: 以前,入口点脚本(__main__
模块)的 __file__
属性只被设置为其基类名,而不是它在捆绑目录中的完整(绝对或相对)路径。因此,PyInstaller 文档曾建议使用 sys._MEIPASS
作为相对于捆绑入口点脚本的资源定位方式。现在,__file__
总是设置为绝对路径,并且是定位此类资源的首选方式。
将数据文件放在捆绑包内的预期位置
**要将数据文件放在代码期望的位置(即,相对于主脚本或捆绑目录),可以使用 **--add-data="source:dest" <--add-data>
命令行开关的 dest 参数。假设你通常在名为 my_script.py
的文件中使用如下代码来定位同一文件夹中的文件 file.dat
:
from os import path
path_to_dat = path.abspath(path.join(path.dirname(__file__), 'file.dat'))
**或 **pathlib 等效版本:
from pathlib import Path
path_to_dat = Path(__file__).resolve().with_name("file.dat")
**如果 **my_script.py
不是某个包的一部分(不在一个包含 __init__.py
的文件夹中),那么其 __file__
将是 [app root]/my_script.pyc
。也就是说,如果使用如下命令,把 file.dat
放在软件包的根目录中:
PyInstaller --add-data="/path/to/file.dat:."
**则无需更改 **my_script.py
,即可在运行时正确找到它。
Note
**Windows 用户需要把上一行命令中的 **
:
替换为;
。
**如果是在一个包或库(例如 **my_library.data
)中检查 __file__
,那么 __file__
将是 [app root]/my_library/data.pyc
,--add-data
应镜像该文件:
PyInstaller --add-data="/path/to/my_library/file.dat:./my_library"
**不过,在这种情况下,改用 **spec 文件并使用 PyInstaller.utils.hooks.collect_data_files
辅助函数要容易得多:
from PyInstaller.utils.hooks import collect_data_files
a = Analysis(...,
datas=collect_data_files("my_library"),
...)
使用 sys.executable
与 sys.argv[0]
运行普通 Python 脚本时,sys.executable
是执行程序,即 Python 解释器的路径。在被冻结的应用程序中,sys.executable
也是执行程序的路径,但此时的执行程序并非 Python;而是单文件应用程序中的 bootloader 或单文件夹应用程序中的可执行文件。这样就有了一种可靠的方法来获取用户启动冻结可执行文件的实际位置的方法。
sys.argv[0]
的值是用户命令中使用的名称或相对路径。它可能是相对路径,也可能是绝对路径,这取决于平台和应用程序的启动方式。
如果用户通过符号链接启动应用程序,sys.argv[0]
将使用符号名称,而 sys.executable
则是可执行文件的实际路径。有时,同一个应用程序会以不同的名称链接,并根据启动时使用的名称而又不同的行为。为处理这种情况,可以测试 os.path.basename(sys.argv[0])
。
**另一方面,有时会要求用户将可执行文件存储在与其操作的文件相同的文件夹中,例如音乐播放器应与其播放的音频文件存储在相同的文件夹中。在这种情况下,可以使用 **os.path.dirname(sys.executable)
。
**下面的小程序探讨了其中的一些可能性。将其保存为 **directories.py
。将其作为 Python 脚本执行,然后捆绑成单文件夹应用程序。然后将其捆绑成单文件应用程序并直接启动或通过符号链接启动:
#!/usr/bin/env python3
import sys, os
frozen = 'not'
if getattr(sys, 'frozen', False):
# 当前正在捆绑包中运行
frozen = 'ever so'
bundle_dir = sys._MEIPASS
else:
# 当前正在普通 Python 环境中运行
bundle_dir = os.path.dirname(os.path.abspath(__file__))
print('we are',frozen,'frozen')
print('bundle dir is', bundle_dir)
print('sys.argv[0] is', sys.argv[0])
print('sys.executable is', sys.executable)
print('os.getcwd is', os.getcwd())
对 LD_LIBRARY_PATH / LIBPATH 的考量
**在 GNU/Linux 和 *****BSD 上的环境变量 **LD_LIBRARY_PATH,或 AIX 上的环境变量 LIBPATH,是库的搜索路径,用于发现库。
**如果它存在,PyInstaller 会将其原始值保存到 ***_ORIG,然后修改搜索路径,以便捆绑代码首先找到捆绑库。
但如果你的代码执行的是一个系统程序,你通常不希望这个系统程序加载你的捆绑库(可能与你的系统程序不兼容)——它应该像通常那样从系统位置加载正确的库。
因此,在使用系统程序创建子进程之前,需要先恢复原来的路径。
env = dict(os.environ) # 创建环境的副本
lp_key = 'LD_LIBRARY_PATH' # 适用于 GNU/Linux 或 *BSD 平台
lp_orig = env.get(lp_key + '_ORIG')
if lp_orig is not None:
env[lp_key] = lp_orig # 恢复未经修改的原始值
else:
# 未设置 LD_LIBRARY_PATH 时会出现这种情况。
# 在万不得已的情况下,删除 env var:
env.pop(lp_key, None)
p = Popen(system_cmd, ..., env=env) # 创建进程
9.使用 Spec 文件
当运行
pyinstaller options myscript.py
**时,PyInstaller 要做的第一件事就是创建一个 spec 文件(specification 的缩写,规范文件)。该文件存储在 **--specpath
目录(默认为当前目录)中。
**Spec 文件告知 PyInstaller 如何处理你的脚本。它编码了脚本名称和大部分传递给 **pyinstaller
命令的选项。Spec 文件实际上是可执行的 Python 代码,PyInstaller 通过执行其内容来构建应用程序。
**对于 PyInstaller 的大部分使用情况,并不需要检查或修改规格文件。通常只需要将所有需要的信息(比如隐式导入)作为选项传递给 **pyinstaller
命令,然后让它运行即可。
有四种情况需要修改 spec 文件:
- 想要将数据文件与应用程序捆绑时。
- 想要包含运行时库(
.dll
或.so
文件)时。 - 想要在可执行文件中添加 Python 运行时选项时。
- 想要创建一个多程序捆绑包,其中包含合并的通用模块时。
这些用法将在下文的专题中介绍。
可以通过如下命令创建 spec 文件:
pyi-makespec options name.py [other scripts ...]
**其中,**options 与上文所述的 pyinstaller
命令中的 options 选项相同。此命令会创建 name.spec
文件,但不会继续构建可执行文件。
**创建 spec 文件并对其进行必要修改后,将 spec 文件传给 **pyinstaller
命令即可构建应用程序:
pyinstaller options name.spec
创建 spec 文件时,大多数命令选项都会被编入其中。通过 spec 文件进行构建时,这些选项将无法更改。如果在命令行中再次添加这些选项,则会被忽略,并由 spec 文件中的选项替代。
通过 spec 文件进行构建时,只有以下命令行选项有效:
--upx-dir
--distpath
--workpath
--noconfirm
--clean
--log-level
Spec 文件选项
在 PyInstaller 创建新的 spec 文件或打开给定的 spec 文件后,pyinstaller
命令把 spec 文件作为代码执行。捆绑应用程序就是通过执行 spec 文件创建的。下面是一个简短的示例,用于最小单文件夹应用程序的 spec 文件:
a = Analysis(['minimal.py'],
pathex=['/Developer/PItests/minimal'],
binaries=None,
datas=None,
hiddenimports=[],
hookspath=None,
runtime_hooks=None,
excludes=None)
pyz = PYZ(a.pure)
exe = EXE(pyz,... )
coll = COLLECT(...)
Spec 文件中的语句会创建四个类的实例:Analysis
、PYZ
、EXE
和 COLLECT
。
- **一个类 **
Analysis
的新实例将脚本名称列表作为输入。它会分析所有导入和依赖关系。生成的对象(赋值给a
)在类成员中包含依赖列表:scripts
:命令行中指定的 Python 脚本;pure
:脚本所需的纯 Python 模块;pathex
:用于搜索导入的路径列表(比如使用PYTHONPATH
),包括由--paths
选项给出的路径;binaries
:脚本所需的非 Python 模块,包括由--add-binary
选项给出的名称;datas
:应用程序中包含的非二进制文件,包括由--add-data
选项给出的文件名;
- **类 **
PYZ
的实例是一个.pyz
归档文件(在检视归档中描述),它包含a.pure
中的所有 Python 模块。 - **根据分析的脚本和 **
PYZ
归档创建一个EXE
实例。该对象创建可执行文件。 - **一个 **
COLLECT
的实例,从其他所有部分创建输出文件夹。
**在单文件模式下,无需调用 **COLLECT
,而 EXE
实例会接收所有脚本、模块和二进制文件。
**你可以修改 spec 文件,向 **Analysis
和 EXE
中传递附加值。
向软件包添加文件
**要将文件添加到捆绑软件包,你需要创建一个描述文件的列表,并将其提供给 **Analysis
调用。捆绑到单个文件夹时(参阅捆绑至单个文件夹),添加的数据文件将被复制到包含可执行文件的文件夹中。捆绑到单个可执行文件时(参阅捆绑至单个文件),添加文件的副本会被压缩到可执行文件中,并在执行前解压到 _MEIxxxxxx
临时文件夹。这意味着,使用单文件模式的可执行程序运行结束后,所有对添加文件做的修改都会丢失。
无论哪种模式,要在运行时查找数据文件,参阅运行时信息。
添加数据文件
**你可以使用 **--add-data
命令选项,或将数据文件作为列表添加到 spec 文件中,从而将它们添加到捆绑包中。
**使用 spec 文件时,提供一个文件路径列表作为 **Analysis
的 datas=
参数值。数据文件列表是一个元组列表,每个元组由两个字符串类型的值组成:
- 首个字符串指定当前系统中的一个或多个文件。
- 第二个指定文件在运行时位于的文件夹名称。
例如,要在单文件夹应用程序的顶层添加一个 README 文件,可以对 spec 文件作如下修改:
a = Analysis(...
datas=[ ('src/README.txt', '.') ],
...
)
与之对应的命令行:
pyinstaller --add-data "src/README.txt:." myscript.py
**这里把只有一个条目的列表作为 **datas=
的参数。它是一个元组,其中第一个字符串表示现有文件为 src/README.txt
。该文件将被查找(相对路径从 spec 文件所在位置开始计算)并复制到捆绑应用程序的顶层。
**字符串中可以使用 **/
或 \
作为路径分隔符。可以使用通配符 *
指定输入文件。例如,包含某个文件夹中所有的 .mp3
文件:
a = Analysis(...
datas= [ ('/mygame/sfx/*.mp3', 'sfx' ) ],
...
)
**文件夹 **/mygame/sfx
中的所有 .mp3
文件将被复制到捆绑程序中名为 sfx
的文件夹中。
如果在单独的语句中创建待添加文件列表,spec 文件的可读性会更好一些:
added_files = [
( 'src/README.txt', '.' ),
( '/mygame/sfx/*.mp3', 'sfx' )
]
a = Analysis(...
datas = added_files,
...
)
也可以包含文件夹中的全部内容:
added_files = [
( 'src/README.txt', '.' ),
( '/mygame/data', 'data' ),
( '/mygame/sfx/*.mp3', 'sfx' )
]
**文件夹 **/mygame/data
将以名称 name
复制到捆绑包中。
使用模块中的数据文件
**如果想要添加的数据文件包含在一个 Python 模块中,可以使用 **pkgutil.get_data()
来获取它们。
**例如,假设应用程序的组成部分之一是一个名为 **helpmod
的模块。在脚本及其 spec 文件所在的文件夹中,有这样的文件树:
$ tree helpmod
helpmod
├── __init__.py
├── helpmod.py
└── help_data.txt
**由于脚本中包含了 **import helpmod
语句,PyInstaller 将在捆绑的应用程序中创建该文件夹。但是它只会包含 .py
文件。数据文件 help_data.txt
不会被自动包含。要使它也被包含,需要在 spec 文件中添加一个 datas
元组:
a = Analysis(...
datas= [('helpmod/help_data.txt', 'helpmod' )],
...
)
**脚本执行时,可以按上一节所述,通过其基文件夹路径找到 **help_data.txt
。不过,该数据文件是模块的一部分,因此也可以使用标准库中的函数 pkgutil.get_data()
来获取其内容:
import pkgutil
help_bin = pkgutil.get_data('helpmod', 'help_data.txt')
**这将以二进制串的形式返回 **help_data.txt
文件的内容。如果原本是字符,则需要解码:
help_utf = help_bin.decode('UTF-8', 'ignore')
添加二进制文件
Note
**二进制文件指的是 DLL、动态链接库、共享对象文件等,PyInstaller 将搜索这些文件以进一步查找二进制依赖。像图像和 PDF 这样的文件应该放到 **
datas
中。
**为实现把二进制文件添加到捆绑包中,可以使用 **--add-binary
命令选项,或将其作为列表添加至 spec 文件。在 spec 文件中,构建一个描述所需文件的元组列表,然后把这个元组列表赋值给 Analysis 的 binaries=
参数。
添加二进制文件的方法与添加数据文件类似。每个元组应包含两个值:
- 第一个字符串指定当前系统中的一个或多个文件。
- 第二个指定文件在运行时位于的文件夹名称。
**通常情况下,PyInstaller 通过分析导入的模块来理解 **.so
和 .dll
库的情况。但有时候,某个模块是否被导入并不明确,这种情况下可以使用 --hidden-import
命令选项。但即便如此,还可能无法找到所有的依赖项。
**假设有一个用 C 语言编写、并使用了 Python C-API 的模块 **special_ops.so
。程序中导入了 special_ops
,PyInstaller 找到并包含了 special_ops.so
。但也许 special_ops.so
链接到了 libiodbc.2.dylib
,PyInstaller 找不到这个依赖关系。可以这样把它添加到捆绑中:
a = Analysis(...
binaries=[ ( '/usr/lib/libiodbc.2.dylib', '.' ) ],
...
或者通过命令行:
pyinstaller --add-binary "/usr/lib/libiodbc.2.dylib:." myscript.py
**如果希望将 **libiodbc.2.dylib
存储在捆绑包内的某个特定文件夹(比如 vendor
)中,则可以使用元组的第二个元素来指定:
a = Analysis(...
binaries=[ ( '/usr/lib/libiodbc.2.dylib', 'vendor' ) ],
...
)
与数据文件一样,如果要添加多个二进制文件,为提高可读性,可以在单独的语句中创建列表,并将列表名称传递。
添加文件的高级方法
PyInstaller 支持一种更高级(也更复杂)的将文件添加到捆绑包的方法,可能对特殊情况有用。参阅目录(TOC)和 Tree 类。
指定 Python 解释器选项
**PyInstaller 打包后的应用程序在独立的嵌入式 Python 解释器中运行程序代码。因此,**向 Python 解释器传递选项的典型方法并不适用,包括:
- 环境变量(例如
PYTHONUTF8
与PYTHONHASHSEED
) - 因为打包的应用程序应与目标系统中可能存在的 Python 环境隔离开来。 - 命令行参数(例如
-v
与-O
) - 因为命令行参数是为应用程序保留的。
**替代性的,PyInstaller 提供了一个选项,通过它自己的 **OPTIONS
机制,为应用程序的 Python 解释器指定永久的运行时选项。要传递运行时选项,需要创建一个三元素元组列表:('选项字符串', None, 'OPTION'),并将其作为附加参数传递给 EXE,置于关键字参数之前。选项元组的第一个元素是选项字符串(有效选项见下文),第二个元素总是 None,第三个总是 'OPTION'。
一个 spec 文件示例,指定了两个运行时选项:
options = [
('v', None, 'OPTION'),
('W ignore', None, 'OPTION'),
]
a = Analysis(
...
)
...
exe = EXE(
pyz,
a.scripts,
options, # <-- 这里把选项列表传递给了 EXE
exclude_binaries=...
...
)
该机制支持以下选项:
'v'
或'verbose'
:增加sys.flags.verbose
的值,这会导致每个模块初始化时都向 stdout 中写入信息。该选项等同于 Python 的-v
命令行选项。当通过 PyInstaller 自带的--debug imports
选项启用 verbose 导入时,它将自动启用。'u'
或'unbuffered'
:启用无缓冲的 stdout 与 stderr。相当于 Python 的-u
命令行选项。'O'
或'optimize'
:增加sys.flags.optimize
的值,相当于 Python 的-O
命令行选项。'W <arg>'
:传递 Python 的 W 选项,控制警告信息。'X <arg>'
:传递 Python 的 X 选项。其中用于控制 UFT-8 模式和开发者模式的utf8
和dev
X 选项将由 PyInstaller 的 bootloader 明确解析,并在解释器预初始化时使用;其余的 X 选项只是传递给解释器配置。'hash_seed=<value>'
:一个用于将打包应用程序中的 Python 哈希种子设置为固定值的选项。相当于PYTHONHASHSEED
环境变量。在撰写本文时,该选项还没有作为 X 选项存在,因此作为自定义选项实现。
进一步举例说明语法:
options = [
# 警告控制
('W ignore', None, 'OPTION'), # 禁用所有警告
('W ignore::DeprecationWarning', None, 'OPTION'), # 禁用已弃用的警告
# UTF-8 模式;除非明确启用/禁用,否则会根据本地语言 locale 自动启用
('X utf8', None, 'OPTION'), # 强制启用 UTF-8 模式 on
('X utf8=1', None, 'OPTION'), # 强制启用 UTF-8 模式 on
('X utf8=0', None, 'OPTION'), # 强制禁用 UTF-8 模式
# 开发者模式;默认禁用
('X dev', None, 'OPTION'), # 启用开发者模式
('X dev=1', None, 'OPTION'), # 启用开发者模式
# 哈希种子
('hash_seed=0', None, 'OPTION'), # 禁用哈希随机化;sys.flags.hash_randomization=0
('hash_seed=123', None, 'OPTION'), # 具有固定种子的哈希随机化
]
用于 macOS 捆绑的 spec 文件选项
**当构建一个窗口化的 macOS 应用程序(即,在 macOS 下运行,指定了 **--windowed
选项)时,spec 文件包含一个额外的语句来创建 macOS 应用程序捆绑包或应用程序文件夹:
app = BUNDLE(exe,
name='myscript.app',
icon=None,
bundle_identifier=None)
BUNDLE
的 icon=
参数将使用由 --icon
选项指定的图标文件路径。bundle_identifier
将包含通过 --osx-bundle-identifier
选项指定的值。
Info.plist
文件是 macOS 捆绑应用程序包的重要组成部分。(关于 Info.plist
内容的讨论,参阅 Apple bundle overview。)
**PyInstaller 会创建一个最小 **Indo.plist
。version
选项可用于设置使用 CFBundleShortVersionString Core Foundation Key 的应用程序的版本。
**通过向 BUNDLE 调用传递一个 **info_plist=
参数,可以添加或覆盖 plist 中的条目。其参数应为一个 Python 字典,由要包含在 Info.plist
文件中的键值组成。PyInstaller 使用 Python 标准库模块 plistlib 来从 info_plist 字典创建 Info.plist
。plistlib 可以处理嵌套的 Python 对象(转换为嵌套的 XML),并将 Python 数据类型转换为适当的 Info.plist
XML 类型。下面是一个例子:
app = BUNDLE(exe,
name='myscript.app',
icon=None,
bundle_identifier=None,
version='0.0.1',
info_plist={
'NSPrincipalClass': 'NSApplication',
'NSAppleScriptEnabled': False,
'CFBundleDocumentTypes': [
{
'CFBundleTypeName': 'My File Format',
'CFBundleTypeIconFile': 'MyFileIcon.icns',
'LSItemContentTypes': ['com.example.myformat'],
'LSHandlerRank': 'Owner'
}
]
},
)
**在上面的例子中,键值对 **'NSPrincipalClass': 'NSApplication'
是允许 macOS 使用视网膜分辨率渲染应用程序所必须的。键 'NSAppleScriptEnabled'
被赋值为 Python 布尔类型 False
,将以 <false/>
的形式正确输出到 Info.plist
中。最后,键 CFBundleDocumentTypes
告知 macOS 应用程序支持哪些文件类型(参阅 Apple document types)。
POSIX 专有选项
**默认情况下,所有用到的系统库都会被捆绑。要在捆绑时排除所有或大部分非 Python 共享系统库,可以在 Analysis 类中添加对函数 **exclude_system_libraries
的调用。对 POSIX 及其相关操作系统,系统库是指在 /lib*
或 usr/lib*
下的文件。该函数接受一个可选参数——例外文件通配符列表,用于在捆绑时不排除与这些通配符匹配的库文件。例如,要排除几乎所有非 Python 系统库,只保留 "libexpat" 以及名称中包含 "krb" 的系统库,这样做:
a = Analysis(...)
a.exclude_system_libraries(list_of_exceptions=['libexpat*', '*krb*'])
Splash
目标
**For a splash screen to be displayed by the bootloader, the **Splash
target must be called at build time. This class can be added when the spec file is created with the command-line option --splash IMAGE_FILE <--splash>
. By default, the option to display the optional text is disabled (text_pos=None
). For more information about the splash screen, see 闪屏(实验性功能) section. The Splash
Target looks like this:
a = Analysis(...)
splash = Splash('image.png',
binaries=a.binaries,
datas=a.datas,
text_pos=(10, 50),
text_size=12,
text_color='black')
Splash bundles the required resources for the splash screen into a file, which will be included in the CArchive.
**A **Splash
has two outputs, one is itself and one is stored in splash.binaries
. Both need to be passed on to other build targets in order to enable the splash screen. To use the splash screen in a onefile application, please follow this example:
a = Analysis(...)
splash = Splash(...)
# onefile
exe = EXE(pyz,
a.scripts,
splash, # <-- both, splash target
splash.binaries, # <-- and splash binaries
...)
**In order to use the splash screen in a **onedir application, only a small change needs to be made. The splash.binaries
attribute has to be moved into the COLLECT
target, since the splash binaries do not need to be included into the executable:
a = Analysis(...)
splash = Splash(...)
# onedir
exe = EXE(pyz,
splash, # <-- splash target
a.scripts,
...)
coll = COLLECT(exe,
splash.binaries, # <-- splash binaries
...)
On Windows/macOS images with per-pixel transparency are supported. This allows non-rectangular splash screen images. On Windows the transparent borders of the image are hard-cuted, meaning that fading transparent values are not supported. There is no common implementation for non-rectangular windows on Linux, so images with per-pixel transparency is not supported.
**The splash target can be configured in various ways. The constructor of the **Splash
target is as follows:
https://pyinstaller.org/en/v6.3.0/spec-files.html#PyInstaller.building.splash.Splash.__init__
多软件包捆绑
有些产品由多个不同的应用程序组成,每个应用程序都可能依赖于一套共同的第三方库,或以其他方式共享代码。在打包此类产品时,如果孤立地处理每个应用程序,把它和所有依赖捆绑在一起,那就太可惜了,因为这意味着要存储重复的代码和库的副本。
可以使用多软件包功能捆绑一组可执行程序,使它们共享单一的库副本。在单文件或单文件夹应用程序中都可以使用这一功能。
单文件夹应用程序中的多软件包捆绑
要合并多个单文件夹应用程序,使用共享 COLLECT 语句。这将把所有单文件夹应用程序的外部资源收集到同一个目录中。
单文件应用程序中的多软件包捆绑
每个依赖项(比如一个 DLL 文件)只打包一次,放在其中一个应用程序中。程序集中其他依赖该 DLL 的应用程序都有一个 "外部引用",告诉它们从包含该 DLL 的应用程序的可执行文件中提取该依赖项。
这样可以节省磁盘空间,因为每个依赖项只存储一次。不过,在应用程序启动时,跟踪外部引用需要额外的时间。除了那个应用程序外,其他所有应用程序的启动时间都会稍慢一些。
二进制文件之间的外部引用包含了输出目录的硬编码路径,不能重新排列。安装应用程序时,必须把所有相关应用程序放在同一目录下。
**要创建这样一组应用程序,必须编写一个自定义的 spec 文件,其中包含对 **MERGE
函数的调用。该函数接收要分析的脚本列表,查找它们的共同依赖关系,并修改分析结果以尽可能减少存储开销。
参数列表中分析对象的顺序很重要。MERGE 函数会把每个依赖项打包到从左到右第一个需要该依赖项的脚本中。列表中排在后面的脚本如果需要相同的文件,就会在外部引用中加上排在列表前面的脚本。你可以对脚本进行排序,将最常用的脚本放在列表的最前面。
适用于多软件包捆绑的 spec 文件包含对 MERGE 函数的一次调用:
MERGE(*args)
MERGE 在 Analysis 分析阶段之后、EXE
之前使用。其参数为任意长度的元组列表,每个元组包含三个元素:
- 第一个元素是一个 Analysis 对象,即 Analysis 类的一个实例,应用于其中一个应用程序。
- **第二个元素是要分析的应用程序脚本名称(不含 **
.py
扩展名)。 - 第三个元素是可执行文件的名称(通常与脚本相同)。
MERGE 会检查分析对象以了解每个脚本的依赖关系。它会修改这些对象,避免库和模块的重复。最终,生成的软件包将相互连接。
示例 MERGE spec 文件
**为多软件包捆绑构建一个 spec 文件的方法之一是,首先为软件包中的每个应用程序构建 spec 文件。假设你有一个由三个应用程序组成的产品,它们被(缺乏新意地)命名为 **foo
、bar
和 zap
:
pyi-makespec 适当的选项... foo.py
pyi-makespec 适当的选项... bar.py
pyi-makespec 适当的选项... zap.py
**检查警告信息,并逐一测试每个应用程序,处理所有隐式导入和其他问题。当三个应用程序都能正常运行时,将 **foo.spec
、bar.spec
和 zap.spec
三个文件中的语句合并如下:
首先复制每个 Analysis 语句并进行修改,给每个 Analysis 对象一个唯一的名称:
foo_a = Analysis(['foo.py'],
pathex=['/the/path/to/foo'],
hiddenimports=[],
hookspath=None)
bar_a = Analysis(['bar.py'], etc., etc...)
zap_a = Analysis(['zap.py'], etc., etc...)
然后调用 MEGRE 方法处理这三个 Analysis 对象:
MERGE((foo_a, 'foo', 'foo'), (bar_a, 'bar', 'bar'), (zap_a, 'zap', 'zap'))
**Analysis 对象 **foo_a
、bar_a
和 zap_a
会被修改,使得后两个对象引用第一个对象中的共同依赖。
**随后,可以从原始的三个 spec 文件中复制 **PYZ
、EXE
和 COLLECT
语句,把原始 spec 文件中使用 a.
的地方替换为 Analysis 对象的唯一名称。修改 EXE 语句,除了在原始 EXE 语句中传递的所有其他参数外,还要传递 Analysis.dependencies
参数。例如:
foo_pyz = PYZ(foo_a.pure)
foo_exe = EXE(foo_pyz, foo_a.dependencies, foo_a.scripts, ... etc.)
bar_pyz = PYZ(bar_a.pure)
bar_exe = EXE(bar_pyz, bar_a.dependencies, bar_a.scripts, ... etc.)
**将合并后的 spec 文件保存为 **foobarzap.spec
,然后构建它:
pyinstaller foobarzap.spec
**输出至 **dist
文件夹的将是全部三个应用程序,但应用程序 dist/bar
和 dist/zap
将引用 dist/foo
中的内容以共享依赖关系。
**记住,spec 文件是可执行的 Python。在创建 Analysis 对象和执行 **PYZ
、EXE
、COLLECT
语句时,可以使用所有的 Python 工具(for
、with
以及标准库 sys
和 io
的成员)。你可能还需要了解目录(TOC)和 Tree 类。
Spec 文件中可用的全局变量
在 spec 文件执行时,它可以访问一组有限的全局名称。这些名称包含 PyInstaller 定义的类:Analysis
、BUNDLE
、COLLECT
、EXE
、MERGE
、PYZ
、TOC
、Tree
和 Splash
,这些已在前面的章节中有过讨论。
其他包含构建环境信息的全局变量:
DISTPATH
** 指向 **dist
文件夹(即,构建捆绑生成的应用程序存储位置)的相对路径。默认路径为当前目录的相对路径。如果使用了 --distpath
选项,则 DISTPATH
包含该值。
HOMEPATH
** PyInstaller 发行版的绝对路径,通常位于当前 Python 的 site-packages 文件夹中。**
SPEC
** 为 **pyinstaller
命令提供的完整的 spec 文件参数,例如 myscript.spec
或 source/myscript.spec
。
SPECPATH
** **SPEC
值的路径前缀,由 os.path.split()
返回。
specnm
** Spec 文件的名称,例如 **myscript
。
workpath
** **build
目录的路径。默认为相对当前路径。如果使用了 workpath=
选项,则 workpath
包含该值。
WARNFILE
** 构建目录中警告文件的完整路径,例如 **build/warn-myscript.txt
。
在 spec 文件中添加参数
**有时,可能希望通过同一个 spec 文件实现不同的构建模式(比如 **debug 调试模式和 production 发布模式)。PyInstaller 不会解析出现在 --
分隔符后面的任何传递给 pyinstaller
的命令行参数,而会把这些参数转发到 spec 文件中。在 spec 文件中你可以实现自己的参数解析,并相应地处理选项。
**例如,对于下面的 spec 文件,如果通过 **pyinstaller example.spec -- --debug
调用,将创建一个启用类控制台的单目录应用程序;如果只使用 pyinstaller example.spec
,则将一个单文件无控制台应用程序。如果你使用基于 argparse
的解析器,而不是使用 sys.argv
自行创建解析器,那么 pyinstaller example.spec --help
将显示你的 spec 选项。
# example.spec
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
options = parser.parse_args()
a = Analysis(
['example.py'],
)
pyz = PYZ(a.pure)
if options.debug:
exe = EXE(
pyz,
a.scripts,
exclude_binaries=True,
name='example',
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
name='example_debug',
)
else:
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
name='example',
console=False,
)
10.特定功能注意事项
待补充
11.当发生错误时
前面几篇中的信息涵盖了 PyInstaller 的大部分常规用法。然而,Python 和第三方库的变化是无穷无尽和不可预测的。当你尝试捆绑应用程序时,可能会出现 PyInstaller 本身或捆绑应用程序以 Python 回溯终止的情况。那么,在寻求技术帮助之前,请依次考虑以下操作。
针对具体问题的配方和示例
**PyInstaller **FAQ 页面有一些常见问题的解决方法。我们的 PyInstaller Recipes 页面提供了一些高级用法和常见问题的代码示例。其中包括:
- 比上述方法更复杂的数据文件收集方法(向软件包添加文件)。
- 捆绑一个典型的 Django 应用程序。
- 使用运行时钩子设置 PyQt5 API 级别。
- Windows 下多进程限制的变通方法。
等。其中许多配方都是由用户贡献的。请随时贡献更多!
找出问题所在
构建时信息
**当 **Analysis
步骤运行时,它会产生错误和警告信息。如果 --log-level
选项允许,这些信息会显示在命令行之后。此外,Analysis 还会在 work-path=
目录中名为 build/name/warn-name.txt
的警告文件中放置信息。
**当 Analysis 检测到导入时,如果找不到模块名称,就会生成一条信息。当一个类或函数声明在一个包(一个 **__init__.py
模块)中,而导入指定了 package.name
时,也会产生一条信息。在这种情况下,Analysis 无法判断 name 是子模块还是包。
"module not found" 信息不被归为错误,因为通常有很多条这样的信息。例如,许多标准模块为不同的平台选择性地导入模块,而这些被导入的模块可能存在也可能不存在。
**所有 "module not found" 信息都会被写入 **build/name/warn-name.txt
文件。由于数量众多,不会显示在标准输出中。检查警告文件;通常会有几十个模块未找到,但它们的缺失不会产生任何影响。
**当你运行捆绑应用程序,但以 ImportError 终止时,就该检查警告文件了。然后参阅下面的 **帮助 PyInstaller 查找模块 继续解决。
构建时依赖图
**每次运行时,PyInstaller 都会在构建文件夹中写入一个关于依赖关系的交叉引用文件:在 **work-path=
目录中的 build/name/xref-name.html
是一个 HTML 文件,它列出了导入图的全部内容,显示了哪些模块被哪些模块导入。你可以在任意网页浏览器中打开它。找到一个模块名称,然后不断点击 "imported by" 链接,直到找到导致该模块被包含在内的顶级导入。
**如果在 **pyinstaller
命令中指定 --log-level=DEBUG
,PyInstaller 会额外生成一个 GraphViz 输入文件,表示依赖关系图。该文件是 work-path=
目录中的 build/name/graph-name.dot
。你可以使用任何 GraphViz 命令(如 dot
)处理该文件,来生成导入依赖关系的图形显示。
这些文件非常大,因为即使是最简单的 "hello world" Python 程序最终也会包含大量的标准模块。因此图形文件在此发行版中不是很有用。
构建时 Python 错误
PyInstaller 有时会以引发一个 Python 异常而终止。大多数情况下,异常消息中的原因都很明确,例如 "Your system is not supported"(你的系统不支持)或 "Pyinstaller requires at least Python 3.8"(PyInstaller 要求至少 Python 3.8)。其他情况则清楚地表明存在一个应该报告的 bug。
然而,其中一个错误可能令人费解:IOError("Python library not found!")
。PyInstaller 需要捆绑 Python libaray,也是 Python 解释器的主要部分,以动态加载库的形式链接。该文件的名称和位置因使用的平台而异。有些 Python 安装默认不包含动态 Python library(可能存在静态链接的,但无法使用)。你可能需要安装某种开发包。或者,library 可能存在,但不在 PyInstaller 搜索的文件夹中。
**在不同的操作系统中,PyInstaller 查找 Python library 的位置不同,但在大多数系统中都会检查 **/lib
和 /usr/lib
。如果无法将 Python library 放在此处,请尝试在 GNU/Linux 的环境变量 LD_LIBRARY_PATH
或 macOS 的环境变量 DYLD_LIBRARY_PATH
中设置正确的路径。
获取调试信息
--debug=all
选项(及其子选项)提供了大量诊断信息。在开发复杂软件包、应用程序似乎无法启动,或只是想了解运行时如何工作时,这些信息可能非常有用。
通常情况下,调试进度信息会发送到标准输出。如果在捆绑 Windows 应用程序时使用了 --windowed
选项,则会将调试进度信息发送到任何附加的调试器。如果你不使用(或者没有)调试器,可以使用免费的(🍺)DebugView 工具来显示这些信息。它必须在运行捆绑应用程序之前启动。
**对于 **--windowed
macOS 应用程序,则不会显示。
**建议在生产版本中不使用 **--debug
捆绑。调试信息需要系统调用,会影响性能。
获取 Python 的详尽导入信息
**你可以使用 **--debug=imports
选项(参见上文获取调试信息),这会将 -v
(verbose imports)标志传递给内嵌的 Python 解释器。这可能非常有用。即使对于那些表面看起来可以正常运行的应用程序,它也能提供一些信息,以确保应用程序从捆绑中获取所有导入,而不是泄漏到本地安装的 Python 中。
**使用 **--windowed
选项时,Python 详细信息和警告信息总是转到标准输出,并且不可见。切记不要在生产版本中使用该选项。
找出 GUI 应用程序无法启动的原因
**如果使用 **--windowed
选项,捆绑应用程序可能无法启动,并显示类似 Failed to execute script my_gui
的错误信息。在这种情况下,你需要获得更多的详细输出,以找出问题所在。
- 对于 macOS,可以通过命令行运行应用程序,即,在终端(Terminal) 中运行
./dist/my_gui
而不是点击my_gui.app
。 - **对于 Windows 系统,需要以不使用 **
--windowed
选项方式重新捆绑应用程序。然后可以通过命令行运行生成的可执行文件,即my_gui.exe
。 - **对于 Unix 和 GNU/Linux 没有 **
--windowed
选项。总之,如果 GUI 应用程序启动失败,可以通过命令行运行应用程序,即./dist/my_gui
。
这样就能找出妨碍应用程序初始化的相关错误,然后就可以继续执行其他调试步骤了。
Operation not permitted 错误
**如果使用了 **--onefile
,但程序运行失败,出现类似这样的错误:
./hello: error while loading shared libraries: libz.so.1:
failed to map segment from shared object: Operation not permitted
**造成这种情况的原因可能是 /tmp 目录权限错误(例如,文件系统在挂载时带有 **noexec
标志)。
**解决这个问题的一种简单方法是在环境变量 TMPDIR 中设置一个路径,指向一个无 **noexec
标志的文件系统挂载中的目录,例如:
export TMPDIR=/var/tmp/
帮助 PyInstaller 查找模块
扩展路径
**如果 Analysis 发现需要某个模块被需要,但却找不到,通常是因为脚本在操作 **sys.path
。在这种情况下,最简单的方法是使用 --paths
选项列出所有脚本可能搜索导入的其他地方:
pyi-makespec --paths=/path/to/thisdir \
--paths=/path/to/otherdir myscript.py
**这些路径将在 spec 文件的 **pathex
参数中注明。
**在分析过程中,它们将被添加到当前的 **sys.path
中。
列出隐式导入
如果 Analysis 认为它已经找到了所有导入,但应用程序却因导入错误而失败,那么问题就出在隐式导入上;也就是是说,一个对分析阶段不可见的导入。
**当代码使用 **__import__
、importlib.import_module
、exec
或 eval
时,可能会出现隐式导入。当一个扩展模块使用 Python/C API 进行导入时,也会出现隐式导入。发生这种情况时,Analysis 什么也检测不到。运行时不会有任何警告,只有一个 ImportError。
**要找到这些隐式导入,使用 **--debug=imports
标志(见上文获取 Python 的详尽导入信息)构建应用程序并运行它。
**一旦知道需要使用哪些模块,就可以使用 **--hidden-import
命令选项、编辑 spec 文件或使用钩子文件(见 Understanding PyInstaller Hooks),将需要的模块添加到捆绑中。
扩展一个包的 __path__
**Python 允许脚本通过 **__path__
机制扩展用于导入的搜索路径。通常,一个导入的模块的 __path__
只有一个条目,即找到 __init__.py
的目录。但是 __init__.py
可以自由地扩展它的 __path__
来包含其他目录。例如,win32com.shell.shell
模块实际上解析为 win32com/win32comext/shell/shell.pyd
。这是因为 win32com/__init__.py
将 ../win32comext
添加到了它的 __path__
。
**由于被导入模块的 **__init__.py
在分析过程中并未实际执行,因此 PyInstaller 无法看到它对 __path__
所做的更改。我们使用与解决隐式导入相同的钩子机制来解决这个问题,并附加了一些额外的逻辑,参阅 Understanding PyInstaller Hooks。
**注意,以这种钩子方式操作 **__path__
只对 Analysis 有影响。在运行时,所有的导入都会在捆绑中被拦截和满足。win32com.shell
的解析方式与 win32com.anythingelse
相同,而 win32com.__path__
对 ../win32comext
一无所知。
有时,这还不够。
改变运行时行为
运行时钩子可以处理更多奇怪的情况。这些小脚本会在主脚本运行前对环境进行操作,从而为脚本提供额外的顶层代码。
**有两种方法提供运行时钩子。可以使用选项 **--runtime-hook
=path-to-script 来指定。
**第二种则是,部分运行时钩子已提供。在分析结束时,Analysis 段落生成的模块列表中的名称,会在 PyInstaller 安装文件夹中的 **loader/rthooks.dat
中进行查找。这个文本文件是一个 Python 字典,键是模块名称,值是钩子脚本路径名列表。如果匹配,那些钩子脚本就会包含在捆绑的应用程序中,并在主脚本启动前被调用。
**使用 **--runtime-hook
选项指定的钩子将按照指定的顺序执行,并在任何已安装的运行时钩子之前执行。如果指定 --runtime-hook=file1.py --runtime-hook=file2.py
,那么运行时的执行顺序将是:
file1.py
的代码。file2.py
的代码。- **在 **
rthooks/rthooks.dat
中找到的为所含模块指定的钩子。 - 你的主脚本。
**以这种方式调用的钩子,虽然需要谨慎处理其导入内容,但几乎可以做任何事情。编写运行时钩子的另一个用途是覆盖某些模块的函数或变量。Django 运行时钩子就是一个很好的例子(参见 PyInstaller 文件夹中的 **loader/rthooks/pyi_rth_django.py
)。Django 动态导入一些模块并寻找一些 .py
文件。但是在单文件捆绑包中没有 .py
文件。我们需要覆盖 django.core.management.find_commands
函数,使其只返回一个值列表。运行时钩子的做法如下:
import django.core.management
def _find_commands(_):
return """cleanup shell runfcgi runserver""".split()
django.core.management.find_commands = _find_commands
获取最新版本
**如果你有理由认为发现了 PyInstaller 中的错误,可以尝试下载最新的开发版本。该版本可能有 **PyPI 尚未提供的修复或功能。可以从 PyInstaller Downloads 页面下载最新的稳定版和最新的开发版。
**也可以使用 **pip 直接安装最新版本的 PyInstaller:
pip install https://github.com/pyinstaller/pyinstaller/archive/develop.zip
向他人寻求帮助
**如果上述建议都无济于事,请通过 **PyInstaller 邮件列表 寻求帮助。
**然后,如果你认为有可能在 PyInstaller 中发现了错误,请参阅 **How to Report Bugs 页面。
12.进阶话题
下面的讨论涉及 PyInstaller 内部方法的细节。在正常使用中,你应该不需要这种程度的细节。但如果你想研究 PyInstaller 代码,并可能按贡献指南对其做出贡献,那会很有帮助。
Bootstrap 流程详解
在捆绑脚本开始执行之前,必须进行许多步骤。这些步骤的摘要已在概述(单文件夹程序如何工作 和单文件程序如何工作)中给出。以下是更多细节,以帮助你了解 bootloader 做的事情以及如何找出问题。
Bootloader
Bootloader 为运行 Python 代码做好一切准备。它启动 setup,然后在另一个进程中返回自身。这种使用两个进程的方法有很大的灵活性,除 Windows 下的单文件夹模式外,所有捆绑程序都使用这种方式。因此,如果你在系统任务管理器中看到捆绑应用程序有两个进程,不必感到惊讶。
执行 bootloader 时会发生什么:
- 第一个进程:bootloader 启动。
- **如果使用了单文件模式,将捆绑文件提取到 **
temppath/_MEIxxxxxx
。 - 修改各种环境变量:
- GNU/Linux:如果已设置,则将 LD_LIBRARY_PATH 的原始值保存到 LD_LIBRARY_PATH_ORIG中,将我们的路径预置到 LD_LIBRARY_PATH。
- AIX:相同做法,只是使用 LIBPATH 和 LIBPATH_ORIG。
- macOS:取消设置 DYLD_LIBRARY_PATH。
- 设置为处理两个进程的信号。
- 运行子进程。
- 等待子进程结束。
- **如果是单文件模式,删除 **
temppath/_MEIxxxxxx
。
- **如果使用了单文件模式,将捆绑文件提取到 **
- 第二个进程:bootloader 本身作为子进程启动。
- 在 Windows 设置激活上下文。
- 加载 Python 动态链接库。动态库的名称嵌入在可执行文件中。
- 初始化 Python 解释器:设置 sys.path、sys.prefix 和 sys.executable。
- 运行 Python 代码。
运行 Python 代码需要几个步骤:
- **运行 Python 初始化代码,为运行用户的主脚本做好一切准备。初始化代码只能使用 Python 内置模块,因为一般的导入机制还不可用。它设置了 Python 导入机制,使其只能从嵌入可执行文件的归档文件中加载模块。它还为 **
sys
内置模块添加了属性frozen
和_MEIPASS
。 - 执行任何运行时钩子:首先是用户指定的钩子,然后是标准钩子。
- **安装 Python "egg" 文件。当模块是一个 zip 文件(.egg)的一部分时,它已被捆绑到 **
./eggs
目录中。安装意味着将 .egg 文件名追加到sys.path
中。Python 会自动检测sys.path
中的项目是 zip 文件还是目录。 - 运行主脚本。
捆绑应用程序中的 Python 导入
PyInstaller 将编译后的 Python 代码(.pyc
文件)嵌入到可执行文件中。PyInstaller 将其代码注入正常的 Python 导入机制。Python 允许这种做法,PEP 302 "New Import Hooks" 中描述了其支持。
**PyInstaller 实现了 PEP 302 规范,用于导入内置模块,导入 "冻结" 模块(与应用程序捆绑的已编译 Python 代码)和 C 扩展。可以在 **./PyInstaller/loader/pyi_mod03_importers.py
中阅读代码。
**在运行时,PyInstaller PEP 302 钩子会被追加到变量 **sys.meta_path
中。在尝试导入模块时,解释器会首先尝试 sys.meta_path
中的 PEP 302 钩子,然后再搜索 sys.path
。因此,Python 解释器会从捆绑的可执行文件中嵌入的归档文件中加载导入的 Python 模块。
下面是捆绑应用程序中导入语句的解析顺序:
- **是内置模块吗?在变量 **
sys.builtin_module_names
中存有一份内置模块列表。 - 它是嵌入在可执行文件中的模块吗?那就从嵌入的归档中加载。
- **是 C 扩展吗?应用程序将尝试查找名为 **
package.subpackage.module.pyd
或package.subpackage.module.so
的文件。 - **接下来检查 **
sys.path
中的路径。其中可能有带有 Python 模块或.egg
文件名的附加位置。 - **如果模块没有找到,则引发 **
ImportError
。
闪屏启动
Note
该功能与 macOS 不兼容。在当前的设计中,闪屏在一个二级线程中运行,而 macOS 上的 Tcl/Tk(或者说底层 GUI 工具包)不允许这样做。
如果应用程序捆绑了闪屏,那么 bootloader 的启动程序和线程模型就会稍微复杂一些。下面描述了捆绑闪屏时的操作顺序:
- Bootloader 会检查它是否作为最外层应用程序运行(而不是 bootloader 启动的子进程)。
- **如果捆绑了闪屏资源,则尝试提取(单文件模式)。提取路径位于 **
temppath/_MEIxxxxxx/__splashx
内。如果在单文件夹模式下,应用程序会认为资源是相对于可执行文件的。 - 将 tcl 和 tk 共享库加载到 bootloader 中。
- **Windows: **
tcl86t.dll
/tk86t.dll
- **Linux: **
libtcl.so
/libtk.so
- **Windows: **
- **通过修改/替换以下函数,为 **Tcl/Tk 解释器准备一个最小环境:
::tclInit
:该命令用于查找 tcl 的标准库。我们替换该命令是为了强制 tcl 只加载/执行捆绑的模块。::tcl_findLibrary
:Tk 使用该函数获取其所有组件的源。覆盖函数会设置所需的环境变量,并评估所请求的文件。::exit
:对该函数进行了修改,以确保正确退出闪屏线程。::source
:该命令执行一个传入的文件的内容。由于我们在最小环境中运行,因此我们会模拟执行未捆绑的文件,并执行捆绑的文件。
- **启动 tcl 解释器,并执行由 PyInstaller 的构建目标 **
Splash
在构建时生成的闪屏脚本。该脚本会创建环境变量_PYIBoot_SPLASH
,Python 解释器也可以使用该变量。它还会初始化一个 tcp 服务端 socket,以接收来自 Python 的命令。
Note
tcl 解释器在单独的线程中启动。只有在 tcl 解释器执行完闪屏脚本后,负责提取/启动 Python 解释器的 bootloader 线程才会恢复。
pyi_splash
模块(详细)
此模块连接 bootloader,向闪屏发送信息。
它将作为 bootloader 所提供函数(比如显示文本或关闭)的 RPC 接口。这使得用户的 Python 程序和与 bootloader 的通信如何实现无关,因为提供了一致的 API。
**要连接 bootloader,它需要连接到一个本地的 tcp 服务端 socket,其端口通过环境变量 **_PYIBoot_SPLASH
传递。Bootloader 通过 Python 模块 _socket
连接到该 socket。虽然这个 socket 是双向的,但模块被配置为只发送数据。由于请求环境变量所需的 os 模块在启动时不可用,所以模块在初始化前不会建立连接。
**该模块不支持在显示闪屏时重新加载,即无法 reload(比如通过 **importlib.reload
),因为当模块实例的连接丢失时,闪屏会自动关闭。
函数
Note
注意,如果
_PYIBoot_SPLASH
环境变量不存在,或者在连接过程中发生错误,模块将不会引发一个错误,而只是不进行初始化(即,pyi_splash.is_alive
将返回False
)。在向闪屏发送命令之前,应检查模块是否已正确初始化,否则将引发一个RuntimeError
。
is_alive()
** ** ** ** ** 表示该模块是否可用。 ** ** ** ** ** **如果模块未初始化或被关闭闪屏禁用,则返回 **False
。否则,模块应该可以使用。
update_text(msg)
** ** ** ** ** 更新闪屏窗口上的文本。 ** ** ** ** ** 参数: msg(str) - 要显示的文本** ** ** ** ** ** 引发: ConnectionError - 如果操作系统无法向 socket 写入;RuntimeError - 如果模块未初始化;
close()
** ** ** ** ** 关闭与 ipc tcp 服务 socket 的连接 ** ** ** ** ** 这将关闭闪屏并使该模块无法使用。调用此函数后,将无法再次打开与闪屏的连接,此模块的所有功能也将无法使用
目录(TOC)和 Tree 类
PyInstaller 以所谓的目录(Table of Contents,TOC)列表格式管理要收集的文件列表。这些列表包含三元素元组,封装了文件的目标名称、完整源路径及其类型信息。
**作为管理 TOC 列表组件的一部分,PyInstaller 提供了一个 **Tree
类,作为从给定目录的内容构建 TOC 列表的便捷方法。该工具类可以在 .spec 文件 中使用,也可以在自定义钩子中使用。
目录(TOC)列表
Analysis
对象会生成多个 TOC 列表,提供待收集文件的相关信息。文件根据其类型或功能被归类到不同的列表中,例如:
Analysis.scripts
:程序脚本Analysis.pure
:纯 Python 模块Analysis.binaries
:二进制扩展模块和共享库Analysis.datas
:数据文件
**生成的 TOC 列表会传递给 **spec 文件中的各种构建目标,例如 PYZ
、EXE
和 COLLECT
。
每个 TOC 列表都包含三元素元组:
(dest_name, src_name , typecode)
其中,dest_name
是目标文件名(即冻结应用程序中的文件名,因此必须始终是相对名称),src_name
是源文件名(收集文件的路径),typecode
是表示文件(或条目)类型的字符串。
**在内部,PyInstaller 使用了许多 **typecode 值,但在一般情况下,你只需要知道这些:
typecode | 描述 | dest_name | src_name |
---|---|---|---|
'DATA' | 任意(数据)文件。 | 冻结应用程序中的名称。 | 文件在构建系统中的完整路径。 |
'BINARY' | 共享库。 | 冻结应用程序中的名称。 | 文件在构建系统中的完整路径。 |
'EXTENSION' | Python 二进制扩展。 | 冻结应用程序中的名称。 | 文件在构建系统中的完整路径。 |
'OPTION' | PyInstaller/Python 运行时选项。 | 选项名称(和选项值,用空格分隔)。 | 忽略。 |
**目标名称与冻结应用程序中的最终名称相对应,相对于应用程序的顶层目录。它可能包含路径元素,例如 **extras/mydata.txt
。
BINARY
和 EXTENSION
类型的实例被假定为代表包含可加载可执行代码(如动态链接库)的文件。通常,EXTENSION
用于表示 Python 扩展模块,例如由 Cython 编译的模块。这两种文件类型的处理方式是相同的;PyInstaller 会扫描它们以查找额外的链接时依赖关系,并收集发现的任何依赖关系。在某些操作系统上,二进制文件和扩展会经过额外的处理(例如在 macOS 上对链接时依赖的路径重写和代码签名)。
**在将 **Analysis
生成的 TOC 列表传递给构建目标之前,可以在 spec 文件中对其进行修改,以加入更多条目(不过最好是通过 binaries 或 Analysis 的 datas 参数传递额外的文件)或删除不需要的条目。
在版本 5.11 中修改: PyInstaller 5.11 之前的版本中,TOC 列表实际上是 TOC
类的实例,它在内部执行隐式条目去重复;也就是说,尝试插入具有已存在目标名称的条目不会导致列表发生任何变化。然而,由于 TOC 类定义松散、语义冲突等缺点,TOC
类已被弃用。TOC 列表现在是普通列表的实例,PyInstaller 会执行显式列表规范化(条目去重复)。显式规范化在 Analysis
实例化结束时执行,此时列表存储在类的属性中(如 Analysis.datas
和 Analysis.binaries
)。同样,一旦构建目标(EXE
、PYZ
、PKG
、COLLECT
、BUNDLE
)将输入的 TOC 列表合并为最终列表,也会执行显式列表规范化。
Tree 类
Tree
类提供了一种创建 TOC 列表(描述给定目录的内容)的便捷方法:
**Tree(**root, prefix=run-time-folder, excludes=string_list, typecode=code | 'DATA' )
- root 参数是表示目录路径的字符串。可以是绝对路径,也可以是相对于 spec 文件目录的路径。
- **可选的 **prefix 参数是应用程序目录中一个子目录的名称,文件将被收集到该子目录中。如果未指定或设置为
None
,文件将被收集到顶层应用程序目录中。 - **可选的 **excludes 参数是一个由一个或多个字符串组成的列表,这些字符串与应从树中排除的 root 文件相匹配。列表中的项目可以是:
- 一个名称,使具有该名称的文件或文件夹被排除
- **一个全局匹配模式(比如 **
*.ext
),使与之匹配的文件被排除
- **可选的 **typecode 参数指定分配给 TOC 列表中所有条目的 TOC typecode 字符串。默认值为
DATA
,适合大多数情况。
例如:
extras_toc = Tree('../src/extras', prefix='extras', excludes=['tmp', '*.pyc'])
**这将创建一个 TOC 列表 **extras_toc
,其中包含相对路径 ../src/extras
中所有文件的条目,但省略了那些以 tmp
为文件名(或位于名为 tmp
的目录中)或以 .pyc
为扩展名的文件。TOC 中的每个元组都有:
- **一个形式为 **
extras/{filename}
的 dest_name。 - **一个 **src_name,对应于该文件在
../src/extras
文件夹中的完整绝对路径(相对于 spec 文件的位置)。 - **一个值为 **
DATA
(默认值)的 typecode。
下面是一个创建 TOC 的示例,列出了一些二进制模块:
cython_mods = Tree('..src/cy_mods', excludes=['*.pyx', '*.py', '*.pyc'], typecode='EXTENSION')
**这将创建一个 TOC 列表,其中包含 **cy_mods
目录中每个文件条目,但不包括扩展名为 .pyx
、.py
或 .pyc
的文件(基本上是只收集 Cython 创建的 .pyd
或 .so
模块)。该 TOC 中的每个元组都有:
- **一个与文件名相对应的 **dest_name(所有文件都收集在顶层应用程序目录中)。
- **一个与在 **
../src/cy_mods
相对于 spec 文件的该文件的完整绝对路径相对应的 src_name。 - **一个值为 **
EXTENSION
的 typecode(也可使用BINARY
)。
检视归档
**归档文件是一种包含其他文件的文件,例如 **.tar
文件、.jar
文件或 .zip
文件。PyInstaller 中使用了两种归档文件,一种是 ZlibArchive,它允许高效地存储 Python 模块,并通过一些钩子直接导入。另一种是 CArchive,类似于 .zip
文件,是一种打包和压缩任意数据块的通用方式。CArchive 之所以叫 CArchive,是因为它既可以在 C 语言中操作,也可以在 Python 中操作。它们都派生自一个共同的基类,因此创建新类型的归档文件非常容易。
ZlibArchive
**ZlibArchive 包含压缩的 **.pyc
或 .pyo
文件。Spec 文件中的 PYZ
类调用会创建一个 ZlibArchive。
**ZlibArchive 中的目录是一个 Python 字典,它将一个键——即 **import
语句中给出的成员名称与 ZlibArchive 中的查找位置和长度相关联。ZlibArchive 的所有部分都以 marshalled 格式存储,因此与平台无关。
**运行时使用 ZlibArchive 导入捆绑的 Python 模块。即使使用最大压缩等级,导入速度也比普通导入快。不需要搜索 **sys.path
,而是在字典中查找。没有目录操作,也不需要打开文件(文件已经打开了)。只需查找、读取和解压缩。
Python 错误跟踪将指向创建归档条目的源文件(.pyc
编译、捕获并保存在归档时的 __file__
属性)。这不会向你的用户提供任何有用的信息,但如果他们向你反馈 Python 错误跟踪,你可以理解它。
CArchive
**CArchive 可用包含任何类型的文件。它非常像 **.zip
文件。用 Python 创建它们很容易,用 C 代码解压也很容易。CArchive 可以附加到另一个文件,如 ELF 和 COFF 可执行文件。为了实现这一点,归档的目录被放在文件的末尾,后面只有一个 cookie,它告诉我们目录从何处开始,归档本身从何处开始。
一个 CArchive 可以嵌入另一个 CArchive 中。内部归档可在原地打开和使用,无需提取。
每个目录条目都有可变长度。条目的第一个字段给出条目的长度。最后一个字段是相应打包文件的名称。文件名以 null 结束。每个成员的压缩都是可选的。
**每个成员还有一个类型代码。自解压可执行文件会使用这些类型代码。如果将 **CArchive
用作 .zip
文件,则无需担心代码问题。
ELF 可执行文件格式(Windows、GNU/Linux 和其他一些操作系统)允许在可执行文件的末尾连接任意数据,而不会影响其功能。因此,CArchive 的目录位于归档的末尾。可执行文件可以二进制文件的形式打开自己,找到末尾并“打开”CArchive。
使用 pyi-archive_viewer
**使用 **pyi-archive_viewer
命令可以检查任何类型的归档文件:
pyi-archive_viewer
archivefile
使用该命令,你可以检查用 PyInstaller 构建的任何归档文件(PYZ
或 PKG
),或任何可执行文件(.exe
文件或 ELF 或 COFF 二进制文件)的内容。可以使用这些命令浏览归档:
O name
** **打开嵌入的归档 name(如果省略会有提示)。例如,在查找单文件可执行文件时,可以打开其中的 PYZ-00.pyz
归档。
U
** **向上一级(返回查看包含归档的页面)。
X name
** **提取 name(如果省略将提示)。提示输出文件名。如果没有给定,则将成员提取到 stdout。
Q
** **退出。
pyi-archive_viewer
命令有如下选项:
-h, --help
** **显示帮助。
-l, --log
** **快速记录日志。
-b, --brief
** **打印可在 Python 中评估的文件内容列表。
-r, --recursive
** **与 -l 或 -b 一起使用时,应用递归行为。
检视可执行文件
**你可以使用 **pyi-bindepend
来检视任何可执行文件:
pyi-bindepend
executable_or_dynamic_library
pyi-bindepend
命令会分析你指定的可执行文件或 DLL,并将其所有二进制依赖关系写入 stdout。这样可以轻松找出哪些 DLL 被可执行文件或另一个 DLL 所需要。
**PyInstaller 在分析过程中使用 **pyi-bindepend
来跟踪二进制扩展的依赖链。
创建可重复的构建
在特定情况下,当使用完全相同的依赖项构建同一个应用程序两次时,两个捆绑包应该完全逐位相同,这非常重要。
通常情况并非如此。Python 使用随机散列来制作字典和其他散列类型,这会影响编译的字节码以及 PyInstaller 内部的数据结构。因此,即使应用程序捆绑包的所有组件都相同,两个应用程序的执行方式也相同,两次编译也可能不会产生逐位完全相同的结果。
**你可以在运行 PyInstaller 之前,将环境变量 **PYTHONHASHSEED
设置为一个已知的整数值,以确保编译时产生相同的位。这将强制 Python 使用相同的随机散列序列,直到 PYTHONHASHSEED
被取消设置或设置为 'random'
。例如,在如下脚本中执行 PyInstaller(适用于 GNU/Linux 和 macOS):
# 将种子设置为已知的可重复整数值
PYTHONHASHSEED=1
export PYTHONHASHSEED
# 以 myscript 创建单文件构建
pyinstaller myscript.spec
# 校验
cksum dist/myscript/myscript | awk '{print $1}' > dist/myscript/checksum.txt
# 让 Python 再度变幻莫测
unset PYTHONHASHSEED
在版本 4.8 中修改: 在组装过程中,生成的 Windows 可执行文件 PE 头中的构建时间戳会设置为当前时间。可以通过 SOURCE_DATE_EPOCH
环境变量指定自定义时间戳值,以实现可重现的编译。
13.钩子配置选项
待完成
14.构建 Bootloader
待完成
15.pyi-makespec 命令
待完成
16.更新日志
6.11.1 (2024-11-10)更新日志
修复问题: (GNU/Linux)修复使用$ORIGIN链接的二进制依赖项解析问题。(#8868)
(Linux)修复在使用uv-install或rye-install安装的Python版本与系统安装的Python版本相同时,Python共享库的发现和收集问题。(#8850)
(Linux/musl)阻止收集ld-musl-x86_64.so.1。(#8868)
(Windows)在单文件临时目录清理中添加重试循环,以减轻操作系统和/或防病毒程序在应用程序进程退出后短时间内锁定捆绑的DLL和Python扩展模块的情况。(#8870)
(Windows)修复Qt运行时钩子未能将顶级应用程序目录添加到PATH的问题,当PATH已经包含顶级应用程序目录的子目录时(例如,由pywin32运行时钩子添加到PATH的pywin32_system32子目录)。这一失败阻止了QtNetwork发现捆绑的OpenSSL DLL,并导致它(尝试)从PATH中其他位置加载它们。(#8857)
修复macOS默认图标从wheels中缺失的问题(在v6.11.0中引入的回归)(#8855)
如果tkinter不可用,则阻止其被收集。(#8868)
钩子: 如果导入了matplotlib,则防止IPython被重复打包。(#8868)
6.11.0 (2024-10-15)更新日志
特性: 实现一种机制,允许钩子通知PyInstaller的二进制依赖分析不应为某些共享库创建指向顶级应用程序目录的符号链接(适用于最初创建此类符号链接的平台)。该机制旨在作为边角案例的解决方案,当这些符号链接干扰运行时发现存储在链接库真实位置的其他共享库时。(#8761)
修复问题: (Windows)允许从SYSTEM用户的主目录(%WINDIR%\system32\config\systemprofile)及其子目录启动PyInstaller,作为通常禁止从Windows目录及其子目录运行的例外(该禁令在#8570中引入)。(#8816)
(Windows)尝试减轻控制台隐藏/最小化机制(#7735)在Windows Terminal作为默认终端应用程序时生效的定时问题。(#8798)
(Windows)修复作为SYSTEM用户运行PyInstaller时,在SYSTEM用户的主目录(%WINDIR%\system32\config\systemprofile)下发现的文件的二进制依赖分析问题。(#8810)
(Windows)修复PyInstaller 6.x和numpy < 1.26导致与numpy PyPI wheels捆绑的共享库重复的问题。(#8736)
(Windows)修复在启用启动画面的单文件应用程序中VCRUNTIME140.dll泄露的问题,这次是在完整应用程序重启的场景中(由#8650引入的回归)。(#8701)
修复尝试使用runpy.run_path运行与冻结应用程序捆绑的Python脚本时的回归问题。(#8767)
钩子: 为PySide6.QtGraphsWidgets添加钩子,该钩子随PySide6 v6.8.0引入。(#8828)
调整setuptools钩子,以最小化收集vendored包/模块及其(元)数据,当使用setuptools >= 71.0时;目标是使收集的vendored包的运行时行为与其非vendored对应物的行为紧密匹配。(#8737)
更新babel钩子,以收集解包捆绑的本地化数据文件所需的所有子模块。(#8750)
更新和现代化PyInstaller的numpy钩子副本,以兼容numpy 1.24.x, 1.25.x, 1.26x和2.x。将PyInstaller的numpy钩子副本的优先级设置为1(使用#8740中的新钩子优先级机制),以便它覆盖上游钩子,试图解决以下问题:
修复在Windows上与numpy < 1.26 PyPI wheels捆绑的共享库重复的问题,这是由PyInstaller 6.x中PyInstaller的二进制依赖分析行为变化引起的(PyInstaller的旧版本的numpy钩子及其上游对应物都是为v5及更早版本的行为编写的)。
避免在使用pip安装的numpy与Anaconda python时触发关于找不到numpy基础发行版的警告。
在Windows上,当使用numpy >= 1.26时,从numpy.libs目录中收集负载顺序文件(如果可用)以及共享库。这应该最小化使用pip安装的numpy >= 1.26与Anaconda python 3.8和3.9时的潜在问题。(#8799)
引导程序: (AIX)修复在AIX下编译引导程序时的错误(在PyInstaller v6.8中引入的回归)。(#8819)
(Cygwin)修复在Cygwin下编译引导程序时的缺失变量错误(在PyInstaller v6.8中引入的回归)。(#8814)
文档: 记录了在Windows Terminal配置为运行时系统默认终端应用程序时,启用冻结应用程序的隐藏/最小化机制的注意事项。(#8798)
PyInstaller核心: (Windows)由于pefile 2024.8.26中的性能回归严重影响PyInstaller的二进制依赖分析和二进制与数据分类,因此固定pefile != 2024.8.26。(#8762)
引导程序构建: 放宽对libdl的检查,以适应将libdl符号放在libc中但不提供遵守POSIX要求的占位符的平台,即-ldl应始终可用,最明显的是OpenWRT。(#7552)
6.10.0 (2024-08-10)更新日志
特性: (Linux)将#8288中收集.hmac文件的机制扩展到fipscheck目录中的.hmac文件。(#8719)
支持Python 3.13。(#8198)
引入新的PYINSTALLER_RESET_ENVIRONMENT环境变量,供应用程序开发者在尝试启动sys.executable基础进程时使用,该进程应该比当前应用程序进程更长命(包括应用程序重启场景)。这被认为是启动相同应用程序新独立实例的官方和首选方法(与修改私有_PYI_ARCHIVE_FILE环境变量相比)。(#8634)
现在,可以在运行时通过新的PYINSTALLER_SUPPRESS_SPLASH_SCREEN环境变量禁用启用了启动画面的冻结应用程序中的启动画面。如果将环境变量设置为1,则不显示启动画面,并且pyi_splash中的函数变为无操作,不会引发错误或显示警告消息。(#8634)
修复问题: (Windows)尝试解决在启用了启动画面的单文件应用程序中,操作系统和/或防病毒程序注入额外依赖VCRUNTIME140.dll的DLLs到进程中时,VCRUNTIME140.dll泄露的问题。(#7106)
(Windows)修复PyInstaller 6.x中的回归问题,导致启用控制台的单文件应用程序在系统会话关闭期间(即,当用户注销或启动系统关闭或重启时)未能清理其临时目录。对于启用控制台的单文件应用程序,这在PyInstaller 6.0之前通过安装的控制台处理程序工作;但由于当代引导程序可执行文件链接到user32.dll,控制台处理程序不再接收CTRL_LOGOFF_EVENT和CTRL_SHUTDOWN_EVENT控制台事件(由于相同的原因,即使在v5.3到6.0之间,这也适用于带有启动画面的构建)。相反,现在会话关闭是通过隐藏窗口和处理WM_QUERYENDSESSION和WM_ENDSESSION事件消息来处理的。(#8648)
(Windows)为了与Windows Terminal兼容,改进单文件构建中CTRL_CLOSE_EVENT控制台事件的处理,以避免用户关闭终端窗口(或标签页)时临时文件泄露。在接收到事件后,父进程现在给予子进程500毫秒的宽限期以退出,之后它终止子进程并继续清理(以前,父进程无限期等待子进程退出,假设CTRL_CLOSE_EVENT也会同时导致子进程退出——这是conhost.exe的情况,但不是Windows Terminal的情况,子进程似乎在操作系统已经终止父进程后才接收到事件)。(#8640)
(Windows)现在,启用了启动画面的单文件/无控制台构建应该在会话关闭期间清理其临时目录(即,当用户注销或启动系统关闭或重启时)。(#8648)
修复PyiFrozenResourceReader.files()在调用时的实现,它应该返回模块的父(包)目录的路径,而不是带有模块名称的子目录。(#8659)
MERGE依赖处理代码现在使用源路径和目标路径作为记账密钥(而不仅仅是源路径)。这解决了在使用MERGE时,应用程序TOC包含被收集多次的文件条目(具有不同的目标名称)的问题。(#8687)
现在,启动画面在通过sys.executable生成的工作子进程中自动被抑制。不显示启动画面,并且pyi_splash中的函数变为无操作,不会引发错误或显示警告消息。(#8634)
不兼容的更改: 尝试通过sys.executable启动新进程并退出当前进程来重启应用程序,现在需要在启动进程之前设置PYINSTALLER_RESET_ENVIRONMENT环境变量。请参阅使用sys.executable启动超越应用程序进程的子进程/实现应用程序重启。(#8634)
弃用: 在v7.0中将移除--manifest的-m简写。(#2560)
钩子: 清理多进程运行时钩子。由于继承的PyInstaller环境检测发生了变化,我们不再需要恢复(现在重命名的)_MEIPASS2环境变量,我们可以移除我们所有的自定义Popen覆盖。(#8634)
支持setuptools >= 7.1.0.0及其新的vendoring依赖方法。(#8720)
引导程序: (Linux)当冻结的可执行文件通过动态链接器/加载器调用启动时(例如,/lib64/ld-linux-x86-64.so.2 /path/to/executable),现在捕获加载器可执行文件的名称,并在重启进程(onedir模式)或启动子进程(onefile模式)时传递给execvp调用。这确保了重启/生成的进程也使用指定的动态加载器,而不是编码在可执行文件的ELF头中的那一个。(#8662)
(Windows)在启用调试的引导程序变体中,现在将调试/警告/错误消息的副本提交给OutputDebugString win32 API,除了它们的主要输出机制(即stderr或消息对话框)。这适用于控制台和无控制台/窗口化引导程序变体。(之前,OutputDebugString仅用于调试启用的无控制台/窗口化引导程序变体中的调试消息,在那里它作为主要输出机制。)(#8642)
(Windows)单文件应用程序的父进程现在设置了一个不可见的窗口,以接收和处理WM_QUERYENDSESSION和WM_ENDSESSION事件消息,这允许它在会话关闭期间正确清理临时文件(即用户注销,或启动系统关闭或重启)。现在,无论是否使用控制台,以及是否使用启动画面,会话关闭期间的清理工作都应该在控制台启用和窗口化/无控制台构建中工作。(#8648)
(Windows)启用了启动画面的单文件应用程序的父进程现在尝试预加载系统范围的VCRUNTIME140.dll副本,并在启动画面设置期间将其优先于捆绑副本(后者将作为Tcl/Tk DLLs的依赖项被加载)。如果系统范围的副本可用,操作系统和/或防病毒程序可能会将其他依赖VCRUNTIME140.dll的第三方DLLs注入到进程中(例如,观察到Trend Micro的User Mode Hooking组件这样做)。这些外部加载的DLLs阻止引导程序在清理阶段卸载VCRUNTIME140.dll,如果加载了捆绑副本,它也会阻止其从应用程序的临时目录中移除。(#8650)
引导程序的调试/错误/警告消息现在总是在临时缓冲区中格式化(即使它们被写入stderr),以确保它们的原子性,并避免在多进程场景中消息部分的交错。(#8642)
将调试/警告/错误消息的前缀从[{PID}]更改为[PYI-{PID}:{SEVERITY}],并在所有引导程序生成的消息中一致应用。(#8642)
通过新引入的_PYI_PARENT_PROCESS_LEVEL环境变量,明确跟踪(子)进程级别。这使我们能够可靠地区分不同的进程类型:在onedir应用程序中,区分主应用程序进程和通过sys.executable生成的工作子进程;在onefile应用程序中,区分父进程、主应用程序进程和通过sys.executable生成的工作子进程。(#8634)
重新工作了继承的PyInstaller环境的检测,现在与原始实现的逻辑相反。到目前为止,运行引导程序的进程被认为是冻结应用程序的(新)顶级进程,除非设置了MEIPASS2环境变量。由于引导程序在运行主应用程序进程中的Python代码之前清除了MEIPASS2环境变量,这意味着应用程序有责任在通过sys.executable生成工作子进程之前恢复MEIPASS2环境变量,例如,防止onefile应用程序再次解包自己。在新实现中,默认假设该进程是同一(实例)应用程序的工作子进程,除非PKG/CArchive的路径已更改(这意味着使用了不同的可执行文件),由新引入的PYI_ARCHIVE_FILE环境变量跟踪。这意味着在多进程场景中通过sys.executable生成工作子进程不需要额外操作,但另一方面,尝试重启应用程序现在需要在生成新应用程序进程之前设置PYINSTALLER_RESET_ENVIRONMENT环境变量。为了防止使用旧版本的PyInstaller构建的应用程序作为子进程启动时出现问题,MEIPASS2环境变量被重命名为PYI_APPLICATION_HOME_DIR;请注意,这指的是内部使用的环境变量,不影响PyInstaller特定的sys._MEIPASS属性。(#8634)
模块加载器: PyiFrozenImporter已从单体元路径查找器(带融合加载器部分)重新设计为路径实例化的路径条目查找器(带融合加载器部分),并注册到Python的默认基于路径的查找器。新的路径实例化设计支持运行时sys.path修改的正确处理;即,现在可以根据sys.path中锚定到顶级应用程序目录(sys._MEIPASS)的条目解析PYZ归档中的模块。这反过来也支持了PEP420命名空间包的完整支持,这些包分散在不同的sys.path位置;无论是在PYZ归档中,在顶级应用程序目录树中的文件系统中,还是完全在外部位置。(#8695)
文档: 在启动画面文档中添加有关启动画面抑制的注释。(#8634)
在高级主题部分扩展新子节,记录PyInstaller引导程序使用的所有公共和私有环境变量。(#8634)
在常见问题和陷阱部分扩展新子节,描述启动sys.executable基础进程的新要求,该进程应该比当前应用程序进程更长命,包括应用程序重启场景。(#8634)
6.9.0 (2024-07-06)更新日志
修复问题: (Windows)解决在32位msys2/mingw32环境中构建启用启动画面的单文件应用程序时,libgcc_s_dw2-1.dll和libwinpthread-1.dll DLL文件未从应用程序的临时目录中删除的问题。(#8587)
(Windows)解决在UPX启用时构建启用启动画面的单文件应用程序时,VCRUNTIME140.dll DLL文件未从应用程序的临时目录中删除的问题。(#7106)
重新允许在分析期间使用连字符名称的隐藏导入(在v6.8.0中被阻止)(#8601)
钩子: 为scipy和numpy 2.0.0之间的不兼容性添加解决方案(模块NotFoundError: No module named 'numpy.f2py'错误)。(#8622)
更新django钩子,以考虑将已弃用的DEFAULT_FILE_STORAGE设置为None的可能性。(#8633)
更新scipy钩子以兼容scipy 1.14.0。(#8622)
引导程序: (Windows)尝试强制卸载单文件父进程中的捆绑DLL:如果单文件应用程序未能删除其临时目录,它现在遍历进程中加载的所有DLL,识别来自其临时目录的那些,并尝试强制卸载它们,然后再次尝试删除临时目录。这应该解决了启动画面使用的Tcl/Tk DLLs加载额外DLLs的问题,并且当它们自己被卸载时未能自动卸载。(#8587)
修复启动画面启用的冻结应用程序中Tcl和Tk共享库卸载的顺序,以防止应用程序清理期间进程崩溃(在Windows上观察到)。(#8587)
6.8.0 (2024-06-08)更新日志
修复问题: (macOS)在macOS上运行代码签名实用程序时,使用硬编码的绝对路径(/usr/bin/codesign)以避免用户PATH中有sigtool的代码签名时出现错误。(#8581)
(Windows)在设置二进制依赖分析的DLL搜索路径时,除了考虑sys.base_prefix指向的目录外,还考虑python可执行文件所在的目录(即os.path.dirname(sys._base_executable)),以防两者不同。这修复了使用从源代码本地构建的python(即,使用随python源代码附带的PCbuild\build.bat脚本)时发现python3.dll的问题。(#8569)
不兼容的更改: 现在阻止在C:\Windows内部进行构建。(#8570)
现在不合法的隐藏导入(例如文件名而不是模块名)现在是构建错误。(#8570)
弃用: 在v7.0中将阻止将Python环境的site-packages目录添加到pathex/--paths。(#8570)
在v7.0中将阻止使用提升的权限运行PyInstaller(例如使用sudo或以管理员身份运行终端)。真正的管理员用户不受影响。(#8570)
引导程序: (POSIX)引导程序现在尝试创建通过--runtime-tmpdir选项给出的运行时临时目录(而不是要求目录已经存在),以匹配Windows上的行为。相对运行时临时目录现在在用于构造应用程序临时目录路径之前解析为绝对完整路径。(#8557)
(Windows)引导程序现在验证通过--runtime-tmpdir选项给出的运行时临时目录,并在驱动器无效或无法创建目录时引发错误(而不是在当前驱动器的根目录中创建应用程序的临时目录)。(#8557)
(Windows)引导程序不再将调试和错误消息从UTF-8转换为本地ANSI代码页,并通过ANSI API(例如fprintf,DebugMessageA)显示它们,而是尝试将这些消息转换为宽字符字符串,并通过宽字符API(例如fwprintf,DebugMessageW)显示它们。(#8557)
引导程序代码进行了重大重构和清理。(#8557)
单文件构建中的启动画面资源现在被提取到应用程序的临时目录中(而不是被提取到应用程序临时目录内的子目录中);因此,它们现在只被提取一次,并在需要时与应用程序本身共享。(#8557)
6.7.0 (2024-05-21)更新日志
修复问题: (POSIX)修复PyInstaller.depend.bindepend.resolve_library_path在ldconfig缓存不可用的情况下的案例(例如,Alpine Linux上的musl libc)。在这种情况下,搜索代码现在区分给出的库名称具有完整后缀(即搜索完全匹配)和库名称没有后缀(即搜索具有匹配基名称的库)的情况。(#8422)
(Windows)修复当脚本位于当前工作目录,且该目录的路径包含两个或更多连续的$或%字符时,入口点脚本的路径被破坏的问题。(#8434)
不兼容的更改: PyInstaller不再尝试展开通过--workpath、--distpath、--specpath和--additional-hooks-dir给出的路径中的环境变量(请注意,其他路径从未受环境变量展开的影响)。作为对波浪线不被shell展开的变通,仍然执行起始波浪线(~)到用户主目录的展开,当通过--workpath=~/path/abc而不是--workpath ~/path/abc传递参数时。(#8441)
钩子: 让sqlalchemy钩子收集所有通过sqlalchemy.dialects和sqlalchemy.plugins入口点注册的方言和插件。这确保了可能在构建环境中可用的第三方方言和插件的收集(例如ibm-db-sa)。(#8465)
pywin32-ctypes钩子现在总是收集win32ctypes.core.ctypes模块,以便始终提供ctypes后端(即,即使我们也由于构建环境中cffi的可用性而收集了cffi后端)。这修复了尽管在构建环境中可用但在运行时不可用的cffi导致的问题(例如,由于通过--exclude-module选项显式排除)。(#8544)
更新pkg_resources钩子以兼容setuptools v70.0.0及更高版本(修复ModuleNotFoundError: No module named 'pkg_resources.extern')。(#8554)
6.6.0 (2024-04-13)更新日志
6.6.0 (2024-04-13) 更新日志
特性: (Windows)实现支持通过符号链接启动时解析可执行文件的真实位置。(#8300)
实现一个选项,可以明确指定收集的Python代码的字节码优化级别,与PyInstaller运行的Python进程中的优化级别无关。在.spec文件级别,这由Analysis构造函数中的可选optimize参数控制。在CLI级别,这由新的--optimize命令行选项控制,它设置了Analysis的optimize参数以及生成的spec文件中的解释器运行时选项。详情请参阅“字节码优化级别”。(#8252)
修复问题: (macOS)显式将版本参数的值转换为字符串,以减轻用户不小心输入整数或浮点数的情况。版本值最终被写入Info.plist作为CFBundleShortVersionString条目,如果这个条目不是字符串类型(例如,是整数),生成的.app捆绑包在启动时崩溃。(#4466)
(Windows)避免在分析包在二进制依赖分析之前所做的动态库搜索修改的子进程中尝试导入PySimpleGUI。当第一次导入时,PySimpleGUI 5.x显示一个“首次运行”对话框,这对于在干净环境中运行的无值守PyInstaller构建来说是一个挑战,例如,在CI流水线中。(#8396)
(Windows)实现一个变通方法,用于在安装了cffi的Python进程中使用-OO(或PYTHONOPTIMIZE=2)运行PyInstaller。我们现在在PyInstaller.compat导入pywin32-ctypes时临时禁用cffi的导入,以确保始终使用ctypes后端,因为cffi后端使用pycparser并且需要文档字符串,这使得它与-OO模式不兼容。(#6345)
钩子: 更新PySide6.Qt3DRender钩子,以兼容PySide6 6.7.0(为PySide6.QtOpenGL模块添加隐藏导入)。(#8404)
**更新scipy.special.***ufuncs钩子,以兼容SciPy 1.13.0(将scipy.special.*cdflib添加到隐藏导入中)。(#8394)
引导程序: (Windows)尝试缩短在窗口/无控制台模式下构建的应用程序启动时旋转等待光标持续的时间。(#8359)
文档: 新增一个名为“字节码优化级别”的文档部分,描述控制收集的Python代码字节码优化级别的新标准方法。(#8252)
在“指定Python解释器选项”中添加一条说明,告知用户仅将优化级别设置为应用程序的嵌入式Python解释器本身,并不会导致以字节码形式收集的模块(即,大多数模块)进行字节码优化。(#8252)
6.5.0 (2024-03-09) 更新日志
特性: (Linux)将收集.hmac文件的机制从#8288扩展到NSS库使用的.chk文件。(#8315)
修复问题: (Linux)修复通过Linux发行版包安装的Qt(以及PySide/PyQt绑定)时,QtWebEngineProcess助手的收集问题。在这种情况下,我们现强制将助手可执行文件收集到绑定包目录下的Qt子目录中的libexec目录中,以匹配PyPI wheel布局。(#8315)
(Linux)修复导致使用PyInstaller v6.x创建的冻结应用程序中locale.getlocale()返回(None, None)而不是用户首选区域设置的回归问题。(#8306)
(Windows)避免在分析包在二进制依赖分析之前所做的动态库搜索修改的子进程中尝试导入pyqtgraph.canvas。在某些情况下,尝试导入pyqtgraph.canvas会导致Python解释器崩溃(这个问题存在于pyqtgraph <= 0.13.3中)。(#8322)
(Windows)修复通过Anaconda在Windows上安装的PySide2和Qt时,QtWebEngineProcess助手的收集问题。助手可执行文件现在被收集到顶级PySide2包目录中,以匹配PyPI wheel布局。(#8315)
(Windows)在Windows 11上抑制关于无法解析的UCRT DLLs(api-ms-win-*.dll)的警告。(#8339)
修复在Windows ARM64上运行Intel构建的Python时找不到引导程序的问题。(#8219)
不兼容的更改: PyInstaller现在明确禁止尝试将多个Qt绑定包(PySide2, PySide6, PyQt5, PyQt6)收集到冻结应用程序中。当执行多个顶级Qt绑定包的钩子时,构建过程会中止,并显示错误消息。此限制适用于单次构建(即单个.spec文件)中的所有Analysis实例。
如果由于这个新限制导致构建错误,请清理您的构建环境(移除您不使用的绑定),或使用--exclude-module明确排除多余的绑定(或等效的排除列表作为参数传递给.spec文件中的Analysis)。自动排除多余的绑定需要通过每个包的钩子来完成,因此请报告有问题的包,以便我们可以为它们编写钩子。(#8329)
钩子: (Linux)在收集QtWebEngine过程中搜索动态加载的NSS库时,考虑到这些库可能位于单独的nss目录或主库目录中。这修复了当代Linux发行版中缺失NSS库的问题,这些发行版不再使用单独的nss目录。(#8315)
为pandas.io.clipboard添加钩子,以排除此模块中对PyQt5的条件导入;该模块主要使用qtpy作为其Qt绑定抽象,而对PyQt5的条件导入干扰了我们的qtpy钩子所做的Qt绑定选择。(#8329)
为qtpy添加钩子,以防止收集多个可用的Qt绑定。该钩子尝试选择单个Qt绑定包,并在PyInstaller.utils.hooks.qt.exclude_extraneous_qt_bindings助手的帮助下排除所有其他Qt绑定包。(#8329)
扩展matplotlib的钩子,以防止收集多个可用的Qt绑定。新的matplotlib.backends.qt_compat钩子尝试通过以下逻辑在PyInstaller.utils.hooks.qt.exclude_extraneous_qt_bindings助手中实现选择单个Qt绑定包:首先,我们检查是否已经运行了任何Qt绑定包的钩子;如果已经运行,则选择那些绑定。如果没有,我们检查QT_API环境变量中用户指定的绑定;如果指定了有效的绑定名称,则选择那些绑定。否则,我们选择一个可用的绑定。一旦选择了一个Qt绑定包,就从钩子包中排除所有其他Qt绑定包的导入。(#8329)
让Qt绑定(PySide2, PySide6, PyQt5和PyQt6)的运行时钩子检查嵌入式:/qt/etc/qt.conf资源的存在,如果不存在,则注入它们自己的版本。这旨在确保捆绑的Qt始终可重新定位,即使包不执行嵌入式qt.conf文件的注入(最明显的是,这似乎是从Linux发行版包中收集的PySide2的情况,以及从Windows、Linux和macOS上的Anaconda收集的PySide2)。(#8315)
PyInstaller现在明确禁止尝试将多个Qt绑定包(PySide2, PySide6, PyQt5, PyQt6)收集到冻结应用程序中。当执行多个顶级Qt绑定包的钩子时,构建过程会中止,并显示错误消息,告知用户情况及如何处理(即,排除多余的包)。此限制适用于spec文件中的所有分析。(#8329)
根据议题8309的讨论,移除win32com的运行时钩子。(#8313)
更新matplotlib.backends的钩子,将QtAgg和Gtk4Agg包含在后端候选列表中。(#8334)
引导程序: 让引导程序在解释器预配置结构中设置configure_locale字段,以便在解释器预初始化期间设置用户首选区域设置。(#8306)
引导程序构建: 在Windows上使用MSVC时,目标架构现在默认为当前Python环境的架构,而不是当前操作系统的架构。(#8219)
6.4.0 (2024-02-10) 更新日志
特性: (Linux)收集伴随共享库的.hmac文件(如果可用)。这允许在启用FIPS的Red Hat Enterprise系统上运行冻结应用程序,这些系统需要OpenSSL加密库实现的自检所要求的HMAC。此外,确保伴随.hmac文件的共享库免于任何额外处理(例如,在构建时使用--strip选项),以避免使HMAC无效。(#8273)
(Windows)使引导程序代码路径在为单文件构建创建临时目录时具备AppContainer感知能力。如果进程在AppContainer内运行,则临时目录的DACL需要明确包含AppContainerSID,否则目录对进程不可访问。(#8291)
(Windows)使PyInstaller的_pyi_rth_utils.tempdir.secure_mkdir的Windows实现具备AppContainer感知能力(matplotlib和win32com运行时钩子用来创建临时目录)。如果进程在AppContainer内运行,则临时目录的DACL需要明确包含AppContainerSID,否则目录对进程不可访问。(#8290)
实施严格的Qt依赖验证,以收集Qt插件和QML组件/插件。我们现在对插件进行初步的二进制依赖分析,并自动排除至少缺少一个Qt依赖的插件。这防止了因缺少Qt共享库而无法使用的插件的收集(例如,从PyPI轮中省略)。此外,我们不允许插件的Qt依赖在Qt共享库的主要位置之外解决,以防止缺失依赖从搜索路径中的替代位置拉取Qt库(例如,当使用PyQt5 PyPI轮时,同时在Linux上还有系统安装的Qt5,在macOS上还有Homebrew安装的Qt5,或者在PATH中自定义的Windows Qt5构建)。(#8226)
修复问题: (Linux)阻止收集libcuda.so.1,这是NVIDIA驱动的一部分,必须与驱动的其他组件匹配。收集副本可能会导致构建和目标系统使用不同版本的NVIDIA驱动时出现问题。(#8278)
(macOS)在验证收集的二进制文件的macOS SDK版本时,处理osxutils.get_macos_sdk_version引发的钩子: (macOS) 让PySide6和PyQt6的运行时钩子在POSIX构建中将sys._MEIPASS添加到DYLD_LIBRARY_PATH前面,以确保QtNetwork能找到捆绑的OpenSSL共享库副本。(#8226)
扩展QtNetwork钩子助手中对PySide2、PyQt5、PySide6和PyQt6的OpenSSL共享库收集,以涵盖所有适用版本的OpenSSL(1.0.2、1.1.x、3.x)。除了Windows,现在Linux和macOS上也会收集OpenSSL共享库。(#8226)
引导程序: (Windows) 更新捆绑的zlib源代码到版本1.3.1。(#8292)
文档: 新增一个名为“常见问题和陷阱”的文档章节,涵盖从冻结应用程序启动外部程序、通过多进程模块进行多进程处理(特别是需要调用multiprocessing.freeze_support()的要求)、在PyInstaller >= 6.0的POSIX构建中使用符号链接及其对分发的影响(例如,复制冻结应用程序或创建zip归档时)、在Windows无控制台构建中sys.stdout和sys.stderr为None的情况。(#8214)
清理文档字符串,移除对exec_command_stdout的提及。(#8173)
更新“构建macOS应用程序包”部分,以反映由PyInstaller 6.0及以后版本生成的macOS应用程序包的布局。添加一条说明,不鼓励使用单文件.app包。(#8214)
更新“理解PyInstaller钩子”部分的引言部分。(#8214)