diff options
| author | Shyamnath Premnadh <Shyamnath.Premnadh@qt.io> | 2024-06-04 15:48:40 +0200 |
|---|---|---|
| committer | Shyamnath Premnadh <Shyamnath.Premnadh@qt.io> | 2024-06-10 10:20:06 +0200 |
| commit | 32e353e9d91f45c23dcb07b0798237c79795cf0a (patch) | |
| tree | 14d6739b4ab6c37882c220fb4a9594004730c186 | |
| parent | 527eec228d98f1c8e23f95dc92d6eed4d5a8725a (diff) | |
Desktop Deployment: Enable Nuitka --standalone mode
- enables the standalone mode of Nuitka for pyside6-deploy
- the mode can be set either through the command line or the config file
- adapt tests
- update documentation
Pick-to: 6.7
Fixes: PYSIDE-2622
Task-number: PYSIDE-1612
Change-Id: I5a10c857d3e79174d2643139eb2e4f7b5e10d955
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
| -rw-r--r-- | sources/pyside-tools/deploy.py | 23 | ||||
| -rw-r--r-- | sources/pyside-tools/deploy_lib/config.py | 23 | ||||
| -rw-r--r-- | sources/pyside-tools/deploy_lib/default.spec | 3 | ||||
| -rw-r--r-- | sources/pyside-tools/deploy_lib/deploy_util.py | 16 | ||||
| -rw-r--r-- | sources/pyside-tools/deploy_lib/nuitka_helper.py | 10 | ||||
| -rw-r--r-- | sources/pyside6/doc/deployment/deployment-pyside6-deploy.rst | 6 | ||||
| -rw-r--r-- | sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py | 10 |
7 files changed, 78 insertions, 13 deletions
diff --git a/sources/pyside-tools/deploy.py b/sources/pyside-tools/deploy.py index aa03d13d0..bab5aa0de 100644 --- a/sources/pyside-tools/deploy.py +++ b/sources/pyside-tools/deploy.py @@ -49,10 +49,20 @@ TOOL_DESCRIPTION = dedent(f""" Linux = .bin """) +HELP_MODE = dedent(""" + The mode in which the application is deployed. The options are: onefile, + standalone. The default value is onefile. + + This options translates to the mode Nuitka uses to create the executable. + + macOS by default uses the --standalone option. + """) + def main(main_file: Path = None, name: str = None, config_file: Path = None, init: bool = False, loglevel=logging.WARNING, dry_run: bool = False, keep_deployment_files: bool = False, - force: bool = False, extra_ignore_dirs: str = None, extra_modules_grouped: str = None): + force: bool = False, extra_ignore_dirs: str = None, extra_modules_grouped: str = None, + mode: bool = False): logging.basicConfig(level=loglevel) if config_file and not config_file.exists() and not main_file.exists(): @@ -91,7 +101,7 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini config = DesktopConfig(config_file=config_file, source_file=main_file, python_exe=python.exe, dry_run=dry_run, existing_config_file=config_file_exists, - extra_ignore_dirs=extra_ignore_dirs) + extra_ignore_dirs=extra_ignore_dirs, mode=mode) # set application name if name: @@ -135,7 +145,8 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini excluded_qml_plugins=config.excluded_qml_plugins, icon=config.icon, dry_run=dry_run, - permissions=config.permissions) + permissions=config.permissions, + mode=config.mode) except Exception: print(f"[DEPLOY] Exception occurred: {traceback.format_exc()}") finally: @@ -182,7 +193,11 @@ if __name__ == "__main__": parser.add_argument("--extra-modules", type=str, help=HELP_EXTRA_MODULES) + parser.add_argument("--mode", choices=["onefile", "standalone"], default="desktop", + help=HELP_MODE) + args = parser.parse_args() main(args.main_file, args.name, args.config_file, args.init, args.loglevel, args.dry_run, - args.keep_deployment_files, args.force, args.extra_ignore_dirs, args.extra_modules) + args.keep_deployment_files, args.force, args.extra_ignore_dirs, args.extra_modules, + args.mode) diff --git a/sources/pyside-tools/deploy_lib/config.py b/sources/pyside-tools/deploy_lib/config.py index d59dd92ad..5d5070bef 100644 --- a/sources/pyside-tools/deploy_lib/config.py +++ b/sources/pyside-tools/deploy_lib/config.py @@ -8,6 +8,7 @@ import warnings from configparser import ConfigParser from typing import List from pathlib import Path +from enum import Enum from project import ProjectData from . import (DEFAULT_APP_ICON, find_pyside_modules, find_permission_categories, @@ -375,8 +376,13 @@ class Config(BaseConfig): class DesktopConfig(Config): """Wrapper class around pysidedeploy.spec, but specific to Desktop deployment """ + class NuitkaMode(Enum): + ONEFILE = "onefile" + STANDALONE = "standalone" + def __init__(self, config_file: Path, source_file: Path, python_exe: Path, dry_run: bool, - existing_config_file: bool = False, extra_ignore_dirs: List[str] = None): + existing_config_file: bool = False, extra_ignore_dirs: List[str] = None, + mode: str = "onefile"): super().__init__(config_file, source_file, python_exe, dry_run, existing_config_file, extra_ignore_dirs) self.dependency_reader = QtDependencyReader(dry_run=self.dry_run) @@ -402,6 +408,12 @@ class DesktopConfig(Config): else: self._find_and_set_permissions() + self._mode = self.NuitkaMode.ONEFILE + if self.get_value("nuitka", "mode") == self.NuitkaMode.STANDALONE.value: + self._mode = self.NuitkaMode.STANDALONE + elif mode == self.NuitkaMode.STANDALONE.value: + self.mode = self.NuitkaMode.STANDALONE + @property def qt_plugins(self): return self._qt_plugins @@ -420,6 +432,15 @@ class DesktopConfig(Config): self._permissions = permissions self.set_value("nuitka", "macos.permissions", ",".join(permissions)) + @property + def mode(self): + return self._mode + + @mode.setter + def mode(self, mode: NuitkaMode): + self._mode = mode + self.set_value("nuitka", "mode", mode.value) + def _find_dependent_qt_modules(self): """ Given pysidedeploy_config.modules, find all the other dependent Qt modules. diff --git a/sources/pyside-tools/deploy_lib/default.spec b/sources/pyside-tools/deploy_lib/default.spec index 0a729d585..2e28b2f7c 100644 --- a/sources/pyside-tools/deploy_lib/default.spec +++ b/sources/pyside-tools/deploy_lib/default.spec @@ -65,6 +65,9 @@ plugins = # eg: NSCameraUsageDescription:CameraAccess macos.permissions = +# mode of using Nuitka. Accepts standalone or onefile. Default is onefile. +mode = onefile + # (str) specify any extra nuitka arguments # eg: extra_args = --show-modules --follow-stdlib extra_args = --quiet --noinclude-qt-translations diff --git a/sources/pyside-tools/deploy_lib/deploy_util.py b/sources/pyside-tools/deploy_lib/deploy_util.py index e8b05e990..1e0e2712a 100644 --- a/sources/pyside-tools/deploy_lib/deploy_util.py +++ b/sources/pyside-tools/deploy_lib/deploy_util.py @@ -7,7 +7,7 @@ import sys from pathlib import Path from . import EXE_FORMAT -from .config import Config +from .config import Config, DesktopConfig def config_option_exists(): @@ -61,17 +61,21 @@ def create_config_file(dry_run: bool = False, config_file: Path = None, main_fil return config_file -def finalize(config: Config): +def finalize(config: DesktopConfig): """ Copy the executable into the final location For Android deployment, this is done through buildozer """ - generated_exec_path = config.generated_files_path / (config.source_file.stem + EXE_FORMAT) + dist_format = EXE_FORMAT + if config.mode == DesktopConfig.NuitkaMode.STANDALONE and sys.platform != "darwin": + dist_format = ".dist" + + generated_exec_path = config.generated_files_path / (config.source_file.stem + dist_format) if generated_exec_path.exists() and config.exe_dir: - if sys.platform == "darwin": - shutil.copytree(generated_exec_path, config.exe_dir / (config.title + EXE_FORMAT), + if sys.platform == "darwin" or config.mode == DesktopConfig.NuitkaMode.STANDALONE: + shutil.copytree(generated_exec_path, config.exe_dir / (config.title + dist_format), dirs_exist_ok=True) else: shutil.copy(generated_exec_path, config.exe_dir) print("[DEPLOY] Executed file created in " - f"{str(config.exe_dir / (config.source_file.stem + EXE_FORMAT))}") + f"{str(config.exe_dir / (config.source_file.stem + dist_format))}") diff --git a/sources/pyside-tools/deploy_lib/nuitka_helper.py b/sources/pyside-tools/deploy_lib/nuitka_helper.py index ac9a83f3f..5d0e9032f 100644 --- a/sources/pyside-tools/deploy_lib/nuitka_helper.py +++ b/sources/pyside-tools/deploy_lib/nuitka_helper.py @@ -1,6 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +# enables to use typehints for classes that has not been defined yet or imported +# used for resolving circular imports +from __future__ import annotations import logging import os import sys @@ -8,6 +11,7 @@ from pathlib import Path from typing import List from . import MAJOR_VERSION, run_command +from .config import DesktopConfig class Nuitka: @@ -52,10 +56,12 @@ class Nuitka: def create_executable(self, source_file: Path, extra_args: str, qml_files: List[Path], qt_plugins: List[str], excluded_qml_plugins: List[str], icon: str, - dry_run: bool, permissions: List[str]): + dry_run: bool, permissions: List[str], + mode: DesktopConfig.NuitkaMode): qt_plugins = [plugin for plugin in qt_plugins if plugin not in self.qt_plugins_to_ignore] extra_args = extra_args.split() + # macOS uses the --standalone option by default to create an app bundle if sys.platform == "darwin": # create an app bundle extra_args.extend(["--standalone", "--macos-create-app-bundle"]) @@ -63,7 +69,7 @@ class Nuitka: for permission in permissions: extra_args.append(permission_pattern.format(permission=permission)) else: - extra_args.append("--onefile") + extra_args.append(f"--{mode.value}") qml_args = [] if qml_files: diff --git a/sources/pyside6/doc/deployment/deployment-pyside6-deploy.rst b/sources/pyside6/doc/deployment/deployment-pyside6-deploy.rst index 980fe2dd1..3b602babc 100644 --- a/sources/pyside6/doc/deployment/deployment-pyside6-deploy.rst +++ b/sources/pyside6/doc/deployment/deployment-pyside6-deploy.rst @@ -144,6 +144,12 @@ The relevant parameters for ``pyside6-deploy`` are: NSCameraUsageDescription:CameraAccess + * ``mode``: Accepts one of the options: ``onefile`` or ``standalone``. The default is ``onefile``. + This option corresponds to the mode in which Nuitka is run. The onefile mode creates a single + executable file, while the standalone mode creates a directory with the executable and all the + necessary files. The standalone mode is useful when you want to distribute the application as a + directory with dependencies and other files required by the app. + * ``extra_args``: Any extra Nuitka arguments specified. It is specified as space-separated command line arguments i.e. just like how you would specify it when you use Nuitka through the command line. By default, it contains the following arguments:: diff --git a/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py b/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py index db60c8c3f..a5d122a37 100644 --- a/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py +++ b/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py @@ -174,6 +174,16 @@ class TestPySide6DeployWidgets(DeployTestBase): self.deploy.main(main_file=fake_main_file, config_file=self.config_file) self.assertTrue("Directory does not contain main.py file." in str(context.exception)) + def testStandaloneMode(self, mock_plugins): + mock_plugins.return_value = self.all_plugins + # remove --onefile from self.expected_run_cmd and replace it with --standalone + self.expected_run_cmd = self.expected_run_cmd.replace(" --onefile", " --standalone") + # test standalone mode + original_output = self.deploy.main(self.main_file, mode="standalone", dry_run=True, + force=True) + + self.assertEqual(original_output, self.expected_run_cmd) + @unittest.skipIf(sys.platform == "darwin" and int(platform.mac_ver()[0].split('.')[0]) <= 11, "Test only works on macOS version 12+") |
