aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShyamnath Premnadh <Shyamnath.Premnadh@qt.io>2024-06-04 15:48:40 +0200
committerShyamnath Premnadh <Shyamnath.Premnadh@qt.io>2024-06-10 10:20:06 +0200
commit32e353e9d91f45c23dcb07b0798237c79795cf0a (patch)
tree14d6739b4ab6c37882c220fb4a9594004730c186
parent527eec228d98f1c8e23f95dc92d6eed4d5a8725a (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.py23
-rw-r--r--sources/pyside-tools/deploy_lib/config.py23
-rw-r--r--sources/pyside-tools/deploy_lib/default.spec3
-rw-r--r--sources/pyside-tools/deploy_lib/deploy_util.py16
-rw-r--r--sources/pyside-tools/deploy_lib/nuitka_helper.py10
-rw-r--r--sources/pyside6/doc/deployment/deployment-pyside6-deploy.rst6
-rw-r--r--sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py10
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+")