Search code examples
python-3.xqmlpyqt5pyinstallerqtquick2

Use PyInstaller to build PyQt5 with QML in --onefile


I want to build a portable executable (at least all the source files in one folder) of a PyQt5 GUI application with QML (for Material themes) via PyInstaller in both Windows 10 and Ubuntu. However, after the executable has been built successfully, it crashes with some error messages.

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: (which is copied from 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") }
    }
}

I use the following commands to build the executable:

pyinstaller ./material.py --onefile

After built, the executable shows the error messages. In 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

In 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)

The file tree of the project is:

. (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

I copied two folders, QtQuick and QtQuick.2, since I had had the same problem with this question and I do the same thing as the answer. I have been looking for the solution for a week, having no clues why it cannot load the library.


Solution

  • I found a workaround with Pyinstaller 3.3.1 and PyQt5 >= 5.11 to solve this problem.

    Use pyrcc5 to compile QML files and import them into Python scripts. Thus, Pyinstaller would package them automatically without the need to put QML files next to the executable.

    In this case, compile basic.qml with pyrcc5,

    pyrcc5 -o src.py src.qrc
    

    Then, modify 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_())
    

    Finally, use the following command to package the source codes.

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

    The hidden imports are required as the new version of PyQt5 has modified some module names and thus made them incompatible with current Pyinstaller hooks.

    However, after the codes have been packaged, the result release binary might have some issue regarding the QtQuick2 Style display (esp. for Material and Imagine themes). You could fix this problem by using the latest developed version of Pyinstaller (3.4 dev).