起因

用 PyQt 写了个 APP,因为 PyInstaller 对 QtWebEngine 支持性糟糕,所以使用了 py2app 打包。

py2app 导入了整个 PyQt,应用打包后体积高达 300M,压缩以后也有 60M。正好对 QtWebEngine 有些不满,遂决定移除 QtWebEngine 代码,迁移到 PyInstaller。

于是走上了一条巨坑之路。PyInstaller 的 BUG 实在是太多了,万万没想到一次迁移花了一整夜的时间。小的 BUG 就不多说了,这里纪录几个比较严重的,备查。

闪退

特征

其效果为,在 binary 目录下,直接运行打包后的 binary 文件,可以正常运行;但无法正常打开 APP文件。

定位

其他目录下运行打包后的 binary 文件,根据报错,打印 abspath/realpath 的值,发现是用户主目录。即,当前路径错误。

查询 issue,发现 PyInstaller 的 realpath 是一个已知问题,但是被作者关闭了。

解决

if getattr(sys, 'frozen', False):
    # PyInstaller 环境
    base_path = sys._MEIPASS
    os.chdir(base_path)

判断处于 PyInstaller 打包的 bundle 中时,读 sys._MEIPASS 替代原来的 os.path.dirname(os.path.realpath(__file__))

而对于部分直接使用相对路径的操作,使用 os.chdir 切换当前目录到正确的目录。

One more thing

最初遇到的情况是,在 binary 目录下,运行 binary 文件,也无法显示 GUI。后来发现是 Icon 没有加载成功,导致菜单栏应用实际工作正常,但是入口变成透明的了……

隐藏 Dock

特征

根据的经验,在 plist 中添加 'LSUIElement': True ,即可隐藏 Dock 图标。

然而,在 Py2Installer 里,失效了

定位

尝试使用 'LSBackgroundOnly': True 彻底隐藏 GUI,仍然无效。

搜 PyInstaller 的 issue,发现很多三年前的 issue,均未解决,且大多数 issue 被开发者直接关闭;

最终在某个 issue 的回复中找到解决方法:修正 booloader 中的宏定义。

解决

fork 一份 PyInstaller 分支,修改 bootloader/src/pyi_launch.c ,找到如下宏定义判断:

if defined(__APPLE__) && defined(WINDOWED)
// 以下略
elif defined(_WIN32)
// 以下略
endif /* if defined(__APPLE__) && defined(WINDOWED) */

删除其中的第一个分支,重新 build,并 commit。

pip 可直接用 git 安装:

pip install git+https://github.com/deepjia/pyinstaller.git

One more thing

pip 版本的 PyInstaller,与 dev 分支,对于 plist 的值处理不同。对于布尔值,前者应使用 "True" ,后者要使用 True……Who knows?

环境变量

特征

subprocesss 无法正常运行。

定位

首先,系统自带命令一切正常;其次,相对路径运行的命令一切正常;

出现问题的基本上是用 Homebrew 安装的命令行应用,显而易见,环境变量缺少 /usr/local/bin/

日志打印 os.environ.get('PATH') ,证实。

解决

为解决问题,直接简单粗暴改 os.environ 解决。

One more thing

官方对于该问题的指引是错误的,不要参考。

尾声

最后,移除 QtWebEngine、迁移到 PyInstaller 的减肥效果,是符合预期的。

打包的 APP 体积,从原来的 300M 减少到现在的 60M。

压缩后 7z 档案的体积,从原来的 60M 减少到现在的 16M。