使用 PyInstaller 在 --onefile 中使用 QML 构建 PyQt5

Posted

技术标签:

【中文标题】使用 PyInstaller 在 --onefile 中使用 QML 构建 PyQt5【英文标题】:Use PyInstaller to build PyQt5 with QML in --onefile 【发布时间】:2018-03-22 13:15:09 【问题描述】:

我想在 Windows 10 和 Ubuntu 中通过 PyInstaller 使用 QML(用于 Material 主题)构建 PyQt5 GUI 应用程序的可移植可执行文件(至少一个文件夹中的所有源文件)。但是,在成功构建可执行文件后,它会崩溃并显示一些错误消息。

material.py:

import os
import sys

from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QUrl
from PyQt5.QtQml import QQmlApplicationEngine

if __name__ == "__main__":
    app = QApplication(sys.argv)
    os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
    engine = QQmlApplicationEngine()
    engine.load(QUrl('basic.qml'))
    sys.exit(app.exec_())

basic.qml:(复制自here)

import QtQuick 2.0
import QtQuick.Controls 2.1
import QtQuick.Controls.Material 2.1

ApplicationWindow 
    visible: true

    Material.theme: Material.Dark
    Material.accent: Material.Purple

    Column 
        anchors.centerIn: parent

        RadioButton  text: qsTr("Small") 
        RadioButton  text: qsTr("Medium");  checked: true 
        RadioButton  text: qsTr("Large") 
    

我使用以下命令来构建可执行文件:

pyinstaller ./material.py --onefile

构建后,可执行文件会显示错误消息。在 Windows 10 中:

QQmlApplicationEngine failed to load component
file:///D:/test/dist/basic.qml:1 plugin cannot be loaded for module "QtQuick": Cannot load library D:\test\dist\QtQuick.2\qtquick2plugin.dll: ???????w?????C

在 Linux 中:

QQmlApplicationEngine failed to load component
file:///media/username/EA9E5E009E5DC5AB/test/dist/basic.qml:1 plugin cannot be loaded for module "QtQuick": Cannot load library /media/username/EA9E5E009E5DC5AB/test/dist/QtQuick.2/libqtquick2plugin.so: (/usr/lib/x86_64-linux-gnu/libQt5Quick.so.5: symbol _ZN3QV46Object11markObjectsEPNS_4Heap4BaseEPNS_15ExecutionEngineE, version Qt_5_PRIVATE_API not defined in file libQt5Qml.so.5 with link time reference)

项目的文件树为:

. (test)
+-- build
|   +-- (some files generate by PyInstaller)
+-- dist
|   +-- QtQuick (the folder copied from Python site-packages)
|   |   +-- (some files copied form Python site-packages)
|   +-- QtQuick.2 (the folder copied from Python site-packages)
|   |   +-- plugins.qmltypes
|   |   +-- qmldir
|   |   +-- qtquick2plugin.dll (or 'libqtquick2plugin.so' in Linux)
|   +-- basic.qml
|   +-- material.exe (or 'material' in Linux)
+-- basic.qml
+-- material.py
+-- material.spec

我复制了两个文件夹,QtQuickQtQuick.2,因为我遇到了与此 question 相同的问题,并且我执行与 answer 相同的操作。我一直在寻找解决方案一个星期,不知道为什么它无法加载库。

【问题讨论】:

当您使用--onefile 选项时,不需要传递.so 或.dll,在我的情况下,我已经完成了测试并且它可以正常工作。你用的是什么版本的pyinstaller? @eyllanesc 我正在使用 PyInstaller 3.3。我不知道你是什么意思,没有必要通过.so.dll 来使用--onefile 选项。如果我不移动我的可执行文件旁边的QtQuickQtQuick.2 这两个文件夹,程序将显示模块“QtQuick.Controls”、“QtQuick”和其他在basic.qml 中导入的东西没有安装。例如,file:///media/username/EA9E5E009E5DC5AB/test/dist/basic.qml:2 module "QtQuick.Controls" is not installed. 另外,关于更多信息,我使用的 PyQt 版本是 5.10.1 和 Python 3.6 【参考方案1】:

我找到了使用 Pyinstaller 3.3.1 和 PyQt5 >= 5.11 的解决方法来解决这个问题。

使用pyrcc5 编译 QML 文件并将它们导入 Python 脚本。因此,Pyinstaller 会自动打包它们,而无需将 QML 文件放在可执行文件旁边。

在这种情况下,编译basic.qmlpyrcc5

pyrcc5 -o src.py src.qrc

然后,修改material.py

import os
import sys

from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QUrl
from PyQt5.QtQml import QQmlApplicationEngine
import src # import the resource file compiled by pyrcc5

if __name__ == "__main__":
    app = QApplication(sys.argv)
    os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
    engine = QQmlApplicationEngine()
    engine.load(QUrl('qrc:/basic.qml')) # modify the url for qrc format
    sys.exit(app.exec_())

最后,使用如下命令打包源码。

pyinstaller material.py --windowed --onefile --hidden-import PyQt5.sip --hidden-import PyQt5.QtQuick

隐藏的导入是必需的,因为新版本的 PyQt5 修改了一些模块名称,从而使它们与当前的 Pyinstaller 挂钩不兼容。

但是,在打包代码后,结果发布二进制文件可能会出现关于 QtQuick2 样式显示的一些问题(尤其是 Material 和 Imagine 主题)。您可以使用最新开发的 Pyinstaller (3.4 dev) 版本来解决此问题。

【讨论】:

不幸的是,这对我不起作用,我已经尝试了你写的所有东西,但仍然是同样的问题。

以上是关于使用 PyInstaller 在 --onefile 中使用 QML 构建 PyQt5的主要内容,如果未能解决你的问题,请参考以下文章

使用 PyInstaller 在 --onefile 中使用 QML 构建 PyQt5

在使用 PyInstaller --onefile 打包 kivy 时包含 .kv/.json 文件?

Pyinstaller --onefile 模式,如何在解包前向控制台写入消息

pyinstaller --onefile 生成0KB exe

如何将 PortAudio 包含到 pyinstaller onefile 构建中

使用 --onefile 和 --noconsole 使用 PyInstaller 编译的 PyQt5 应用程序,但 exe 无法启动