"""
@author: Tingzhang H
@contact: [email protected]
@desc: Pyinstaller文档中文版
"""
版权归原作者所有 译作权归本人所有
ii
版本 PyInstaller 5.11.0+g7bc
主页 https://pyinstaller.org/
联系邮箱 [email protected]
作者 David Cortesi,基于 Giovanni Bajo 和 William Caban 的结构,基于 Gordon McMil-
lan 的手册
版权 所有权已放弃。
PyInstaller 将 Python 应用程序及其所有依赖项打包成单个程序包。用户可以在没有安装 Python 解释器或任何模块的情况下运行打包的应用程序。PyInstaller 支持 Python 3.7 及更高版本,并正确地打包了许多重要的 Python 包,如 numpy、matplotlib、PyQt、wxPython 等。
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 文件夹中可用。
2.1.1 Windows
PyInstaller 在 Windows 8 和更新版本上运行。它可以创建图形窗口应用程序 (不需要命令窗口)。
2.1.2 macOS
PyInstaller 在 macOS 10.15 (Catalina) 或更新版本上运行。它可以构建图形窗口应用程序 (不使用终端窗口)。PyInstaller 构建的应用程序与您运行它的 macOS 版本兼容,并且与后续版本兼容。它可以在 macOS 机器上构建 x86_64、arm64 或混合 universal2 二进制文件。有关详细信息,请参见“macOS 多架构支持”。
2.1.3 GNU/Linux
PyInstaller 需要 ldd 终端应用程序来发现每个程序或共享库所需的共享库。它通常在分发包 glibc 或 libc-bin 中找到。
它还需要 objdump 终端应用程序从对象文件中提取信息,以及 objcopy 终端应用程序将数据附加到 bootloader。这些通常在分发包 binutils 中找到。
2.1.4 AIX、Solaris、FreeBSD 和 OpenBSD
用户报告在这些平台上运行 PyInstaller 取得了成功,但未经过测试。需要 ldd 和 objdump 命令。
每个打包的应用程序包含一个引导程序,它设置应用程序并启动它 (请参见 “详细说明引导程序过程” )。
当您使用 pip 安装 PyInstaller 时,安装程序会尝试为该平台构建 bootloader。如果成功,则安装将继续,并准备使用 PyInstaller。
如果 pip 设置未能构建引导程序,或者您未使用 pip 进行安装,则必须手动编译引导程序。该过程在 “构建引导” 下进行了说明。
PyInstaller 采用双重许可方案,使用 GPL 2.0 许可证,并附加了一个例外,允许您使用它来构建商业产品 - 列在下面 - 和 Apache 许可证 2.0 版,仅适用于某些文件。要查看 Apache 许可证适用的文件以及 GPL 适用的文件,请参见 PyInstaller 源代码库中的“COPYING.txt”文件。
GPL 许可证例外的快速摘要:
欢迎您的贡献! PyInstaller 是由一群志愿者维护的。所有贡献,如社区支持、错误报告、错误修复、文档改进、增强和想法都是受欢迎的。
PyInstaller 是一项由志愿者创建和维护的自由软件项目。它的生存和发展基于来自社区的支持,而您甚至考虑为 PyInstaller 贡献是非常慷慨的。
由于现在所有的核心开发人员都在业余时间里工作在 PyInstaller 上,您可以帮助我们(和项目)最好的方式是遵循一些简单的指南。贡献质量越高,我们合并它们的工作量就会越少,而我们合并它们的时间就会越早
如果您在任何时候遇到困难,可以在 GitHub 上创建一个支持工单。
关于我们的开发流程和方法的更多信息,请参见“开发指南”。
2.3.1 一些你可以提供帮助的想法
这里有一些你可以提供帮助的想法:
6 第二章目录:
非常感谢!
如果您计划经常贡献,请申请对主Git仓库的写访问权限。我们很高兴能够欢迎您加入我们的团队!
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
2.4.1 从源存档安装
发布版本的PyInstaller的源代码存档可在PyPI和PyInstaller下载页面上找到。
**注意:**即使源存档提供了setup.py脚本,安装方式通过python setup.py install
已经被弃用了,不再使用。相反,请从解压缩的源目录中运行pip install
。如下所述。
安装程序是:
相同的过程适用于从手动GIT检出安装:
git clone https://github.com/pyinstaller/pyinstaller
cd pyinstaller
pip install .
如果您打算对源代码进行更改并希望立即生效,而无需每次都重新安装包,则可以以可编辑模式安装它:
2.4. 如何安装 PyInstaller 7
pip install -e
对于Windows、GNU/Linux和macOS之外的平台,必须首先为您的平台构建引导程序:请参见“构建引导程序”。构建引导程序后,使用pip install .命令完成安装。
2.4.2 验证安装
在所有平台上,命令pyinstaller现在应该存在于执行路径上。要验证这一点,请输入以下命令:
pyinstaller --version
结果应类似于已发布版本的4.n,并且应开发分支的4.n.dev0-xxxxxx。
如果找不到该命令,请确保执行路径包括正确的目录:
要在Windows中显示当前路径,命令是echo % path%,在其他系统中,echo $ PATH。
**注意:**如果由于脚本目录不在PATH中而无法使用pyinstaller命令,则可以改InsteadInokePyInstaller模块,通过运行python -m PyInstaller(注意模块名,大小写敏感)来实现。
当您在多个Python环境中安装了PyInstaller,并且您无法确定pyinstaller命令将从哪个安装中运行时,这种调用形式也很有用。
2.4.3 已安装命令
完整的安装会将这些命令放在执行路径上:
8 第二章目录:
本节介绍了PyInstaller的基本思想。这些思想适用于所有平台。特定选项和案例在下面讨论,详见“使用PyInstaller”。
PyInstaller读取由您编写的Python脚本。它分析您的代码以发现脚本在执行时需要的每个其他模块和库。然后它收集所有这些文件的副本-包括活动的Python解释器! -并将它们与您的脚本放在单个文件夹中,或者可选地放在单个可执行文件中。
对于绝大多数程序,可以用一个简短的命令完成这一过程:
pyinstaller myscript.py
或者,你可以通过添加一些选项使其成为单文件的可执行窗口应用程序:
pyinstaller --onefile --windowed myscript.py
你将这个捆绑文件夹或文件分发给别人,他们可以执行你的程序。对于你的用户来说,这个应用程序是自包含的。他们不需要安装任何特定版本的Python或任何模块。他们甚至无需安装Python。
注意:PyInstaller的输出是特定于操作系统和Python版本的。这意味着,如果要为:
准备发行版,你必须在该操作系统下,在该Python版本下运行PyInstaller。执行PyInstaller的Python解释器是捆绑中的一部分,它特定于操作系统和字长。
2.5.1 分析:查找程序需要的文件
你的脚本还需要哪些模块和库才能运行?(有时这些称为“依赖项”。)
为了找出来,PyInstaller会查找你脚本中所有的import语句。它会找到导入的模块,并在其中查找import语句,以此递归下去,直到获得可能需要使用的代码模块清单。
PyInstaller理解常用于Python包的“egg”分布格式。如果你的脚本从“egg”导入模块,PyInstaller会将“egg”及其依赖项添加到所需文件集中。
PyInstaller还了解许多主要的Python包,包括GUI包Qt(通过PyQt或PySide导入)WxPython,TkInter,matplotlib和其他主要包。有关完整列表,请参见支持的包。
一些Python脚本以PyInstaller无法检测的方式导入模块:例如,使用可变数据的__import __()函数,使用importlib.import_module(),或在运行时操作sys.path值。如果你的脚本需要PyInstaller不知道的文件,你必须协助它:
可以在pyinstaller命令行上提供其他文件。
可以在命令行上提供其他导入路径。
您可以编辑PyInstaller为您的脚本第一次运行时编写的myscript.spec文件,该spec文件可以告诉PyInstaller有关您脚本独有的代码模块。
您可以编写“hook”文件来通知PyInstaller隐式导入的模块。如果您为其他用户可能也会使用的包创建“hook”文件,则可以将您的hook文件贡献给PyInstaller。
如果您的程序依赖于访问某些数据文件,还可以将它们包含在捆绑包中。这可以通过修改spec文件来实现,这是一个高级主题,在“使用spec文件”中介绍。
为了在运行时定位包含的文件,您的程序需要能够以一种方式在运行时学习其路径,无论它是否从捆绑中运行。这在“运行时信息”下介绍。
PyInstaller不会包含应该存在于此OS的任何安装库。例如,在GNU / Linux中,它不会捆绑任何/lib或/usr/lib文件,因为这些被认为会在每个系统中找到。
2.5.2 打包到一个文件夹
当你应用PyInstaller到myscript.py时,结果默认为单个名为myscript的文件夹。此文件夹包含所有脚本的依赖项,以及一个名为myscript的可执行文件(在Windows中为myscript.exe)。
你可以将该文件夹压缩为myscript.zip并传输给你的用户。他们只需解压缩即可安装程序。用户可以通过打开文件夹并在其中启动myscript可执行文件来运行你的应用程序。
使用一个文件夹模式调试出现的问题非常容易。你可以清楚地看到PyInstaller收集到的所有文件。
一个文件夹包的另一个优点是,当你更改代码时,只要它导入 完全相同的依赖项集,你可以发送仅更新的myscript可执行文件。这通常比整个文件夹小得多。 (如果你更改脚本,以便它导入更多或不同的依赖项,或者如果依赖项被更新,则必须重新分发整个捆绑。)
一个小的不利之处是,单个文件夹格式意味着一个文件夹包含大量文件。你的用户必须在一长串名称或大量图标数组中找到myscript可执行文件。此外,用户可以通过意外拖动文件出文件夹来创建问题。
2.5.3 一个文件夹程序是如何工作的
一个捆绑的程序始终从PyInstaller引导程序开始执行。这是文件夹中myscript执行文件的核心。
PyInstaller引导程序是特定于平台(Windows,GNU / Linux,macOS等)的二进制可执行程序。当用户启动您的程序时,就是引导程序在运行。引导程序创建一个临时的Python环境,使得Python解释器可以在myscript文件夹中找到所有导入的模块和库。
引导程序启动一个Python解释器的副本来执行你的脚本。如果所有必要的支持文件都包含在内,一切都会如常进行。
(这是一个概述。有关更多详细信息,请参见下面的“详细引导过程”。)
2.5.4 打包到一个文件
PyInstaller可以将你的脚本和所有依赖项捆绑到一个名为myscript的单个可执行文件中(在Windows中为myscript.exe)。
优点是,用户可以获得他们理解的单个可执行文件来启动应用程序。缺点是,任何相关文件,例如README,必须单独分发。此外,单个可执行文件的启动速度比一个文件夹更慢一些。
在尝试打包到一个文件之前,请确保你的应用程序在打包到一个文件夹时正常工作。在一个文件夹模式下,诊断问题要容易得多。
2.5.5 一个文件的程序是如何工作的
引导程序也是单个文件的捆绑。当它启动时,它在这个操作系统的适当的临时文件夹位置中创建一个临时文件夹。该文件夹的名称为_MEIxxxxxx,其中xxxxxx是一个随机数。
单个可执行文件包含所有你脚本使用的Python模块的嵌入式档案,以及任何非Python支持文件(例如.so文件)的压缩副本。引导程序解压缩支持文件并将副本写入临时文件夹中。这可能需要一些时间。这就是为什么一个单文件的应用程序启动比一个单文件夹慢一点的原因。
注意:目前PyInstaller不会保留文件属性,请参见#3926。
创建临时文件夹后,引导程序在临时文件夹的上下文中按照一个文件夹捆绑的方式进行,当捆绑的代码终止时,引导程序删除临时文件夹。
(在GNU/Linux及相关系统中,可以使用“no-execution”选项挂载/tmp文件夹。但该选项与PyInstaller的一文件打包不兼容。它需要在/tmp中执行代码。如果您了解目标环境,–runtime-tmpdir 可能会是一个解决方法。)
由于程序会创建一个带有唯一名称的临时文件夹,因此您可以运行多个应用程序副本;它们不会相互干扰。但是,运行多个副本会占用大量磁盘空间,因为没有共享。
如果程序崩溃或被终止(在Windows上由任务管理器终止,在macOS上选择“强制退出”,在Unix上使用“kill -9”命令),则_MEIxxxxxx临时文件夹不会被删除。因此,如果您的应用程序经常崩溃,用户将失去多个_MEIxxxxxx临时文件夹的磁盘空间。
可以通过使用–runtime-tmpdir命令行选项控制_MEIxxxxxx文件夹的位置。指定的路径存储在可执行文件中,引导程序将在指定文件夹内创建_MEIxxxxxx文件夹。有关详细信息,请参阅“定义提取位置”。
**注意:**不要在Windows上给单文件可执行文件赋予管理员权限(“以管理员身份运行此程序”)。有一种不太可能但不可能的方式可以使恶意攻击者在引导程序准备临时文件夹时破坏其中的共享库之一。在普遍情况下分发特权程序时,请确保文件权限防止共享库或可执行文件被篡改。否则,具有这些文件写访问权的未提升进程可能会通过修改这些文件来升级权限。
**注意:**使用os.setuid()的应用程序可能会遇到权限错误。捆绑应用程序运行的临时文件夹在调用setuid之后可能无法读取。如果您的脚本需要调用setuid,则最好使用单文件夹模式,以便更好地控制其文件的权限。
pyinstaller命令的语法为:
pyinstaller[options]script[script...]|specfile
在最简单的情况下,将当前目录设置为程序myscript.py的位置,然后执行:
pyinstaller myscript.py
PyInstaller会分析myscript.py并:
在dist文件夹中,您会找到分发给用户的捆绑应用程序。
通常情况下,您只需要在命令行上命名一个脚本。如果命名了多个脚本,则所有脚本都会被分析并包含在输出中。但是,命名的第一个脚本提供的是.spec文件的名称以及可执行文件夹或文件的名称。运行时,其代码是第一个要执行的代码。
对于某些用途,您可以编辑mscript.spec的内容(在“使用规范文件”下进行描述)。完成此操作后,您将规范文件的名称命名为PyInstaller而不是脚本:
pyinstaller myscript.spec
myscript.spec文件包含大部分通过带脚本文件作为参数运行pyinstaller(或pyi-makespec)时指定的选项提供的信息。通常情况下,运行具有规范文件的pyinstaller时,无需指定任何选项。在从规范文件构建时,只有少数命令行选项会生效。
您可以为脚本或规范文件提供路径,例如
pyinstaller options... ~/myproject/source/myscript.py
或在Windows上:
pyinstaller "C:\Documents and Settings\project\myscript.spec"
2.6.1 选项
pyinstaller命令的完整选项列表如下:
位置参数
脚本名称
要处理的脚本文件的名称或一个.spec文件。如果指定了.spec文件,则大多数选项都不需要,不会起作用。
可选参数
-h,-help
显示此帮助消息并退出。
-v,--version
显示程序版本信息并退出。
--dispath DIR
放置捆绑应用程序的位置(默认值:./dist)。
--workpath WORKPATH
放置所有临时工作文件、.log、.pyz 等的位置(默认值:./build)。
-y,--noconfirm
替换输出目录(默认值:SPECPATH/dist/SPECNAME)而无需确认。
--upx-dirUPX_DIR
UPX实用程序的路径(默认值:搜索执行路径)。
-a,--ascii
不包括Unicode编码支持(默认情况下,如果可用,则包括Unicode编码支持)。
--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 文件名称(默认值:第一个脚本的基本名称)。
2.6. 使用 PyInstaller 13
捆绑什么,在哪里搜索
--add-data
添加非二进制文件或文件夹到可执行文件中。路径分隔符是平台特定的,使用 os.pathsep(在 Windows 上是‘;’,在大多数 Unix 系统上是‘:’)。该选项可以多次使用。
--add-binary
添加二进制文件到可执行文件中。有关更多详细信息,请参见--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 HOOKS_PATH
用于搜索钩子的附加路径。该选项可以多次使用。
--runtime-hook RUNTIME_HOOKS
自定义运行时钩子文件路径。运行时钩子是与可执行文件捆绑的代码,并在任何其他代码或模块之前执行,以设置运行时环境的特殊特性。该选项可以多次使用。
--exclude-module EXCLUDES
忽略的可选模块或包(非路径名而是 Python 名称)。该选项可以多次使用。
--splash IMAGE_FILE
(实验性)向应用程序添加一个带有图像 IMAGE_FILE 的启动画面。在解压缩时,启动画面可以显示进度更新。
14 第 2 章 内容:
如何生成
-d {all,imports,bootloader,noarchive},--debug {all,imports,bootloader,noarchive}
提供帮助调试冻结应用程序。可多次提供此参数以选择以下多个选项。- all:所有三个选项。- imports:指定-v选项给基础 Python 解释器,使其在每次初始化模块时打印一条消息,显示从哪个位置(文件名或内置模块)加载模块。请参见 https://docs.python.org/3/using/cmdline.html#id4。 - bootloader:告诉启动程序在初始化和启动捆绑应用程序时发布进度消息。用于诊断缺失导入的问题。 - noarchive:存储所有冻结的 Python 源文件,而不是将它们存储为结果执行文件中的存档。
--python-option PYTHON_OPTION
指定要在运行时传递给 Python 解释器的命令行选项。当前支持“v”(等效于“--debug imports”)、“u”和“W ”。
-s,--strip
对可执行文件和共享库应用符号表削减(不建议在 Windows 上使用)。
--noupx
即使可用,也不要使用 UPX(在 Windows 和 *nix 之间的工作方式不同)。
--upx-exclude FILE
当使用 upx 时,防止对二进制文件进行压缩。如果在压缩期间 upx 损坏某些二进制文件,则通常使用此选项。FILE 是不带路径的二进制文件名。该选项可以多次使用。
**仅适用于 Windows 和 Mac OS X 的选项**
-c,--console,--nowindowed
为标准 i/o 打开控制台窗口(默认值)。在 Windows 上,如果第一个脚本是“.pyw”文件,则该选项无效。
-w,--windowed,--noconsole
Windows 和 Mac OS X:不为标准 i/o 提供控制台窗口。在 Mac OS 上,这也会触发构建 Mac OS .app 捆绑包。在 Windows 上,如果第一个脚本是“.pyw”文件,则自动设置此选项。*NIX 系统上忽略此选项。
-i ,--icon
FILE.ico:将图标应用于 Windows 可执行文件。FILE.exe,ID:从 exe 中提取具有 ID 的图标。FILE.icns:将图标应用于 Mac OS 上的 .app 捆绑包。如果输入的图像文件不是平台格式(Windows 上的 ico、Mac 上的 icns),则 PyInstaller 尝试使用 Pillow 将图标转换为正确的格式(如果安装了 Pillow)。使用“NONE”来不应用任何图标,从而使操作系统显示一些默认值(默认值:应用 PyInstaller 的图标)。该选项可以多次使用。
--disable-windowed-traceback
禁用窗口(noconsole)模式下无处理异常的 traceback 转储(仅适用于 Windows 和 macOS),并显示一条消息,说明禁用了此功能。
**2.6. 使用 PyInstaller 15**
**仅适用于 Windows 的选项**
--version-fileFILE
将版本资源从FILE加入exe文件中。
-m ,--manifest
加入manifest文件或XML到exe文件中。
--no-embed-manifest
生成外部的.exe.manifest文件而不是将manifest嵌入exe文件中。仅适用于onedir模式; 在onefile模式中,无论这个选项如何,manifest都会被嵌入。
-r RESOURCE,--resource RESOURCE
向Windows可执行文件添加或更新资源。RESOURCE有一到四个项,FILE[,TYPE[,NAME[,LANGUAGE]]]。FILE可以是数据文件或exe/dll。对于数据文件,至少必须指定TYPE和NAME。LANGUAGE默认为0,或可以指定为通配符*以更新指定类型和名称的所有资源。对于exe/dll文件,如果省略了TYPE、NAME和LANGUAGE,或将其指定为通配符*,所有来自FILE的资源都将添加/更新到最终可执行文件中。此选项可以多次使用。
--uac-admin
使用此选项创建一个请求在应用程序启动时提升权限的清单。
--uac-uiaccess
使用此选项可允许提升后的应用程序使用远程桌面。
Windows Side-By-Side Assembly Searching Options (Advanced)
--win-private-assemblies
将打包到应用程序中的任何共享组件更改为私有组件。这意味着这些组件的确切版本将始终被使用,并且在用户计算机上以系统级别安装的任何较新版本都将被忽略。
--win-no-prefer-redirects
在搜索共享或私有组件以打包到应用程序中时,PyInstaller将不喜欢遵循重定向到较新版本的策略,并会尝试打包组件的精确版本。
Mac Os Specific Options
--argv-emulation
启用macOS应用程序捆绑包的argv仿真。如果启用,初始打开文档/URL事件将由引导加载程序处理,并将传递的文件路径或URL附加到sys.argv中。
--osx-bundle-identifier BUNDLE_IDENTIFIER
Mac OS .app绑定标识符被用作用于代码签名的默认唯一程序名称。通常的形式是DNS反转表示法的分层名称。例如:com.mycompany.department.appname(default:firstscript’sbasename)
--target-architectureARCH,--target-arch ARCH
目标架构(仅适用于macOS;有效值:x86_64、arm64、universal2)。允许在通用2和单架构版本的冻结应用程序之间切换(只要python安装支持目标架构)。如果没有指定目标架构,就会以当前运行的架构为目标。
--codesign-identifier IDENTITY
代码签名标识符(仅适用于macOS)。使用提供的标识符对收集到的二进制文件和生成的可执行文件进行签名。如果没有提供签名标识符,则执行自发签名。
**第16章2.内容:**
--osx-entitlements-file FILENAME
在代码签名收集的二进制文件时使用的权限文件(仅适用于macOS)。
**Rarely Used Special Options**
--runtime-tmpdirPATH
在_onefile_ -mode中提取库文件和支持文件的位置。如果给出此选项,则引导程序将忽略运行时操作系统定义的任何临时文件夹位置。_MEIxxxxxx_-文件夹将在此处创建。只有当您知道自己在做什么时,请使用此选项。
--bootloader-ignore-signals
告诉引导程序忽略信号而不是将它们转发给子进程。在诸如监管进程向引导程序和子进程发送信号(例如通过进程组)的情况下非常有用,以避免向子进程发出两次信号。
2.6.2缩短命令
由于其众多选项,完整的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文件行延续:
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
2.6.使用PyInstaller 17
2.6.3从Python代码中运行PyInstaller
如果您想要从Python代码运行PyInstaller,可以使用PyInstaller.__main__中定义的run函数。例如,以下代码:
import PyInstaller.__main__
PyInstaller.__main__.run([
'my_script.py',
'--onefile',
'--windowed'
])
等价于:
pyinstaller my_script.py --onefile --windowed
2.6.4使用UPX
UPX是一个免费的可执行文件和库文件压缩实用程序。它适用于大多数操作系统,可以压缩大量的可执行文件格式。请查看UPX主页进行下载,并查看支持的文件格式列表。
当UPX可用时,PyInstaller使用它来逐个压缩每个收集到的二进制文件(可执行文件、共享库或python扩展),以减少冻结应用程序(one-dir捆绑目录或one-file可执行文件)的总体大小。冻结应用程序的可执行文件本身没有经过UPX压缩(不管是one-dir还是one-file模式),因为其大部分大小包含了已经包含了经过压缩的文件的嵌入式档案。
PyInstaller 在标准可执行路径(由PATH环境变量定义)或通过–upx-dir 命令行选项指定的路径中查找 UPX。如果找到它,将自动使用。可以使用–noupx 命令行选项完全禁用 UPX。
注意: UPX 目前仅在 Windows 上使用。在其他操作系统上,即使找到 UPX,也不会处理收集的二进制文件。在现代 linux 发行版上构建的共享库(例如,Python 共享库)似乎在使用 UPX 处理时会破坏,导致无法运行的应用程序包。在 macOS 上,UPX 目前无法处理 dylib 共享库;此外,经过 UPX 压缩的文件未能通过codesign实用程序的验证检查,因此无法进行代码签名(这是 Apple 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 排除。例如,据报告,来自 PySide2 软件包的 32 位 Windows 二进制文件(Qt5 DLL 和 Python 扩展模块)会被 UPX 破坏。
18 第 2 章 内容:
从版本 5.0 开始:与之前的版本不同,它将提供的 UPX 排除名称与收集二进制文件的基本名称进行比较(由于不完整的大小写规范化,在 Windows 上需要提供小写排除名称),现在 UPX 排除模式匹配使用 OS 默认的大小写敏感性,并支持通配符 (*) 运算符。它还支持指定文件的(完整或部分)父路径。
提供的 UPX 排除模式名称与收集的二进制文件的源路径进行匹配,并从右到左执行匹配。
例如,要排除来自 PySide2 软件包的 Qt5 DLL,请使用–upx-exclude “Qt*.dll”,要排除来自 PySide2 软件包的 Python 扩展,请使用–upx-exclude “PySide2*.pyd”。
2.6.5 起动画面(实验性)
**注意:**此功能与 macOS 不兼容。在当前设计中,启动画面在次要线程中运行,这是 macOS 上的 Tcl/Tk(或者更确切地说是底层 GUI 工具包)所不允许的。
一些应用程序可能需要在应用程序(引导程序)启动后尽快显示起动画面,因为特别是在单文件模式下,大型应用程序可能需要长时间的提取/启动时间,而引导程序准备所有这些时,用户不能判断应用程序是否成功启动。
引导程序能够显示一张单一的图像(即仅图像),该图像会在实际的主提取过程开始之前显示。启动画面支持不透明和硬剪切透明图像作为背景图像,因此也可以显示非矩形起动画面。
此启动画面基于 Tcl/Tk,这是 Python 模块 tkinter 使用的同一库。PyInstaller 在编译时将 tcl 和 tk 的动态库捆绑到应用程序中。这些库在启动应用程序时在引导程序中加载(如果程序已封装为单文件存档)。由于所需动态库的文件大小非常小,在应用程序启动和启动画面之间几乎没有延迟。启动画面所需文件的压缩大小约为 1.5 MB。
作为附加功能,可以在启动画面上选择性地显示文本。这可以从 Python 中更改/更新。这提供了在 Python 程序的较长启动过程(例如等待网络响应或将大型文件加载到内存中)中显示起动画面的可能性。您还可以在启动画面后启动一个 GUI,只有在完全初始化后才能关闭起动画面。可选择设置文本的字体、颜色和大小。然而,必须在用户系统上安装字体,因为它未捆绑。如果字型不可用,则使用备用字型。
如果已将起动画面配置为显示文本,则它将自动(作为单个文件存档)显示当前正在解压缩的文件名,这充当进度条。
2.6.6 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()
当然,为了防止程序被用作普通的 Python 脚本而没有引导程序,应将其导入放在 try … except 块内。有关详细描述,请参阅 pyi_splash 模块(详细)。
2.6.7 定义提取位置
在罕见情况下,当您将软件包捆绑为单个可执行文件(请参见 Bundling to One File 和 How the One-File Program Works)时,您可能希望在编译时控制临时目录的位置。可以使用 --runtime-tmpdir 选项来做到这一点。如果给出此选项,则引导程序将忽略运行时 OS 定义的任何临时文件夹位置。请仅在您知道自己在做什么时使用此选项。
2.6.8 支持多个平台
如果您只为操作系统和 Python 的一个组合分发应用程序,请像安装任何其他程序包一样安装 PyInstaller,并在常规开发设置中使用它。
支持多个 Python 环境
当您需要将应用程序捆绑在一个操作系统中,但需要支持不同版本的Python和支持库,比如Python 3.6版本和Python 3.7版本,或者使用Qt4的支持版本和使用Qt5的开发版本,我们建议您使用venv。使用_venv_,您可以维护不同的Python和安装包的组合,并轻松切换一个组合到另一个组合。这些称为虚拟环境或短称为_venvs_。
使用_venv_创建任意数量的开发环境,每个环境都有其自己独特的Python和安装包的组合。
在每个虚拟环境中安装PyInstaller。
在每个虚拟环境中使用PyInstaller构建您的应用程序。
请注意,使用_venv_时,PyInstaller命令的路径是:
在Windows下,pip-Win软件包使设置不同环境并在它们之间切换变得特别容易。在GNU/Linux和macOS下,您可以在命令行中切换环境。
有关Python虚拟环境的详细信息,请参阅PEP 405和官方的Python教程。
第20章2. 目录:
支持多个操作系统
如果您需要将应用程序分发给多个操作系统,例如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
…必要的其他选项…
./myscript.py
PyInstaller从共享的同步文件夹读取脚本,但将其工作文件和捆绑的应用程序写入本地虚拟机文件夹。
如果您在多个平台上共享相同的主目录,例如GNU/Linux和macOS,则需要在每个平台上设置PYINSTALLER_CONFIG_DIR环境变量,否则PyInstaller可能会为一个平台缓存文件并在另一个平台上使用它们,因为默认情况下,它使用你的主目录的子目录作为其缓存位置。
据说可以使用免费的Wine环境在GNU/Linux下进行Windows交叉开发。需要更多详细信息,请参见如何贡献。
第2.6.9节 捕获Windows版本数据
Windows应用程序可能需要一个版本资源文件。版本资源包含一组数据结构,有些包含二进制整数,有些包含字符串,用于描述可执行文件的属性。有关详细信息,请参见Microsoft版本信息结构页面。
版本资源很复杂,一些元素是可选的,另一些是必需的。在查看属性对话框的版本选项卡时,显示的数据与资源的结构之间没有简单的关系。因此,PyInstaller包括pyi-grab_version命令。它使用具有版本资源的任何Windows可执行文件的完整路径名调用:
pyi-grab_version executable_with_version_resource
该命令将以可读形式写入表示版本资源的文本,输出到标准输出。可以从控制台窗口复制它,也可以将其重定向到文件。然后,您可以编辑版本信息,以使其适应您的程序。使用pyi-grab_version,您可以查找显示所需信息的可执行文件,复制其资源数据,并修改以适应您的套餐。
版本文本文件采用UTF-8编码,可能包含非ASCII字符。 (版本资源字符串字段允许Unicode字符。) 除非您确定它仅包含ASCII字符串值,请一定使用UTF-8编辑和保存文本文件。
您编辑的版本文本文件可以使用–version-file选项提供给pyinstaller或pyi-makespec。文本数据被转换为版本资源,并安装在捆绑的应用程序中。
在版本资源中,有两个64位二进制值,FileVersion和ProductVersion。在版本文本文件中,这些值以四个元素的元组给出,例如:
第2.6章 使用PyInstaller 21
filevers=(2, 0, 4, 0),
prodvers=(2, 0, 4, 0),
每个元组的元素从最高位到最低位表示16位值。例如,值(2, 0, 4, 0)解析为16进制数0002000000040000。
您还可以在捆绑的应用程序创建后使用pyi-set_version命令将版本资源从文本文件安装:
pyi-set_version version_text_file executable_file
pyi-set_version实用程序读取由pyi-grab_version编写的版本文本文件,将其转换为Version资源,并将该资源安装在指定的_executable_file_中。
对于高级使用,检查由pyi-grab_version编写的版本文本文件。您会发现它是创建VSVersionInfo对象的Python代码。
VSVersionInfo的类定义在PyInstaller发行版文件夹utils/win32/versioninfo.py中。您可以编写导入versioninfo的程序。在该程序中,您可以eval版本信息文本文件的内容以生成VSVersionInfo对象。您可以使用该对象的.toRaw()方法以二进制形式生成版本资源。或者,您可以应用unicode()函数到对象以重现版本文本文件。
2.6.10 构建macOS应用程序包
在macOS下,PyInstaller始终在dist中构建一个UNIX可执行文件。如果您指定了–onedir,则输出是一个名为myscript的文件夹,其中包含支持文件和名为myscript的可执行文件。如果您指定了–onefile,则输出是一个名为myscript的单个UNIX可执行文件。任何一个可执行文件都可以从终端命令行启动。标准输入和输出通过该终端窗口正常工作。
如果您使用–windowed和任意一个选项,则dist文件夹还包含名为myscript.app的macOS应用程序。
如您所知,应用程序是一种特殊的文件夹类型。由PyInstaller构建的应用程序包含一个名为Contents的文件夹,该文件夹始终存在,并包含:
使用–icon参数指定自定义应用程序图标。它将被复制到Resources文件夹中。
(如果不指定图标文件,PyInstaller将提供一个带有PyInstaller标志的fileicon-windowed.icns文件。)
使用–osx-bundle-identifier参数添加应用程序包标识符。这将成为用于代码签名的CFBundleIdentifier(请参见PyInstaller代码签名食谱,以及更多细节,请参见Apple代码签名概览技术说明)。
您可以通过编辑spec文件来向Info.plist添加其他项目。有关macOS包的_Spec文件选项_,请参阅下文。
第二十二章 2. 目录:
2.6.11 平台特定注释
GNU/Linux
使GNU/Linux应用程序向前兼容
在GNU/Linux下,PyInstaller不会将C标准库(通常是Gnu版本的glibc)与应用程序捆绑在一起。相反,应用程序期望动态链接到运行它的本地操作系统中的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运行时,该运行时已更名为“通用CRT”,并成为Windows 10的一部分。对于Windows Vista到Windows 8.1,有Windows Update包,可能已安装在目标系统中。因此,您有以下选项:
2.6. 使用PyInstaller 23
macOS
使macOS应用程序向前兼容
在macOS上,来自OS一个版本的系统组件通常与后续版本兼容,但是可能无法与较旧版本兼容。虽然PyInstaller不会收集OS的系统组件,但收集的第三方二进制文件(例如Python扩展模块)是根据特定版本的OS库构建的,可能支持或不支持较旧的OS版本。
因此,确保您的冻结应用程序支持旧版OS的唯一方法是在您希望支持的最旧版本的OS上冻结它。这特别适用于使用Homebrew Python构建时,因为其二进制文件通常明确地针对正在运行的OS。
例如,要确保与“Mojave”(10.14)和更高版本兼容,您应该在macOS 10.14的副本中设置完整环境(即,在必要时使用虚拟机安装python、PyInstaller、应用程序的代码和其所有依赖项)。然后使用PyInstaller在该环境中冻结您的应用程序;生成的冻结应用程序应与该版本以及更高版本的macOS兼容。
在macOS中构建32位应用程序
**注意:**此节大部分已经过时,因为在macOS 10.15 Catalina中删除了对32位应用程序的支持(有关现代版本的macOS的64位多体系结构支持,请参见_此处_)。然而,PyInstaller仍支持构建32位启动加载程序,并且仍然可以从python.org获取32位/64位Python安装程序(某些版本的Python 3.7)。
较旧版本的macOS支持32位和64位可执行文件。PyInstaller使用用于执行它的Python的字长构建应用程序。那通常是Python的64位版本,导致生成64位可执行文件。要创建32位可执行文件,请在32位Python下运行PyInstaller。
要验证安装的Python版本是否支持以64位或32位模式执行,请使用file命令对Python可执行文件进行如下操作:
$ 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
第二章 第24节 内容:
注意: PyInstaller 不再为 macOS 提供预编译的 32 位 bootloader,如果要使用 PyInstaller 和 32 位 Python,需要自己构建 bootloader,使用支持编译 32 位的 XCode 版本。根据编译器/工具链的不同,您还需要显式传递–target-arch=32bit参数给waf命令。
获取已打开的文档名称
当用户双击已注册应用程序的某种类型的文档或将其拖放到应用程序图标上时,macOS 会启动应用程序并以 OpenDocument AppleEvent 的形式提供打开文档的名称。
这些事件通常通过应用程序中安装的事件处理程序来处理(例如,使用 CarbonAPI 通过 ctypes,或者使用 UI 工具包提供的工具,例如 tkinter 或 PyQt5)。
或者,PyInstaller 还支持将打开的文档/URL 事件转换为附加到 sys.argv 的参数。这仅适用于在应用程序启动期间接收到的事件,即在启动冻结代码之前。要处理已在应用程序运行时分派的事件,需要设置相应的事件处理程序。
有关详情,请参阅本节。
根据 Python 是作为 32 位还是 64 位可执行文件构建的,您可能需要设置或取消设置环境变量 OBJECT_MODE。以下命令可用于确定大小:
$ python -c "import sys; print(sys.maxsize <= 2**32)"
True
当回答是 True(如上所示)时,Python 是作为 32 位可执行文件构建的。
在使用 32 位 Python 可执行文件时,请按以下步骤进行操作:
$ unset OBJECT_MODE
$ pyinstaller
在使用 64 位 Python 可执行文件时,请按以下步骤进行操作:
$ export OBJECT_MODE=64
$ pyinstaller
您的应用程序应在包中正常运行,就像从源代码运行时一样。但是,您可能需要在运行时了解应用程序是从源代码运行还是捆绑(“冻结”)。您可以使用以下代码来检查“我们是否已捆绑?”:
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')
当捆绑的应用程序启动时,引导程序设置了sys.frozen属性并将捆绑文件夹的绝对路径存储在sys._MEIPASS中。对于一个文件夹捆绑,这是该文件夹的路径。对于一个单文件捆绑,这是引导程序创建的临时文件夹的路径(见单文件程序的工作方式)。
当您的应用程序正在运行时,可能需要访问以下位置之一中的数据文件:
程序可以访问这些用途的几个变量。
2.7.1 使用__file__
当您的程序未捆绑时,Python 变量__file__是指所包含模块的当前路径。从捆绑的脚本导入模块时,PyInstaller 引导程序将设置模块的__file__属性为相对于捆绑文件夹的正确路径。
例如,如果从捆绑的脚本导入mypackage.mymodule,则该模块的__file__属性将是sys._MEIPASS+‘mypackage/mymodule.pyc’。因此,如果在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 引导程序总是将__file__变量设置在__main__模块内的绝对路径中,就像字节编译的入口点脚本在那里存在一样。
例如,如果你的入口点脚本名为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__始终设置为绝对完整路径,并且是定位此类资源的首选方式。
第二章 第26节 内容:
将数据文件放置在捆绑包中预期的位置
为了将数据文件放置在您的代码期望的位置(即相对于主脚本或捆绑目录),您可以使用–add-data=source:dest命令行开关的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。
**注意:**Windows用户应该在上述行中使用;而不是:。
如果__file__是从包或库(例如my_library.data)内部检查的,则__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"),
...)
2.7.2 使用sys.executable和sys.argv[0]
当普通的Python脚本运行时,sys.executable是执行的程序的路径,即Python解释器。在冻结的应用程序中,sys.executable也是执行的程序的路径,但它不是Python,而是在单文件应用程序或单文件夹应用程序中的可执行文件。这为您提供了一种可靠的方式来定位用户实际启动的冻结可执行文件。
sys.argv[0]的值是用户命令中使用的名称或相对路径。它可能是相对路径,也可能是绝对路径,具体取决于平台和应用程序如何启动。
如果用户通过符号链接启动应用程序,则sys.argv[0]将使用该符号名称,而sys.executable是实际路径。有时相同的应用程序会链接到不同的名称,并且希望根据用于启动其名称而表现出不同的行为。针对这种情况,您将测试os.path.basename(sys.argv[0])。
2.7.运行时信息 27
另一方面,有时用户被告知将可执行文件存储在与其操作的文件相同的文件夹中,例如应存储在与其播放音频文件相同的文件夹中的音乐播放器。针对此情况,您将使用os.path.dirname(sys.executable)。
以下小程序探讨了这些可能性。将其保存为directories.py。将其作为Python脚本执行,然后作为一个文件夹应用程序捆绑,然后将其捆绑为一个文件夹应用程序,并通过符号链接直接启动它和启动它:
#!/usr/bin/env python3
import sys, os
frozen ='not'
if getattr(sys,'frozen',False):
# we are running in a bundle
frozen ='ever so'
bundle_dir = sys._MEIPASS
else:
# we are running in a normal Python environment
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() )
2.7.3 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_origis not None:
env[lp_key] = lp_orig # 恢复原始、未修改的值
else:
# 这发生在未设置LD_LIBRARY_PATH时。
# 仅作为最后一手:
env.pop(lp_key, None)
p = Popen(system_cmd, ..., env=env) # 创建进程
第2.8节 使用Spec文件
当您执行
pyinstaller options ..myscript.py
PyInstaller执行的第一件事就是构建一个spec(specification)文件myscript.spec。该文件存储在–specpath目录中,默认为当前目录。
spec文件告诉PyInstaller如何处理您的脚本。它对pyinstaller命令给出的脚本名称和大多数选项进行编码。实际上,spec文件是可执行的Python代码。PyInstaller通过执行spec文件的内容来构建应用程序。
对于许多使用PyInstaller的情况,您不需要检查或修改spec文件。通常,仅需将所有所需的信息(例如隐藏导入)作为pyinstaller命令的选项提供,并让其运行即可。
有四种情况下,修改spec文件会很有用:
这些用途在以下主题中介绍。
您可以使用以下命令创建一个spec文件:
pyi-makespec 选项 文件名.py [其他脚本...]
_options_是pyinstaller命令中文档化的选项。此命令将创建name.spec文件,但不会继续构建可执行文件。
创建spec文件并根据需要进行修改后,将spec文件传递给pyinstaller命令来构建应用程序:
pyinstaller 选项 name.spec
创建spec文件时,大多数命令选项都编码在spec文件中。构建来自spec文件时,这些选项无法更改。如果在命令行中给出这些选项,则会被忽略并替换为spec文件中的选项。
仅当从spec文件进行构建时,以下命令行选项才会生效:
2.8.使用Spec文件29
2.8.1 Spec文件操作
在PyInstaller创建spec文件或在给定脚本之外提供spec文件时,pyinstaller命令会将spec文件作为代码执行。您的绑定应用程序是通过执行spec文件来创建的。下面是一个最小、单文件夹应用程序的spec文件缩略版示例:
block_cipher = None
a = Analysis(['minimal.py'],
pathex=['/Developer/PItests/minimal'],
binaries=None,
datas=None,
hiddenimports=[],
hookspath=None,
runtime_hooks=None,
excludes=None,
cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz, ...)
coll = COLLECT(...)
spec文件中的语句创建四个类(Analysis、PYZ、EXE和COLLECT)的实例。
在单文件模式下,不存在对COLLECT的调用,并且EXE实例收到所有脚本、模块和二进制文件。
您可以修改spec文件以向Analysis和EXE传递其他值。
第2.8.2节 将文件添加到bundle
要添加文件到bundle,您需要创建一个描述文件的列表,并将其提供给Analysis调用。当打包到单个文件夹(参见“打包到单个文件夹”)时,添加的数据文件将复制到具有可执行文件的文件夹中。当您将打包到单个可执行文件(参见“打包到单个文件”)时,添加文件的副本将压缩到可执行文件中,并在执行之前扩展到_MEIxxxxxx临时文件夹中。这意味着,当应用程序结束时,一个文件应用程序对添加的文件所做的任何更改都将丢失。
在任一情况下,要在运行时查找数据文件,请参阅“运行时信息”。
添加数据文件
您可以通过使用–add-data命令选项或将它们作为列表添加到spec文件来添加数据文件。
在使用spec文件时,将描述文件的列表提供为Analysis的datas=参数值。数据文件列表是一个元组列表。每个元组都有两个值,两个值都必须是字符串:
例如,要向单文件夹应用程序的顶级添加单个README文件,您可以修改spec文件如下:
a = Analysis(...
datas=[ ('src/README.txt','.') ],
...
)
并且命令行等效项(有关特定平台的详细信息,请参见“What To Bundle, Where To Search”):
pyinstaller --add-data 'src/README.txt:.' myscript.py
您已经将datas=参数设置为一个单项列表。该项是一个元组,在该元组的第一个字符串中指定现有文件是src/README.txt。该文件将被查找(相对于spec文件的位置),并复制到打包应用程序的顶层。
字符串可以使用/或\作为路径分隔符字符。您可以使用“glob”缩写来指定输入文件。例如,要包括某个文件夹中的所有.mp3文件:
a = Analysis(...
datas= [ ('/mygame/sfx/*.mp3','sfx' ) ],
...
)
文件夹/mygame/sfx中的所有.mp3文件都将复制到名为sfx的文件夹中的捆绑应用程序中。
如果在spec文件中创建添加文件的清单,则spec文件更易于阅读:
added_files = [
( 'src/README.txt', '.' ),
( '/mygame/sfx/*.mp3','sfx' )
]
a = Analysis(...
datas=added_files,
(continues on next page)
...
)
你也可以包含文件夹的全部内容:
added_files = [
( 'src/README.txt', '.' ),
( '/mygame/data','data' ),
( '/mygame/sfx/*.mp3','sfx' )
]
文件夹/mygame/data将会在bundle中以名为data的文件夹复制出来。
**从模块中使用数据文件 **
如果要添加的数据文件包含在Python模块中,你可以使用pkgutil.get_data()来检索它们。
例如,假设您的应用程序的一部分是一个名为helpmod的模块。 在您的脚本和其规范文件的同一文件夹中,您有以下文件夹设置:
helpmod
__init__.py
helpmod.py
help_data.txt
因为您的脚本包括语句import helpmod,PyInstaller会在您的bundled app中创建这个文件夹结构。 但是,它只会包括.py文件,数据文件help_data.txt不会自动包含。 要使它也被包括,您需要在spec文件中添加一个数据元组:
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')
第2章 内容:32
添加二进制文件
注意:“二进制”文件是指DLL,动态库,共享对象文件等,PyInstaller将进一步搜索这些“二进制”依赖项。 像图像和PDF之类的文件应该放在datas中。
您可以通过使用–add-binary命令选项或将其作为列表添加到spec文件来将二进制文件添加到bundle中。 在spec文件中,制作描述所需文件的元组列表。 将元组列表分配给Analysis的binaries=参数。
添加二进制文件的方法与添加数据文件的方法类似。 如_Adding Binary Files_所述,每个元组都应该有两个值:
通常,PyInstaller通过分析导入的模块了解.so和.dll库。 有时,不清楚该模块是否被导入; 在这种情况下,您使用–hidden-import命令选项。 但是即使如此,它也可能找不到所有依赖项。
假设您有一个使用Python C-API编写的名为special_ops.so的模块。 您的程序importsspecial_ops,并且PyInstaller找到并includesspecial_ops.so。 但是可能special_ops.solinks到libiodbc.2.dylib。 PyInstaller找不到此依赖项。 您可以通过以下方式将其添加到bundle中:
a = Analysis(...
binaries=[ ('/usr/lib/libiodbc.2.dylib', '.' ) ],
...
或通过命令行(再次,请参阅_What To Bundle,Where To Search_以获取特定平台的详细信息):
pyinstaller --add-binary'/usr/lib/libiodbc.2.dylib:.' myscript.py
如果要将libiodbc.2.dylib存储在bundle内的特定文件夹中,例如vendor,则可以将其指定为元组的第二个元素:
a = Analysis(...
binaries=[ ('/usr/lib/libiodbc.2.dylib', 'vendor' ) ],
...
与数据文件一样,如果您有多个二进制文件要添加,为了提高可读性,请在单独的语句中创建列表并按名称传递列表。
添加文件的高级方法
PyInstaller支持一种更高级(和更复杂)的方法来向bundle中添加文件,这对于特殊情况可能有用。 见 下面的目录(TOC)列表和树类。
2.8.使用spec文件33
2.8.3提供运行时Python选项
您可以向Python解释器传递命令行选项。 解释器接受许多命令行选项,但仅支持以下选项:
要传递其中一个或多个选项,请创建一个元组列表,每个元组对应一个选项,并将列表作为EXE调用的附加参数传递。每个元组都有三个元素:
例如,按以下方式修改spec文件:
options = [ ('v',None, 'OPTION'), ('W ignore',None,'OPTION') ]
a = Analysis( ...
)
...
exe = EXE(pyz,
a.scripts,
options, <--- added line
exclude_binaries=...
)
**注意:**无缓冲的stdio模式(选项u)在所有受支持的Python版本上启用了不带缓冲区的stdout和stderr流的二进制层。 无缓冲的文本层需要Python 3.7或更高版本。
2.8.4 macOS Bun的规范文件选项
当您构建一个窗口的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内容的讨论,请参见苹果捆绑概述。)
PyInstaller创建了一个最小的Info.plist。version选项可用于使用CFBundleShortVersionString Core基础键设置应用程序版本。
您可以通过将info_plist参数传递给BUNDLE调用来添加或覆盖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,将正确输出到Info.plist易于。最后,Key CFBundleDocumentTypes告诉macOS您的应用程序支持的文件类型(请参见Apple文档类型)。
2.8.5 POSIX特定选项
默认情况下,所有所需的系统库都已捆绑。要从捆绑中排除所有或大多数非Python共享系统库,请向Analysis类中添加exclude_system_libraries函数的调用。系统库定义为来自/lib或/usr/lib下的文件,这是在POSIX和相关操作系统上的情况。该函数接受一个可选参数,该参数是文件通配符异常列表,以不排除匹配这些通配符的库文件在bundle中。例如,要排除所有非Python系统库,除了“libexpat”和包含“krb”的任何内容,请使用以下内容:
a = Analysis(...)
a.exclude_system_libraries(list_of_exceptions=['libexpat*', '*krb*'])
2.8.6 Splash Target
要显示引导加载程序的闪屏,请在构建时调用Splash目标。可以在创建规格文件时使用命令行选项–splash IMAGE_FILE添加此类。默认情况下,不启用显示可选文本的选项(text_pos = None)。有关闪屏的更多信息,请参见闪屏屏幕(实验性)部分。SplashTarget如下所示:
a = Analysis(...)
splash = Splash('image.png',
binaries=a.binaries,
datas=a.datas,
(在下一页继续)
该Splash将闪屏所需的资源捆绑到一个文件中,该文件将包含在CArchive中。
Splash有两个输出,一个是它本身,另一个存储在splash.binaries中。为了启用闪屏,请将两者传递给其他构建目标。要在onefile应用程序中使用闪屏,请按照以下示例进行操作:
a = Analysis(...)
splash = Splash(...)
#### onefile
exe = EXE(pyz,
a.scripts,
splash, # <-- both, splash target
splash.binaries, # <-- and splash binaries
...)
要在onedir应用程序中使用闪屏,只需要做出一些小更改即可。由于不需要将闪屏二进制文件包含在可执行文件中,因此必须将splash.binaries属性移动到COLLECT目标中:
a = Analysis(...)
splash = Splash(...)
#### onedir
exe = EXE(pyz, splash, # <-- splash target
a.scripts,
…)
coll = COLLECT(exe,
splash.binaries, # <-- splash binaries
…)
在Windows / macOS图像上支持每像素透明度。这允许非矩形闪屏图像。在Windows上,图像的透明边界是硬切的,这意味着不支持淡化透明值。在Linux上,没有关于非矩形窗口的普遍实现,因此不支持每个像素的透明度图像。
Splash目标可以以各种方式进行配置。 Splash目标的构造函数如下所示:
Splash.init( image_file , binaries , datas , **kwargs )
参数
注意:如果提供了不同的文件格式并且安装了PIL(Pillow),则文件将被自动转换。
注意:Windows系统下:图像或文本中不能使用颜色“magenta”/“#ff00ff”,因为这被启动界面用于指示透明区域。请使用相似的颜色(例如“#ff00fe”)代替。
注意:如果安装了PIL(Pillow),并且图像大于“max_img_size”,则该图像将被调整大小以适合指定的区域。
- binaries(list) – Analysis构建目标发现的扩展模块及其二进制依赖项的TOC列表。这是确定用户程序是否使用_tkinter_所必需的。
- datas(list) – Analysis构建目标发现的数据文件的TOC列表。此TOC包括模块的所有数据文件依赖项。这是用于检查是否可以捆绑所有闪屏屏幕的要求所必需的。
**关键字参数**
- text_pos– 可选的两个整数元组,表示闪屏屏幕图像上的文本起点。文本的原点是其左下角。在相应的坐标系中,一个单位是图像的一个像素,其原点位于图像的左上角。该参数还充当文本功能的开关。如果省略,则闪屏屏幕上不会显示任何文本。此文本将用于在单个文件模式下显示文本进度。
- text_size– 字体的期望大小。如果大小参数是正数,则将其解释为点大小。如果大小为负数,则将其绝对值解释为像素大小。默认值:12。
- text_font– 文本字体的可选名称。此字体必须安装在用户系统上,否则使用系统默认字体。如果省略此参数,则也使用默认字体。
- text_color– 文本的可选颜色。支持HTML颜色代码(“#40e0d0”)和颜色名称(“turquoise”)。默认值:“black”(Windows系统下,颜色“magenta”/“#ff00ff”用于表示透明度,不应使用)。
- text_default– 开始提取之前将显示的默认文本。默认值:“Initializing”。
- full_tk– 默认情况下,Splash仅捆绑了闪屏屏幕所需的文件(一些tk组件)。此选项启用添加完整的tk并将其设置为要求,这意味着在启动闪屏屏幕之前将解压缩所有tk文件。这在闪屏屏幕脚本开发过程中非常有用。默认值:False。
- minify_script– 闪屏屏幕是通过执行一个Tcl/Tk脚本创建的。此选项启用脚本的最小化,即从脚本中删除所有非必要部分。默认情况下为True。
- rundir– Tcl/tk在运行时将被提取的文件夹名称。在应用程序中不应有匹配的文件夹,以避免冲突。默认值:“__splash”。
- name– .res文件的可选替代文件名。如果未指定,则会生成一个名称。
- script_name– 将生成的Tcl脚本的可选替代文件名。如果未指定,则会生成一个名称。
**2.8. 使用Spec文件**
- max_img_size– 闪屏屏幕图像的最大尺寸。如果提供的图像超出此限制,则会调整大小以适合最大宽度(保持原始长宽比)。可以通过将其设置为None来禁用此选项。默认值:(760,480)。
- always_on_top– 强制闪屏屏幕始终置于其他窗口之上。如果禁用,其他窗口(例如来自其他应用程序的窗口)可以通过用户将它们置于前面来覆盖闪屏屏幕。这对于启动时间长的固定应用程序可能很有用。默认值:True。
**2.8.7 多包捆绑**
一些产品由多个不同的应用程序组成,每个应用程序可能依赖于一组第三方库或以其他方式共享代码。当打包这样的产品时,将每个应用程序隔离处理,将其与所有依赖项捆绑在一起将导致存储重复的代码和库。
您可以使用多包装功能捆绑一组可执行应用程序,以便它们共享库的单个副本。您可以使用单文件或单文件夹应用程序执行此操作。
**使用单文件应用程序进行多包装**
每个依赖项(例如DLL)仅在其中一个应用程序的包中打包。其他集合中依赖此DLL的任何其他应用程序都具有对它的“外部引用”,告诉它们从包含它的应用程序的可执行文件中提取该依赖项。
这样做可节省磁盘空间,因为每个依赖项仅存储一次。但是,在启动应用程序时,跟随外部引用需要额外的时间。集合中的所有应用程序,除一个之外,都将具有稍慢的启动时间。
二进制文件之间的外部引用包括硬编码的路径到输出目录,不能重排。当安装应用程序时,必须将所有相关应用程序放置在同一目录中。
要构建这样的应用程序集合,必须编写自定义spec文件,其中包含对MERGE函数的调用。此函数接受已分析脚本的列表,查找它们的公共依赖项,并修改分析以最小化存储成本。
参数列表中的分析对象的顺序很重要。MERGE函数将每个依赖项打包到从左到右的第一个需要该依赖项的脚本中。在列表中后来的需要相同文件的脚本将具有对列表中前面脚本的外部引用。您可以对这些脚本进行排序,将最常使用的脚本放在列表的前面。
用于多包装捆绑的自定义spec文件包含对MERGE函数的一次调用:
MERGE(*args)
MERGE在分析阶段之后并在EXE之前使用。其变量长度的参数列表由一个元组列表组成,每个元组具有三个元素:
- 第一个元素是Analysis对象,是类Analysis的一个实例,应用于一个应用程序。
- 第二个元素是分析应用程序的脚本名称(不包括“.py”扩展名)。
- 第三个元素是可执行文件的名称(通常与脚本相同)。
MERGE检查Analysis对象以了解每个脚本的依赖项。它修改这些对象以避免库和模块的重复。结果生成的包将是连接的。
**示例 MERGE 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...
现在调用MERGE方法以处理三个Analysis对象:
MERGE((foo_a,'foo','foo'),(bar_a,'bar','bar'),(zap_a,'zap','zap'))
将修改Analysis对象foo_a、bar_a和zap_a,使后两个对象引用第一个对象以实现共同依赖性。
接下来,您可以从原始的三个规范文件中复制PYZ,EXE和COLLECT语句,并在原始规范文件使用a的位置替换Analysis对象的唯一名称。修改EXE语句以传递Analysis.dependencies,以及传递给原始EXE语句的所有其他参数。例如:
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.
将组合的规范文件保存为foobarzap.spec然后构建它:
pyinstaller foobarzap.spec
dist文件夹中的输出将是所有三个应用程序,但是appsdist/bar和dist/zap会引用dist/foo的共享依赖项。
请记住,规范文件是可执行的Python文件。您可以在创建 Analysis 对象并执行 PYZ、EXE 和 COLLECT 语句时使用所有 Python 工具 (与sys和io的成员for和with)。您还可以了解并使用下面描述的_目录(TOC)列表和Tree Class 。_
**2.8 用于规范文件的 Notable 特性**
**2.8.8 规范文件中可用的全局变量**
规范文件在执行时可以访问一组有限的全局名称。 这些名称包括由 PyInstaller 定义的类: Analysis,BUNDLE,COLLECT,EXE,MERGE,PYZ,TOC,Tree 和 Splash,这些类在前面的部分中进行了讨论。
其他全局变量包含有关构建环境的信息:
DISTPATH应用程序将存储的rel相对路径。 默认路径相对于当前目录。 如果使用--distpath选项,DISTPATH包含该值。
HOMEPATH PyInstaller 分发的绝对路径,通常在当前 Python site-packages 文件夹中。
SPEC给定给pyinstaller命令的完整规范文件参数,例如myscript.spec或source/myscript.spec。
SPECPATH按os.path.split()返回的SPEC值的路径前缀。
specnm 规范文件的名称,例如myscript。
workpath 到 build 目录的路径。 默认路径相对于当前目录。 如果使用workpath=选项,workpath包含该值。
WARNFILE建议的目录中警告文件的完整路径,例如build/warn-myscript.txt。
### 2.9 关于特定功能的注释
**2.9.1 Ctypes 依赖关系**
Ctypes 是 Python 的外部函数库,允许调用共享库中存在的函数。这些库未作为 Python 包导入,因为它们不是通过 Python 导入捕获的。其中传递的路径实际上传递给 ctypes;这导致 PyInstaller 的导入检测机器无法检测到这些库,从而无法构建独立的 PyInstaller 可执行文件:
from ctypes import *
### 2.10 这将在 PyInstaller 检测机器下不被检测到,
#因为它不是直接导入的。
handle = CDLL(“/usr/lib/library.so”)
handle.function_call()
**PyInstaller 中的解决方案**
PyInstaller 包含 Ctypes 依赖项的实用的实现:它将搜索 ctypes 的简单标准用法并自动跟踪和捆绑引用的库。 将正确检测以下用法:
CDLL(“library.so”)
WinDLL(“library.so”)
ctypes.DLL(“library.so”)
cdll.library# 仅在 Windows 下有效-是 ctypes 的限制,而不是 PyInstaller 的
windll.library# 仅在 Windows 下有效-是 ctypes 的限制,而不是 PyInstaller 的
cdll.LoadLibrary(“library.so”)
windll.LoadLibrary(“library.so”)
更详细地说,以下限制适用:
- **仅处理通过裸文件名(例如无前导路径)引用的库**;处理绝对路径是不可能的,因为必须对字节码进行修改(请记住,当运行 frozen 时,ctypes 会继续在该绝对位置上搜索库,而无法保证在主机系统上存在该位置),而处理相对路径则需要在冻结的可执行文件中重新创建导向库的相同目录层次结构,另外还需要跟踪当前工作目录是什么;
- **仅会检测由字符串表示的库路径并包含在最终的可执行文件中**:PyInstaller 的导入检测是通过检查原始 Python 字节码来完成的,而由于可以通过字符串将库路径传递给 ctypes(可以由代码中的文字表示,也可以由变量、任意复杂函数的返回值等表示),因此不可能合理地检测**所有** ctypes 依赖项;
- 仅会处理在 ctypes 调用的相同上下文中引用的库。
我们认为这足以涵盖大多数 ctypes 用法,并且在您的代码中需要进行很少或没有修改。
如果 PyInstaller 未检测到某个库,则可以通过将相应信息传递给--add-binary 选项或 _将其列出在.spec 文件中_将其添加到捆绑包中。不能保证冻结的应用程序将能够在运行时拾取库,因为这取决于详细实现。
**警告**
在“分析时间”,`ctypes`检测系统基于`ctypes.util.find_library()`。这意味着您必须确保,在执行分析并运行冻结程序时,`find_library()`用于搜索库的所有环境值都与运行非冻结程序时的环境值相一致。例如,使用`LD_LIBRARY_PATH`或`DYLD_LIBRARY_PATH`来扩大`find_library()`的范围。
**2.9.2 SWIG支持**
PyInstaller尝试检测由SWIG创建的二进制模块。此检测需要:
- 在你的应用程序中的任何模块中导入Python包装模块。
- 包装模块必须作为源代码可用,并且第一行必须包含SWIG自动生成的文本。
- C模块必须与包装模块具有相同的名称,前缀为下划线(_)。 (这已经是SWIG限制了。)
- C模块必须坐在包装模块旁边(因此相对导入将有效)。
此外,由于SWIG包装器的实现方式,也适用一些限制:
- C模块将变成全局模块。因此,您不能使用两个具有相同基本名称的SWIG模块(例如pkg1._cmod和pkg2._cmod),因为一个将覆盖另一个。
**2.9.3 Cython支持**
PyInstaller可以跟踪引用Cython C对象模块并打包它们的import语句,就像对于任何其他用C实现的模块一样。
但是-就像对于任何其他用C实现的模块一样-PyInstaller无法确定Cython C对象模块是否在导入某些Python模块。这些通常会显示在回溯中(注意.pyx扩展名):
回溯(最近的调用最先):
[…]
File “myapp\cython_module.pyx”, line 3, in init myapp.cython_module
ModuleNotFoundError: No module named ‘csv’
**2.9.关于特定功能的注意事项41**
因此,如果您正在使用导入Python模块的Cython C对象模块,则必须将其列为--hidden-import。
**2.9.4 macOS多架构支持**
随着苹果Silicon M1的引入,现在有几个针对Python可用的架构选项:
- 使用细二进制文件的单架构x86_64:旧版_python.org_构建、在M1 Mac上本地运行的Homebrew python或在rosetta2下运行
- 使用细二进制文件的单架构arm64:在M1 macs上本地运行的Homebrew python
- 包含x86_64和arm64片段的多架构universal2瘦二进制文件:最近的universal2_python.org_构建
PyInstaller旨在支持源于上述选项的所有可能的组合:
- 使用相应单架构Python创建的单架构应用程序
- 使用universal2python创建的universal2应用程序
- 使用universal2python创建的单架构应用程序(即将universal2fat二进制文件缩减为x86_64或arm64细二进制文件)
**默认情况下,PyInstaller针对当前正在运行的体系结构并生成单架构二进制文件**(在Intel Mac上运行时为x86_64或在M1 Mac上运行rosetta2时为x86_64,或在M1 Mac上运行时为arm64)。这是因为即使在universal2python环境下,一些软件包可能最终只提供单架构二进制文件,从而使创建功能性universal2frozen应用程序变得不可能。
因此,必须明确启用替代选项,例如创建冻结应用程式的universal2版本,或使用universal2环境创建非本机单架构版本。可以通过在.spec文件中通过EXE()的target_arch参数或通过--target-arch开关在命令行中指定目标架构来完成。有效值为x86_64、arm64和universal2。
**二进制收集期间的架构验证**
为了防止二进制文件中缺少或不匹配的架构片段引起的运行时问题,二进制文件收集过程执行严格的架构验证。它检查收集的二进制文件是否包含所需的架构片段,如果没有,则构建过程将中止并出现有关问题二进制文件的错误消息。
在这种情况下,除非手动解决缺少架构片段的问题(例如,通过下载与缺少的架构相对应的wheel,并使用lipoutility将有问题的二进制文件粘合在一起),否则将无法为所选目标架构创建冻结应用程序。
从版本4.10开始:在早期的PyInstaller版本中,架构验证是在所有收集的二进制文件(例如python扩展模块和由这些扩展引用的共享库)上执行的。从PyInstaller4.10开始,架构验证仅限于python扩展模块。
多架构universal2扩展中的单独架构片段可以链接到(由)universal2共享库的(片段)中,也可以链接到不同的单架构细共享库中。后一种情况使得无法可靠地验证收集的共享库的架构相对于目标应用程序架构。
但是,扩展模块确实需要与目标应用程序架构完全兼容。因此,它们的持续验证应该足以检测尝试使用不兼容的单架构Python软件包的企图* ^ 0。
(^0)尽管实际上没有什么阻止一个软件包有不同的、具有特定于架构的扩展模块......
**42第2章 内容:删除单架构目标的厚二进制文件片段**
当目标一个架构时,构建过程从任何收集的fat二进制文件中提取相应的架构片段,包括引导加载程序。这将导致即使在构建universal2python环境中也会产生完全细的构建。
**2.9.5 macOS二进制代码签名**
随着Apple Silicon M1体系结构的引入,即使是无实际代码签名标识,macOS也引入了强制性的代码签名。这意味着收集的二进制文件中的arm64架构片段(但可能也是universal2二进制文件中的x86_64片段)始终带有签名。
PyInstaller处理二进制文件的处理(例如,在二进制文件头中重写库路径)会使它们的签名无效。因此,必须重新生成签名,否则操作系统将拒绝加载二进制文件。
**默认情况下,PyInstaller会对所有收集的二进制文件和生成的可执行文件进行ad-hoc(重新)签名。**可以使用实际的代码签名标识,而不是adhoc签名。要这样做,可以通过spec文件的codesign_identity参数或通过--codesign_identity开关在命令行中指定身份验证。
能够提供codesign标识符使用户能够确保所有在onefile或onedir build中收集的二进制文件都使用其标识符进行了签名。这很有用,因为对于onefile构建,无法在后处理步骤中执行嵌入二进制文件的签名。
**注意:** 当指定 codesign 身份时,PyInstaller 也通过 --options=runtime 将 _硬化运行时_ 打开给 codesign 命令。这要求 codesign 身份必须是有效的由 Apple 发布的代码签名证书,而自签名证书将无法使用。
尝试使用自签名证书作为codesign身份将导致共享库加载失败,并报告以下原因:
[libname]:代码签名([libname])无法在使用库验证的进程中使用:映射的文件没有 Team ID,也不是平台二进制文件(使用自定义身份或adhoc签名
此外,在签名收集的二进制文件和可执行文件时,还可以指定要使用的授权文件。这可以通过在.spec文件中使用 entitlements_file=argument toEXE() 或通过命令行使用 --osx-entitlements-file 开关来完成。
**程序包**
PyInstaller 还自动尝试使用 _adhoc_ 身份或实际的签名身份来签名 _.app 包_,如果通过 --codesign-identity 开关提供。除传递与签名收集的二进制文件相同的选项(identity、hardened runtime、entitlement)、通过通过向 codesign 实用程序传递 --deep 选项启用深层签名。
如果无论何种原因,该包的签名失败,从 codesign 实用程序收到的错误消息将打印到控制台,并提示需要手动干预和手动签名包的警告。
**2.9. 特定功能的注意事项43**
**2.9.6 macOS 应用程序包中的事件转发和参数模拟**
macOS 应用程序包的用户交互是通过所谓的 Apple 事件进行的。当用户双击应用程序图标时,应用程序启动并接收到一个“开放应用程序”('oapp')事件。将文档拖动到应用程序图标上或尝试打开应用程序注册的文件会生成“打开文档”('odoc')事件。类似地,使用应用程序注册模式启动 URL 会生成启动 / 获取 URL ('GURL') 事件。通常,长时间运行的 UI 应用程序在其运行时安装Carbon或Cocoa事件处理程序(或由更高级别的 UI 工具包提供的等效处理程序)来处理这些请求。
PyInstaller 为 macOS 事件处理提供了两个方面的支持:自动事件转发,它使冻结的应用程序能够在onefile模式下接收事件,以及可选的_argv模拟_,用于将初始打开事件转换为sys.argv参数。这两个方面仅适用于应用程序包(即,windowedbootloader 变体),而不适用于 POSIX(命令行)冻结应用程序。
在版本5.0中更改:在早期的 PyInstaller 版本中,argv 模拟总是在 onefile 模式下启用,并且在 onedir 模式下不可用。随着 PyInstaller 5.0,必须显式选择 argv 模拟,并且在 onefile 和 onedir 模式下都可用。
**事件转发**
在 PyInstaller onedir 包中,应用程序作为单个进程运行,因此像其他 macOS 应用程序一样正常接收 Apple 事件。
在onefile包中,应用程序有一个父启动器进程和一个子进程;由用户生成的打开文档请求由父进程接收,并自动转发到子进程,其中运行冻结的 Python 代码。
对以下类型的 Apple 事件实现了事件转发:
- kAEOpenDocuments('odoc'):打开文档请求
- kAEGetURL('GURL'):打开 / 启动 URL 请求
- kAEReopenApplication('rapp'):重新打开应用程序
- kAEActivate('actv'):激活应用程序(置于前端)
**可选参数模拟**
PyInstaller 实现了一个名为 argv 模拟的可选功能,可通过在 _.spec 文件_ 中使用argv_emulation=argument toEXE()来切换,或通过命令行使用--argv-emulation标志启用。
如果启用,引导加载程序执行初始的 Apple 事件处理,以在应用程序的启动序列期间拦截事件,并将通过打开文档 / URL('odoc' 和 'GURL')事件接收到的文件路径或 URL 追加到 sys.argv 中,就像它们是通过命令行接收到的一样。
此功能适用于不实现事件处理但仍希望处理初始打开文档请求的简单应用程序。仅适用于初始打开事件;在启动冻结的 Python 代码后发生的事件通过事件队列分派(在 onedir 模式下直接分派,在 onefile 模式下转发到子进程。),因此需要通过事件处理程序处理。
**注意:** 此功能不适合需要在其生命周期中处理多个打开请求的长时间运行的应用程序。这种应用程序需要适当的事件处理,因此不受由 _argv 模拟_ 处理初始事件影响的影响。
**44 第 2 章 内容:**
警告:引导程序在 onedirmode 中执行的初始事件处理可能会干扰冻结 Python 应用程序使用的 UI 工具包,例如 Tcl/Tk 或 viatkinter 模块。症状可能从窗口未被置于前端的应用程序启动到应用程序崩溃和分段错误。虽然 PyInstaller 尝试在其端缓解问题,但我们建议不要结合使用 UI 工具包使用 argv 模拟。
**实际示例**
本节提供一些实际示例,展示如何通过在简单的一次性程序中使用 _argv 模拟_ 或通过在 GUI 应用程序中安装事件处理程序来处理 macOS 应用程序包中的文件和 URL 打开事件。
**注册支持的文件类型和自定义 URL 模式**
为了使 macOS 应用程序包能够处理对文件和自定义 URL 模式的打开操作,需要向操作系统通知应用程序支持哪些文件类型和哪些 URL 模式。这是通过包的 Info.plist 文件完成的,通过CFBundleDocumentTypes 和 CFBundleURLTypes 条目完成:
[...]
2.9. Notes about specific Features 45
(continued from previous page)
在上面的示例中,应用程序声明自己为.mcf文件和以my-url://开头的URL的查看器。
PyInstaller会自动生成一个Info.plist文件用于您的应用程序包。要将上面显示的条目包含在其中,请将info_plist参数添加到.spec文件中的BUNDLE()指令中,并将其内容设置如下:
app = BUNDLE(
info_plist={
‘CFBundleURLTypes’: [{
‘CFBundleURLName’:‘MyCustomUrlSchema’,
‘CFBundleTypeRole’:‘Viewer’,
‘CFBundleURLSchemes’: [‘my-url’, ],
}],
‘CFBundleDocumentTypes’: [{
‘CFBundleTypeName’:‘MyCustomFileType’,
‘CFBundleTypeExtensions’: [‘mcf’, ],
‘CFBundleTypeRole’: “Viewer”,
}],
}
)
使用argv模拟的打开事件处理
考虑以下Python脚本,最初是作为从终端调用的命令行实用程序而创建的:
python3 img2gray.py image1.png image2.png …
该脚本对传递的每个图像进行处理,将其转换为灰度并将其保存在原始文件旁边,并在文件名后面添加_gray:
import sys
import os
import PIL.Image
if len(sys.argv) < 2:
print(f"Usage:{sys.argv[0]} [filenames…]")
sys.exit(1)
forinput_filenamein sys.argv[1:]:
filename, ext = os.path.splitext(input_filename)
output_filename = filename + ‘-gray’+ ext
img = PIL.Image.open(input_filename)
(continues on next page)
46 Chapter 2. Contents:
(continued from previous page)
img_g = img.convert('L')
img_g.save(output_filename)
如果生成应用程序包(而不是命令行POSIX应用程序),与该包的图标拖放图像文件或使用“打开方式…”操作相比,用户交互的最可能方式将是从图像文件的上下文菜单中生成打开文件事件。此类交互将生成打开文件事件,并且通常需要您的应用程序代码实现事件处理。
在PyInstaller中启用argv模拟可使其引导加载程序在应用程序启动期间处理事件,并通过任何文件路径或URL扩展sys.argv,这些路径或URL可能已通过打开文件或URL请求接收到。这使得您的应用程序可以处理接收到的文件名,就像它们是通过命令行传递的一样,而无需对代码本身进行任何修改。
以下的.spec文件提供了一个完整的例子,用于一个one-directory应用程序包,允许转换.png和.jpg图像:
a = Analysis([‘img2gray.py’], )
pyz = PYZ(a.pure, a.zipped_data)
exe = EXE(
pyz,
a.scripts,
exclude_binaries=True,
name=‘img2gray’,
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
console=False,
argv_emulation=True, # enable argv emulation
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=False,
upx_exclude=[],
name=‘img2gray’
)
app = BUNDLE(
coll,
name=‘img2gray.app’,
info_plist={
‘CFBundleDocumentTypes’: [{
‘CFBundleTypeName’: “可转换图片类型”,
‘CFBundleTypeExtensions’: [
(续下页)
‘png’, ‘jpg’,
],
‘CFBundleTypeRole’: “浏览器”,
}],
}
)
现在用户可以将图像文件拖到生成的img2gray应用程序包图标上,或在图像文件的上下文菜单中选择Open with...中的img2gray。
注意:_argv模拟仅处理初始打开事件,在启动冻结的Python代码之前接收到该事件。如果您希望在应用程序仍在运行时处理后续打开请求,则需要在Python代码中实现适当的事件处理。
在基于tkinter的GUI应用程序中处理打开事件
tkinter使用的Tcl/Tk框架允许应用程序通过注册macOS特定的命令为预定义的Apple事件类型提供事件处理程序。
打开文件事件的处理程序可以通过```:: tk :: mac :: OpenDocument```命令进行注册,而打开URL事件的处理程序可以通过```:: tk :: mac :: LaunchURL```命令进行注册。后者从Tcl/Tk 8.6.10开始†^0可用。
以下应用程序说明了使用tkinter的事件处理程序,通过将所有接收到的打开文件/URL事件记录到可滚动的文本小部件中:
```# eventlogger_tk.py
import sys
import tkinter
import tkinter.scrolledtext
class Application:
def __init__(self):
# 创建UI
self.window = tkinter.Tk()
self.window.geometry('800x600')
self.window.title("基于Tk的事件记录器")
self.text_view = tkinter.scrolledtext.ScrolledText()
self.text_view.pack(fill=tkinter.BOTH, expand=1)
self.text_view.configure(state='disabled')
# 注册事件处理程序
# 请参见https://tcl.tk/man/tcl/TkCmd/tk_mac.html了解
# macOS特定命令的列表
self.window.createcommand(":: tk :: mac :: OpenDocument", self.open_document_handler)
self.window.createcommand(":: tk :: mac :: LaunchURL", self.open_url_handler) #␣ works with Tcl/Tk >= 8.6.10
写作时,python.org builds使用Tcl/Tk 8.6.5,除了Python 3.9.x _macOS 64位universal2 installer_ builds使用
Tcl/Tk 8.6.10。Homebrew Python requirestkinter显式安装为python-tk,并使用最新版本的Tcl/Tk 8.6.11。注册
:: tk :: mac :: LaunchURLcommand与早于8.6.10的Tcl/Tk版本基本无操作。
第48章。 内容:
def append_message(self, msg):
"""将消息附加到文本视图。"""
self.text_view.configure(state='normal')
self.text_view.insert('end', msg + '\n')
self.text_view.configure(state='disabled')
def run(self):
"""运行主循环。"""
app.append_message("Application started!")
app.append_message(f"Args:{sys.argv[1:]}")
self.window.mainloop()
# 事件处理程序
def open_document_handler(self, *args):
app.append_message(f"打开文档事件:{args}")
def open_url_handler(self, *args):
app.append_message(f"打开URL事件:{args}")
if __name__ == '__main__':
app = Application()
app.run()
对应的_.spec文件_,构建了一个onedir应用程序包,其中包含自定义文件关联(.pyi_tk)和自定义URL模式(pyi-tk://):
a = Analysis(['eventlogger_tk.py'])
pyz = PYZ(a.pure, a.zipped_data)
exe = EXE(
pyz,
a.scripts,
exclude_binaries=True,
name='eventlogger_tk',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
console=False,
argv_emulation=False, # 不必要,因为应用程序处理事件
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
2.9. 特定功能的注释 49
(接上一页)
a.datas,
strip=False,
upx=False,
name='eventlogger_tk'
)
应用程序运行后,它将记录所有接收的打开文件和打开URL请求。这些请求是通过使用UI打开带有.pyi_tk扩展名的文件或从终端使用open命令生成的:
$ touch file1.pyi_tk file2.pyi_tk file3.pyi_tk file4.pyi_tk
$ open file1.pyi_tk
$ open file2.pyi_tk
$ open pyi-tk://test1
$ open pyi-tk://test2
$ open file3.pyi_tk file4.pyi_tk
**在基于Qt的GUI应用程序中处理打开事件**
在基于Qt的应用程序中,安装应用范围的事件过滤器以处理打开文件和打开URL请求的QFileOpenEvent。
此事件抽象了打开文件和打开URL请求,其中文件打开请求具有file://URL模式。事件包含单个文件名或URL,因此包含多个目标的打开请求会生成相应数量的QFileOpenEvent事件。
以下是一个示例应用程序及其对应的_.spec文件_:
# eventlogger_qt.py
import sys
(接上一页)
import signal
from PySide2 import QtCore, QtWidgets
class Application(QtWidgets.QApplication):
"""
带有针对macOS打开文档/URL事件的额外处理的QtWidgets.QApplication。
"""
openFileRequest = QtCore.Signal(QtCore.QUrl, name='openFileRequest')
defevent(self, event):
if event.type() == QtCore.QEvent.FileOpen:
self.openFileRequest.emit(event.url())
return True
returnsuper().event(event)
class MainWindow(QtWidgets.QMainWindow):
"""
主窗口。
"""
def__init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.resize(800, 600)
self.setWindowTitle("基于Qt的事件记录器")
# 构造UI界面
self.scroll_area = QtWidgets.QScrollArea()
self.scroll_area.setWidgetResizable(True)
self.setCentralWidget(self.scroll_area)
self.text_edit = QtWidgets.QTextEdit()
self.scroll_area.setWidget(self.text_edit)
self.text_edit.setReadOnly(True)
def append_message(self, msg):
"""
在文本视图中追加消息。
"""
self.text_edit.append(msg)
def handle_open_file_request(self, url):
self.append_message(f"打开请求:{url.toString()}")
(续下一页)
2.9. 特定功能说明 51
(接上页)
if __name__ =='__main__':
# 使Ctrl+C正常工作
signal.signal(signal.SIGINT, signal.SIG_DFL)
app = Application(list(sys.argv))
window = MainWindow()
window.show()
window.append_message("应用程序已启动!")
window.append_message(f"参数:{sys.argv[1:]}")
app.openFileRequest.connect(window.handle_open_file_request)
app.exec_()
a = Analysis(['eventlogger_qt.py'])
pyz = PYZ(a.pure, a.zipped_data)
exe = EXE(
pyz,
a.scripts,
exclude_binaries=True,
name='eventlogger_qt',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
console=False,
argv_emulation=False, # 因为应用程序处理事件,所以不需要
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=False,
name='eventlogger_qt'
)
app = BUNDLE(
coll,
name='eventlogger_qt.app',
# 注册自定义协议处理程序和自定义文件扩展名
info_plist={
'CFBundleURLTypes': [{
'CFBundleURLName':'MyCustomUrlSchemaQt',
'CFBundleTypeRole':'Viewer',
'CFBundleURLSchemes': ['pyi-qt'],
}],
'CFBundleDocumentTypes': [{
'CFBundleTypeName':'MyCustomFileTypeQt',
'CFBundleTypeExtensions': [
'pyi_qt',
],
'CFBundleTypeRole': "Viewer",
}],
}
)
该应用程序的行为方式与Tkinter为基础的同类应用程序相同,只是相关的文件扩展名和URL模式已被调整,以防止两个示例应用程序之间的干扰。
打开事件
本节介绍了应用程序收到的初始打开事件的行为,这是由冻结的Python代码(或其使用的UI工具包)观察到的。
当正常打开应用程序时,这是通过打开应用程序(‘oapp’)事件完成的,它是应用程序接收到的第一个事件。如果以响应于打开文档或打开URL请求(即在进行请求时应用程序尚未运行)打开应用程序,则首先接收到的事件是“odoc”或“GURL”。
在PyInstaller-frozenonefilebundles中,子进程始终以“oapp”事件开始,无论如何启动应用程序。这是因为子进程始终是以“正常方式”启动的,而实际的打开事件是由父进程接收的;如果父进程以“odoc”或“GURL”事件打开,则事件将被转发到子进程或转换为传递给子进程的sys.argv,这取决于是否启用了_argv模拟。
因此,在单个文件模式下,“argv仿真”对于初始打开事件的直接影响是没有的(由冻结的python代码控制),初始打开事件始终为“oapp”。
在单个目录束中,应用程序由单个进程接收事件。如果没有“argv仿真”,初始打开事件(由冻结的python代码控制)可能是“oapp”、“odoc”或“GURL”,具体取决于应用程序的启动方式。
但是,在单个目录束中启用“argv仿真”时,它对初始事件的处理会使事件队列为空。缺少初始打开事件似乎会在Tcl/Tk 8.6.11和Homebrew Python 3.9.6(#5581)中导致分段错误。作为解决方法,引导加载程序尝试向自身提交“oapp”事件,这样当冻结的python代码检查事件队列时,它就会发现初始打开事件(即“oapp”)。这些“argv仿真”对UI工具包的潜在副作用是我们建议不要将它们结合使用的原因。
2.9.7 控制台Windows应用程序和单个文件应用程序中的信号处理
清除
在Windows中,控制台应用程序中的信号处理与基于POSIX的操作系统(如Linux和macOS)不同。虽然由异常情况生成的信号(例如由C代码调用Abort引起的异常终止的信号,SIGFPE(浮点错误)和SIGSEGV(非法存储器访问)),可以使用通过signal函数安装的处理程序处理,但对于与程序中断和终止相关联的信号,则不是这种情况。
具体来说,通过按“Ctrl + C”中断启用控制台的程序不会生成SIGINT信号,而是生成一种特殊的“控制台控制信号”,称为CTRL_C_EVENT,其可以通过通过SetConsoleCtrlHandler win32 API函数安装的处理程序来处理。
同样地,如MSDN文档中关于信号的注释所示,在Windows下,不会生成SIGTERM信号。相反,存在几种控制台控制信号:
当生成控制台控制信号时,由SetConsoleCtrlHandler安装的处理程序(如果有)在程序进程中由操作系统产生的单独线程中执行。换句话说,处理程序函数与主程序线程并行执行,这是必要的,因为后者可能正在等待阻塞操作或执行无限循环。
如此提醒,在接收到CTRL_CLOSE_EVENT、CTRL_LOGOFF_EVENT或CTRL_SHUTDOWN_EVENT时,处理程序函数可以执行任何必要的清除,并且:
换句话说,所有选项都会最终终止程序。
另一方面,默认的CTRL_C_EVENT和CTRL_BREAK_EVENT处理程序也会终止进程,但是可以通过在用户安装的处理程序中返回TRUE来修改此行为,以抑制默认处理程序。
控制台控制信号的另一个重要方面是,处理CTRL_CLOSE_EVENT、CTRL_LOGOFF_EVENT和CTRL_SHUTDOWN_EVENT是受系统强制的超时限制的(例如,对于CTRL_CLOSE_EVENT是5秒)。如果进程未在超时限制内退出,则操作系统本身会无条件终止该进程。
上述实际上意味着一旦程序接收到这样的控制信号,其终止是不可避免的(即无法忽略该信号)。最好的办法是将终止延迟到执行任何必要的清除,但即使这样,也必须在系统规定的时间限制内完成清除。
在Python应用程序中处理控制台控制信号的示例
以下代码演示了优雅控制台应用程序关闭的基本实现。如果应用程序由用户按“Ctrl + C”或“Ctrl + Break”中断,或由于用户关闭控制台窗口而关闭,则应用程序的状态将存储到文件中,以便在后续运行中可以恢复。
import sys
import time
import pathlib
import win32api # pip install pywin32
(^1)较高级的编程语言(如Python)可能会模拟标准信号;但底层机制仍涉及本节中讨论的控制台控制信号。
(^2)注意,此时程序本质上是一个多线程程序,因此可能适用于通常的多线程注意事项。
#安装控制台处理程序
win32api.SetConsoleCtrlHandler(console_handler, 1)
#恢复状态,如果有
state_file = pathlib.Path.home() / 'counter_state.txt'
if state_file.is_file():
print(f"从{state_file}还原状态...", file = sys.stderr)
try:
with open(state_file,'r')as fp:
counter = int(fp.readline())
except Exception:
print("无法从文件中恢复状态!", file = sys.stderr)
counter = 0
else:
print("不存在状态文件!", file = sys.stderr)
counter = 0
print(f"初始计数器值:{counter}", file = sys.stderr)
#主循环
while keep_running:
print(f"计数器值:{counter}")
counter += 1
time.sleep(1)
#清理
print(f"将状态存储到{state_file}中...", file = sys.stderr)
try:
with open(state_file,'w') as fp:
print(f"{counter}", file = fp)
except Exception:
print(f"无法将状态存储到{state_file}中!", file = sys.stderr)
print("再见!")
time.sleep(1) # 延迟1秒后退出
上面的代码中的控制台控制信号处理程序处理所有控制台信号。这包括_Ctrl+C_事件,否则将在程序的主线程中生成aKeyboardInterruptexception
。
通过用全局布尔变量信号循环通知退出,处理程序会睡眠“forever”。此方法有效,因为处理程序在单独的线程中执行,而该线程在进程结束时终止,无论是由于主
线程到达其末尾,还是由于操作系统终止进程。
当作为未冻结的python脚本执行时,上述代码应该按预期工作,当作为onefile应用程序冻结时,PyInstaller也应该按预期工作。然而,onefile应用程序
被PyInstaller版本5.3之前的版本冻结时会遇到问题;由于父应用程序进程中缺少控制台控制信号处理,它总是立即终止,并留下了解压后的临时目录。
从版本5.3开始更改:在冻结应用程序的父进程中实现了控制台控制信号处理,这允许我们将其终止延迟到子进程被终止之后,并清理解压后的临时目录。但是,
仍然存在各种注意事项,后面的小节将进行讨论。
onefile模式和临时目录清理
在PyInstaller的onefile模式下,使用两个进程。启动应用程序时,父进程将嵌入式存档的内容解压缩到临时目录中,设置环境和库搜索路径,并启动子进程。
子进程设置嵌入式python解释器并运行冻结的python应用程序。同时,父进程等待子进程退出;当发生这种情况时,它清理提取的临时数据,然后退出。
从父进程的角度来看,无论子进程干净地退出(即成功代码),或者以错误代码退出(例如,python代码抛出未处理的异常),或者异常退出(例如,由于异常操作引起的崩溃
引发SIGABRTsignal),或者被操作系统终止(例如,从任务管理器中)。在所有情况下,在子进程退出或终止后,父进程执行清理,然后以从子进程返回的退出代码退出。
因此,为了清理应用程序的临时目录,父进程不能被强制终止(例如,通过TerminateProcess函数)。如果发生这种情况,清理代码没有机会运行,并留下了临时目录。另一方面
,从临时目录清理的角度来看,子进程可以以任何方式终止,甚至强制终止。为了在通过控制台控制信号(例如,在按_Ctrl+C_或由于关闭控制台窗口而触发时)触发的正常关机期间进行适当的清理
触发,PyInstaller 5.3及更高版本中的引导程序尝试延迟父进程关闭,以便子进程有时间退出,父进程的主线程有机会运行清理代码。
以下各节为不同情况下的此行为提供了附加详细信息。
通过Ctrl+C或Ctrl+Break中断
在控制台窗口中按_Ctrl+C或_Ctrl+Break_时,CTRL_C_EVENTorCTRL_BREAK_EVENT发送给所有连接到该控制台的进程。^4.
在onefile冻结应用程序中,父进程忽略/抑制信号,因此结果取决于子进程中的冻结python代码如何处理信号。如果python代码退出(例如,没有安装handler和KeyboardInterruptexception中断程序流程),则父进程执行清理并退出。如果子进程中的python代码处理信号而没有关闭子进程,则该应用程序将继续运行。
任何PyInstaller版本都可以使用此行为;在版本5.3之前的版本中,父进程显式忽略了SIGABRT和SIGBREAK信号,从而实现了与处理相应控制台控制信号的相同结果,这在5.3版本中得以实现。
(4)如果从控制台启动windowed/noconsole应用程序,则该应用程序在具有窗口的情况下完全独立于控制台。如果应用程序没有窗口(即,“隐藏”应用程序),则其进程不会收到响应
_Ctrl+C_和_Ctrl+Break_按下时发出的CTRL_C_EVENT和CTRL_BREAK_EVENT信号,但是在关闭控制台时仍然被终止。终止似乎是立即而无条件的,即,不存在CTRL_CLOSE_EVENT信号。
关闭控制台窗口
当关闭控制台窗口(通过在标题栏上按_X_按钮)时,CTRL_CLOSE_EVENT发送到连接到该控制台的所有进程。?
在onefile冻结应用程序中,父进程接收信号并暂停处理程序的执行线程20秒。这种方法,父进程的终止被延迟,以便子进程(也接收到该信号)有时间退出,并且父进程的主线程执行清理和退出(然后也终止处理程序的执行线程)。在PyInstaller 5.3中实现了这种行为,以确保关闭控制台窗口可清除应用程序的临时目录。
在5.3版本之前的版本中,CTRL_CLOSE_EVENT不被处理。父进程立即终止,无法执行清理,留下应用程序的临时目录。
**注:**子进程(即冻结的Python应用程序代码)可能会安装自己的控制台控制信号处理程序,以执行自己的清理(例如保存应用程序状态)。 如果是这样,则重要的是要记住系统规定的五秒超时时间,并且父进程只能在子进程退出后执行临时目录清理。 换句话说,如果子进程中的清理接近五秒钟,那么在操作系统终止进程之前,父进程可能没有机会执行自己的清理。
通过任务管理器终止应用程序
通过任务管理器终止应用程序有些不可预测,因为“应用”和“后台进程”之间存在区别。
“应用”通过向应用程序发送关闭请求来关闭。如果这些应用程序在响应请求时关闭其窗口,或者如果它们有控制台,它们处理CTRL_CLOSE_EVENT控制台控制信号,则这些应用程序可以正常关闭。
“后台进程”使用TerminateProcess无条件终止,没有希望进行优雅的关机和清理。
这两者之间的区别是基于程序是否具有可见窗口,但实际上,有关控制台启用的应用程序和具有多个进程的应用程序存在额外的细微差别。
要在每个进程基础上查看详细分类,请右键单击任务管理器中进程列表视图的标题,并启用Type列的显示。 新添加的列将显示每个进程的进程分类,而不仅仅是整个进程组。
在以下子部分中,我们详细说明与冻结应用程序相关的不同进程关闭行为。 粗略地说,行为高度取决于以下因素:
2.9. 特定功能的注释 57
有窗口/无控制台单进程应用程序
在有窗口/无控制台的单进程应用程序中,没有控制台的单进程应用程序是最容易理解任务管理器和关闭行为的应用程序。
如果应用程序具有窗口(例如基于Qt的GUI),则它被视为“应用程序”。它在“应用程序”下列出,并且其进程名称列在列表中的顶级条目旁边。通过“结束任务”关闭它会导致窗口关闭事件被发布,从而允许进行优雅的应用程序关闭。
如果应用程序没有窗口(无窗口和无控制台的“隐藏”应用程序),则它被视为“后台进程”,并且在“后台进程”下列出。通过“结束任务”关闭它会导致其被无条件终止,没有希望进行优雅的应用程序关闭。
如早期部分所述,无论是不是从控制台启动,有窗口/无控制台应用程序都不依赖于控制台,只要它们有窗口。另一方面,如果应用程序没有窗口,则控制台进程的关闭会导致立即和无条件地终止应用程序进程(控制台内的后台进程)。
由于onedir应用程序不需要将其内容解压缩到临时目录,因此终止模式从PyInstaller的角度并不会影响清理。但是,如果应用程序希望执行一些清理工作(例如在关机期间保存当前状态,如前面的示例所示),则可能会引起关注。
启用控制台单进程应用程序
任务管理器和启用控制台的单进程应用程序的关闭行为取决于应用程序本身是否具有窗口(例如启用控制台的基于Qt的GUI应用程序)或无窗口(“纯”控制台应用程序),以及应用程序是否拥有控制台窗口。
通过双击运行纯控制台onedir应用程序
通过双击可执行文件运行纯控制台应用程序将打开一个新的控制台,其中应用程序在其中运行。 进程列表中的顶级条目被放置在“应用程序”下之下; 然而,它没有进程名称列在其旁边。相反,它是一个由“控制台窗口宿主”(一个“Windows进程”)和实际的应用程序进程组成的组,后者分类为“应用程序”。
通过“结束任务”关闭整个组(即顶级条目)会导致一切被无条件终止。
关闭应用程序进程会导致它接收CTRL_CLOSE_EVENT进行优雅和安全的关闭。
通过现有控制台运行纯控制台onedir应用程序
打开新的命令提示符会导致在“应用程序”下添加一个新的“Windows命令处理器”组条目。它由“控制台窗口宿主”(一个“Windows进程”)和“命令提示符”(一个“应用程序”)组成。从打开的命令提示符中运行纯控制台应用程序会导致新进程添加在现有的“Windows命令处理器”组中,并且在进程分类为“后台进程”。
因此,关闭整个组会导致一切被无条件终止。
关闭应用程序进程会导致它被无条件终止。
关闭“命令提示符”进程会导致应用程序进程接收CTRL_CLOSE_EVENT优雅和安全的关闭。
在双击运行的有窗口/启用控制台单进程应用程序
通过双击运行具有窗口的启用控制台的应用程序类似于对应的纯控制台应用程序情况。所得到的进程列表条目被放置在“应用程序”下,是一个由“控制台窗口宿主”(一个“Windows进程”)和实际应用程序进程组成的组,后者分类为“应用程序”。
通过关闭整个组,会导致一切被无条件终止。
关闭应用程序进程会导致它接收CTRL_CLOSE_EVENT进行优雅和安全的关闭。
通过现有控制台运行有窗口/启用控制台单进程应用程序
从现有命令提示符中运行带窗口的启用控制台的应用程序不会将应用程序进程放到现有的“Windows命令处理器”组中,而是结果是在进程列表中添加一个新的“应用程序”顶级条目。此条目的行为类似于windowed onedircase;它在其旁边列出进程名称,通过“结束任务”关闭它可以发布窗口关闭事件,从而允许进行优雅的应用程序关闭。
关闭整个“Windows命令处理器”会关闭控制台,但应用程序本身仍在运行(尽管其控制台句柄可能无效)。
关闭“Windows命令处理器”组内的“命令提示符”进程结果应用程序进程接收到CTRL_CLOSE_EVENT以进行优雅的关闭。
启用控制台的单文件应用程序
启用控制台的单文件应用程序的关闭行为变得复杂是因为涉及两个进程,并且应用程序内容需要提取到临时目录中,该目录在关闭应用程序时应理想地进行清理。
通过双击运行的纯控制台单文件应用程序
通过双击可执行文件运行纯控制台应用程序会在其中运行应用程序的新控制台中打开。进程列表中的顶级条目位于“应用程序”下方,其中包括:
关闭整个组会导致所有内容被无条件终止。临时目录保留在系统中。
关闭子进程会导致其立即无条件终止。在子进程终止后,父进程执行临时目录清除并退出,也会关闭控制台。这种情况的唯一潜在缺点是应用程序代码无法执行自己的清理操作。
关闭父进程会导致父进程和子进程都接收到CTRL_CLOSE_EVENT。在子进程执行其清理操作(如果有的话)并退出后,父进程也会执行临时目录清除并退出。这是最理想的情况。
(^5)句柄无效的控制台可能会导致应用程序代码尝试使用它们时出错,例如向(现在不存在的)控制台打印消息。
(^6)假设应用程序代码中的潜在清理操作不会延迟关闭到操作系统在父进程有机会执行临时目录清理之前杀死父进程的时候……
通过现有控制台运行的纯控制台单文件应用程序
在打开的命令提示符中运行纯控制台应用程序会导致在其中添加两个新进程的“Windows命令处理器”组中,两个进程都被分类为“后台进程”。
关闭整个“Windows命令处理器”组会导致其中的所有内容被无条件终止,并留下临时目录。
关闭父进程会导致其立即无条件终止。控制台再次接受输入,而子进程(实际应用程序)仍在后台运行(即仍将其输出写入控制台)。由于父进程在执行清理操作之前被终止,因此临时目录被保留。
关闭子进程同样会导致其立即无条件终止。在子进程终止后,父进程执行临时目录清除并退出。这种情况的唯一潜在缺点是应用程序代码无法执行自己的清理操作。
关闭“命令提示符”进程是最佳选择,因为它会导致父进程和子应用程序进程都接收到CTRL_CLOSE_EVENT进行优雅的关闭。
但是,在这种情况下,最可靠的关闭应用程序的方法可能是使用Ctrl +C或Ctrl + Break,甚至关闭控制台窗口。
通过双击运行的具有窗口的启用控制台的单文件应用程序
通过双击运行带有窗口的启用控制台应用程序会导致在进程列表中出现两个顶级条目。
第一个条目是属于父级进程的组;其中包含一个“控制台窗口主机”(一个“Windows进程”)和被分类为“应用程序”的父进程。
子进程被列为单独的顶级条目,也被分类为“应用程序”,其进程名称在旁边列出。
关闭整个父进程组会导致其中的所有事物被无条件终止,而子进程(实际应用程序)仍在运行。临时目录保留在系统中。
关闭父进程会导致父进程和子进程都接收到CTRL_CLOSE_EVENT。在子进程执行其清理操作(如果有的话)并退出后,父进程执行临时目录清除并退出。这是最理想的情况。
关闭子进程会导致其接收到CTRL_CLOSE_EVENT以进行优雅的关闭。在子进程执行其清理操作(如果有的话)并退出后,父进程执行临时目录清除并退出。这是最理想的情况;在这种情况下,即使子进程超出信号处理超时并被操作系统强制终止,父进程也会执行临时目录清除。
在打开的命令提示符中运行具有窗口的启用控制台应用程序会将父进程添加到现有的“Windows命令处理器”组中,作为“后台进程”。
子进程被列为单独的顶级条目,被分类为“应用程序”,其进程名称在旁边列出。
关闭整个“Windows命令处理器”会关闭控制台,并导致父进程立即无条件终止。子进程(应用程序本身)继续运行(尽管其控制台句柄可能无效)。临时目录保留在系统中。
关闭父进程会导致其立即无条件终止。控制台保持打开并再次接受输入,而子进程(实际应用程序)仍在后台运行(即仍将其输出写入控制台)。由于父进程在执行清理操作之前被终止,因此临时目录被保留。
关闭子进程会导致其接收到CTRL_CLOSE_EVENT以进行优雅的关闭。在子进程执行其清理操作(如果有的话)并退出后,父进程执行临时目录清除并退出。这是最理想的情况;在这种情况下,即使子进程超出信号处理超时并被操作系统强制终止,父进程也会执行临时目录清除。
关闭“命令提示符”进程会导致父进程和子应用程序进程都接收到CTRL_CLOSE_EVENT以进行优雅的关闭。这是最理想的情况。
带窗口/不带控制台的单文件应用程序
对于带窗口/不带控制台的单文件应用程序,应用程序的父进程通常被分类为“后台进程”。子进程的分类取决于应用程序是否具有窗口。
没有窗口的不带控制台单文件应用程序,通过双击运行
通过双击可执行文件运行“隐藏”应用程序(没有控制台/没有窗口的应用程序)会导致父进程和子进程作为两个不同的顶级条目添加到进程列表中,在“后台进程”下方。
关闭父进程会导致它立即且无条件地终止。子进程(即实际应用程序)继续运行。由于父进程在执行清理之前就被终止了,所以临时目录被留下。
关闭子进程也会导致其立即且无条件地终止。在子进程被终止后,父进程执行临时目录清理并退出。这种情况唯一的潜在缺陷是应用程序代码无法执行自己的清理。
在已存在的控制台窗口中运行无窗口一文件应用程序
从已打开的命令提示符运行“隐藏”的应用程序会产生两个新进程,都被归类为“后台进程”,并添加到现有的“Windows命令处理器”组中。
关闭整个“Windows命令处理器”组会导致一切被无条件地终止,并且临时目录被留下。
关闭父进程会导致它立即且无条件地终止。子进程(即实际应用程序)作为后台进程继续运行。由于父进程在执行清理之前就被终止了,所以临时目录被留下。
同样地,关闭子进程会导致其立即且无条件地终止。在子进程被终止后,父进程执行临时目录清理并退出。这种情况唯一的潜在缺陷是应用程序代码无法执行自己的清理。
关闭“命令提示符”进程会关闭控制台,但父进程和子进程仍会作为后台进程运行。它们的条目被移动到“后台进程”下的新组条目中,而被移除的“Windows命令处理器”组中不再存在。
2.9. 特殊功能的注意事项 61
带有窗口的无控制台一文件应用程序,通过双击运行
通过双击运行常规的GUI无控制台应用程序会导致父进程被归类为“后台进程”,子进程被归类为“应用程序”。它们都在进程列表中拥有自己的顶级条目(分别在“后台进程”和“应用程序”下),并且它们的进程名称会在它们旁边列出。
关闭父进程会导致它立即且无条件地终止。子进程(即实际应用程序)继续运行。由于父进程在执行清理之前就被终止了,所以临时目录被留下。
关闭子进程会导致向子进程发送一个窗口关闭请求(以及CTRL_CLOSE_EVENT信号)以进行优雅的关闭。在子进程执行其清理(如果有的话)并退出之后,父进程执行临时目录清理并退出。这是理想的情况;在这种情况下,即使子进程超过信号处理超时并被操作系统强制终止,父进程也会执行临时目录清理。
带有窗口的无控制台一文件应用程序,通过已有的控制台运行
从现有控制台运行常规的GUI无控制台应用程序与通过双击运行它类似,只是父进程(归类为“后台进程”)在“Windows命令处理器”组中的“应用程序”下而不是在“后台进程”下拥有独立条目。
关闭整个“Windows命令处理器”会关闭控制台并导致父进程被立即且无条件地终止。子进程(即应用程序本身)继续运行。临时目录被留下。
关闭父进程会导致它立即且无条件地终止。这不会对控制台或子进程产生影响,它们两个都会继续运行。由于父进程在执行清理之前就被终止了,所以临时目录被留下。
关闭子进程会导致它接收CTRL_CLOSE_EVENT以进行优雅的关闭。在子进程执行其清理(如果有的话)并退出之后,父进程执行临时目录清理并退出。这是理想的情况;在这种情况下,即使子进程超过信号处理超时并被操作系统强制终止,父进程也会执行临时目录清理。
关闭“命令提示符”进程会关闭控制台并导致父进程立即且无条件地终止。由于父进程在执行清理之前就被终止了,所以临时目录被留下。
上述信息涵盖了PyInstaller的大多数正常用途。然而,Python和第三方库的变化是无法预测的。当您尝试捆绑应用程序时,可能会出现PyInstaller本身或捆绑的应用程序中止Python回溯的情况。此时,请依次考虑以下操作,再寻求技术帮助。
62第2章.目录:
2.10.1 针对特定问题的配方和示例
PyInstaller FAQ页面提供了一些常见问题的解决方案。我们的PyInstaller Recipes页面提供了一些高级使用和一些常见问题的代码示例。其中一些菜谱包括:
以及其他。其中许多配方是由用户贡献的。请随时贡献更多的食谱!
2.10.2 查找出错原因
构建时的信息
当运行Analysis步骤时,它会产生错误和警告信息。如果–log-level选项允许,这些信息将显示在命令行之后。Analysis还会在名为build / name / warn-name.txt的警告文件中,放置信息。
当检测到导入并且命名的模块找不到时,Analysis会创建一条消息。当在包中声明一个类或函数(即__init__.py模块),并且导入指定了包名称时,可能也会产生消息。在这种情况下,分析无法确定名称是否应该引用子模块或包。
"找不到模块"消息不被归类为错误,因为通常情况下会有许多这样的消息。例如,许多标准模块会有条件地导入可能存在或可能不存在的不同平台的模块。
所有“模块未找到”的消息都写入到build / name / warn-name.txt文件中。它们不会显示在标准输出中,因为有许多这样的消息。检查警告文件,通常会有许多未找到的模块,但它们的缺失没有影响。
当您运行捆绑的应用程序并且它因导入错误而终止时,那就是检查警告文件的时候了。然后,请查看下面的_帮助PyInstaller找到模块_以了解如何继续。
构建时依赖关系图
在每次运行PyInstaller时,PyInstaller会将有关依赖关系的交叉引用文件写入build/name/目录下。xref-name.html文件在work-path目录下,它是一个html文件,列出了导入图的完整内容,显示哪些模块是由哪些模块导入的。您可以在任何Web浏览器中打开它。查找一个模块名称,然后不断点击“导入自”的链接,直到找到导致该模块被包含的最高级别导入为止。
如果您将–log-level=DEBUG指定给pyinstaller命令,则PyInstaller还会生成表示依赖关系图的Graphviz输入文件。该文件是build/name/graph-name.dot在work-path=directory中。您可以使用任何GraphViz命令,例如dot,处理它,以生成导入依赖项的图形显示。
这些文件非常大,因为即使是最简单的“hello world” Python程序,也最终包括大量的标准模块。因此,在此版本中,图形文件并不是非常有用。
2.10. 当事情出了问题 63
构建时的Python错误
PyInstaller有时会通过引发Python异常来终止。在大多数情况下,异常消息中的原因是清楚的,例如“您的系统不受支持”,或“Pyinstaller需要至少Python 3.7”。其他明显指出应报告的错误存在。
然而,其中一个错误可能令人困惑:IOError(“找不到Python库!”)PyInstaller需要打包Python库,这是Python解释器的主要部分,作为动态加载库链接。该文件的名称和位置因使用的平台而异。某些Python安装默认情况下不包括动态Python库(可能存在静态链接的库,但不能使用)。您可能需要安装某种开发包。或者,库可能存在,但不在PyInstaller正在搜索的文件夹中。
PyInstaller寻找python库的位置在不同操作系统中是不同的,但/lib和/usr/lib在大多数系统中都会被检查。如果您无法将python库放在那里,请尝试在GNU/Linux中的环境变量LD_LIBRARY_PATH或macOS中的DYLD_LIBRARY_PATH中设置正确的路径。
获取调试消息
–debug=all选项(及其_choices_)提供了大量诊断信息。这在开发复杂包或应用程序看起来不起作用时很有用,或者仅了解运行时如何工作。
通常,调试进度消息会发送到标准输出。如果在打包Windows应用程序时使用了–windowed选项,则会将它们发送到任何附加的调试器。如果您没有使用调试器(或没有调试器),则可以使用DebugView免费(啤酒)工具来显示此类消息。它必须在运行打包的应用程序之前启动。
对于–windowedmacOS应用程序,它们不会被显示。
考虑为您的生产版本打包而不使用–debug。调试消息需要系统调用并对性能产生影响。
获取Python的Verbose Imports
您可以使用–debug=imports(请参见_获取Debug消息_上面)构建应用程序,这将向嵌入式Python解释器传递-v(详细导入)标志。这可能非常有用。即使是表面上工作良好的应用程序,也可能会提供信息,以确保它们从装捆包中获得所有导入,而不会泄漏到本地安装的Python中。
Python冗长和警告消息始终进入标准输出,并且在使用–windowed选项时不可见。请记住,不要将其用于您的生产版本。
弄清楚为什么您的GUI应用程序无法启动
如果您使用了–windowed选项,则打包应用程序可能无法启动,并显示类似“Failedtoexecute script my_gui”的错误消息。在这种情况下,您需要获取更多详细的输出以了解情况。
这应该为您提供阻止应用程序初始化的相关错误,然后您可以继续进行其他调试步骤。
64 第2章。 内容:
操作不允许的错误
如果您使用–onefile并且它无法运行,您的程序将出现错误,例如:
./hello: errorwhile loading shared libraries: libz.so.1:
failed to map segmentfrom sharedobject: Operationnotpermitted
这可能是由/tmp目录的错误权限引起的(例如,文件系统使用noexec标志进行挂载)。
解决此问题的简单方法是,在环境变量TMPDIR中设置一个在没有noexec标志挂载的文件系统中的目录的路径,例如:
export TMPDIR=/var/tmp/
2.10.3 帮助PyInstaller查找模块
扩展路径
如果分析识别到需要模块却找不到该模块,则通常是因为脚本正在操作sys.path。在这种情况下,最简单的方法是使用–paths选项列出脚本可能搜索导入的所有其他位置:
pyi-makespec --paths=/path/to/thisdir \
--paths=/path/to/otherdir myscript.py
这些路径将在spec文件中以pathex参数的形式被记录下来。它们将被添加到当前分析期间的sys.path中。
列出隐藏的导入
如果分析认为已找到所有导入项,但应用程序却以导入错误失败,则问题是隐藏导入项;也就是说,在分析阶段不可见的导入项。
当代码使用__import__(),importlib.import_module()或可能使用exec()或eval()时,可能会出现隐藏导入项。当扩展模块使用Python/C API执行导入时,也可能会出现此情况。发生这种情况时,分析不能检测到任何东西。没有警告,只有运行时的ImportError。
要找到这些隐藏的导入项,请使用–debug=imports标志(请参见_获取Python的Verbose Imports_上面)构建应用程序并运行它。
知道需要哪些模块之后,您可以使用–hidden-import命令选项、编辑spec文件或使用钩子文件(请参见_理解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在分析过程中实际上不会被执行,它对__path__所做的更改不会被PyInstaller所看到。我们使用与隐藏导入使用的相同的hook机制来解决这个问题,再加上一些附加逻辑;请参阅下文中的了解PyInstaller Hooks。
请注意,以这种方式挂钩__path__的操纵仅适用于分析。在运行时,所有导入都会拦截并从包内部满足。win32com.shell解决方法与win32com中的其他任何东西相同,而win32com.__path__对…/win32comext一无所知。
偶尔并不足够。
改变运行时行为
更奇怪的情况可以通过运行时挂钩来容纳。这些是操作环境的小脚本,使您的脚本运行之前提供附加的顶级代码。
有两种提供运行时挂钩的方法。您可以使用选项–runtime-hook= _path-to-script_来命名它们。
其次,一些运行时挂钩是提供的。在分析结束时,由分析阶段产生的模块列表中的名称在PyInstaller安装文件夹中查找loader/rthooks.dat。这个文本文件是一个Python字典的字符串表示形式。键是模块名称,值是挂接脚本路径名的列表。如果有匹配,这些脚本将被包含在捆绑应用程序中,并在启动您的主脚本之前调用。
使用选项命名的挂钩按给定顺序执行,并在任何安装的运行时挂钩之前执行。如果您指定–runtime-hook=file1.py --runtime-hook=file2.py,那么运行时的执行顺序将是:
以这种方式调用的挂钩虽然需要小心其导入内容,但几乎可以做任何事情。编写运行时钩子的一个原因是覆盖一些模块的某些函数或变量。 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
2.10.4获取最新版本
如果您有某种原因认为在PyInstaller中发现了一个错误,则可以尝试下载最新的开发版本。这个版本可能具有尚未在PyPI中的修复或功能。您可以从PyInstaller下载页面下载最新的稳定版本和最新的开发版本。
您也可以直接使用pip安装PyInstaller的最新版本:
pip install https://github.com/pyinstaller/pyinstaller/archive/develop.zip
当上述建议都无法帮助时,请在PyInstaller电子邮件列表上寻求帮助。
然后,如果您认为您发现了PyInstaller中的BUG,参考如何报告BUG页面。
下面的讨论涵盖了PyInstaller内部方法的详细信息。您不应该需要这个级别的细节来正常使用,但是如果您想调查PyInstaller代码并可能为其做出贡献,则这些细节是有帮助的,如如何做出贡献所述。
在打包的脚本可以开始执行之前,必须执行许多步骤。概述了这些步骤(单文件和单文件夹程序的工作方式部分)。以下是更多细节,以帮助您了解引导程序做了什么以及如何解决问题。
引导程序
引导程序为运行Python代码准备了一切。它开始设置,然后在另一个进程中返回自己。这种使用两个进程的方法允许了很大的灵活性,并且在Windows的单文件夹模式以外的所有捆绑中都使用。因此,如果在您的系统任务管理器中看到捆绑的应用程序作为两个进程,请不要感到惊讶。
引导程序执行期间会发生什么:
A.第一个进程:启动引导程序。
1.如果是单文件模式,请将捆绑的文件提取并复制到temppath/_MEIxxxxxx。
2.修改各种环境变量:
- GNU / Linux:如果设置了,则将LD_LIBRARY_PATH的原始值保存到LD_LIBRARY_PATH_ORIG中。将我们的路径前缀添加到LD_LIBRARY_PATH中。
- AIX:做同样的事情,但使用LIBPATH和LIBPATH_ORIG。
- OSX:取消设置DYLD_LIBRARY_PATH。
3.设置以处理两个进程的信号。
4.运行子进程。
5.等待子进程完成。
6.如果是单文件模式,请删除temppath/_MEIxxxxxx。
B.第二个进程:引导程序本身作为子进程启动。
1.在Windows上设置激活上下文。
2.加载Python动态库。动态库的名称嵌入在可执行文件中。
3.初始化Python解释器:设置sys.path、sys.prefix和sys.executable。
4.运行python代码。
运行Python代码需要执行几个步骤:
1.运行Python初始化代码,它为运行用户的主脚本准备了一切。初始化代码只能使用Python内置模块,因为一般的导入机制还不可用。它设置Python导入机制,只从嵌入在可执行文件中的存档中加载模块。它还将frozen和_MEIPASS属性添加到sys内置模块中。
2.执行任何运行时钩子:先是用户指定的钩子,然后是任何标准钩子。
3.安装python“egg”文件。当一个模块是一个zip文件(.egg)的一部分时,它已经被捆绑到./eggs目录中。安装意味着将.egg文件名附加到sys.path。Python自动检测sys.path中的项是zip文件还是目录。
4.运行主脚本。
Python在捆绑的应用程序中导入
PyInstaller将已编译的Python代码(.pyc文件)嵌入可执行文件中。PyInstaller将它的代码注入到正常的Python导入机制中。Python允许这样做;该支持在PEP 302“新的导入钩子”中有所描述。
PyInstaller实现了PEP 302规范,用于导入内置模块、导入“冻结”模块(与应用程序捆绑的已编译Python代码)和C扩展。该代码可以在./PyInstaller/loader/pyi_mod03_importers.py中进行阅读。
在运行时,PyInstaller的PEP 302钩子被附加到variables中的sys.meta_path变量上。在尝试导入模块时,解释器将首先尝试使用sys.meta_path中的PEP 302钩子,然后才会在sys.path中进行搜索。因此,Python解释器从捆绑的可执行文件中嵌入的存档中加载导入的python模块。
这是捆绑的应用程序中导入语句的解决顺序:
启动画面
注意: 此功能与MacOS不兼容。在当前设计中,启动画面在一个次要线程中运行,在MacOS上不允许使用Tcl/Tk(或者说底层GUI工具包)。
如果应用程序中捆绑了启动画面,则引导程序的启动过程和线程模型会更加复杂。如果捆绑了启动画面,以下是操作顺序:
注意: Tcl解释器在单独的线程中启动。仅在Tcl解释器执行了启动画面脚本之后,负责提取/启动Python解释器的引导程序线程才会恢复。
2.11.2 pyi_splash模块(详细)
该模块连接到引导程序以向启动界面发送消息。
它旨在充当提供的引导程序功能的RPC接口,例如显示文本或关闭。这使用户的Python程序与如何实现与引导程序通信无关,因为提供了一致的API。
要连接到引导程序,它连接到一个本地tcp服务器套接字,套接字的端口通过环境变量_PYIBoot_SPLASH传递。引导程序通过Python模块_socket连接到套接字。尽管该套接字是双向的,但该模块仅配置为发送数据。由于在启动时需要os模块才能请求环境变量,因此该模块在初始化之前不建立连接。
该模块不支持在显示闪屏时重新加载,即无法重新加载(例如通过importlib.reload()),因为与此模块实例的连接断开时,闪屏自动关闭。
函数
注意: 请注意,如果_PYIBoot_SPLASH环境变量不存在或在连接过程中发生错误,则模块将不会引发错误,但仅仅是不初始化自己(即pyi_splash.is_alive()将返回False)。在向启动画面发送命令之前,应检查模块是否正确初始化,否则将引发RuntimeError。
is_alive()
指示模块能否使用。
如果模块未初始化或通过关闭启动画面被禁用,则返回False; 否则,该模块可用。
update_text(msg)
更新闪屏窗口上的文本。
**参数msg(str)- 要显示的文本
引发
close()
关闭与ipc tcp服务器套接字的连接
这将关闭启动画面并使该模块无法使用。调用此函数后,无法再次打开与闪屏的连接,并且如果此模块的所有函数都变得无法使用
(dest_name, src_name, typecode)
其中,dest_name 是目标文件名(即冻结应用程序中的文件名;因此,必须始终是相对名称),src_name 是源文件名(从哪个位置收集文件的路径),而 typecode 是表示文件或条目类型的字符串。
在内部,PyInstaller 使用许多 typecode 值,但对于普通情况,您只需知道以下值:
type-code
说明 dest_name src_name
'DATA' 任意(数据)文件。 在冻结的应用程序中的名称。 在构建系统上文件的完整路径。
'BINARY'
共享库。 在冻结的应用程序中的名称。 在构建系统上文件的完整路径。
'EXTENSION'
Python 二进制扩展。
在冻结的应用程序中的名称。 在构建系统上文件的完整路径。
'OPTION'
PyInstaller/Python 运行时选项。
选项名称(以及可选值,由空格分隔)。
目标名称对应于冻结的应用程序中的最终文件名,相对于顶级应用程序目录的名称。 它可以包括路径元素,例如 extras/mydata.txt。
第 2.1 章. 内容:
类型为 BINARY 和 EXTENSION 的条目假定表示包含可加载可执行代码的文件,例如动态库。 通常,EXTENSION 用于表示 Python 扩展模块,例如由 Cython 编译的模块。 这两种文件类型以相同的方式进行处理; PyInstaller 扫描它们以获取任何发现的附加链接时间依赖项,并收集它们。 在某些操作系统上,二进制文件和扩展会经历额外的处理(例如针对链接时间依赖项的路径重写和在 macOS 上的代码签名)。
在将 TOC 列表传递给构建目标之前,Analysis 生成的 TOC 列表可以在 spec 文件中进行修改,以包括附加条目(虽然最好通过 Analysis 的 binaries 或 datas 参数传递要包括的额外文件)或删除不需要的条目。
在 PyInstaller 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=runtime-folder, excludes=string_list, typecode=code| 'DATA')
例如:
extras_toc = Tree (‘…/src/extras’,prefix =‘extras’,excludes =[‘tmp’,‘*.pyc’)
这将创建 extras_to 作为一个 TOC 列表,其中包含来自相对路径 …/src/extras 的所有文件的条目,省略具有名称为 tmp 或具有 .pyc 扩展名的文件(或位于一个名为 tmp 的文件夹中)。 这个 TOC 中的每个元组都有:
创建列表一些二进制模块的示例:
cython_mods = Tree(‘…src/cy_mods’, excludes=[‘.pyx’, '.py’, ‘*.pyc’], typecode=‘EXTENSION’)
这将创建一个 TOC 列表,其中包含文件夹 cy_mods 中每个文件的条目,排除扩展名为 .pyx、.py 或 .pyc 的文件(因此,可能仅收集 Cython 创建的 .pyd 或 .so 模块)。 这个 TOC 中的每个元组都有:
2.11.4 检查 archive
档案是包含其他文件的文件,例如 .tar 文件、.jar 文件或 .zip 文件。 PyInstaller 中使用两种类型的档案。一种是 ZlibArchive,它允许有效地存储 Python 模块,并且通过一些 import 钩子可以直接导入。 另一个是 CArchive,类似于 .zip 文件,是一种通用的打包(和可选压缩)任意数据块的方式。 它由于易于从 C 以及 Python 中轻松操纵而得名。 两者都源自共同的基类,因此相对容易创建新的档案类型。
ZlibArchive
ZlibArchive包含已压缩的.pyc或.pyo文件。在规范文件中,PYZ类的调用将创建一个ZlibArchive。
ZlibArchive中的目录是一个Python字典,将一个键(即在import语句中给出的成员名称)与ZlibArchive中的位置和长度相关联。ZlibArchive的所有部分都存储在序列化格式中,因此是与平台无关的。
在运行时,ZlibArchive用于导入捆绑的Python模块。即使使用最大压缩,这也比正常导入更快。在字典中进行查找,而不是在sys.path中搜索。没有目录操作和不需要打开文件(文件已经打开)。只需搜索、读取和解压缩。
Python错误跟踪将指向创建存档条目的源文件(__file__属性来自编译.pyc的时间,捕获并保存在存档中)。这不会告诉用户任何有用的信息,但如果他们向你发送了Python错误跟踪,你可以理解它。
CArchive
CArchive可以包含任何类型的文件。它非常类似于.zip文件。它们在Python中很容易创建,并且从C代码中很容易解压缩。CArchive可以附加到其他文件中,例如ELF和COFF可执行文件。为了允许这样做,归档文件是在文件末尾附带其目录的,只有一个cookie告诉目录从哪里开始,存档本身从哪里开始。
CArchive可以嵌入其他CArchive中。可以在不必解压它的情况下打开并使用内部归档。
每个目录条目具有可变长度。条目中的第一个字段给出条目的长度。最后一个字段是相应打包文件的名称。名称以空字符结尾。压缩对于每个成员是可选的。
每个成员还有一个类型代码。自解压可执行文件使用这些类型代码。如果将CArchive用作.zip文件,则无需担心代码。
ELF可执行文件格式(Windows、GNU/Linux和其他一些操作系统)允许将任意数据连接到可执行文件的末尾,而不会破坏其功能。因此,CArchive的目录位于存档文件的末尾。可执行文件可以将自身作为二进制文件打开,寻找到末尾并“打开”CArchive。
72章2.目录:
图1:ZlibArchive的结构
图2:CArchive的结构
2.11. 高级主题73:
图3:自解压可执行文件的结构
使用pyi-archive_viewer
使用pyi-archive_viewer命令检查任何类型的存档文件:
pyi-archive_viewer archivefile
使用此命令,可以检查使用PyInstaller(即PYZ或PKG)构建的任何存档文件,或者任何可执行文件(.exe文件或ELF或COFF二进制文件)。可以使用以下命令浏览归档文件:
pyi-archive_viewer命令具有以下选项:
-h, --help 显示帮助信息。
-l, --log 快速内容日志。
-b, --brief 打印Python可评估的内容文件名列表。
-r, --recursive 与-l或-b一起使用,应用递归行为。
74章2.目录:
2.11.5检查可执行文件
可以使用pyi-bindepend检查任何可执行文件:
pyi-bindepend executable_or_dynamic_library
pyi-bindepend命令分析您指定的可执行文件或DLL,并将所有二进制依赖项写入stdout。这很方便,可以查找可执行文件或其他DLL需要哪些DLL。
PyInstaller在分析过程中使用pyi-bindepend跟踪二进制扩展的依赖链。
2.11.6创建可再现的构建
在某些情况下,重要的是当您使用完全相同的依赖项构建相同的应用程序时,两个程序包应完全、逐比特相同。
通常情况下不是这样的。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环境变量可以指定自定义时间戳值,以实现可重复的构建。
注意: 我们强烈建议程序包开发人员为其程序包提供钩子。有关如何轻松实现此方法,请参阅“提供PyInstaller钩子”部分。
总之,“钩子”文件扩展了PyInstaller,使其适应Python包使用的特殊需求和方法。
“钩子”一词用于两种类型的文件。一个运行时钩子帮助引导程序启动应用程序。更多关于运行时钩子的信息,请参见“更改运行时行为”。其他钩子在分析应用程序时运行。它们帮助分析阶段找到所需的文件。
大多数Python包使用正常的依赖项导入方法,PyInstaller可以轻松定位它们的所有文件。但有些软件包对Python导入机制的使用方式较为不同或在运行时做出了聪明的更改。因此,PyInstaller无法可靠地找到所有必需的文件,或者可能包含太多的文件。钩子可以告诉我们需要补充导入的其他源文件或者数据文件,或者需要排除的文件。
钩子文件是一个Python脚本,并且可以使用所有Python功能。它还可以从PyInstaller.utils.hooks 中导入辅助方法以及从PyInstaller.compat中导入有用的变量。这些辅助程序在下面有文档记录。
钩子文件的名称是hook-full.import.name.py,其中_full.import.name_是引入的脚本或模块的完全限定名称。您可以在PyInstaller发行版文件夹的hooks文件夹中浏览现有的钩子,并查看为哪些软件包编写了钩子的名称。例如hook-PyQt5.QtCore.py是一个钩子文件,它告诉我们模块PyQt5.QtCore所需的隐藏导入项。当您的脚本包含import PyQt5.QtCore或from PyQt5 import QtCore时,分析注意到hook-PyQt5.QtCore.py的存在,然后调用它。
许多钩子只包含一个语句,即对hiddenimports的赋值。例如,针对dnspython软件包的钩子,称为hook-dns.rdata.py,只有以下语句:
hiddenimports = [
"dns.rdtypes.*",
"dns.rdtypes.ANY.*"
]
当Analysis看到import dns.rdata或from dns import rdata时,它会调用hook-dns.rdata.py并检查其中hiddenimports的值。结果,就好像您的源文件也包含以下内容:
import dns.rdtypes.*
import dsn.rdtypes.ANY.*
钩子还可以导致添加数据文件,并且可以导致某些文件不被导入。下面是这些操作的示例。
当需要这些隐藏导入的模块仅对您的项目有用时,请将钩子文件存储在靠近源文件的位置。然后使用–additional-hooks-dir选项将其位置指定给pyinstaller或pyi-makespec命令。如果钩子文件与脚本位于同一级别,则该命令可以简单地为:
pyinstaller --additional-hooks-dir=. myscript.py
如果您为他人使用的模块编写了钩子,请请求程序包开发人员将该钩子包含在她/他的软件包中或发送钩子文件给我们,以便我们可以使其可用。
钩子是在分析对象查找钩子的文件夹中命名为hook-full.import.name.py的模块。每次Analysis检测到一个import时,它都会查找有匹配名称的钩子文件。当找到一个钩子文件时,Analysis将其代码导入Python命名空间。这将导致执行钩子源中的所有顶部语句,例如import语句,全局名称的分配和函数定义。这些语句定义的名称以命名空间属性的形式对Analysis可见。
因此,钩子是一个普通的Python脚本,可以使用所有正常的Python工具。例如,它可以测试sys.version并根据其调整对hiddenimports的分配。PyInstaller安装中有许多钩子,但社区钩子包中可以找到更大的集合。请浏览它们以获取示例。
作为软件包开发人员,您可以在软件包内为PyInstaller提供钩子。这样做的主要好处是,当您的软件包更改时,您可以轻松地采用这些钩子。因此,您的软件包的用户不需要等待PyInstaller可能跟上这些更改。如果PyInstaller和您的软件包为某个模块提供了钩子,则优先使用您的软件包的钩子,但仍可以通过命令行选项–additional-hooks-dir进行覆盖。
您可以通过在软件包中定义一些简单的setuptools入口点来告诉PyInstaller有关其他钩子。因此,请将以下条目添加到您的setup.cfg中:
[options.entry_points]
pyinstaller40 =
hook-dirs = pyi_hooksample.__pyinstaller:get_hook_dirs
tests = pyi_hooksample.__pyinstaller:get_PyInstaller_tests
这定义了两个入口点:
一个提供PyInstaller钩子和测试的指南的示例项目可在https://github.com/pyinstaller/hooksample中找到。本项目演示了如何定义包括PyInstaller钩子及其钩子的测试以及用于集成到CD/CI测试的示例文件的库。有关此示例项目的详细文档,请访问https://pyinstaller-sample-hook.readthedocs.io/en/latest/。
绝大多数现有的钩子都完全由一个或多个以下全局变量的值分配组成。如果钩子定义了其中任何一个,Analysis将采纳它们的值并将其应用于正在创建的绑定。
hiddenimports = ['_gdbm', 'socket','h5py.defs']
hiddenimports是一个绝对模块名称列表,应该不包括在打包的应用程序中。如果一个被排除的模块仅由被hook模块或其子模块引用,则被排除的名称及其子模块将不会成为bundle的一部分。 (如果在源文件中或某个其他模块中明确导入了被排除的名称,它将被保留。)几个hook使用此功能以防止自动添加thetkinter module。例如:
excludedimports = ['tkinter']
2.12.理解PyInstaller Hooks 77
datas是要作为数据与应用程序捆绑的文件列表。列表中的每个条目是包含两个字符串的元组。第一个字符串指定该系统中的文件(或文件“glob”),第二个字符串指定文件在bundle中所需的名称(这与datas = argument中使用的格式相同,请参见_Adding Data Files_)。例如:
datas = [('/usr/share/icons/education_*.png','icons')]
如果需要收集多个目录或嵌套目录,则可以使用PyInstaller.utils.hooks模块中的helper函数来创建此列表(请参见下文),例如:
datas = collect_data_files('submodule1')
datas + = collect_data_files('submodule2')
在极少数情况下,您可能需要应用逻辑以在文件系统中定位特定文件,例如因为文件在不同平台或不同版本下处于不同位置。然后,您可以编写一个hook()函数,如下所述 在The hook(hook_api) Function下。
binaries是要捆绑为二进制文件的文件或目录列表。格式与datas相同(具有指定源和目标的字符串元组)。二进制文件是datas的一种特殊情况,因为PyInstaller将检查每个文件是否依赖于其他动态库。例如:
binaries = [('C:\\ Windows \\ System32 \\ * .dll','dlls')]
许多hooks使用PyInstaller.utils.hooks模块中的小助手来创建此列表(请参见下文):
binaries = collect_dynamic_libs('zmq')
warn_on_missing_hiddenimports是一个布尔标志,指示是否应该生成缺失的隐藏导入(通过hiddenimports设置)警告。默认情况下,缺少的隐藏导入会生成警告,但是单个hooks可以选择退出此行为,方法是将此变量设置为False。例如:
warn_on_missing_hiddenimports = False
module_collection_mode是控制模块的集合模式的设置。该变量的值可以是字符串或字典。
当设置为字符串时,该变量控制被挂起的包/模块的收集模式。有效的值为:
第78章。 内容:
例子:
# hook-mypackage.py
# 由于这个包在文件系统上搜索 .py 文件,必须以源代码形式收集……
module_collection_mode ='py'
例子:
# hook-mypackage.py
# 仅收集一个子包/模块作为源代码
# (不创建子包的钩子)。
module_collection_mode = {
'mypackage.src_subpackage ':'py'
}
例子:
# hook-mypackage.py
# 将整个包作为源代码收集,但一个子包除外
# (不创建子包的钩子)。
module_collection_mode = {
'mypackage': 'py',
'mypackage.bin_subpackage':'pyz'
}
例子:
# hook-mypackage.py
# 强制以源代码形式收集其他包。
module_collection_mode = {
'myotherpackage1':'py',
'myotherpackage2':'py'
}
能够从给定的钩子控制其他模块/包的收集模式是为那些钩子提供功能的情况而设计的,这些功能需要那些其他模块以源代码形式收集(例如某些深度学习框架中可用的JIT编译)。但是,通过字节码扫描检测特定的函数导入和调用需要访问模块图,因此需要使用hook_api函数。在这种情况下,可以使用thehook_api对象的set_module_collection_mode方法来修改收集模式,而不是设置全局的hook变量。
2.12.理解PyInstaller的钩子79
2.12.4 PyInstaller.compat中的有用项
提供一些与之前的Python版本向后兼容的各种类和函数。
钩子可以从 PyInstaller.compat中导入以下名称,例如:
from PyInstaller.compat importbase_prefix, is_win
is_py36, is_py37, is_py38, is_py39, is_py310 is_py311
当Python的当前版本至少为3.6、3.7、3.8、3.9或3.10、3.11时,值为True。
is_win
在Windows系统中为True。
is_cygwin
当sys.platform == 'cygwin’时,值为True。
is_darwin
在 macOS 中为True。
is_linux
在任何 GNU/Linux 系统中为True。
is_solar
在Solaris中为True。
is_aix
在AIX中为True。
is_freebsd
在FreeBSD中为True。
is_openbsd
在OpenBSD中为True。
is_venv
在任何虚拟环境(virtualenv或venv)中为True。
base_prefix
字符串,正确的基础Python安装路径,无论安装是本地的还是虚拟的环境。
EXTENSION_SUFFIXES
Python C 扩展名列表。用于在文件夹中查找所有二进制依赖项;参见hook-cryptography.py的示例。
2.12.5 PyInstaller.utils.hooks中的有用项
钩子可以从 PyInstaller.utils.hooks 中导入有用的函数。使用完全限定的导入语句,例如:
from PyInstaller.utils.hooks import collect_data_files, eval_statement
此处列出的函数通常是有用的,并在许多现有的钩子中使用。
exec_statement(_statement_)
在外部生成的解释器中执行单个Python语句,并将结果的标准输出作为字符串返回。
例子:
80第2章 内容:
tk_version = exec_statement("from _tkinter import TK_VERSION; print(TK_VERSION)")
mpl_data_dir = exec_statement("import matplotlib; print(matplotlib.get_data_path())
˓→")
datas = [(mpl_data_dir, "")]
说明
自 v5.0 开始,不再建议使用此函数,而是使用新的 PyInstaller.isolatedmodule。
eval_statement( _statement_ )
在外部生成的解释器中执行单个 Python 语句,并评估其输出(如果有)。
例如:
databases = eval_statement('''
import sqlalchemy.databases
print(sqlalchemy.databases.__all__)
''')
fordb indatabases:
hiddenimports.append("sqlalchemy.databases." + db)
说明
自 v5.0 开始,不再建议使用此函数,而是使用新的 PyInstaller.isolatedmodule。
is_module_satisfies( _requirements_ , _version=None_ , _version_attr='__version__'_ )
测试是否安装了符合 PEP 0440 的要求。
参数
- requirements(str) - 以 _pkg_resources.Requirements.parse()_ 格式表示的要求。
- version(str) - 可选的 PEP 0440 兼容版本(例如,_3.14-rc5_ ),用于替代此模块的当前版本。如果非 _None_ ,则此函数忽略此模块的所有 _setuptools_ 发行版,而是将此版本与传递的要求中嵌入的版本进行比较。这将忽略传递的要求中嵌入的模块名称,从而以健壮的方式比较任意版本。请参见下面的示例。
- version_attr(str) - 此模块定义的版本属性的可选名称,默认为 ___version___。如果此模块存在 _setuptools_ 分发(通常是这样),并且 _version_ 参数为 _None_(通常是这样),则将忽略此参数。
**返回** 所需验证的布尔结果。
**返回类型** bool
**引发**
- AttributeError - 如果此模块不存在 _setuptools_ 分发并且此模块未定义其名称为传递的 _version_attr_ 参数的属性。
- ValueError - 如果传递的规范不符合 pkg资源。_resources_要求语法。
2.12. 理解 PyInstaller 钩子 81
示例
# 假设已安装 PIL 2.9.0、Sphinx 1.3.1 和 SQLAlchemy 0.6。
>>>from PyInstaller.utils.hooks import is_module_satisfies
>>> is_module_satisfies('sphinx >= 1.3.1')
True
>>> is_module_satisfies('sqlalchemy != 0.6')
False
>>> is_module_satisfies('sphinx >= 1.3.1; sqlalchemy != 0.6')
False
# 比较两个任意版本。在这种情况下,模块名称 “sqlalchemy” 被忽略了。
>>> is_module_satisfies('sqlalchemy != 0.6', version='0.5')
True
# 由于提供 PIL 的“pillow”项目通过自定义的 “PILLOW_VERSION” 属性发布其版本
# (而不是标准的“__version__”属性),因此通过传递属性名称作为回退来验证 PIL
# 当未由 setuptools 安装时。由于 PIL 通常是由 setuptools 安装的,因此此
# 可选参数通常会被忽略。
>>> is_module_satisfies('PIL == 2.9.0', version_attr='PILLOW_VERSION')
True
另请参阅:
pkg_resources.Requirements 的语法详细信息。
collect_all( _package_name_ , _include_py_files=True_ , _filter_submodules=None_ , _exclude_datas=None_ ,
_include_datas=None_ , _on_error='warn once'_ )
收集给定包名称的所有内容。
参数
- package_name -可导入的包名称。
- include_py_files - 转发给collect_data_files()。
- filter_submodules - 转发给collect_submodules()。
- exclude_datas - 转发给collect_data_files()。
- include_datas - 转发给collect_data_files()。
- on_error - 转发到collect_submodules()。
返回
元组(datas,binaries,hiddenimports),其中包含:
- 所有数据文件,原始 Python 文件(如果包括 **include_py_files** ),以及包元数据文件夹。
- collect_dynamic_libs() 返回的所有动态库。
- **packagename** 及其依赖项的所有子模块。
返回类型 元组
典型用法:
82 第2章 内容:
datas, binaries, hiddenimports = collect_all('my_module_name')
collect_submodules( package , filter=
列出给定包的所有子模块。
参数
# 收集Sphinx的所有子模块不包含单词``测试``。
hiddenimports = collect_submodules(
"Sphinx",`filter=lambda name:'test' not in name)
版本4.5中的更改:添加on_error参数。
is_module_or_submodule(_name_, _mod_or_submod_)
此辅助函数旨在通过返回True,以集合子模块(collect_submodules())的filter参数使用给定名称是否为模块或mod_or_submod的子模块。
例子
以下排除foo.test和foo.test.one但不排除foo.testifier。
collect_submodules('foo', lambda name: not is_module_or_submodule(name,'foo.test
˓→'))
is_package(_module_name_)
检查Python模块是否真正是包含其他模块的模块或包,而不导入主进程中的任何内容。
参数 module_name(str) – 要检查的模块名称。
Return 如果模块是软件包,则返回True,否则返回False。
collect_data_files(_package_, _include_py_files=False_, _subdir=None_, _excludes=None_, _includes=None_)
此函数生成在包中存储的(source,dest)非Python(即数据)文件的列表。它的输出可以直接分配给钩子脚本中的datas;例如,见hook-sphinx.py。
参数:
2.12.了解PyInstaller Hooks 83
collect_dynamic_libs(_package_, _destdir=None_, _search_patterns =['*.dll','*.dylib','lib*.so']_)
此函数生成在package中存储的动态库文件(源,dest)的列表。其输出可以直接分配给钩子脚本中的binaries。package参数必须是指定包名的字符串。
参数
get_module_file_attribute(package)
获取指定模块或包的绝对路径。
分析期间不得直接导入模块和包。因此,为避免漏洞,此函数在需要导入模块并获取其__ file__属性时使用隔离的子过程。
参数 package(str) – 模块或包的完全限定名称。
Returns 此模块的绝对路径。
Return type str
get_module_attribute(module_name, attr_name)
如果指定模块定义此属性或否则引发_AttributeError,则从指定模块中获取传递属性的字符串值。
由于无法在分析期间直接导入模块,因此此函数会生成导入此模块并返回此模块中此属性的字符串值的子过程。
参数
84第2章. 内容:
引发AttributeError - 如果未定义此属性。
get_package_paths(package)
给定一个包,返回存储在此计算机上的包的路径,并返回此特定包的路径。例如,如果pkg.subpkg位于/ abs / path / to / python / libs中,则此函数返回(/ abs /路径/ python / libs,/ abs /路径/ python / libs / pkg / subpkg)。
注意:由于向后兼容性问题,此函数仅返回一个包路径及其基本目录。对于具有多个位置的PEP 420命名空间包,仅返回第一个位置。要获取所有包路径,请使用get_all_package_paths函数,并使用package_base_path帮助程序获得相应的基本目录。
copy_metadata(package_name, recursive=False)
收集分发元数据,以便pkg_resources.get_distribution()可以找到它。
该函数返回list应该分配给datas全局变量。这个列表指示PyInstaller将给定包的元数据复制到冷冻应用程序的数据目录中。
参数
示例
>>> from PyInstaller.utils.hooks import copy_metadata
>>> copy_metadata('sphinx')
[('c:\python27\lib\site-packages\Sphinx-1.3.2.dist-info',
'Sphinx-1.3.2.dist-info')]
某些软件包依赖于通过pkg_resources模块访问的元数据文件。通常,PyInstaller不包括这些元数据文件。如果一个软件包在没有这些文件的情况下失败,则可以在钩子文件中使用此功能轻松添加它们到冷冻束中。返回列表中的元组有两个字符串。第一个是此系统中文件夹的完整路径名。第二个只是文件夹的名称。当这些元组添加到datas中时,文件夹将绑定在顶层。
从版本4.3.1开始更改:防止将dist-info元数据文件夹重命名为破解egg-info(参见#3033)。
从版本4.4.0开始更改:添加递归选项。
collect_entry_point(_name_)
收集给定入口点的所有出口商的模块和元数据。
参数 name(str) - 入口点的名称。请检查使用入口点的库的文档以找到其名称。
返回值 应分别分配给数据和hiddenimports的(datas,hiddenimports)对。
对于像pytestorkeyring这样依赖于插件来扩展其行为的库。
2.12.了解PyInstaller钩子85
示例
pytest使用名为“pytest11”的入口点来进行扩展。为了收集所有这些扩展,使用:
datas,hiddenimports = collect_entry_point(“pytest11”)
这些值可以在挂钩中使用或添加到.specfile的datas和hiddenimports参数中。参见使用Spec文件。
从版本4.3开始。
get_homebrew_path(_formula='')
返回所请求的配方的homebrew路径,或在没有参数调用时返回全局前缀。
以字符串形式返回路径,如果找不到,则返回None。
include_or_exclude_file(_filename_, _include_list=None_, _exclude_list=None_)
基于文件名和包含和排除模式列表的通用包含/排除决策函数。
参数
collect_delvewheel_libs_directory(_package_name_, _libdir_name=None_, _datas=None_, _binaries=None_)
从delvewheel启用的python轮的.libs目录中收集数据文件和二进制文件。这些轮在位于软件包目录旁边的.libs目录中运送共享库,因此不在collect_dynamic_libs()实用程序函数的范围内。
参数
示例
收集属于Windowsscipywheel的scipy.libsdelvewheel目录:
datas,binaries = collect_delvewheel_libs_directory(“scipy”)
当应将收集的条目添加到现有的datas和binaries列表中时,可以使用以下形式以避免使用中间临时变量并将其合并到现有的列表中:
datas,binaries = collect_delvewheel_libs_directory(“scipy”,datas = datas,binaries = binaries)
版本5.6中新增。
Conda支持
用于专门处理Anaconda发行版的帮助程序方法位于PyInstaller.utils.hooks.conda_support中,其设计目的是模仿(虽然是松散的)importlib.metadata包。这些函数从conda-meta目录中的json文件中找到并解析分发元数据。
新版本4.2.0。
如果在Conda环境中运行,则仅在条件子句中包装使用此模块:
from PyInstaller.compat importis_pure_conda
if is_pure_conda:
from PyInstaller.utils.hooks importconda_support
#代码在此。例如
binaries = conda_support.collect_dynamic_libs(“numpy”)
...
所有包都是用于安装它的_distribution name_引用的,而不是您导入它的_package name_。即,使用distribution(“pillow”)而不是distribution(“PIL”)或使用package_distribution(“PIL”)。
distribution(name)
获取给定分布名称(即,您将conda install)的分布信息。
返回类型分配
package_distribution( name )
获取package 的分发信息(即导入的东西)。
返回类型 Distribution
例如,包 pkg_resources 属于分发 setuptools,其中包含三个 packages。
>>> package_distribution("pkg_resources")
Distribution(name="setuptools",
packages=['easy_install', 'pkg_resources', 'setuptools'])
files( name , dependencies=False , excludes=None )
列出属于一个分发的所有文件。
参数
2.12. 了解 PyInstaller Hooks 87
conda_support.distribution(name).files
requires( name , strip_versions=False )
列出分发的要求。
参数
class Distribution( json_path )
Conda 分发的存储桶类表示。
这个类导出以下属性:
变量
class PackagePath( *args )
相对于 Conda 根目录(sys.prefix)的文件名。
即使在非 Posix 操作系统上也继承自 pathlib.PurePosixPath。要转换为指向实际文件的 pathlib.Path,请使用 locate() 方法。
locate()
返回此路径对应于指向文件的 path-like 对象。
walk_dependency_tree( initial , excludes=None )
收集分发及其所有直接和间接依赖项。
参数
第 88 章。目录:
collect_dynamic_libs( name , dest=‘.’ , dependencies=True , excludes=None )
收集分发 name 的 DLL。
参数
2.12.6 使用 PyInstaller.isolated 进行子进程隔离
PyInstaller 钩子通常需要导入它们编写的包,但这样做可能以某种方式操纵 globals,例如 sys.path 或 os.environ,影响构建。例如,在 Windows 上,Qt 的二进制文件被添加到并通过 PATH 加载,以这样的方式,如果您在一个会话中引入多个 Qt 变体,则不能保证每个变体将获得哪个变体的二进制文件!
为了解决这个问题,PyInstaller 在孤立的 Python 子进程中执行任何此类任务,并在钩子中使用PyInstaller.isolated子模块进行操作。
from PyInstaller import isolated
此子模块提供:
call( function , *args , **kwargs )
使用单独的子 Python 调用函数并检索其返回值。
参数
# 定义一个在孤立状态下运行的函数。
def get_matplotlib_data_path():
import matplotlib
# 示例,调用 get_matplotlib_data_path 函数
result = isolated.call(get_matplotlib_data_path)
第 2.12 节。了解 PyInstaller Hooks 89
(接上一页)
返回matplotlib的数据路径
# 使用 isolated.call() 方法调用。
get_matplotlib_data_path = isolated.call(matplotlib_data_path)
对于像上面那样不带参数的单次使用函数,可以稍微滥用修饰器语法来定义并一次性执行函数。
>>> @isolated.call
... def matplotlib_data_dir():
... import matplotlib
... return matplotlib.get_data_path()
>>> matplotlib_data_dir
'/home/brenainn/.pyenv/versions/3.9.6/lib/python3.9/site-packages/matplotlib/mpl-data'
函数可以接受位置和关键字参数并返回大多数通用的Python数据类型。
>>> def echo_parameters(*args, **kwargs):
... return args, kwargs
>>> isolated.call(echo_parameters, 1, 2, 3)
((1, 2, 3), {})
>>> isolated.call(echo_parameters, foo=["bar"])
((), {'foo': ['bar']})
说明
要使一个函数在孤立环境中表现出不同的行为,请检查__isolated__全局变量。
if globals().get("__isolated__", False):
# 我们现在在一个子进程里面。
...
else:
# 这是主进程。
...
decorate(_function_)
装饰一个函数,使其始终在孤立的子进程中调用。
示例
使用方法是编写一个函数,然后在函数前添加@isolated.decorate。
@isolated.decorate
def add_1(x):
'''Add 1 tox, displaying the current process ID.'''
import os
print(f"Process {os.getpid()}: Adding 1 to{x}.")
return x + 1
结果add_1()函数现在可以像正常函数一样调用,它将自动使用一个子进程。
90 第2章. 目录:
>>> add_1(4)
Process 4920: Adding 1 to 4.
5
>>> add_1(13.2)
Process 4928: Adding 1 to 13.2.14.2
类Python(_strict_mode=None_)
启动并连接到一个单独的Python子进程。
这是本模块提供的最低级公共API。直接使用该类的优点是可以在单个子进程中评估多个函数,使其比多次调用call()快。
strict_mode参数控制在子进程无法关闭时的行为。如果启用了严格模式,则会引发错误,否则只记录警告。如果strict_mode的值为None,则使用PyInstaller.compat.strict_collect_mode的值(这取决于PYINSTALLER_STRICT_COLLECT_MODE环境变量)。
示例
要调用一些预定义函数x=foo(),y=bar("numpy")
和z=bazz(some_flag=True)
,所有使用相同的孤立子进程,请使用:
with isolated.Python() as child:
x=child.call(foo)
y=child.call(bar,"numpy")
z=child.call(bazz,some_flag=True)
call( function , *args , **kwargs )
在子 Python 中调用函数。检索其返回值。该方法的用法与 call() 函数完全相同。
2.12.7 the hook(hook_api) Function
除了设置全局值之外,挂钩还可以定义函数 hook(hook_api)。只有在挂钩需要应用复杂逻辑或在源机器上进行复杂搜索时,才需要使用hook()函数。
分析对象调用该函数,并将其传递给hook_api对象,该对象具有以下不可变属性:
name:引起挂钩调用的模块的完全限定名称,例如six.moves.tkinter。
file:模块的绝对路径。如果它是:
path:如果它是包,它是由该模块所有目录的绝对路径组成的列表,否则为 None。通常,列表仅包含包目录的绝对路径。
co:由__file__中的内容编译而成的代码对象(例如,通过内置的compile())。
analysis:加载钩子的Analysis对象。
Thehook_apiobject还提供了以下方法:
2.12 理解PyInstaller Hooks 91
add_imports(* names):Thenames参数可以是单个字符串或字符串列表,用于给出要导入的模块的完全限定名称。这具有与将名称添加到hiddenimports全局变量中相同的效果。
add_datas(tuple_list):Thetuple_list参数具有与datas全局变量使用的格式相同。此调用会将项目添加到该列表中。
add_binaries(tuple_list):Thetuple_list参数具有binaries全局变量使用的格式。此调用会将项目添加到该列表中。
set_module_collection_mode(name,mode):为指定的包/模块名称设置包集合模式。 mode的有效值为:“pyz”,“pyc”,“py”,“pyz + py”,“py + pyz”和None。“None”会在给定包/模块名称的上下文的当前挂钩内清除/重置设置!可以为挂钩的包,其子模块或子包,或其他包设置集合模式。如果name为None,则替换为挂钩包/模块名称。
hook()函数可以使用hook_api的上述方法添加、移除或更改包含的文件。或者,它可以简单地在四个全局变量中设置值,因为这些变量将在hook()返回后进行检查。
钩子可以通过在spec文件中调用get_hook_config()函数访问给定的钩子config参数中给出的用户参数。
get_hook_config(hook_api,module_name,key):获取钩子的用户设置。
参数
a = Analysis(["my-app.py"],
...
hooksconfig = {
"gi": {
"icons": ["Adwaita"],
"themes": ["Adwaita"],
"languages": ["en_GB", "zh_CN"],
},
},
...
)
2.12.8 pre_find_module_path(pfmp_api)方法
您可以使用特殊函数pre_find_module_path(pfmp_api)编写钩子。当Analysis首次看到已挂钩的模块名称时,将调用此方法,而在此之前尚未定位到该模块或包的路径(因此称为“预查找模块路径”)。
仅当将此类钩子存储在名为pre_find_module_path的子文件夹中时,Hooks才能识别它们,这些子文件夹位于分布式钩子文件夹或additional-hooks-dir文件夹中。对于同一模块,您可以同时拥有常规钩子和此类钩子。例如,PyInstaller包括ahooks/hook-distutils.py以及ahooks/pre_find_module_path/hook-distutils.py。
传递的pfmp_api对象具有以下不可变属性:
第92章 内容:
module_name:字符串,已挂接模块的完全限定名称。
pfmp_api对象具有一个可变属性search_dirs。这是一个字符串列表,用于指定将搜索挂钩模块的绝对路径或路径。列表中的路径将按顺序搜索。pre_find_module_path()函数可以替换或更改pfmp_api.search_dirs的内容。
从pre_find_module_path()返回后,search_dir的内容将用于查找和分析该模块。
有关用法示例,请参见hooks/pre_find_module_path/hook-distutils.py文件。当PyInstaller在虚拟环境中执行时,它使用此方法重定向对distutils的搜索。
2.12.9 pre_safe_import_module(psim_api)方法
您可以使用特殊函数pre_safe_import_module(psim_api)编写钩子。已找到已挂钩的模块,但在将其及其递归导入的所有内容添加到已导入模块的“图形”之前,将调用此方法。仅在以下情况下使用先安全导入钩子:
使用此类钩子将动态生成的名称告知PyInstaller。 PyInstaller不会尝试查找动态名称,失败并将它们报告为丢失。但是,如果这些名称有常规挂钩,它们将被调用。
仅当将此类钩子存储在名为pre_safe_import_module的子文件夹中时,Hooks才能识别它们,这些子文件夹位于分布式钩子文件夹或additional-hooks-dir文件夹中(有关示例,请参见分布式hooks/pre_safe_import_module文件夹)。
您可以同时拥有常规钩子和此类钩子,用于同一模块。例如,分布式系统具有ahooks/hook-gi.repository.GLib.py和ahooks/pre_safe_import_module/hook-gi.repository.GLib.py。
传递的psim_api对象提供以下属性,所有属性都是不可变的(尝试更改其中一个会引发异常):
module_basename:字符串,已挂接模块的未定名称,例如text。
module_name:字符串,已挂接模块的完全限定名称,例如email.mime.text。
module_graph:代表到目前为止处理的所有导入的模块图。
parent_package:如果该模块是其包的顶级模块,则为None。否则,表示导入顶级模块的图形节点。
最后两个item,module_graph和parent_package,与module-graph相关,module-graph是PyInstaller用于记录所有导入的内部数据结构。通常,您不需要了解module-graph。
psim_api对象还提供以下方法:
add_runtime_module(fully_qualified_name):使用此方法添加一个函数定义的模块,其名称可能不会出现在源代码中,因为它在运行时动态定义。这对于将模块通知PyInstaller并避免误导性警告很有用。一种典型用法是应用psim_api中的名称:
psim_api.add_runtime_module(psim_api.module_name)
add_alias_module(real_module_name,alias_module_name):real_module_name是现有的模块的完全限定名称,可以按名称导入(如果尚未导入,则将其添加到图形中)。alias_module_name是可能在源文件中引用的名称
2.12. 理解 PyInstaller 的 Hooks 93
但应该被视为真正的模块名称。此方法确保如果 PyInstaller 处理alias_module_name的导入,将使用real_module_name。
append_package_path(directory) : 钩子可以使用此方法将要由 PyInstaller 搜索的软件包路径添加到 PyInstaller,通常是导入模块如果模块被正常执行,那么该路径将被动态添加到路径。directory是一个字符串,是要添加到__path__属性的路径名。
从版本4.4开始,PyInstaller 实现了一种机制,用于向钩子传递配置选项。撰写本文时,此功能仅在_.spec文件_中受支持,并没有相应的命令行接口。
钩子配置选项由一个传递给具有hooksconfig参数的 Analysis对象的字典组成。字典的键表示_hook identifiers_,而值是钩子特定键和值的字典,对应于钩子设置:
a = Analysis(
["program.py"],
...,
hooksconfig={
"some_hook_id": {
"foo": ["entry1", "entry2"],
"bar": 42,
"enable_x":True,
},
"another_hook_id": {
"baz": "value",
},
},
...,
)
2.13.1 支持的钩子和选项
本节列出了实现配置选项支持的钩子。对于每个钩子(或钩子组),我们提供_hook identifier_和支持选项列表。
GObject 内省(gi)钩子
在_gi_钩子标识符下传递的选项控制与 GObject 内省(即,hook-gi.*)相关的各种钩子中的 GLib/Gtk 资源(主题、图标、翻译)的收集。
在 Linux 上冻结基于Gtk3的应用程序时,它们特别有用,因为它们允许用户限制从系统 /usr/share 目录收集的主题和图标数量。
钩子标识符: gi
选项
示例
仅收集Adwaita主题和图标,将收集的翻译限制为英式英语和简体中文,并使用Gtk版本3.0和GtkSource版本4:
a = Analysis(
["my-gtk-app.py"],
...,
hooksconfig={
"gi": {
"icons": ["Adwaita"],
"themes": ["Adwaita"],
"languages": ["en_GB", "zh_CN"],
"module-versions": {
"Gtk": "3.0",
"GtkSource": "4",
},
},
},
...,
)
注意: 目前,module-versions 配置仅适用于GtkSource 、Gtk 和 Gdk。
GStreamer (gi.repository.Gst) 钩子
GStreamer 的收集受到通用 gi 钩子配置(例如,由 languages 选项控制的翻译文件的收集)和名为gstreamer^1的特殊钩子配置的控制,该特殊钩子配置控制 GStreamer 插件的收集。
GStreamer 框架具有许多插件,通常作为单独的软件包安装 (gstreamer-plugins-base、gstreamer-plugins-good、gstreamer-plugins-bad 和 gstreamer-plugins-ugly,在打包系统之间的名称可能会有所不同)。默认情况下,PyInstaller 收集 所有 可用插件及其二进制依赖项;因此,在构建环境中安装所有 GStreamer 插件可能会导致收集许多不必要的插件,并由于单个插件和底层共享库的复杂依赖关系而导致冻结的应用程序大小增加。
钩子标识符: gstreamer?
选项
(^1) 虽然钩子称为 gi.repository.Gst,但选择与 Gstreamer 相关的选项的标识符选择简单的gstreamer。
2.13. 钩子配置选项 95
包含和排除列表都期望基础插件名称(例如,audioparsers、matroska、x264、flac)。在内部,每个名称都转换为模式(例如, ‘**/flac.’),并使用 fnmatch 根据实际的插件文件名进行匹配。因此,也可以在插件名称中包含通配符(*)。
基本示例:排除不需要的插件
排除 OpenCV GStreamer 插件,防止将 OpenCV 共享库引入冻结的应用程序。
a = Analysis(
["my-gstreamer-app.py"],
...,
hooksconfig={
"gstreamer": {
"exclude_plugins": [
"opencv",
],
},
},
...,
)
高级示例:仅包括特定的插件
当优化冻结的应用程序大小时,显式包含实际上应用程序需要的子集插件通常更有效。
考虑以下简单的播放器应用程序:
# audio_player.py
import sys
import os
import gi
gi.require_version('Gst','1.0')
from gi.repository importGLib, Gst
if len(sys.argv) != 2:
print(f"Usage:{sys.argv[0]} " )
sys.exit(-1)
filename = os.path.abspath(sys.argv[1])
if notos.path.isfile(filename):
print(f"Input file{filename}does not exist!")
sys.exit(-1)
Gst.init(sys.argv)
mainloop = GLib.MainLoop()
playbin = Gst.ElementFactory.make("playbin", "player")
playbin.set_property('uri', Gst.filename_to_uri(filename))
playbin.set_property('volume', 0.2)
playbin.set_state(Gst.State.PLAYING)
mainloop.run()
(^2)还可以避免意外指定插件前缀,该前缀通常为_libgst_,但可能是_gst_,具体取决于用于构建GStreamer的工具链。
第96章 2. 内容:
假设虽然应用程序使用通用的playbinandplayerelements,但我们打算冻结应用程序仅播放音频文件。在这种情况下,我们可以如下限制收集的插件:
# 用于在Linux 和Windows上播放FLAC(和可能一些其它音频文件)的gstreamer插件列表,并未完全优化。
gst_include_plugins = [
# gstreamer
"coreelements",
# gstreamer-plugins-base
"alsa", # Linux audio output
"audioconvert",
"audiomixer",
"audiorate",
"audioresample",
"ogg",
"playback",
"rawparse",
"typefindfunctions",
"volume",
"vorbis",
# gstreamer-plugins-good
"audioparsers",
"auparse",
"autodetect",
"directsound",# Windows audio output
"flac",
"id3demux",
"lame",
"mpg123",
"osxaudio", # macOS audio output
"pulseaudio", # Linux audio output
"replaygain",
"speex",
"taglib",
"twolame",
"wavparse",
# gstreamer-plugins-bad
"wasapi", # Windows audio output
]
a = Analysis(
["audio_player.py"],
...,
hooksconfig={
"gstreamer": {
"include_plugins": gst_include_plugins,
},
},
...,
)
确定需要收集哪些插件可能需要对GStreamer管道及其插件系统有很好的了解,并可能导致进行几次测试迭代,以查看所需的多媒体功能是否按预期工作。不幸的是,当涉及像这样使用插件系统的应用程序时,优化应用程序大小并没有免费的午餐。请记住,除了明显命名的插件(例如与FLAC相关的功能flac),您可能还需要收集至少一些来自gstreamer本身的插件(例如coreelements)以及至少一些来自gstreamer-plugins-base。
Matplotlib钩子
用于matplotlib包的钩子允许用户通过backendsoption在matplotlib标识符下控制后端收集行为,如下所述。
**钩子标识符:**matplotlib
选项
后端选择过程
如果backendsoption设置为“auto”(或未指定),则钩子通过扫描代码格式matplotlib.use() 函数调用,收集字面参数,进行所使用后端的自动检测。例如,在代码中使用matplotlib.use(‘TkAgg’)将导致收集TkAgg后端。如果未找到此类调用,则将默认后端确定为第一个可导入的基于GUI 的后端,使用与matplotlib.get_backend() 和matplotlib.pyplot.switch_backend()函数内部使用的相同优先级列表:[‘MacOSX’, ‘Qt5Agg’, ‘Gtk3Agg’, ‘TkAgg’, ‘WxAgg’]。如果没有可导入的GUI后端,则收集无头的’Agg’。
注意: 由于字节码扫描方法的局限性,仅特定形式的matplotlib.use() 调用可以自动检测。必须将后端指定为字符串文字(而不是通过变量传递)。第二个可选参数force也可以被指定,但它也必须是文字,而且不能作为关键字参数指定。
import matplotlib
matplotlib.use('TkAgg') # 被检测到
matplotlib.use('TkAgg', False) # 被检测到
backend ='TkAgg'
matplotlib.use(backend) # 没有被检测到
matplotlib.use('TkAgg', force=False) # 没有被检测到
除了matplotlib模块名称外,其常见别名mpl也可以被识别:
import matplotlib as mpl
mpl.use('TkAgg') # 被检测到
从模块导入函数也应该有效:
from matplotlib import use
use('TkAgg') # 被检测到
如果backendsoption设置为’all’,则选择所有(可导入的)后端,这对应于PyInstaller 4.x和早期版本的行为。可导入后端的列表取决于环境中安装的包;例如,如果安装了PyQt5或PySide2包中的任一包,则Qt5Agg后端将变得可导入。
否则,backendsoption的值将被视为后端名称(如果是字符串)或后端名称列表(如果是列表)。对于用户提供的后端名称,不执行其他验证;无论它们是否可导入,都会收集后端。
示例
a = Analysis(
["my-matplotlib-app.py"],
...,
hooksconfig={
"matplotlib": {
"backends": "auto", # 自动检测;默认行为
# "backends": "all", # 收集所有后端
# "backends": "TkAgg", # 收集特定的后端
# "backends": ["TkAgg", "Qt5Agg"], # 收集多个后端
},
},
...,
)
注意: Qt5Agg后端有条件地导入PyQt5和PySide2包。因此,如果两者都安装在您的环境中,则PyInstaller最终将收集两者。除了增加冻结应用程序的大小外,这还可能导致收集的共享库版本之间发生冲突。为了防止这种情况,请使用–exclude-module选项来排除其中一个包(即–exclude-module PyQt5 或–exclude-module PySide2)。
2.13.2 添加钩子选项
实现对钩子选项的支持需要访问hook_api对象,而该对象仅在hook实现了hook_api函数时可用(如此处所述)。
可以使用get_hook_config()函数获取钩子配置选项的值:
# hook-mypackage.py
from PyInstaller.utils.hooks import get_hook_config
# 与hook选项无关的处理,使用全局hook值
binaries, datas, hiddenimports = ...
# 收集额外数据
def hook(hook_api):
# 布尔选项'collect_extra_data'
if get_hook_config(hook_api,'mypackage', 'collect_extra_data'):
extra_datas = ... # 收集额外数据
hook_api.add_datas(extra_datas)
实现了钩子中的选项处理后,请在“支持的钩子和选项”下添加一个文档部分,以通知用户该选项的可用性及其值的含义。
上述钩子示例允许用户通过在_.spec file_中设置相应选项来切换从mypackage收集的额外数据:
a = Analysis(
["program-using-mypackage.py"],
...,
hooksconfig={
"mypackage": {
"collect_extra_data":True,
},
},
...,
)
PyInstaller在发布文件夹的bootloader文件夹中为某些平台提供预编译的bootloader。如果当前平台(操作系统和字长)没有预编译的bootloader,pip设置将尝试构建一个。
如果您的平台没有预编译的bootloader,或者您想修改bootloader源代码,则需要构建bootloader。为此,请执行以下操作:
这将为您当前的平台生成引导加载程序可执行文件(当然,对于Windows,这些文件将具有.exe扩展名):
引导加载程序架构默认为机器架构,但可以使用–target-arch选项更改,前提是安装了适当的编译器和开发文件。例如,在64位机器上构建32位引导加载程序:
python ./waf all --target-arch=32bit
如果出现错误,请阅读接下来的详细说明,然后寻求技术帮助。
通过设置环境变量PYINSTALLER_COMPILE_BOOTLOADER,pip设置将尝试为您的平台构建引导加载程序,即使已经存在。
支持的平台有:
贡献平台有:
有关交叉构建的更多信息,请阅读以下内容并注意Vagrantfile中提供的虚拟机部分。
2.14.1针对GNU/Linux的构建
开发工具
为了构建引导加载程序,您需要一个开发环境。您可以运行以下命令安装所需的所有内容:
sudo apt-get install build-essential zlib1g-dev
sudo yum groupinstall "Development Tools"
sudo yum install zlib-devel
请参阅发行版的文档以获取其他发行版的信息。
现在,您可以按照上面的方法构建引导加载程序。
或者,您可能想使用Vagrantfile提供的_linux64_ build-guest(请参见下文)。
构建符合Linux标准基准(LSB)的二进制文件(可选)
默认情况下,GNU/Linux上的引导加载程序是”普通“的非LSB二进制文件,对于所有GNU/Linux发行版都应该可以正常工作。
如果出于某种原因您想构建符合Linux标准基准(LSB)的二进制文件,则可以通过在waf命令行中指定–lsb来执行此操作,如下所示:
python ./waf distclean all --lsb
成功构建引导程序需要LSB版本4.0。有关与LSB构建相关的其他选项,请参阅python ./waf --help。
(^1)Linux标准基准(LSB)是一组开放标准,应在GNU/Linux发行版之间增加兼容性。不幸的是,它没有被广泛采用,并且Debian和Ubuntu在2015年秋季放弃了对LSB的支持。因此,PyInstaller引导加载程序不再作为LSB二进制文件提供。
2.14. 构建引导加载程序101
2.14.2构建macOS
在macOS上,请安装Xcode,Apple的用于开发macOS软件的工具套件。您可以安装并使用Xcode的命令行工具,而不必安装完整的_Xcode__包。安装任一项都将提供_clang__编译器。
如果工具链支持universal2二进制文件,则64位引导加载程序默认情况下将构建为支持x86_64和arm64架构的universal2 fat二进制文件。这需要较新的_Xcode__(12.2或更高版本)。在缺乏对universal2二进制文件支持的旧工具链上,将构建单一的x86_64 thin引导加载程序。可以通过将__universal2__或__no-universal2__标志传递给waf build命令来控制此行为。如果尝试使用__universal2__标志和不支持universal2二进制文件的工具链,则会导致配置错误。
no-universal2__标志将目标架构未指定,使结果可执行文件的架构为C编译器的默认值(几乎肯定是构建机器的架构)。如果要构建任一架构的thin可执行文件,请使用__no-universal2__标志,然后可选择通过CC环境变量覆盖编译器并添加-arch__标志。
构建一个thin本机可执行文件:
python waf --no-universal2 all
构建一个thin,x86_64可执行文件(无论构建机器的架构如何):
CC=‘clang -arch=x86_64’ python waf --no-universal2 all
构建一个thin,arm64可执行文件(无论构建机器的架构如何):
CC='clang -arch=arm64’python waf --no-universal2 all
默认情况下,构建脚本针对macOS 10.13,可以通过导出MACOSX_DEPLOYMENT_TARGET环境变量来覆盖。
针对macOS的交叉构建
对于macOS的交叉编译,您需要Clang/LLVM编译器、cctools (ld、lipo 等) 和OSX SDK。
Clang/LLVM 默认是一个交叉编译器,并且几乎可在每个GNU/Linux发行版上使用,因此您只需要一个正确版本的cctools和macOS SDK 即可。
获取它们很容易,只需做一次,然后将结果传输到构建系统中。然后,构建系统可以是正常的(比较新的)GNU/Linux系统^2
(^2) 请注意,为了避免问题,您用于准备步骤的系统应具有与构建系统相同的架构(并且可能还有相同的GNU/Linux发行版版本)。
102 第二章. 内容:
准备工作:获取SDK和构建工具
为了准备SDK和构建cctools,我们使用了OS X Cross工具链中非常有用的脚本。如果您对详情感兴趣以及OS X Cross提供的其他功能,请参阅其主页。
为了避免您阅读OSXCross的文档,我们准备了一份虚拟机定义,可以执行所有所需步骤。如果您感兴趣获得精确的命令,请参考Vagrantfile中的packages_osxcross_debianoid,prepare_osxcross_debianiod和build_osxcross。
请按照以下步骤操作:
1.下载Xcode 12.2或更高版本的命令行工具。您需要一个_Apple ID_来搜索和下载文件;如果您还没有,则可以免费注册。
请确保您遵守相应软件包的许可证。
2.将已下载的_.dmg_文件保存到bootloader/_sdks/osx/Xcode_tools.dmg。
3.使用Vagrantfile自动构建SDK和工具:
vagrant up build-osxcross && vagrant halt build-osxcross
这将创建bootloader/_sdks/osx/osxcross.tar.xz文件,然后安装到构建系统中。
如果由于某种原因失败,请尝试运行vagrant provision build-osxcross。
4.此虚拟机不再使用,您现在可以使用vagrant destroy build-osxcross进行丢弃。
构建引导程序
同样地,使用Vagrantfile自动构建macOS引导程序:
export TARGET=OSX #让Vagrantfile生成macOS平台的引导程序
vagrant up linux64 && vagrant halt linux
这将在*…/PyInstaller/bootloader/Darwin-*/中创建引导程序。
如果由于某种原因失败,请尝试运行vagrant provision linux64。
3.此虚拟机不再使用,您现在可以使用:
vagrant destroy build-osxcross
4.如果您已经完成了macOS引导程序的生成,则再次取消设定_TARGET_:
unset TARGET
如果您不想使用Vagrant文件提供的构建客户机,请执行以下步骤(请参见Vagrantfile中的build_bootloader_target_osx):
mkdir -p ~/osxcross
tar -C ~/osxcross --xz -xf /vagrant/sdks/osx/osxcross.tar.xz
PATH=~/osxcross/bin/:$PATH
python ./waf all CC=x86_64-apple-darwin15-clang
python ./waf all CC=i386-apple-darwin15-clang
2.14. 构建引导程序103
2.14.3 构建Windows引导程序
PyInstaller提供的预编译引导程序是自包含静态可执行文件,对使用的Python版本没有限制。
当您自己构建引导程序时,必须在以下三个选项之间仔细选择:
1.使用Visual Studio C++编译器。
这允许创建自包含静态可执行文件,可用于所有Python版本。这就是为什么随PyInstaller提供的引导程序是使用Visual Studio C++编译器构建的原因。
需要Visual Studio 2015或更高版本。
2.使用MinGW-w64套件。
这允许创建更小的,动态链接的可执行文件,但需要使用与编译Python所使用的同一级别的Visual Studio^3。因此,此引导程序将绑定到特定版本的Python。
原因是,与Unix样系统不同,Windows不提供系统标准C库,而是将其留给编译器。但Mingw-w64没有标准C库。它链接到msvcrt.dll,这在许多Windows安装中存在-但不保证存在。
3.使用cygwin和MinGW。
这将为cygwin创建可执行文件,而不是用于’普通’Windows。
在所有情况下,您可能希望
-设置路径以包含Python,例如设置PATH=%PATH%;C:\python35,
-窥探Vagrantfile或…/appveyor.yml以了解我们如何构建。
您还可以为cygwin构建引导程序。
使用Visual Studio C++构建
-使用我们的_wscript_文件时,您无需运行vcvarsall.bat以在VC++安装和目标架构之间’切换’环境。实际的C++版本无关紧要,使用–target-arch=选项选择目标架构即可。
-如果您没有为其他工作使用Visual Studio,那么仅安装独立的C++构建工具可能是最佳选择,因为它避免了将您不需要的东西充斥在系统中(并节省了非常多的安装时间)。
提示:我们建议使用chocolatey软件包管理器安装build-tools软件。初看起来像过载,但这是安装
C++构建工具的最简单方法。这涉及在管理模式下的两行代码:
...如chocolatey首页上所述的一键安装
choco install -y python3 visualstudio2019-workload-vctools
安装C++构建工具后,您可以按照上述方法构建引导程序。
(^3) 这个描述似乎不准确。我应该依赖于C++运行时库。如果你了解细节,请提交一个问题。
第104章 第2节 内容:
使用MinGW-w64构建
请注意上面提到的限制。
如果Visual Studio不方便,您可以从以下位置之一下载并安装MinGW发行版:
注意:在Windows上,使用MinGW-w64时,请将PATH_TO_MINGW_BIN添加到系统PATH。变量。在构建引导程序之前运行:
set PATH = C:\ MinGW \ bin;%PATH%
现在,您可以按照上面示例的方式构建引导程序。如果您已经安装了Visual C ++和MinGW,则可能需要添加runpython ./waf --gcc all。
使用cygwin和MinGW构建
请注意,这将创建适用于cygwin的可执行文件,而不是适用于“普通”Windows的可执行文件。
使用cygwin的setup.exe安装_python_和_mingw__。
现在,您可以按照上面示例的方式构建引导程序。
2.14.4构建AIX
-默认情况下,AIX构建32位可执行文件。
-对于64位可执行文件,请设置环境变量OBJECT_MODE。
如果Python被构建为64位可执行文件,则处理二进制文件(例如.o和.a)的AIX实用程序可能需要标志-X64。而不是为每个命令提供此标志,提供此设置的首选方式是使用环境变量OBJECT_MODE。根据Python是32位还是64位可执行文件构建,您可能需要设置或取消设置环境变量OBJECT_MODE。
可以使用以下命令确定大小:
$ python -c“import sys; print(sys.maxsize <= 2**32)”
当答案为True(如上所述)时,Python构建为32位可执行文件。
使用32位Python可执行文件时,请按以下方式进行操作:
撤消OBJECT_MODE
./waf configure all
使用64位Python可执行文件时,请按以下方式进行操作:
export OBJECT_MODE = 64
./waf configure all
** 2.14.构建引导程序105 **
**注意:**在使用PyInstaller打包应用程序时,还需要正确设置OBJECT_MODE。
要构建引导程序,您需要与用于构建python的编译器兼容(相同)的编译器。
**注意:**使用不同版本的gcc编译的Python与您使用的可能不兼容。 GNU工具并不总是二进制兼容。
如果您不知道使用的编译器是哪一个,此命令可以帮助您确定编译器是否为gcc或IBM编译器:
python -c“import sysconfig; print(sysconfig.get_config_var(‘CC’))”
如果编译器是gcc,则您可能需要安装其他RPM以支持GNU运行时依赖项。
当使用IBM编译器时,不需要其他先决条件。 IBM编译器的建议值为_:command:xlc_r_。
2.14.5构建自由BSD
FreeBSD引导加载程序可以使用FreeBSD机器上的_通常的步骤_使用clang构建。但是要注意,在FreeBSD上本地编译的任何可执行文件都只能在相等或更新的版本的FreeBSD上运行。为了支持较旧版本的FreeBSD,必须编译出所需支持的最旧OS版本。
或者,可以使用Docker和FreeBSD交叉编译器映像从Linux交叉编译FreeBSD引导加载程序。该映像与最旧的非生命周期结束的FreeBSD发布保持同步,因此在其上编译的任何东西都将在所有活动的FreeBSD版本上运行。
在随机目录中:
-启动docker守护程序(通常是systemctl start docker-可能需要sudo如果您没有设置无根docker)。
-从此处下载最新的交叉编译器.tar.xz映像。
-导入图像:docker image load -i freebsd-cross-build.tar.xz。跨编译器映像现在以名称freebsd-cross-build保存。如果愿意,可以丢弃.tar.xz文件。
然后从此存储库的根目录开始:
-运行:
docker run -v $ (pwd):/io -it freebsd-cross-build bash -c“cd / io / bootloader; ./waf all”
2.14.6 Vagrantfile虚拟机
PyInstaller维护一组虚拟机描述,用于测试和(交叉)构建。为了管理这些框,我们使用vagrant。
所有guests^4在运行vagrant up GUEST或vagrant provision GUEST时将自动构建引导程序。它们将构建32位和64位引导程序。
(^)(4)除了guest osxcross,它将按照Cross-Building for macOS部分中描述的方式构建OS X SDK和cctools。
第106章 第2节 内容:
在构建引导程序时,客人们共享PyInstaller分发文件夹,并将构建的可执行文件放置在构建主机上(进入…/PyInstaller/bootloader/)。
大多数盒子需要安装两个_Vagrant_插件:
vagrant plugin install vagrant-reload vagrant-scp
示例用法:
vagrant up linux64 # 还将构建引导加载程序
vagrant halt linux64 # 或者 'destroy'
# 验证引导加载程序已经重建
git status ../PyInstaller/bootloader/
您可以通过设置环境变量来传递一些参数以配置Vagrantfile,如下:
GUI=1 TARGET=OSX vagrant up linux64
或者像这样:
export TARGET=OSX
vagrant provision linux64
我们当前提供这些客户端:
linux64 GNU/Linux(某些最新版本),用于构建GNU/Linux引导加载程序。
注:Windows box使用密码验证,因此在某些情况下必须输入密码(密码为Passw0rd!)。
build-osxcross GNU/Linux guest用于构建如“为macOS跨构建”部分中所述的OS X SDK和cctools。
2.15.1下一个版本(2023-05-22)
特征
2.15.PyInstaller的107个变更日志
错误修复
-(Linux/macOS)修复了PySide2和PySide6运行时挂钩中的Qt目录路径覆盖。通过QT_PLUGIN_PATH和QML2_IMPORT_PATH环境变量设置的这些路径与PySide2和PySide6一起使用,这些构建使用系统范围的Qt安装,而默认情况下不可移植(例如,Homebrew)。(#7649)
-(Windows)修复VSVersionInfo的字符串序列化,以便考虑StringStruct值可能包含引号字符的可能性。(#7630)
2.15.2 5.11.0(2023-05-13)
特征
错误修复
-(Windows)除非启用了二进制拆分或upx处理,否则不会将收集的二进制文件写入二进制缓存中。(#7595)
过时
第108章2.2.1。目录:引导加载程序
文档
PyInstaller核心
2.15.3 5.10.1(2023-04-14)
错误修复
2.15.4 5.10.0 (2023-04-11)
缺陷修复
2.15. PyInstaller 109更新日志
不兼容性更改
钩子
文档
2.15.5 5.9.0 (2023-03-13)
功能
缺陷修复
2.15.6 5.8.0 (2023-02-11)
功能
缺陷修复
不兼容性更改
钩子
模块加载器
2.15.7 5.7.0 (2022-12-04)
功能
2.15. PyInstaller 111更新日志
缺陷修复
第112章 第2节 内容:
不兼容的更改
弃用
钩子
引导程序
引导程序构建
2.15. PyInstaller 113的变更日志
2.15.8 5.6.2 (2022-10-31)
缺陷修复
2.15.9 5.6.1 (2022-10-25)
错误修复
2.15.10 5.6(2022-10-23)
功能
错误修复
114第二章。目录:
不兼容的更改
引导程序
2.15.11 5.5(2022-10-08)
功能
错误修复
钩子
2.15.12 5.4.1(2022-09-11)
错误修复
2.15。PyInstaller 113的更改日志:
2.15.13 5.4(2022-09-10)
功能
错误修复
不兼容的更改
钩子
第2章第15.14节5.3(2022-07-30)
特性
缺陷修复
钩子
文档
第2章第15.15节5.2(2022年7月8日)
特性
缺陷修复
钩子
118第2章 内容:
2.15.16 5.1(2022-05-17)
Bugfix
挂钩
引导程序
引导程序构建
2.15.PyInstaller 119的Changelog
2.15.17 5.0.1(2022-04-25)
Bugfix
钩子
2.15.18 5.0(2022-04-15)
特征
Bugfix
不兼容的更改
挂钩
PyInstaller 121的更改日志
启动器
文档
PyInstaller核心
启动器构建
2.15.19 4.10(2022年3月5日)
功能
缺陷修复
122第2章。 内容:
挂钩
启动器
2.15.20 4.9(2022年2月3日)
缺陷修复
2.15.21 4.8 (2022-01-06)
特性
2.15.关于PyInstaller 123的更改日志
错误修正
钩子
第124章。目录:
启动加载器
引导加载器构建
2.15.22 4.7 (2021-11-10)
错误修正
钩子
启动加载器
2.15. PyInstaller 125版本更新内容
2.15.23 4.6(2021-10-29)
新增功能
错误修正
126章2. 内容:
不兼容的更改
挂钩
引导程序
2.15. PyInstaller 127 变更日志
2.15.24 4.5.1 (2021-08-06)
** Bugfix**
2.15.25 4.5.0 (2021-08-01)
功能
.spec
文件的Analysis类中排除大多数或全部Bugfix
Add_MEIPASS
到 DLL 搜索路径中,以修复在 cygwin 环境下制作的 onefile 构建中加载 Python 共享库的问题,并在其外部运行。 (#6000)ldd
输出中针对“未找到”行(即 libsomething.so=>not found
)显示缺失库警告,而不是悄悄地忽略它们。 (#6015)python
可执行文件的导入推断库路径的问题,以便检测 Python 共享库。 (#6021)__LINKEDIT
段中的 vmsize
字段的值。 (#6039)ERROR
降级到 WARNING
。 (#6015)shiboken2
(PySide2)和 shiboken6
(PySide6)共享库。 (#6015)不兼容更改
第 128 章内容:
钩子
pandas.io.formats.style
的钩子,以处理间接导入 jinja2
和缺少的模板文件。 (#6010)PySide2.QWebEngineWidgets
和 PyQt5.QWebEngineWidgets
。 (#6020)文档
PyInstaller 核心
引导程序构建
2.15.26 4.4.0 (2021-07-13)
功能
2.15. PyInstaller 129 变更日志
pyi_splash
模块从 Python 中控制闪屏。 可以使用–splash IMAGE_FILE 选项添加闪屏。 如果启用了可选文本,则闪屏将在 onefile 模式下显示解包进度。 此功能仅受 Windows 和 Linux 支持。 感谢 @Chrisg2000 编写此功能。(#4354、#4887)PyQt6
。(#5865)PySide6
。(#5865)hooksconfig
传递给 Analysis,允许指定与 Gtk 应用程序一起打包的图标集、主题和语言环境。a = Analysis(["my-gtk-app.py"],
...,
hooksconfig={
"gi": {
"icons": ["Adwaita"],
"themes": ["Adwaita"],
"languages": ["en_GB", "zh_CN"]
}
},
...)
第130章 内容:
错误修复
2.15. PyInstaller 131更改日志
挂钩
引导程序
文献资料
引导程序构建
2.15.27 4.3(2021-04-16)
特征
错误修复
2.15 版更改日志 PyInstaller 133
钩子
引导
文档
PyInstaller 核心
134 第2章 内容:
重大变化
2.15.28 4.2 (2021-01-13)
功能
故障修复
** 2.15 PyInstaller 135的更改日志**
挂钩
引导加载程序
PyInstaller Core
136章2.内容摘录:
2.15.29 4.1(2020-11-18)
功能
故障修复
挂钩
addexclude_datas
、include_datas
和filter_submodules
参数到collect_all()
函数中,这些参数与collect_data_files
函数的excludes
和includes
参数以及collect_submodules
函数的_filter_
参数相对应。(#5113)difflib
钩子以避免拉取doctests,这仅在作为主程序运行时才需要。distutils.util
钩子以避免拉取lib2to3的unittests,这在冻结软件包中很少使用。heapq
钩子以避免拉取doctests,这仅在作为主程序运行时才需要。multiprocessing.util
钩子以避免拉取python测试套件,因此例如tkinter也不会被拉取。numpy._pytesttester
钩子以避免拉取pytest。pickle
钩子以避免拉取doctests和argpargs,这仅在作为主程序运行时才需要。PIL.ImageFilter
钩子以避免拉取numpy,这是一个可选组件。setuptools
钩子以避免拉取numpy,仅在已安装时导入,不用作依赖。zope.interface
钩子以避免拉取pytest的unittests,这将在冻结软件包中很少使用。hook-gi.repository.HarfBuzz
以修复Gtk应用程序的Typelib错误。(#5133)exec_script()
和eval_script()
中的参数顺序。(#5300)2.15.PyInstaller 137更新日志
引导程序
free()
而不是PyMem_RawFree()
释放Python分配的内存导致的问题。(#4441)文档
2.15章PyInstaller核心:
测试套件和持续集成
引导程序构建
2.15.30更新日志4.0(2020-08-08)
特点
collect_data_files
的钩子实用程序函数添加 excludes
和 includes
参数。2.15. PyInstaller 139 更改记录
Bug 修复
utils/misc.py
中出现的 FileNotFoundError
,该错误发生在将命名空间处理为文件名时。(#4034)_MERGE_
类现在将具有正确的共享依赖项之间的相对路径,可以被引导程序正确地打开。(#1527、#4303)不兼容的更改
2.15. PyInstaller 139 内容:
钩子
2.15. PyInstaller 141更改日志
Bootloader
文档
PyInstaller核心
引导程序构建
2.15.31旧版本
PyInstaller 3.0 – 3.6更改日志
**重要提示:**这是支持Python 2.7的PyInstaller的最后一个版本。Python 2已经结束生命周期,许多软件包即将放弃对Python 2.7的支持-或已经做到了。
安全
特性
漏洞修复
钩子
2.15. PyInstaller 143的变更日志
引导程序
PyInstaller核心
引导程序构建
特性
144第2章. 内容:
漏洞修复
-(conda)修复conda/anaconda平台的检测。
-(GNU/Linux)修复Anaconda Python库的搜索。(#3885,#4015)
-(Windows)通过嵌入清单修复单文件模式下的UAC。 (#1729,#3746)
-(Windows\Py3.7)现在可以在python.exe PE Header中列出VERSION.dll而不是pythonXY.dll时定位pylib(#3942,#3956)
不兼容的更改
PyInstaller 145的更改日志
钩子
引导器
文档
项目和进程
PyInstaller核心
测试套件和持续集成
功能特性
2.15. PyInstaller 147的更改日志
Bug修复
不兼容的更改
钩子
第148章2的内容:
- 改进了查找qt.conf的方法。
- 包括PyQt5的OpenGL回退DLL。 (#3568)。
- 将PyQt5 DLL放在正确的位置。 (#3583)
引导加载程序
模块加载程序
文档
2.15. PyInstaller 149的更改日志
项目和进程
PyInstaller核心
Info.plist
中设置LSBackgroundOnly=True,以隐藏dock中的应用程序图标。这仍然可能被_.spec_文件中的info_plist覆盖。(#1917,#3566)测试套件和持续集成
添加在docker中运行测试的脚本和dockerfile。 (已投稿,不再维护)(#3519)
避免日志消息被写入(和捕获)两次。
修复decoratorskipif_no_compiler。
修复关于“W”运行时Python选项的测试,以验证模块_警告_实际上可以导入。(#3402,#3406)
在不通过pytest捕获输出时修复Unicode错误。
运行pyinstaller -h以验证其是否起作用。
test_setuptools_nspkgno longer modifies source files.
Appveyor:
- 添加用于appveyor.yml的Appveyor变量文档。
- 重大清理appveyor.yml (#3107)
- 其他测试产生>1小时的运行时间。将每个任务分为两个任务。
- 在2个内核上运行Appveyor测试;因此,同时运行2个作业。
- 减少磁盘使用。
- 将Python 2.7测试分为两个作业,以避免1小时限制。
- 更新使用Windows Server 2016。(#3563)
Travis
- 使用构建阶段。
- 清理travis.yml (#3108)
- 修复(OS X)上的Python安装。(#3361)
- 仅为“Test - Libraries”阶段启动X11服务器。
- 使用目标Python解释器编译启动加载程序以检查此Python版本的构建工具是否可以使用。
引导加载程序构建
2.15. PyInstaller 151的更改日志
已知问题
钩子
引导程序
引导程序构建
PyInstaller Core
测试套件和持续集成
文档
2.15. PyInstaller 153的更改日志:
已知问题
不兼容的更改
钩子
第154章 内容:
∗修复homebrew中查找qmake的路径 (#2354)
∗修复Qt dll位置 (#2403)
∗捆绑 PyQT 5.7 DLLs (#2152)
∗PyQt5: 返回包括子目录的qml插件路径 (#2694)
∗修复PyQt5.QtQuick的钩子 (#2743)
∗PyQt5.QtWebEngineWidgets: 包括QWebEngine所需的文件
- GKT+ 和相关的
∗修复windows上的Gir文件路径。
∗当GI的typelib存在时,修复不必要的文件搜索和生成
∗gi: 更改从虚拟环境运行时的gir搜索路径
∗gi: 在OSX代码签名不可识别的目录中打包gdk-pixbuf
∗gi: 在Linux上运行时重新编写GdkPixbuf加载器缓存
∗gi: 为GdkPixbuf支持onefile模式
∗gi: 在gdk-pixbuf-query-loaders-64存在时支持使用
∗gi:OSX仅需要GIR文件
∗gio: 也复制mime.cache
∗修复windows平台上的PyGObject的钩子 (#2306)
引导程序
2.15. PyInstaller 155变更日志
引导程序构建
引导程序的构建得到了很大的改进。在wscript中,构建不再依赖于Python解释器的位大小,而是依赖于编译器。我们拥有一台用于构建Windows引导程序和跨平台构建OS X引导程序的机器。因此,所有维护者现在都能够为所有支持的平台构建引导程序。
模块加载器
PyInstaller核心
分析:检查Python版本是否需要重新构建时进行测试。
分析:在模块中存在语法错误时不应终止,只需忽略它们。
没有找到"数据"时提供更好的错误消息。 (#2308)
构建:OSX:创建Info.plist XML时使用unicode文本。
构建:如果"数据"文件名包含glob特殊字符,则不会失败。 (#2314)
构建:从.spec文件中读取运行时tmpdir。
构建:更新注释。
如果bincache损坏,building将提醒用户。 (# 2614)
Cli-utils: 删除过时命令行选项的优雅处理。
Configure: 当移动旧cache-dir时,要创建新的parent-dir。 (# 2679)
Depend: 包括 Windows 上的vcruntime140.dll。 (# 2487)
Depend: 如果分析的脚本有语法错误,则打印错误消息。
Depend: 在扫描ctypes库时删除非基本名称二进制文件。
增强运行时ctypes导入错误的错误消息。
修复# 2585:py2非Unicode sys.path受到os.path.abspath()引诱。 (# 2585)
如果扩展模块对ctypes有隐藏的导入,则修复崩溃。 (# 2492)
修复过时命令行选项的处理。 (# 2411)
修复Python 3.x上versioninfo.py的破坏性 (# 2623)
修复:“Unicode-objects must be encoded before hashing” (# 2124)
修复:UnicodeDecodeError - collect_data_files未以Unicode格式返回文件名 (# 1604)。
删除过时命令行选项的优雅处理。 (# 2413)
使非Windows的grab版本更有礼貌。 (# 2054)
使utils/win32/versioninfo.py正确处理版本信息。
Makespec:修复PyCrypto的版本号处理。 (# 2476)
对modulegraph进行优化和重构,并扫描ctypes依赖项。
当在源代码中遇到编码错误时,pyinstaller不应崩溃。 (# 2212)
拷贝COLLECT和EXE之前删除目标。 (# 2701)
在添加未找到的数据文件时,要删除无信息的回溯。 (# 2346)
处理导入过程中的线程问题。 (# 2010)
utils/hooks: 在collect_data_files中添加日志记录。
(win32)支持使用pypiwin32或pywin32-ctypes。 (# 2602)
(win32) 使用os.path.normpath确保排除系统库。
(win32) 在Windows MSYS2中查找libpython%.%.dll。 (# 2571)
(win32) 使versioninfo.py正确处理版本信息。 (# 2599)
(win32) 在check_requirements被调用之前不导入pywin32。
(win32) pyi-grab_version和-version-file不起作用? (# 1347)
(win32)关闭PE()对象以避免mmap内存泄漏。 (# 2026)
(win32) 修复:在某些情况下,Windows版本信息中的ProductVersion不显示。 (# 846)
(win32) 修复python2中多字节路径启动程序导入问题。 (# 2585)
(win32)通过_arch_命令转发DYLD_LIBRARY_PATH。 (# 2035)
(win32) 为Python 3.5和3.6添加vcruntime140.dll到_win_includes。 (# 2487)
(OS X)添加libpython%d.%dm.dylib到Darwin(is_darwin)PYDYLIB_NAMES。 (# 1971)
(OS X) macOSbundle Info.plist应为UTF-8。 (# 2615)
2.15 PyInstaller 157的更改日志
实用程序
测试套件和持续集成
158第2章。 内容:
- appveyor:验证构建的引导程序具有期望的arch。
文档
已知问题
-新的、更新的和修复的钩子:botocore(#2094)、gi(#2347)、jira(#2222)、PyQt5.QtWebEngineWidgets
(#2269)、skimage(#2195,2225)、sphinx(#2323,)xsge_gui(#2251)。
解决以下问题:
2.15. PyInstaller 159的更改日志
-(Windows)正确解码由pefile生成的字节对象(#1981)
-(Windows)使用pyinstaller打包pefile。这部分撤消了3.2中的某些更改,在这些更改中,打包的pefile被删除以使用pypi版本。在某些应用程序中,pypi版本的性能要慢得多,并且在PY3上仍存在一些小问题。 (#1920)
-(OS X)MacOS上的PyQt5包装问题(#1874)
-(OS X)替换运行时搜索路径关键字(#1965)
-(OS X)(重新)添加OSX的argv仿真,64位(#2219)
-(OS X)在getImports_macholib()中使用decode(“utf-8”)将字节转换为字节(#1973)
-(Bootloader)修复了段错误(#2176)
-(setup.py)仅在GNU / Linux上通过选项-no-lsb(#1975)
-文档、手册等更新和修复。 (#1986、2002、#2153、#2227、#2231)
-现在,即使是“主”脚本,也会进行字节编译(#1847,#1856)
-手册现在在readthedocs.io上(#1578)
-在安装时尝试编译引导加载程序(如果当前平台没有引导加载程序)(#1377)
-(Unix)使用objcopy创建一个有效的ELF文件(#1812,#1831)
-(Linux):用_FORTIFY_SOURCE(#1820)编译
-新的、更新的和固定的钩子:CherryPy(#1860)、Cryptography(#1425,#1861)、杳(1562),
gi.repository.GdkPixbuf(#1843)、gst(#1963)、Lib2to3(#1768)、PyQt4、PyQt5、PySide(#1783、#1897、#1887)、
SciPy(#1908、#1909)、sphinx(#1911、#1912)、SQLAlchemy(#1951)、traitlets wx.lib.pubsub(#1837、#1838),
-针对窗口模式,为我们的虚拟NullWriter添加isatty()(#1883)
-在SystemExit的情况下不要抑制“无法执行脚本”(#1869)
-不要对引导加载程序文件应用Upx压缩器(#1863)
-为由ctypes使用的lib提供绝对路径(#1934)
-(OSX)修复在NFS上的二进制缓存(#1573,#1849)
-(Windows)在grab_version中修复消息(#1923)
-(Windows)修复Windows示例中错误的图标参数(#1764)
-(Windows)修复win32 Unicode处理(#1878)
-(Windows)通过重新构建winmanifest,修复不必要的重新构建(#1933)
-(Cygwin)修复Cygwin 64位找到Python库的问题(#1307,#1810,#1811)
-(OS X)修复编译问题(#1882)
-(Windows)不再打包pefile,而是使用pypi为windows提供的包
-提供更强大的执行Python脚本的方式
-AIX修复。
-将waf更新到1.8.20版(#1868)
-修复了排除的导入,以更可预测的顺序应用钩子#1651
160章。目录:
-内部改进和代码清理(#1754、#1760、#1794、#1858、#1862、#1887、#1907、#1913)
-测试套件的清理修复和改进
已知问题
-Windows 10和Python 3.5构建的应用程序可能无法在早于10的Windows版本上运行(#1566)。
-目前,多包(MERGE)功能(#1527)已损坏。
-(OSX)支持OpenDocument事件(#1309)已损坏。
解决以下问题:
-修正setuptools 19.4的问题(#1772、#1773、#1790、#1791)
2.15. PyInstaller 161的变更日志
已知问题
162第2章的内容:
已知问题
2.1(2013-09-27)
2.15 PyInstaller 163 重大更新
164 章节 2 内容:
2.15 PyInstaller 165 重大更新
1.5.1 (2011-08-01)
166 第二章 内容:
2.15. PyInstaller 167 的更改日志
智能支持 ctypes:PyInstaller 现在能够跟踪程序源代码中使用 ctypes 的所有地方,并自动捆绑通过 ctypes 访问的动态库(感谢 Lorenzo Mancini 提交)。这在使用支持自定义动态库时非常有用。
在 Windows 下使用PyInstaller 构建的可执行程序现在可以进行数字签名。
在 Python 2.5+ 中增加对绝对导入的支持(感谢 Arve Knudsen)。
在 Python 2.5+ 中增加对相对导入的支持。
增加交叉编译支持:PyInstaller 现在能够在 Linux 下运行时构建 Windows 可执行文件。有关更多详细信息,请参见文档。
增加对 .egg 文件的支持:PyInstaller 现在能够在 .egg 文件中寻找依赖项,打包它们并在运行时提供所有标准功能(入口点等)。
部分支持 .egg 目录:PyInstaller 将其视为普通软件包,因此不会捆绑元数据。
在 Linux/Mac 上,即使系统包没有 .pyc 或 .pyo 文件可用,也可以构建可执行文件,并且系统目录只能由 root 写入。实际上,PyInstaller 将在构建临时目录中动态生成所需的 .pyc/.pyo 文件。
自动为许多第三方包添加导入钩子,包括:
- PyQt4(感谢 Pascal Veret),支持完整的插件。
- pyodbc(感谢 Don Dwiggins)
- cElementTree(本机版本和 Python 2.5 版本)
- lxml
- SQLAlchemy(感谢 Greg Copeland)
- Python 2.5 中的邮件(虽然它不支持 Python 2.4 的旧式语法)
- gadfly
- PyQWt5
- mako
- 改进的 PyGTK(感谢 Marco Bonifazi 和 foxx)。
- paste(感谢 Jamie Kirkpatrick)
- matplotlib
解决了非常讨厌的“未能提取 MSVCRT71”错误,这是由于 DLL 被两次打包造成的(感谢 Idris Aykun)。
从 bootloader 中删除了 C++ 风格的注释,以与 AIX 编译器兼容。
修复了 Linux 下以 DOS 行结尾的 .py 文件的支持(修复了 PyOpenGL)。
在不使用顶层软件包(“import Image”)导入 PIL 时进行修复。
在 NT 下修复了 PyXML 导入钩子(感谢 Lorenzo Mancini)
修复 PyInstaller 拾取错误 optparse 的问题。
提高了 UPX’d/strip’d 文件的二进制缓存的正确性。这解决了在多个相同的第三方库版本之间切换时出现问题(例如,wxPython 允许这样做)。
修复了在 Linux 下导入 optparse 模块的错误(感谢 Louai Al-Khanji)。
在 Python 2.4+ 中,如果在包内导入模块时引发异常,则该模块现在将从父命名空间中移除(以匹配 Python 本身的行为)。
修复了一次性文件包启动时的随机竞争条件,导致生成此异常:“PYZ条目 ‘encodings’(0j)不是有效的代码对象”。
修复了在路径元素中包含unicode字符串时出现的问题。
修复了在非控制台模式下使用“打印”时出现的随机异常(实际上是在 Python 3.0 中修复的pythonw“漏洞”)。
当在 Linux 上运行时,有时临时目录在程序退出时没有被删除。
在64位平台(如x86-64)上启动时修复了随机段错误。
2.15. PyInstaller 169的更改日志
- PyOpenGL(测试2.0.1.09)
- dsnpython(测试1.3.4)
- KInterasDB(由Eugene Prigorodov提供)
1.0(2005-09-19),有关McMillan Python Installer 5b5的支持
感谢所有友好的PyInstaller贡献者,他们提供了新代码、错误报告、修复、评论和想法。下面是一个简短的列表,请告诉我们如果您的姓名被意外地省略了:
2.16.1 对PyInstaller 5.11.0的贡献
170 第2章 内容:
2.16.2 对PyInstaller 5.10.1贡献
2.16.3 对PyInstaller 5.10.0的贡献
2.16.4 对PyInstaller 5.9.0的贡献
2.16.5 对PyInstaller 5.8.0的贡献
2.16.6 对PyInstaller 5.7.0的贡献
2.16. 致谢 171
** 2.16.7 对PyInstaller 5.6.2的贡献**
2.16.8 对PyInstaller 5.6.1的贡献
2.16.9 对PyInstaller 5.6的贡献
2.16.10 对PyInstaller 5.5的贡献
2.16.11 对PyInstaller 5.4.1的贡献
Rok Mandeljc - 核心开发人员
Brénainn Woodsend - 核心开发人员
Jasper Harrison (Legorooj) - 核心开发人员、维护者、发布管理员
Hartmut Goebel - 核心开发人员、维护者
xoviat
Dan Yeaw、Bruno Oliveira、Maxim Kalinchenko、Max Mäusezahl、 Olivier FAURAX、richardsheridan、memo-off
Hartmut Goebel - 核心开发人员,维护者和发布经理。
Bryan A. Jones - 核心开发人员和 PyQt5 管理员。
David Vierra - 核心开发人员和编码专家。
xoviat - 勇敢的贡献者
Hugo vk - 勇敢的贡献者
Mickaël Schoentgen, Charles Nicholson, Jonathan Springer, Benoît Vinot, Brett Higgins, Dustin Spicuzza,
Marco Nenciarini, Aaron Hampton, Cody Scot, Dave Cortesi, Helder Eijs, Innokenty Lebedev, Joshua Klein,
Matthew Clapp, Misha Turnbull, ethframe, Amir Ramezani, Arthur Silva, Blue, Craig MacEachern, Cédric RI-
CARD, Fredrik Ahlberg, Glenn Ramsey, Jack Mordaunt, Johann Bauer, Joseph Heck, Kyle Stewart, Lev Maxi-
mov, Luo Shawn, Marco Nenciarini, Mario Costa, Matt Reynolds, Matthieu Gautier, Michael Herrmann, Moritz
Kassner, Natanael Arndt, Nejc Habjan, Paweł Kowalik, Pedro de Medeiros, Peter Conerly, Peter Würtz, Rémy
Roy, Saurabh Yadav, Siva Prasad, Steve Peak, Steven M. Vascellaro, Steven M. Vascellaro, Suzumizaki-Kimitaka,
ThomasV, Timothée Lecomte, Torsten Sommer, Weliton Freitas, Zhen Zhang, dimitriepirghie, lneuhaus, s3goat,
satarsa,
2.16.28 对 PyInstaller 4.1 的贡献
2.16.29 对 PyInstaller 4.0 的贡献
2.16.30 对 PyInstaller 3.6 的贡献
2.16.31 对 PyInstaller 3.5 的贡献
2.16.32 对 PyInstaller 3.4 的贡献
2.16.33 对 PyInstaller 3.3.1 的贡献
2.16.34 PyInstaller 3.3的贡献
特别感谢xiovat实现了Python3.6的支持,以及Jonathan Springer和xoviat稳定了持续集成测试。
第178章2. 内容:
Guillaume Thiolliere
Justin Harris
Kenneth Zhao
Paul Müller
giumas
y2kbugger
Adam Clark、AndCycle、Andreas Schiefer、Arthur Silva、Aswa Paul、Bharath Upadhya、Brian Teague、Charles Duffy、Chris Coutinho、Cody Scott、Czarek Tomczak、Dang Mai、Daniel Hyams、David Hoese、Eelco van Vliet、Eric Drechsel、Erik Bjäreholt、Hatem AlSum、Henry Senyondo、Jan Čapek、Jeremy T. Hetzel、Jonathan Dan、Julie Marchant、Luke Lee、Marc Abramowitz、Matt Wilkie、Matthew Einhorn、Michael Herrmann、Niklas Rosenstein、Philippe Ombredanne、Piotr Radkowski、Ronald Oussoren、Ruslan Kuprieiev、Segev Finer、Shengjing Zhu、Steve、Steven Noonan、Tibor Csonka、Till Bey、Tobias Gruetzmacher、(float)
2.16.35 PyInstaller 3.2.1的贡献
特别感谢Thomas Waldmann和David Vierra在新构建系统方面的支持。
2.16.积分 179
2.16.36 PyInstaller 3.2的贡献
第180章2. 内容:
2.16.37 PyInstaller 3.1.1的贡献
2.16.38 PyInstaller 3.1的贡献
2.16.积分 181
2.16.39 PyInstaller 3.0 贡献
182 第二章 内容:
2.16.40 PyInstaller 2.1 和旧版本的贡献
2.16. Credits 183
2.17.1 pyinstaller
pyinstaller<参数> 脚本文件…
pyinstaller<参数> .spec 文件名
PyInstaller 是一个可以把 Python 程序打包成独立的可执行文件的程序,适用于 Windows、GNU/Linux、macOS、FreeBSD、OpenBSD、Solaris 和 AIX。相对于类似工具,它的主要优点是 PyInstaller 能与Python3.7-3.11 一起使用,它通过透明压缩构建更小的可执行文件,它是完全多平台的,并使用操作系统支持来加载动态库,从而确保完全兼容性。
您可以传递一个或多个 Python 脚本文件名或一个单独的 .spec 文件名。在第一种情况下,PyInstaller 会生成一个 .spec 文件(与 pyi-makespec 相同),并立即对其进行处理。
如果您传递了 .spec 文件,它将按照规定进行处理,命令行上给出的大多数选项将不会生效。有关详细信息,请参阅 PyInstaller 手册。
位置参数
脚本名
脚本文件名或者确切的一个.spec文件。如果指定了.spec文件,大部分选项都是不必要的,并且会被忽略。
可选参数
-h,--help 显示帮助信息并退出
-v,--version 显示程序版本信息并退出。
--distpath DIR 希望将打包好程序放到的文件夹路径 (默认值: ./dist)
--workpath WORKPATH 为临时工作文件夹,所有的.log文件,.pyz文件等都应放在其中 (默认值:./build)
-y,--noconfirm 无需确认询问,直接替换输出路径(默认值: SPECPATH/dist/SPECNAME)
--upx-dir UPX_DIR UPX utility 工具的路径 (默认按执行路径进行搜索)
-a,--ascii 不包括 unicode 编码支持 (默认包括,如果可用)
--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 为生成的打包应用和打包文件指定名称 (默认值:第一个脚本的根名称)
2.17. Man Pages 185
如何打包、搜索
--add-data 添加非二进制文件或文件夹到可执行文件中,路径分隔符因操作系统而异,一般是使用os.pathsep(如 Windows 平台上是分号";",在大多数 Unix 系统上是冒号":")来分隔,可多次使用此选项。
--add-binary 添加二进制文件到可执行文件中。更多细节参见--add-data 选项,可以多次使用该选项。
-p DIR,--paths DIR 搜索导入模块的路径(类似于使用PYTHONPATH)。可多次使用该选项,路径可以用":"分隔或者使用该选项多次。等价于 中使用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 (实验性质) 添加一个启动画面(splash screen),包含IMAGE_FILE 图像文件。启动画面可在解压包前显示进度更新。
186 Chapter 2. Contents:
如何打包
-d {all,imports,bootloader,noarchive},–debug {all,imports,bootloader,noarchive}
提供一个选项协助调试冻结的应用程序,可以提供多个参数来选择下面的几个选项。
all:包括下面三个选项。
imports:指定 -v 选项给下层 Python 解释器,使其在初始化每个模块时都打印一条消息,显示加载该模块的位置(文件名或内置模块)。参考 https://docs.python.org/3/using/cmdline.html#id4 了解更多信息。
bootloader:告诉引导程序在初始化和启动打包好的应用程序时发出进度消息。用于诊断缺失导入问题。
noarchive:使用该选项,不会像存储在结果可执行文件中的归档文件一样存储所有冻结的 Python 源文件,而是将它们作为文件存储在输出目录中。
--python-option PYTHON_OPTION 指定传递给 Python 解释器的命令行选项。目前支持“v”(相当于“--debug imports”)、“u”和“W <警告控制>”。
-s,--strip 对可执行文件和共享库应用符号表削减(不建议在 Windows 上使用)。
--noupx 即使可用,也不使用 UPX(在 Windows 和 *nix 上运行的方式不同)。
--upx-exclude FILE 在使用 upx 时,不要压缩某些二进制文件。这通常是在压缩过程中由 upx 破坏某些二进制文件时使用的。FILE 是没有路径名的文件名。可多次使用该选项。
Windows 和 Mac Os X 特定选项
-c, --console, --nowindowed 打开一个用于标准输入/输出的控制台窗口(默认)。在Windows上,如果第一个脚本是一个“.pyw”文件,则此选项无效。
-w,--windowed,--noconsole Windows和Mac OS X:不提供用于标准输入/输出的控制台窗口。在Mac OS上,这也会触发构建Mac OS.app包。在Windows上,如果第一个脚本是“.pyw”文件,则此选项会自动设置。此选项在*NIX系统上被忽略。
-i,--icon
FILE.ico:将图标应用于Windows可执行文件。FILE.exe,ID:从exe中提取ID的图标。FILE.icns:将图标应用于Mac OS上的.app包。如果输入的图像文件不是平台格式(Windows上的ico,Mac上的icns),则PyInstaller尝试使用Pillow将图标转换为正确的格式(如果安装了Pillow)。使用“ NONE”来不应用任何图标,从而使操作系统显示一些默认值(默认:应用PyInstaller的图标)。此选项可多次使用。
--disable-windowed-traceback 禁用窗口(无控制台)模式(仅适用于Windows和macOS)下未处理异常的traceback dump,并显示一条消息,表示禁用了此功能。
2.17.手册页187
Windows特定选项
--version-file FILE 添加来自FILE的版本资源到exe中。
-m ,--manifest 将文件或XML添加到exe中。
--no-embed-manifest 生成外部.exe.manifest文件,而不是将清单嵌入exe中。仅适用于onedir模式;在onefile模式中,无论此选项如何,都将嵌入清单。
-r RESOURCE,--resource RESOURCE 将资源添加或更新到Windows可执行文件中。RESOURCE是1到4个项目,FILE [,TYPE [,NAME [,LANGUAGE]]]。FILE可以是数据文件或exe / dll。对于数据文件,必须至少指定类型和名称。语言默认为0或可以指定为通配符*,以更新给定类型和名称的所有资源。对于exe / dll文件,如果省略了TYPE,NAME和LANGUAGE或将它们指定为通配符*,则将添加/更新文件中的所有资源到最终可执行文件中。此选项可多次使用。
--uac-admin 使用此选项创建Manifest,该Manifest将在应用程序启动时请求提升。
--uac-uiaccess 使用此选项允许提升的应用程序与远程桌面一起使用。
Windows并排装配搜索选项(高级)
--win-private-assemblies 将捆绑到应用程序中的任何共享程序集更改为私有程序集。这意味着这些程序集的确切版本将始终被使用,并且在用户机器上的系统级别安装的任何新版本都将被忽略。
--win-no-prefer-redirects 在搜索要捆绑到应用程序中的共享或私有程序集时,PyInstaller将优先考虑不遵循重定向到新版本的策略,并尝试捆绑程序集的确切版本,而不是绑定策略。中的任何程序集的确切版本。
Mac Os特定选项
--argv-emulation 对于macOS应用程序包启用argv仿真。如果启用,启动时初始打开文档/URL事件将由引导加载程序处理,并将传递的文件路径或URL附加到sys.argv中。
--osx-bundle-identifier BUNDLE_IDENTIFIER Mac OS.app包标识符用作代码签名目的的默认唯一程序名称。通常的形式是反向DNS表示法中的分层名称。例如:com.mycompany.department.appname(默认值:第一个脚本的基本名称)
--target-architecture ARCH,--target-arch ARCH 目标架构(仅限macOS;有效值:x86_64,arm64,universal2)。启用在冰冻应用程序的通用2和单一版本之间切换(前提是Python安装支持目标架构)。如果没有指定目标体系结构,则目标当前运行的体系结构。
--codesign-identity IDENTITY 签名代码身份(仅限macOS)。使用提供的身份来签名所收集的二进制文件和生成的可执行文件。如果未提供签名身份,则执行自适应签名。
188章2.内容:
--osx-entitlements-file FILENAME 在代码签名收集二进制文件时使用的权利文件(仅限macOS)。
Rarely Used Special Options
--runtime-tmpdir PATH 在onefile-模式下提取库和支持文件的位置。如果给出此选项,引导程序将忽略由运行时OS定义的任何临时文件夹位置。将在这里创建_MEIxxxxxx文件夹。请仅在确知自己正在做什么的情况下使用此选项。
--bootloader-ignore-signals 告诉引导程序忽略信号,而不是将其转发给子进程。在含有监管进程信号两个引导程序和子进程的情况下非常有用(例如,通过进程组),以避免向子进程发送两次信号。
PYINSTALLER_CONFIG_DIR。这将更改PyInstaller缓存某些文件的目录。这个位置的默认位置取决于操作系统,但通常是主目录的子目录。
pyi-makespec(1),PyInstaller手册https://pyinstaller.readthedocs.io/,项目主页[http://www。](http://www。)
pyinstaller.org
2.17.2 pyi-makespec
pyi-makespec SCRIPT [SCRIPT …]
规范文件是您希望PyInstaller对程序执行的描述。pyi-makespec是一个简单的向导,用于创建涵盖基本用法的规范文件:
pyi-makespec [–onefile] yourprogram.py
默认情况下,pyi-makespec生成一个spec文件,告诉PyInstaller创建一个包含主可执行文件和动态库的发布目录。选项–onefile指定您希望PyInstaller构建一个包含所有内容的单个文件。
在大多数情况下,pyi-makespec生成的规范文件就是您所需的。如果不是,请参阅手册中的“当事情出错时”并确保阅读“规范文件”介绍。
2.17.手册页189
位置参数
脚本名称
可选参数
-h,--help显示此帮助消息并退出
--log-level LEVEL生成时间控制台消息的详细程度。 LEVEL可以是TRACE、DEBUG、INFO、WARN、DEPRECATION、ERROR、FATAL之一(默认值:INFO)。也可以通过并覆盖环境变量PYI_LOG_LEVEL。
产生什么
-D, --onedir 创建一个包含可执行文件的单文件夹文件束(默认)
-F, --onefile 创建一个包含所有文件的单文件可执行文件。
--specpath DIR 存储生成规范文件的文件夹(默认:当前目录)
-n NAME, --name NAME 分配给文件束应用和规范文件的名称(默认:第一个脚本的 base-name)
要打包的内容,要搜索的位置
--add-data 需要添加到可执行文件中的其他非二进制文件或文件夹。路径分隔符是特定于平台的,正被用到os.pathsep(在Windows上是;,在大多数Unix系统上是:)。可以多次使用此选项。
--add-binary 需要添加到可执行文件中的其他二进制文件。有关详细信息,请参阅–add-data 选项。可以多次使用此选项。
-p DIR, --paths DIR 搜寻导入的路径(类似于使用 PYTHONPATH)。可以使用多个路径,用': '分隔或多次使用此选项。相当于在规范文件中提供pathex类型的参数。
--hidden-import MODULENAME, --hiddenimport MODULENAME 命名不可见的导入模块在脚本的代码中(可多次使用此选项)。
--collect-submodules MODULENAME 收集指定包或模块的所有子模块。可以多次使用此选项。
--collect-data MODULENAME, --collect-datas MODULENAME 收集指定包或模块的所有数据。可以多次使用此选项。
--collect-binaries MODULENAME 收集指定包或模块的所有二进制文件。可以多次使用此选项。
--collect-all MODULENAME 收集指定包或模块的所有子模块、数据文件和二进制文件。可以多次使用此选项。
--copy-metadata PACKAGENAME 复制指定包的元数据。可以多次使用此选项。
第二章190:内容:
--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}
R|提供用于调试打包应用程序的帮助。可以提供此参数多次以选择以下选项中的多个选项。 -all:所有以下选项。 -imports:指定-v选项到基础Python解释器,导致其每次初始化模块时打印一条消息,显示加载该模块的位置(文件名或内置模块)。有关详细信息,请参见https://docs.python.org/3/using/cmdline.html#id4。 - bootloader:告诉启动加载器在初始化和启动打包应用程序时发出进度消息。用于诊断缺失导入的问题。 - noarchive:而不是将所有冻结的Python源文件存储为结果可执行文件中的归档文件,将它们作为文件存储在结果输出目录中。
--python-option PYTHON_OPTION 指定要在运行时传递给Python解释器的命令行选项。当前支持“v”(等效于“-debug imports”)、“u”和“W”。
-s, --strip 对可执行文件和共享库应用符号表剥离(不推荐在Windows上使用)
--noupx 即使它可用也不要使用UPX(在Windows和*nix之间工作方式不同)
--upx-exclude FILE 在使用upx进行压缩时,防止对二进制文件进行压缩。如果upx在压缩期间破坏了某些二进制文件,则通常会使用此选项。 文件是不带路径的二进制文件的文件名。可以多次使用此选项。
Windows和Mac OS X特定的选项
-c, --console, --nowindowed 为标准输入/输出打开控制台窗口(默认)。在 Windows 上,如果第一个脚本是“.pyw”文件,则此选项无效。
-w, --windowed, --noconsole Windows和Mac OS X:不为标准i/o提供控制台窗口。在Mac上,这还会触发构建Mac OS .app束。在Windows上,如果第一个脚本是“.pyw”文件,则自动设置此选项。在*nix系统上,此选项被忽略。
-i , --icon
FILE.ico:应用于Windows可执行文件的图标。 FILE.exe,ID:从.exe中提取带有ID的图标
第二章17.手册:191
--disable-windowed-traceback 禁用窗口(无控制台)模式(仅适用于Windows 和macOS)的未处理异常的跟踪转储,而是显示一条消息,说明已禁用此功能。
Windows特定选项
--version-file FILE 将FILE文件中的版本资源添加到exe文件中。
-m ,--manifest 将manifest文件或XML添加到exe文件中。
--no-embed-manifest 生成一个外部的.exe.manifest文件而不是将manifest嵌入exe文件中。只适用于onedir模式;在onefile模式下,无论是否使用此选项,manifest都将被嵌入执行文件中。
-r RESOURCE,--resource RESOURCE 添加或更新Windows可执行文件中的资源。RESOURCE为1到4个项,FILE[,TYPE[,NAME[,LANGUAGE]]]。FILE可以是数据文件或exe/dll文件。对于数据文件,必须至少指定TYPE和NAME。LANGUAGE默认为0,也可以指定为通配符*以更新给定TYPE和NAME的所有资源。对于exe/dll文件,如果类型、名称和语言被省略或使用通配符*指定,则将添加/更新FILE中的所有资源到最终的可执行文件中。此选项可以多次使用。
--uac-admin 使用此选项会创建一个Manifest,该Manifest会在应用程序启动时请求提升权限。
--uac-uiaccess 使用此选项允许提升权限的应用程序与远程桌面一起使用。
**Windows Side-By-Side Assembly Searching Options(Advanced)**
--win-private-assemblies 对于应用程序捆绑的任何共享程序集将更改为专用程序集。这意味着这些程序集的确切版本将始终被使用,用户机器上系统级别安装的任何更高版本都将被忽略。
--win-no-prefer-redirects 在搜索要捆绑到应用程序中的共享或专用程序集时,PyInstaller将优先不遵循重定向到较新版本的策略,并尝试捆绑程序集的确切版本。
Mac Os Specific Options
--argv-emulation 启用macOS应用程序包的argv仿真。如果启用,引导程序将处理初始打开文档/URL事件,并将传递的文件路径或URL附加到sys.argv。
--osx-bundle-identifier Mac OS .app包标识符用作唯一的程序名称以用于签名。通常的形式是反向DNS符号表示的分层名称。例如:com.mycompany.department.appname(默认值:第一个脚本的basename)
--target-architecture ARCH,--target-arch ARCH 目标体系结构(仅适用于macOS;有效值:x86_64、arm64、universal2)。启用在通用2和单一架构版本的冻结应用程序之间切换(只要Python安装支持目标体系结构)。如果未指定目标体系结构,则目标运行体系结构。
--codesign-identity IDENTITY 代码签名身份(仅适用于macOS)。使用提供的身份来签署收集的二进制文件和生成的可执行文件。如果未提供签名标识,则执行自动签名。
--osx-entitlements-file FILENAME 用于在代码签名所收集的二进制文件上使用的权利文件(仅限于macOS)。
Rarely Used Special Options
--runtime-tmpdir PATH 在onefile模式下,使用此选项将库和支持文件提取到的路径。如果给出此选项,启动程序将忽略运行时操作系统定义的任何临时文件夹位置。_MEIxxxxxx-文件夹将在此处创建。请仅在确信自己知道所做的内容时才使用此选项。
--bootloader-ignore-signals 告诉启动程序忽略信号而不是将信号转发给子进程。在需要监管进程向引导程序和子进程发送信号的情况下非常有用(例如通过进程组)。
PYINSTALLER_CONFIG_DIR 这会更改PyInstaller缓存某些文件的目录。这个默认位置取决于操作系统,但通常是home目录的子目录。
pyinstaller(1),The PyInstaller Manual https://pyinstaller.readthedocs.io/,项目主页 http://www.pyinstaller.org
2.17. Man Pages 193
2.18.1 快速入门
git clone https://github.com/pyinstaller/pyinstaller
pip install -r tests/requirements-tools.txt
2.18.2 初次使用GitHub或Git?
我们的开发工作流程建立在Git和GitHub之上。请花时间了解这些。如果您是GitHub新手,GitHub有指导您入门的说明。如果您是Git新手,则有在线教程和优秀的书籍可供阅读。
进一步阅读
2.18.3 编码约定
PyInstaller项目遵循PEP 8 Python代码样式指南。它使用yapf自动执行大部分格式化(主要是自动把空格放在正确的位置),并使用ruff验证PEP 8规则,yapf不支持的地方。
在提交更改到PyInstaller之前,请使用这两个工具检查您的代码。
要安装它们,请运行:
pip install ruff toml yapf==0.32.0
使用yapf自动重新格式化您的代码:
yapf -rip.
然后根据ruff给出的建议手动调整您的代码:
ruff --fix.
请勿重新格式化现有的代码,即使它没有遵循PEP 8。我们不会接受重新格式化的更改,因为它们使审查更加困难,并且在长期内跟进更改更加困难。有关完整的理由,请参见#2727。
2.18.4运行测试套件
请按以下方式运行测试套件。
1.如果您没有PyInstaller的git克隆,请首先使用pip获取当前的开发主要版本,…:
pip download --no-deps https://github.com/pyinstaller/pyinstaller/archive/develop.
˓→zip
unzip develop.zip
cd pyinstaller-develop/
…或使用git:
git clone https://github.com/pyinstaller/pyinstaller.git
cd pyinstaller
2.然后设置一个新的虚拟环境以运行测试套件,并安装所有必需的工具:
pip install --user virtualenv
virtualenv /tmp/venv
。/tmp/venv/bin/activate
pip install -r tests/requirements-tools.txt
3.要运行单个测试,请使用例如:
pytest tests/unit -k test_collect_submod_all_included
4.运行测试套件:
pytest tests/unit tests/functional
这仅运行核心功能的测试以及Python标准库中的一些软件包的测试。
2.18.发展指南195个
5.要获得更好的覆盖范围,包括许多可用的挂钩,需要下载要测试的Python软件包。请运行:
pip install -U -r tests/requirements-libraries.txt
pytest tests/unit tests/functional
要了解如何在持续集成测试中运行测试套件,请参见.travis.yml(适用于GNU / Linux和macOS)和appveyor.yml(适用于Windows)。
2.18.5提交指南
请帮助使代码和更改易于理解。提供遵循本指南的易于阅读的提交历史。
提交
避免一次提交几个不相关的更改。它使合并变得困难,也使确定如果出现错误,则哪个更改是罪魁祸首更加困难。
如果在提交之前进行了几个不相关的更改,git gui使提交选定的部分甚至选定行变得容易。尝试在窗口差异区域内使用上下文菜单。
这将导致更可读的历史记录,使人更容易理解为什么进行了变更。如果出现问题,可以更轻松地使用_git bisect_找到破坏变更并回滚这些破坏变更。
详述
提交应该是一个(而且仅是一个)逻辑单元。它应该是某人可能想要整体修补或还原的东西,而不是逐个修补。如果它可以分开使用,请进行单独的提交。
196章2。目录:
请编写好的提交消息
请帮助使代码和更改易于理解。编写好的提交消息遵循此指南。
提交消息应提供足够的信息,以使第三方能够决定更改是否与他们相关,并且是否需要阅读更改本身。
PyInstaller自2005年以来一直得到维护,我们经常需要在许多年后理解为什么特定的更改已实现如此。当更改应用时似乎很明显的内容多年后可能只是晦涩。原始贡献者可能无法联系,而其他开发人员需要理解原始作者考虑的原因,副作用和决策。
我们得知提交消息对于理解更改很重要,因此我们对它们有点挑剔。
我们可能会要求您重新编写提交消息。在这种情况下,请使用git rebase-i …和git push-f …更新您的拉取请求。有关详细信息,请参见“更新拉取请求”。
提交消息的内容
编写有意义的提交消息。
提交消息的第一行应该
提交消息正文
提交日志的正文应:
(^1)将这些消息作为应用提交的说明。此外,这种约定与诸如git merge和git revert命令生成的提交消息相匹配。
2.18.开发指南197
标准前缀
请将此提交所涉及的“子系统”作为前缀在第一行中声明。要了解其他人使用的前缀,请使用git log --oneline path/to/file/or/dir。
“子系统”的示例包括:
-钩子用于钩子相关更改
-引导加载程序,引导加载程序构建引导加载程序或其构建系统
-依赖项检测部分(PyInstaller / depend)的依赖部分
-用于构建部分的构建部分(PyInstaller / building)
-用于不同Python版本的兼容性的代码相关的兼容性(primaryPyInstaller / compat.py)
-加载器
-工具,工具/钩子
-测试,测试/ CI:用于更改测试套件(包括要求)的,CI。.
请设置正确的作者
请确保您已设置git以使用正确的名称和电子邮件提交。在可能推送的所有机器上使用相同的姓名和电子邮件。
例如:
#设置名称和电子邮件
git config --global user.name “Firstname Lastname”
git config --global user.email “[email protected]”
第198章。目录:
这将设置此名称和电子邮件地址以用于此系统上正在使用的所有git-repos。要将其设置为
仅适用于PyInstaller repo,请删除-global标志。
或者,您可以使用git gui→ _Edit_→ _Options …_来设置这些值。
进一步阅读
有关编写良好提交消息的进一步提示和教程也可以在以下位置找到:
-自由BSD Committer的指南
-[http://365git.tumblr.com/post/3308646748/writing-git-commit-messages](http://365git.tumblr.com/post/3308646748/writing-git-commit-messages)
-[http://wincent.com/blog/commit-messages:](http://wincent.com/blog/commit-messages:)好,坏和丑。
-[http://wiki.scummvm.org/index.php/Commit_Guidelines](http://wiki.scummvm.org/index.php/Commit_Guidelines)
-[http://lbrandy.com/blog/2009/03/writing-better-commit-messages/](http://lbrandy.com/blog/2009/03/writing-better-commit-messages/)
-[http://blog.looplabel.net/2008/07/28/best-practices-for-version-control/](http://blog.looplabel.net/2008/07/28/best-practices-for-version-control/)
-[http://subversion.apache.org/docs/community-guide/conventions.html](http://subversion.apache.org/docs/community-guide/conventions.html)(目标过于注重子版本使用,我们强烈要求使用如此细粒度的提交。)
积分
这个页面是从以下材料中组成的
-[http://hackage.haskell.org/trac/ghc/wiki/WorkingConventions/Git](http://hackage.haskell.org/trac/ghc/wiki/WorkingConventions/Git)
-[http://lbrandy.com/blog/2009/03/writing-better-commit-messages/](http://lbrandy.com/blog/2009/03/writing-better-commit-messages/)
-[http://365git.tumblr.com/post/3308646748/writing-git-commit-messages](http://365git.tumblr.com/post/3308646748/writing-git-commit-messages)
-[http://www.catb.org/esr/dvcs-migration-guide.html](http://www.catb.org/esr/dvcs-migration-guide.html)
-https://git.dthompson.us/presentations.git/tree/HEAD:/happy-patching
-和其他地方。
2.18.6 改进和构建文档
PyInstaller的文档使用Sphinx创建。 Sphinx使用reStructuredText作为其标记语言,许多
从reStructuredText及其解析和转换套件Docutils的强度中获得。
文档随代码一起维护在Git存储库中,推向develop分支将创建一个新版本https://pyinstaller.readthedocs.io/en/latest/。
对于小更改(如打字错误),您可以只是在Github上 fork PyInstaller,在线编辑文档并创建
拉请求。
对于任何其他事情,我们要求您克隆存储库并验证您的更改:
pip install -r doc/requirements.txt
cd doc
make html
xdg-open _build/html/index.html
2.18.开发指南199
请在构建文档时注意任何警告和错误。在推送更改和创建拉请求之前,请在浏览器中检查标记是否有效。
请也运行:
make clean
...
make html
再次验证一切是否正确。谢谢!
我们可能会要求您重新设计更改或重新设计提交消息。在这种情况下,请使用git rebase -i …和
git push -f …来更新您的拉请求。有关详细信息,请参见“更新拉请求”。
PyInstaller扩展
对于PyInstaller文档,除了Sphinx和docutils中的角色之外,还提供可用的角色*^0。
:commit:
引用提交,创建到在线git存储库的web-link。为了可读性,提交ID将缩短为8位数字。例如:commit:a1b2c3d4e5f6a7b8c9
将变成@a1b2c3d。
:issue:
链接到Github的问题或拉请求编号。例如:issue:123
将变成#123。
reStructuredText备忘单
安装PyInstaller最简单的方法是使用 |pip|_::
.. |pip| replace:: :command:`pip`
.. _pip: https://pip.pypa.io/
2.18.7 创建 Pull-Request
示例
git clone [email protected]:YOUR_GITHUB_USERNAME/pyinstaller.git
cd pyinstaller
git checkout -b my-patch
(^0) 具体定义在doc/_extensions/pyi_sphinx_roles.py中
第200章 内容:
- 如果你要实现一个 hook,那么请先创建一个最小化的构建测试(见下文)。你需要测试你的 hook,为什么不从一开始就用构建测试呢?
- 将您的更改合并到 PyInstaller 中。
- 运行所有构建测试以确保没有其他问题。请尽可能在尽可能多的平台上进行测试。
- 您可以在提交消息中引用相关问题(例如 #1259),以便 GitHub 将问题和提交链接在一起,而使用“fixes #1259”这样的短语,您甚至可以自动关闭相关问题。
git remote add upstream https://github.com/pyinstaller/pyinstaller.git
git checkout my-patch
git pull --rebase upstream develop
git log --online --graph
git remote add upstream https://github.com/pyinstaller/pyinstaller.git
git fetch upstream develop
git checkout my-patch
git merge upstream/develop
git log --online --graph
有关详细信息,请参见在 GitHub 上同步 fork。
git push
更新 Pull-Request
我们可能会要求您更新您的 pull-request 以提高其质量或出于其他原因。在这种情况下,使用git rebase
-i…和git push -f…如下所述。^1 请不要关闭 pull-request 并打开一个新的 one – 这会杀死讨论线程。
这是没有实际更改基础的工作流:
git checkout my-branch
# 找到您的分支从“develop”分叉的提交
mb=$(git merge-base --fork-point develop)
# 交互式变基而不实际更改基础
git rebase -i $mb
# 处理重新基础
git push -f my-fork my-branch
或者,如果您想实际基于当前的开发头:
(^1) 更新 pull-request 还有其他方法,例如“修正”提交。但对于随意(和非常随意的 用户rebase -i可能是最简单的方法。
**第2.18节 开发指南 201 **
git checkout my-branch
# 在develop上交互式变基
git rebase -i develop
# 处理重新基础
git push -f my-fork my-branch
2.18.8 更新日志条目
如果您的更改是值得注意的,则需要一条日志条目,以便我们的用户可以了解它!
为避免合并冲突,我们使用towncrier包来管理我们的更改日志。towncrier使用独立的文件为每个 pull-request – 称为_news fragments_ 而不是一个巨大的 changelog 文件。在发布时,这些新闻片段将编译成我们的doc/CHANGELOG.rst。
您不需要安装towncrier,只需要遵守一些简单的规则:
请记住,新闻条目是为最终用户而设计的,并且只应包含对最终用户相关的细节。
第202章 内容:
2.18.9 pyenv 和 PyInstaller
**注意:**此部分仍处于起草阶段。请帮助扩展它。
git clone https://github.com/yyuu/pyenv.git ~/.pyenv
git clone https://github.com/yyuu/pyenv-virtualenv.git \
~/.pyenv/plugins/pyenv-virtualenv
# 将'pyenv'添加到PATH环境变量中。
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
# 为pyenv启用shims和自动补全。
eval "$(pyenv init -)"
# 通过将以下内容添加到〜/ .zshrc文件中来自动加载pyenv-virtualenv:
#
eval "$(pyenv virtualenv-init -)"
env PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install 3.5.0
2.18.10 PyInstaller的分支模型
develop branch 我们将origin/ develop视为主要分支,其中HEAD的源代码始终反映了下一个发布的最新开发更改的状态。有些人称之为“集成分支”。
master branch 我们认为origin/ master是源代码的主要分支,其中HEAD始终反映出生产就绪状态。将每个提交到主分支视为新版本,并将其标记。
PyInstaller项目不使用长期存在的分支(除了_master_和_develop_),因为我们不会同时支持几个主要版本的bugfix。
偶尔地,您可能会在代码库中找到这些分支:^1
release/ branches 这些分支用于准备下一个版本的发布。例如:更新版本号、完善变更日志、重新编译引导加载程序、重建手册。有关发布过程以及必须执行哪些步骤的详细信息,请参见ref: release-workflow。
(^1)此分支模型基本上与Vincent Driessen在此博客中描述的相同。但是,目前我们并没有严格遵循它。
2.18.开发指南203
hotfix/ branches 这些分支也是为了准备新的生产版本,尽管是非计划的。这就是通常所说的“热修复”。
feature/ branches 特性分支(有时也称为主题分支)用于开发即将发布或将来发布的新功能。
第204章 内容:
pyi_splash,69
PyInstaller.compat,80
PyInstaller.isolated,89
PyInstaller.utils.hooks,80
PyInstaller.utils.hooks.conda,87
init()( Splash方法 ), 36
--runtime-hook RUNTIME_HOOKS
,16--runtime-tmpdir PATH
,14--specpath DIR
,17--splash IMAGE_FILE
,13--strip
,14--target-arch ARCH
,15--target-architecture ARCH
,16--uac-admin
,16--uac-uiaccess
,16--upx-dir UPX_DIR
,16--upx-exclude FILE
,13--version
,13--version-file FILE
,15--win-no-prefer-redirects
,16--win-private-assemblies
,16--windowed
,16--workpath WORKPATH
,15-a
,13-c
,13-d {all,imports,bootloader,noarchive}
,15-h
,13-i
,13-m
,15-n NAME
,16-p DIR
,13-r RESOURCE
,14-s
,16-v
,15-w
,13-y
,15base_prefix
(在模块PyInstaller.compat
中),80call()
(在模块PyInstaller.isolated
中),89call()
(Python方法),91CC
,106close()
(在模块pyi_splash
中),70collect_all()
(在模块PyInstaller.utils.hooks
中),82collect_data_files()
(在模块PyInstaller.utils.hooks
中),83collect_delvewheel_libs_directory()
(在模块PyInstaller.utils.hooks
中),86collect_dynamic_libs()
(在模块PyInstaller.utils.hooks
中),84collect_dynamic_libs()
(在模块PyInstaller.utils.hooks.conda
中),88collect_entry_point()
(在模块PyInstaller.utils.hooks
中),85collect_submodules()
(在模块PyInstaller.utils.hooks
中),83-D
,13-F
,13--add-binary
,14--add-data
,14--additional-hooks-dir HOOKSPATH
,14--argv-emulation
,16--ascii
,13--bootloader-ignore-signals
,17--clean
,13--codesign-identity IDENTITY
,16--collect-all MODULENAME
,14--collect-binaries MODULENAME
,14--collect-data MODULENAME
,14--collect-datas MODULENAME
,14--collect-submodules MODULENAME
,14--console
,15--copy-metadata PACKAGENAME
,14--debug {all,imports,bootloader,noarchive}
,15--disable-windowed-traceback
,15--distpath DIR
,13--exclude-module EXCLUDES
,14--help
,13--hidden-import MODULENAME
,15--hiddenimport MODULENAME
,14--icon
,15--log-level LEVEL
,16--manifest
,13--name NAME
,16--no-embed-manifest
,15--noconfirm
,13--noconsole
,15--noupx
,15--nowindowed
,15--onedir
,13--onefile
,13--osx-bundle-identifier BUNDLE_IDENTIFIER
,16--osx-entitlements-file FILENAME
,16--paths DIR
,14--python-option PYTHON_OPTION
,15--recursive-copy-metadata PACKAGENAME
,14--resource RESOURCE
,16--runtime-hook RUNTIME_HOOKS
,14--runtime-tmpdir PATH
,17--specpath DIR
,13--splash IMAGE_FILE
,14--strip
,15--target-arch ARCH
,16--target-architecture ARCH
,16--uac-admin
,16--uac-uiaccess
,16--upx-dir UPX_DIR
,13--upx-exclude FILE
,15--version
,13--version-file FILE
,16get_homebrew_path() (在 PyInstaller.utils.hooks 模块中),86
get_hook_config() (在 PyInstaller.utils.hooks 模块中),92
get_module_attribute() (在 PyInstaller.utils.hooks 模块中),84
get_module_file_attribute() (在 PyInstaller.utils.hooks 模块中),84
get_package_paths() (在 PyInstaller.utils.hooks 模块中),85
include_or_exclude_file() (在 PyInstaller.utils.hooks 模块中),86
is_aix(在 PyInstaller.compat 模块中),80
is_alive()(在 pyi_splash 模块中),69
is_cygwin(在 PyInstaller.compat 模块中),80
is_darwin(在 PyInstaller.compat 模块中),80
is_freebsd(在 PyInstaller.compat 模块中),80
is_linux(在 PyInstaller.compat 模块中),80
is_module_or_submodule() (在 PyInstaller.utils.hooks 模块中),83
is_module_satisfies() (在 PyInstaller.utils.hooks 模块中),81
is_openbsd(在 PyInstaller.compat 模块中),80
is_package()(在 PyInstaller.utils.hooks 模块中),83
第209条索引
is_solar (在 PyInstaller.compat 模块中),80
is_venv (在 PyInstaller.compat 模块中),80
is_win (在 PyInstaller.compat 模块中),80
issue(角色),200
locate()(PackagePath 方法中),88
模块
pyi_splash,69
PyInstaller.compat,80
PyInstaller.isolated,89
PyInstaller.utils.hooks,80
PyInstaller.utils.hooks.conda,87
OBJECT_MODE,25,105,106
package_distribution()(在 PyInstaller.utils.hooks.conda 模块中),87
PackagePath(PyInstaller.utils.hooks.conda 中的类),88
pyi_splash
模块,69
PyInstaller.compat
模块,80
PyInstaller.isolated
模块,89
PyInstaller.utils.hooks
模块,80
PyInstaller.utils.hooks.conda
模块,87
Python(PyInstaller.isolated 中的类),91
Python增强提案
PEP 0440,81
PEP 239,126
PEP 263,108
PEP 302,68
PEP 405,20
PEP 527,154
PEP 552,144
PEP 8,195,196
PYTHONHASHSEED,75
PYTHONPATH,30
requires()(在 PyInstaller.utils.hooks.conda 模块中),88
脚本名称
命令行选项,13
update_text()(在 pyi_splash 模块中),69
walk_dependency_tree()(在 PyInstaller.utils.hooks.conda 模块中),88
第210条索引