qt-packaging
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePackaging Qt Python Applications
打包Qt Python应用
PyInstaller (most common)
PyInstaller(最常用)
Critical: Virtual Environment Isolation
The official Qt for Python docs document a known PyInstaller gotcha: if a system-level PySide6 is installed, PyInstaller silently picks it instead of your venv version. Before building:
bash
undefined重点:虚拟环境隔离
Qt for Python官方文档提到一个PyInstaller的常见陷阱:如果系统级PySide6已安装,PyInstaller会静默选择它而非你的venv版本。构建前请执行:
bash
undefinedRemove ALL system-level PySide6 installs from the build machine
从构建机器中移除所有系统级PySide6安装包
pip uninstall pyside6 pyside6_essentials pyside6_addons shiboken6 -y
pip uninstall pyside6 pyside6_essentials pyside6_addons shiboken6 -y
Verify only venv version remains
验证仅保留venv版本
python -c "import PySide6; print(PySide6.file)"
python -c "import PySide6; print(PySide6.file)"
Must show a path inside .venv/, not /usr/lib or system site-packages
路径必须显示在.venv/内部,而非/usr/lib或系统site-packages
**`--onefile` limitation:** For Qt6, `--onefile` bundles cannot deploy Qt plugins automatically. The one-directory (`dist/MyApp/`) approach is reliable. Use `--onefile` only if you understand its limitations and handle Qt plugins manually.
**Installation:**
```bash
uv add --dev pyinstallerBasic one-directory build:
bash
pyinstaller --name MyApp \
--windowed \
--icon resources/icons/app.ico \
src/myapp/__main__.pySpec file (reproducible builds):
python
undefined
**`--onefile`限制**:对于Qt6,`--onefile`捆绑包无法自动部署Qt插件。单目录(`dist/MyApp/`)方式更可靠。仅当你了解其限制并能手动处理Qt插件时,才使用`--onefile`。
**安装:**
```bash
uv add --dev pyinstaller基础单目录构建:
bash
pyinstaller --name MyApp \
--windowed \
--icon resources/icons/app.ico \
src/myapp/__main__.pySpec文件(可重现构建):
python
undefinedMyApp.spec
MyApp.spec
block_cipher = None
a = Analysis(
["src/myapp/main.py"],
pathex=[],
binaries=[],
datas=[
("src/myapp/resources", "resources"), # (src, dest inside bundle)
],
hiddenimports=[
"PySide6.QtSvg", # SVG support
"PySide6.QtSvgWidgets", # SVG widgets
"PySide6.QtXml", # required by some Qt modules
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=["tkinter", "matplotlib"],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name="MyApp",
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
console=False, # True for CLI apps
disable_windowed_traceback=False,
argv_emulation=False, # macOS: use True for drag-and-drop files
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon="resources/icons/app.ico",
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=False,
upx_exclude=[],
name="MyApp",
)
Run: `pyinstaller MyApp.spec`
**Qt plugin detection issues:** PySide6 often needs explicit plugin imports. Add to `hiddenimports`:
```python
hiddenimports = [
"PySide6.QtSvg", "PySide6.QtSvgWidgets",
"PySide6.QtPrintSupport", # required by QTextEdit on some platforms
"PySide6.QtDBus", # Linux
]QRC compiled resources: Include compiled resource files in or ensure they're importable. The cleanest approach is importing in so PyInstaller detects it automatically.
.pydatasrc_resources__init__.pyblock_cipher = None
a = Analysis(
["src/myapp/main.py"],
pathex=[],
binaries=[],
datas=[
("src/myapp/resources", "resources"), # (源路径,捆绑包内目标路径)
],
hiddenimports=[
"PySide6.QtSvg", # SVG支持
"PySide6.QtSvgWidgets", # SVG组件
"PySide6.QtXml", # 部分Qt模块所需
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=["tkinter", "matplotlib"],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name="MyApp",
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
console=False, # CLI应用设为True
disable_windowed_traceback=False,
argv_emulation=False, # macOS:如需处理拖放文件设为True
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon="resources/icons/app.ico",
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=False,
upx_exclude=[],
name="MyApp",
)
运行:`pyinstaller MyApp.spec`
**Qt插件检测问题**:PySide6通常需要显式导入插件。添加至`hiddenimports`:
```python
hiddenimports = [
"PySide6.QtSvg", "PySide6.QtSvgWidgets",
"PySide6.QtPrintSupport", # 部分平台上QTextEdit所需
"PySide6.QtDBus", # Linux平台
]QRC编译资源:将编译后的资源文件加入,或确保其可被导入。最简洁的方式是在中导入,让PyInstaller自动检测到它。
.pydatas__init__.pyrc_resourcesBriefcase (cross-platform, preferred for distribution)
Briefcase(跨平台,分发首选)
Briefcase produces native platform installers (, , ):
.msi.dmg.AppImagebash
pip install briefcase
briefcase create # create platform package
briefcase build # compile
briefcase run # run from package
briefcase package # create installerpyproject.toml for Briefcase:
toml
[tool.briefcase]
project_name = "MyApp"
bundle = "com.myorg.myapp"
version = "1.0.0"
url = "https://myorg.com"
license = "MIT"
author = "My Name"
author_email = "me@myorg.com"
[tool.briefcase.app.myapp]
formal_name = "My Application"
description = "Description here"
icon = "resources/icons/app" # no extension — briefcase uses platform-appropriate format
sources = ["src/myapp"]
requires = ["PySide6>=6.6"]Briefcase handles Qt plugin bundling more reliably than PyInstaller for PySide6.
Briefcase可生成原生平台安装程序(、、):
.msi.dmg.AppImagebash
pip install briefcase
briefcase create # 创建平台包
briefcase build # 编译
briefcase run # 从包中运行应用
briefcase package # 创建安装程序Briefcase对应的pyproject.toml:
toml
[tool.briefcase]
project_name = "MyApp"
bundle = "com.myorg.myapp"
version = "1.0.0"
url = "https://myorg.com"
license = "MIT"
author = "My Name"
author_email = "me@myorg.com"
[tool.briefcase.app.myapp]
formal_name = "My Application"
description = "此处填写描述"
icon = "resources/icons/app" # 无需扩展名——Briefcase会使用平台适配的格式
sources = ["src/myapp"]
requires = ["PySide6>=6.6"]对于PySide6,Briefcase处理Qt插件捆绑的可靠性优于PyInstaller。
Windows: windeployqt + Code Signing
Windows:windeployqt + 代码签名
After PyInstaller builds the one-directory package, run from the Qt SDK to copy any missing Qt plugins and translations:
windeployqtbash
undefinedPyInstaller构建单目录包后,运行Qt SDK中的以复制缺失的Qt插件和翻译文件:
windeployqtbash
undefinedRun from the Qt SDK tools directory (or add to PATH)
从Qt SDK工具目录运行(或添加至PATH)
windeployqt dist/MyApp/MyApp.exe
This ensures platform plugins (`qwindows.dll`) and other Qt plugin DLLs are present. PyInstaller hooks should collect most of them automatically, but `windeployqt` catches stragglers.
```bashwindeployqt dist/MyApp/MyApp.exe
这能确保平台插件(`qwindows.dll`)和其他Qt插件DLL存在。PyInstaller钩子通常会自动处理大部分情况,但`windeployqt`能补全遗漏的部分。
```bashSign the executable (requires a code signing certificate)
签名可执行文件(需要代码签名证书)
signtool sign /fd SHA256 /a /tr http://timestamp.digicert.com dist/MyApp.exe
Unsigned Windows executables trigger SmartScreen warnings. For internal distribution, instruct users to right-click → Properties → Unblock.signtool sign /fd SHA256 /a /tr http://timestamp.digicert.com dist/MyApp.exe
未签名的Windows可执行文件会触发SmartScreen警告。内部分发时,可告知用户右键→属性→解除阻止。macOS: App Bundle
macOS:App捆绑包
PyInstaller produces a bundle. For distribution outside the App Store:
.appbash
undefinedPyInstaller会生成捆绑包。如需在App Store外分发:
.appbash
undefinedAd-hoc signing (no developer ID)
临时签名(无需开发者ID)
codesign --force --deep --sign - dist/MyApp.app
codesign --force --deep --sign - dist/MyApp.app
With developer ID
使用开发者ID签名
codesign --force --deep --sign "Developer ID Application: Name (TEAM_ID)" dist/MyApp.app
codesign --force --deep --sign "Developer ID Application: Name (TEAM_ID)" dist/MyApp.app
Notarization (required for Gatekeeper)
公证(Gatekeeper要求)
xcrun notarytool submit dist/MyApp.zip --apple-id me@example.com --team-id TEAM_ID
undefinedxcrun notarytool submit dist/MyApp.zip --apple-id me@example.com --team-id TEAM_ID
undefinedLinux: AppImage via PyInstaller
Linux:通过PyInstaller制作AppImage
bash
undefinedbash
undefinedBuild one-directory first, then package as AppImage
先构建单目录包,再打包为AppImage
appimagetool dist/MyApp/ MyApp-x86_64.AppImage
undefinedappimagetool dist/MyApp/ MyApp-x86_64.AppImage
undefinedBuild Automation (CI)
构建自动化(CI)
yaml
undefinedyaml
undefined.github/workflows/build.yml
.github/workflows/build.yml
jobs:
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- run: pip install pyinstaller PySide6
- run: pyinstaller MyApp.spec
- uses: actions/upload-artifact@v4
with:
name: windows-build
path: dist/MyApp/
undefinedjobs:
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- run: pip install pyinstaller PySide6
- run: pyinstaller MyApp.spec
- uses: actions/upload-artifact@v4
with:
name: windows-build
path: dist/MyApp/
undefinedCommon Packaging Pitfalls
常见打包陷阱
- Missing Qt platform plugins: — ensure
qt.qpa.plugin: Could not find the Qt platform pluginis included. PyInstaller hooks usually handle this; rebuild if not.PySide6/Qt/plugins/platforms/ - Missing SVG support: Import in
PySide6.QtSvgor the app will crash loading SVGs silently.hiddenimports - Relative path assumptions: Use for locating resource files in development; use
Path(__file__).parentfor PyInstaller runtime paths (or bundle via QRC to avoid the problem entirely).sys._MEIPASS - App freezes on macOS: Set in the spec if the app needs to handle file associations.
argv_emulation=True
- 缺失Qt平台插件:——确保包含
qt.qpa.plugin: Could not find the Qt platform plugin。PyInstaller钩子通常会处理此问题,若未解决请重新构建。PySide6/Qt/plugins/platforms/ - 缺失SVG支持:在中导入
hiddenimports,否则应用加载SVG时会静默崩溃。PySide6.QtSvg - 相对路径假设:开发环境中使用定位资源文件;PyInstaller运行时使用
Path(__file__).parent(或通过QRC捆绑资源彻底避免此问题)。sys._MEIPASS - macOS上应用冻结:如果应用需要处理文件关联,在spec中设置。
argv_emulation=True