How to deploy pyside2 applications? - The Qt way

2020-03-04 03:16发布

问题:

I need to deploy a pyside2 application based on Qt 5.12.1 for all major 3 Operative Systems (Windows, Linux and MacOS).

I already checked on How to make a Python script standalone executable to run without ANY dependency? but that is not what I want because I need a Qt-related approach like windeployqt, macdeployqt, linuxdeployqt (separate project).

As pointed by eyllanesc: "python is a scripting language that does not generate a binary". However, the The Qt Company should figure that too and make easier for us to deploy pyside2 applications. At least as easier as deploying C++/QML applications.

So I want a tool like windowsdeployqt, macdeployqt, linuxdeployqt... That works with pyside2 applications.

[UPDATE] eyllanesc recommended fbs (fman build system) as a start point as there is not an official tool to deploy pyside2 applications. That should work as a workaround. New answers are welcome too.
Please answer as soon as possible when The Qt Company releases an official tool.

[NOTE]: I'm using Qt Creator 4.8.1 based on Qt 5.12.1

回答1:

There is currently no Qt way to deploy PySide2 applications (and I do not think there will be any at least in the near future)

On this subject there are the following reports: PYSIDE-901, PYSIDE-913, in it this points out that possibly for Qt for Python 5.13 the documentation will be updated and there will be a section for the deployment. You can see the progress here.

See Deployment

In it 3 options to do deployment:

The options for a project are:
1. Sending a normal zip-file with the application's content.
2. Building a proper Python package(wheel): https://packaging.python.org
3. Freezing the application in a single binary file, or into a directory.

And of the third method they comment on the pro and against of the tools like PyInstaller, cx_Freeze, py2exe and py2app indicating in the end that the best options for them is cx_Freeze or Pyinstaller. There is also another interesting tool that is the fbs project(based on Pyinstaller).

In my personal opinion I would choose fbs because it offers a simple way to package projects based on PyQt5 or PySide2



回答2:

Pack all necessary dependencies in a folder and distribute that.

Simple idea but the problem is finding the minimal set. The packagers proposed on qt site create complicated file structure and their own EXEs, which is too much for a little-script case (IMHO). Luckily, there are ways to log dependencies: Process Monitor on Windows and strace on Linux. They list all system calls of a monitored program. I wrote a small python script to pick the dependencies from such log.

It's convenient to do this in a virtual environment as shown here, or python embedded distribution with pre-installed essential modules:

    > python -m venv ./MyVEnv
    > cd ./MyVEnv
    > ./.../python -m pip install pyside2
  1. start syscall monitor (even 10sec log is huge so don't leave it running for long) and start your script that uses Qt to load all dependencies (make it do all possible actions to open all necessary dependencies) and close it (to unlock locked ones); may be useful to use cpython's -B flag to avoid having cached files in your final pack; also you could manually filter out needless syscalls in the monitor program to reduce logs

on Windows:

    > Procmon /AcceptEula /NoFilter /BackingFile log1
    > .\MyVEnv\...\python -B yourScript.py
    > Procmon.exe /OpenLog log1.PML /SaveAs logFile.csv

on Linux:

    > 2>logFile strace ./bin/python3 -B yourScript.py
  1. after that launch my script to duplicate all logged dependencies into a folder, preserving original file structure:

    > python .\depspicker.py
    

which is:

    #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)
  1. now you can replace everything inside the 'basePath' with duplicated file tree from 'destPath' to free space; to distribute your program you could take python embedded, put the new dependencies there, and also further remove all unnecessary files similar way - or just use embedded instead of venv from the start