起因
用 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。