hyb
2026-01-09 4cb426cb3ae31e772a09d4ade5b2f0242aaeefa0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# -----------------------------------------------------------------------------
# Copyright (c) 2024, PyInstaller Development Team.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
#
# The full license is in the file COPYING.txt, distributed with this software.
#
# SPDX-License-Identifier: Apache-2.0
# -----------------------------------------------------------------------------
 
import os
import importlib
import atexit
 
# Helper for ensuring that only one Qt bindings package is registered at run-time via run-time hooks.
_registered_qt_bindings = None
 
 
def ensure_single_qt_bindings_package(qt_bindings):
    global _registered_qt_bindings
    if _registered_qt_bindings is not None:
        raise RuntimeError(
            f"Cannot execute run-time hook for {qt_bindings!r} because run-time hook for {_registered_qt_bindings!r} "
            "has been run before, and PyInstaller-frozen applications do not support multiple Qt bindings in the same "
            "application!"
        )
    _registered_qt_bindings = qt_bindings
 
 
# Helper for relocating Qt prefix via embedded qt.conf file.
_QT_CONF_FILENAME = ":/qt/etc/qt.conf"
 
_QT_CONF_RESOURCE_NAME = (
    # qt
    b"\x00\x02"
    b"\x00\x00\x07\x84"
    b"\x00\x71"
    b"\x00\x74"
    # etc
    b"\x00\x03"
    b"\x00\x00\x6c\xa3"
    b"\x00\x65"
    b"\x00\x74\x00\x63"
    # qt.conf
    b"\x00\x07"
    b"\x08\x74\xa6\xa6"
    b"\x00\x71"
    b"\x00\x74\x00\x2e\x00\x63\x00\x6f\x00\x6e\x00\x66"
)
 
_QT_CONF_RESOURCE_STRUCT = (
    # :
    b"\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01"
    # :/qt
    b"\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02"
    # :/qt/etc
    b"\x00\x00\x00\x0a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03"
    # :/qt/etc/qt.conf
    b"\x00\x00\x00\x16\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00"
)
 
 
def create_embedded_qt_conf(qt_bindings, prefix_path):
    # The QtCore module might be unavailable if we collected just the top-level binding package (e.g., PyQt5) without
    # any of its submodules. Since this helper is called from run-time hook for the binding package, we need to handle
    # that scenario here.
    try:
        QtCore = importlib.import_module(qt_bindings + ".QtCore")
    except ImportError:
        return
 
    # No-op if embedded qt.conf already exists
    if QtCore.QFile.exists(_QT_CONF_FILENAME):
        return
 
    # Create qt.conf file that relocates Qt prefix.
    # NOTE: paths should use POSIX-style forward slashes as separator, even on Windows.
    if os.sep == '\\':
        prefix_path = prefix_path.replace(os.sep, '/')
 
    qt_conf = f"[Paths]\nPrefix = {prefix_path}\n"
    if os.name == 'nt' and qt_bindings in {"PySide2", "PySide6"}:
        # PySide PyPI wheels on Windows set LibraryExecutablesPath to PrefixPath
        qt_conf += f"LibraryExecutables = {prefix_path}"
 
    # Encode the contents; in Qt5, QSettings uses Latin1 encoding, in Qt6, it uses UTF8.
    if qt_bindings in {"PySide2", "PyQt5"}:
        qt_conf = qt_conf.encode("latin1")
    else:
        qt_conf = qt_conf.encode("utf-8")
 
    # Prepend data size (32-bit integer, big endian)
    qt_conf_size = len(qt_conf)
    qt_resource_data = qt_conf_size.to_bytes(4, 'big') + qt_conf
 
    # Register
    succeeded = QtCore.qRegisterResourceData(
        0x01,
        _QT_CONF_RESOURCE_STRUCT,
        _QT_CONF_RESOURCE_NAME,
        qt_resource_data,
    )
    if not succeeded:
        return  # Tough luck
 
    # Unregister the resource at exit, to ensure that the registered resource on Qt/C++ side does not outlive the
    # `_qt_resource_data` python variable and its data buffer. This also adds a reference to the `_qt_resource_data`,
    # which conveniently ensures that the data is not garbage collected before we perform the cleanup (otherwise garbage
    # collector might kick in at any time after we exit this helper function, and `qRegisterResourceData` does not seem
    # to make a copy of the data!).
    atexit.register(
        QtCore.qUnregisterResourceData,
        0x01,
        _QT_CONF_RESOURCE_STRUCT,
        _QT_CONF_RESOURCE_NAME,
        qt_resource_data,
    )