我需要为所有主要的3种操作系统(Windows,Linux和MacOS)部署基于 Qt 5.12.1 的 pyside2 应用。
我已经检查了How to make a Python script standalone executable to run without ANY dependency?,但这不是我想要的,因为我需要与Qt相关的方法,例如windeployqt
,macdeployqt
,linuxdeployqt
(独立项目)。
如eyllanesc所指出:“ python是一种不会生成二进制文件的脚本语言” 。但是,Qt公司也应该考虑到这一点,并使我们更容易部署 pyside2 应用程序。至少与部署C ++ / QML应用程序一样容易。
所以我想要一个像 windowsdeployqt , macdeployqt , linuxdeployqt 这样的工具,它可以与 pyside2 应用程序一起使用
[UPDATE]
eyllanesc建议以fbs (fman build system)作为起点,因为尚无用于部署 pyside2 应用程序的官方工具。这应该作为一种解决方法。也欢迎新的答案。
The Qt Company发布正式工具后,请尽快回答。
[注意]::我正在使用基于 Qt 5.12.1
的 Qt Creator 4.8.1答案 0 :(得分:2)
当前没有Qt方式来部署PySide2应用程序(而且我认为至少在不久的将来不会有这种方式)
关于此主题,有以下报告:PYSIDE-901,PYSIDE-913,其中指出可能是针对Qt for Python 5.13的文档将更新,并且会有一部分用于部署。您可以看到进度here。
其中3个选项可以进行部署:
项目的选项是:
1.发送带有应用程序内容的普通zip文件。
2.构建适当的Python程序包(轮子):https://packaging.python.org
3.将应用程序冻结在单个二进制文件或目录中。
在第三种方法中,他们对PyInstaller,cx_Freeze,py2exe和py2app之类的工具的优缺点进行了评论,最终表明最佳选择对于他们来说是cx_Freeze或Pyinstaller。还有一个有趣的工具是fbs项目(基于Pyinstaller)。
我个人认为fbs是因为它提供了一种基于PyQt5或PySide2打包项目的简单方法
答案 1 :(得分:1)
简单的想法,但问题在于找到最小的集合。在qt网站上提出的打包程序会创建复杂的文件结构和它们自己的EXE,对于小脚本案例(IMHO)而言,这太多了。 幸运的是,有一些方法可以记录依赖项:在Windows上为Process Monitor,在Linux上为strace。它们列出了受监视程序的所有系统调用。我写了一个小python脚本来从此类日志中选择依赖项。
在如下所示的虚拟环境中执行此操作很方便,或者在预安装了基本模块的python嵌入式发行版中进行此操作:
> python -m venv ./MyVEnv
> cd ./MyVEnv
> ./.../python -m pip install pyside2
-B
标志可能会很有用,以避免在最终文件包中缓存文件;您也可以在监控程序中手动过滤掉不必要的系统调用以减少日志在Windows上:
> Procmon /AcceptEula /NoFilter /BackingFile log1
> .\MyVEnv\...\python -B yourScript.py
> Procmon.exe /OpenLog log1.PML /SaveAs logFile.csv
在Linux上:
> 2>logFile strace ./bin/python3 -B yourScript.py
在启动我的脚本之后,将所有记录的依赖项复制到一个文件夹中,并保留原始文件结构:
> python .\depspicker.py
这是:
#depspicker.py
#changes from Windows to Linux version commented
logF = r".\logFile.CSV" #Linux: ./logFile
basePath = r".\...\site-packages" #base of the file-tree to be copied (where the needed dependencies originally reside)
destPath = r".\site-packages" #destination of copy
import csv, shutil
from pathlib import Path
logF = Path(logFile)
basePath = Path(basePath).resolve()
destPath = Path(destPath).resolve()
with open(logF, newline='', encoding="utf-8") as log:
checked = set()
reader = csv.DictReader(log) # Linux: -
for row in reader: #Linux: for row in log:
try:
src = Path(row["Path"]) # Linux: src = Path(row.split('"')[1])
src = src.resolve()
if src in checked or not (src.is_file() and\
basePath.parts == src.parts[:len(basePath.parts)]):
continue
except (OSError, IndexError): #not a file
continue
finally:
checked.add(src)
dst = destPath / src.relative_to(basePath)
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dst.parent)