-
-
Notifications
You must be signed in to change notification settings - Fork 2k
macOS: Symlink duplicated QtWebEngine files instead of copying? #6896
Description
Is your feature request related to a problem? Please describe.
For macOS framework builds, the Qt hook copies various Qt frameworks to two different locations, so that they are accessible from the QtWebEngineProcess:
pyinstaller/PyInstaller/utils/hooks/qt.py
Lines 726 to 762 in 63f30ef
| if is_macos_framework: | |
| # On macOS, Qt shared libraries are provided in form of .framework bundles. However, PyInstaller collects shared | |
| # library from the bundle into top-level application directory, breaking the bundle structure. | |
| # | |
| # QtWebEngine and its underlying Chromium engine, however, have very strict data file layout requirements due to | |
| # sandboxing, and does not work if the helper process executable does not load the shared library from | |
| # QtWebEngineCore.framework (which also needs to contain all resources). | |
| # | |
| # Therefore, we collect the QtWebEngineCore.framework manually, in order to obtain a working QtWebEngineProcess | |
| # helper executable. But because that bypasses our dependency scanner, we need to collect the dependent | |
| # .framework bundles as well. And we need to override QTWEBENGINEPROCESS_PATH in rthook, because the | |
| # QtWebEngine python extensions actually load up the copy of shared library that is located in | |
| # sys._MEIPASS (as opposed to the manually-copied one in .framework bundle). Furthermore, because the extension | |
| # modules use Qt shared libraries in sys._MEIPASS, we also copy all contents of | |
| # QtWebEngineCore.framework/Resources into sys._MEIPASS to make resource loading in the main process work. | |
| # | |
| # Besides being ugly, this approach has three main ramifications: | |
| # 1. we bundle two copies of each Qt shared library involved: the copy used by main process, picked up by | |
| # dependency scanner; and a copy in manually-collected .framework bundle that is used by the helper process. | |
| # 2. the trick with copying contents of Resource directory of QtWebEngineCore.framework does not work in onefile | |
| # mode, and consequently QtWebEngine does not work in onefile mode. | |
| # 3. copying contents of QtWebEngineCore.framework/Resource means that its Info.plist ends up in sys._MEIPASS, | |
| # causing the main process in onedir mode to be mis-identified as "QtWebEngineProcess". | |
| # | |
| # In the near future, this quagmire will hopefully be properly sorted out, but in the mean time, we have to live | |
| # with what we have been given. | |
| data_path = qt_library_info.location['DataPath'] | |
| libraries = [ | |
| 'QtCore', 'QtWebEngineCore', 'QtQuick', 'QtQml', 'QtQmlModels', 'QtNetwork', 'QtGui', 'QtWebChannel', | |
| 'QtPositioning' | |
| ] | |
| for i in libraries: | |
| framework_dir = i + '.framework' | |
| datas += hooks.collect_system_data_files( | |
| os.path.join(data_path, 'lib', framework_dir), os.path.join(rel_data_path, 'lib', framework_dir), True | |
| ) | |
| datas += [(os.path.join(data_path, 'lib', 'QtWebEngineCore.framework', 'Resources'), os.curdir)] |
This however blows up the total app bundle a lot - with my project with PyQt5, from 400 MB (with those files replaced by symlinks, see below) to 630 MB.
Describe the solution you'd like
Instead of copying those files, PyInstaller could symlink them. I didn't figure out how to accomplish this, however, because the hook only seems to add them to datas, and I don't see a way for a hook to modify the resulting bundle.
Describe alternatives you've considered
Manually patching the resulting .app: qutebrowser's build_release.py.
This has served me well for a few years, but recently introduced the issue mentioned below, and doesn't quite seem to work yet for Qt 6.
Additional context
There seems to be somewhat related discussion here:
I suspect my manual modifying to cause this: