I have an app using a QWebEngineView widget, and when I create a distribution package with PyInstaller, I get a different behaviour if I sign the app or not. I created a small reproducible example (tester.py):
import time
import sys
from PySide6.QtCore import QUrl
from PySide6.QtWebEngineWidgets import QWebEngineView
from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout
app = QApplication(sys.argv)
web = QWebEngineView()
web.setHtml('<html><body></body></html>')
wdg = QWidget()
vl = QVBoxLayout(wdg)
btn1 = QPushButton('Clear')
btn2 = QPushButton('Something')
btn3 = QPushButton('Google')
vl.addWidget(web)
vl.addWidget(btn1)
vl.addWidget(btn2)
vl.addWidget(btn3)
wdg.setLayout(vl)
btn1.clicked.connect(lambda x: web.setHtml('<html><body></body></html>'))
btn2.clicked.connect(lambda x: web.load(QUrl("https://something.com")))
btn3.clicked.connect(lambda x: web.load(QUrl("https://google.com")))
wdg.show()
sys.exit(app.exec())
This works fine using python tester.py
, the contents can be cleared and both sites load fine. If I create a distribution using pyinstaller tester.py
, then running ./dist/tester/tester
works just as well:
However, if I sign the app with pyinstaller --codesign-identity XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX tester.py
, then when running the binary, I get different behaviours. In a Mac with an Intel Core i7 running MacOS Big Sur, the clear page and something.com load fine, but google.com seems to disable the QWebEngineView widget. If I sign the app on a Mac with an Apple Silicon M2 Max running MacOS Sequoia 15.0.1, then the widget seems to be constantly disabled:
The codesign identity is valid (masked above for obvious reasons). I've tried using a windowed version with -w, no difference. I also tried specifying the target architecture with --target-architecture, still no difference.
All test computers used Python 3.12, PyInstaller 6.7.0, PySide6 6.6.1.
Any ideas? I need to distribute the app, hence needing to sign it.
I've figured out a solution for this. First, upgrade to the latest PyInstaller. The recipes for app bundle creation evolve constantly; PyInstaller 6.7.0 for example created a few broken links in the bundle structure, whereas 6.10.0 has fixed that.
It still doesn't sign apps with QWebEngineView properly, however. The reason it is so hard to sign these apps is that the QWebEngineCore.framework requires a helper application, QWebEngineProcess. This app runs in the background whenever you execute an application with QWebEngineView.
PyInstaller properly packages the helper into the bundle, but somehow the signing does not work, so when you run the app, there is no QtWebEngineProcess running in the background. This is what causes the app to run, but with the QtWebEngineView widget broken.
My solution was to sign the bundle by hand with codesign
. It no longer provides a means to deep sign a bundle, so you need to know which files to sign. This seemed to work for me:
Create the PyInstaller bundle without signing:
pyi-makespec --log-level INFO --onedir --name yyyy --windowed --osx-bundle-identifier xx.xxx.xxxx --version 1.0 src/tester.py
pyinstaller yyyy.spec
Sign the QtWebEngineProcess; it is important to use the PyInstaller entitlements file, and to sign it before the QtWebEngineCore.framework:
codesign --force --verify --verbose --sign "XXXXXXXXXXXX" --entitlements dist/yyyy.app/Contents/Frameworks/PySide6/Qt/lib/QtWebEngineCore.framework/Versions/A/Helpers/QtWebEngineProcess.app/Contents/Resources/QtWebEngineProcess.entitlements --timestamp --identifier "xx.xxx.xxxx" --options runtime dist/yyyy.app/Contents/Frameworks/PySide6/Qt/lib/QtWebEngineCore.framework/Versions/Current/Helpers/QtWebEngineProcess.app/Contents/MacOS/QtWebEngineProcess
Sign all other required files/folders:
for what in "framework" "dylib" "so" "pak" "dat" "bin" "xcprivacy" "conf" "plist"; do
for file in `find dist/yyyy.app -name *.$what`; do
codesign --force --verify --verbose --sign "XXXXXXXXXXXX" --timestamp --identifier "xx.xxx.xxxx" --options runtime $file
done
done
Sign the binary:
codesign --force --verify --verbose --sign "XXXXXXXXXXXX" --timestamp --identifier "xx.xxx.xxxx" --options runtime dist/yyyy.app/Contents/MacOS/yyyy
Sign the bundle:
codesign --force --verify --verbose --sign "XXXXXXXXXXXX" --timestamp --identifier "xx.xxx.xxxx" --options runtime dist/yyyy.app
You obviously need a valid Apple code signing identity, etc. The above gets accepted by Apple's notary service.