aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside-tools/deploy_lib
diff options
context:
space:
mode:
Diffstat (limited to 'sources/pyside-tools/deploy_lib')
-rw-r--r--sources/pyside-tools/deploy_lib/android/__init__.py10
-rw-r--r--sources/pyside-tools/deploy_lib/android/android_helper.py59
-rw-r--r--sources/pyside-tools/deploy_lib/android/buildozer.py84
-rw-r--r--sources/pyside-tools/deploy_lib/android/recipes/PySide6/__init__.tmpl.py43
-rw-r--r--sources/pyside-tools/deploy_lib/android/recipes/shiboken6/__init__.tmpl.py24
-rw-r--r--sources/pyside-tools/deploy_lib/config.py156
-rw-r--r--sources/pyside-tools/deploy_lib/default.spec38
-rw-r--r--sources/pyside-tools/deploy_lib/deploy_util.py22
-rw-r--r--sources/pyside-tools/deploy_lib/python_helper.py1
9 files changed, 426 insertions, 11 deletions
diff --git a/sources/pyside-tools/deploy_lib/android/__init__.py b/sources/pyside-tools/deploy_lib/android/__init__.py
new file mode 100644
index 000000000..12eb6830c
--- /dev/null
+++ b/sources/pyside-tools/deploy_lib/android/__init__.py
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 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
+
+WIDGET_APPLICATION_MODULES = ["Core", "Gui", "Widgets"]
+QUICK_APPLICATION_MODULES = ["Core", "Gui", "Widgets", "Network", "OpenGL", "Qml", "Quick",
+ "QuickControls2"]
+
+from .android_helper import (create_recipe, extract_and_copy_jar, get_wheel_android_arch,
+ AndroidData)
+from .buildozer import Buildozer
diff --git a/sources/pyside-tools/deploy_lib/android/android_helper.py b/sources/pyside-tools/deploy_lib/android/android_helper.py
new file mode 100644
index 000000000..b0c8254ab
--- /dev/null
+++ b/sources/pyside-tools/deploy_lib/android/android_helper.py
@@ -0,0 +1,59 @@
+# Copyright (C) 2023 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
+
+import logging
+from pathlib import Path
+from jinja2 import Environment, FileSystemLoader
+from zipfile import ZipFile
+from dataclasses import dataclass
+
+
+@dataclass
+class AndroidData:
+ wheel_pyside: Path
+ wheel_shiboken: Path
+ ndk_path: Path
+ sdk_path: Path
+
+
+def create_recipe(version: str, component: str, wheel_path: str, generated_files_path: Path):
+ '''
+ Create python_for_android recipe for PySide6 and shiboken6
+ '''
+ rcp_tmpl_path = Path(__file__).parent / "recipes" / f"{component}"
+ environment = Environment(loader=FileSystemLoader(rcp_tmpl_path))
+ template = environment.get_template("__init__.tmpl.py")
+ content = template.render(
+ version=version,
+ wheel_path=wheel_path,
+ )
+
+ recipe_path = generated_files_path / "recipes" / f"{component}"
+ recipe_path.mkdir(parents=True, exist_ok=True)
+ logging.info(f"Writing {component} recipe into {recipe_path}")
+ with open(recipe_path / "__init__.py", mode="w", encoding="utf-8") as recipe:
+ recipe.write(content)
+
+
+def extract_and_copy_jar(wheel_path: Path, generated_files_path: Path) -> str:
+ '''
+ extracts the PySide6 wheel and copies the 'jar' folder to 'generated_files_path'.
+ These .jar files are added to the buildozer.spec file to be later use by buildozer
+ '''
+ jar_path = generated_files_path / "jar"
+ jar_path.mkdir(parents=True, exist_ok=True)
+ archive = ZipFile(wheel_path)
+ jar_files = [file for file in archive.namelist() if file.startswith("PySide6/jar")]
+ for file in jar_files:
+ archive.extract(file, jar_path)
+ return jar_path
+
+
+def get_wheel_android_arch(wheel: str):
+ wheel = Path(wheel)
+ supported_archs = ["aarch64", "armv7a", "i686", "x86_64"]
+ for arch in supported_archs:
+ if arch in wheel.stem:
+ return arch
+
+ return None
diff --git a/sources/pyside-tools/deploy_lib/android/buildozer.py b/sources/pyside-tools/deploy_lib/android/buildozer.py
new file mode 100644
index 000000000..91d55f4ca
--- /dev/null
+++ b/sources/pyside-tools/deploy_lib/android/buildozer.py
@@ -0,0 +1,84 @@
+# Copyright (C) 2023 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
+
+import logging
+from pathlib import Path
+from .. import run_command, BaseConfig, Config
+
+
+class BuildozerConfig(BaseConfig):
+ def __init__(self, buildozer_spec_file: Path, pysidedeploy_config: Config, dry_run: bool):
+ super().__init__(buildozer_spec_file, dry_run, comment_prefixes="#")
+ self.set_value("app", "title", pysidedeploy_config.title)
+ self.set_value("app", "package.name", pysidedeploy_config.title)
+ self.set_value("app", "package.domain",
+ f"org.{pysidedeploy_config.title}")
+
+ include_exts = self.get_value("app", "source.include_exts")
+ include_exts = f"{include_exts},qml"
+ self.set_value("app", "source.include_exts", include_exts)
+
+ self.set_value("app", "requirements", "python3,shiboken6,PySide6")
+
+ if pysidedeploy_config.ndk_path:
+ self.set_value("app", "android.ndk_path", str(pysidedeploy_config.ndk_path))
+
+ if pysidedeploy_config.sdk_path:
+ self.set_value("app", "android.sdk_path", str(pysidedeploy_config.sdk_path))
+
+ self.set_value("app", "android.add_jars", f"{str(pysidedeploy_config.jars_dir)}/*.jar")
+
+ platform_map = {"aarch64": "arm64-v8a",
+ "armv7a": "armeabi-v7a",
+ "i686": "x86",
+ "x86_64": "x86_64"}
+ arch = platform_map[pysidedeploy_config.arch]
+ self.set_value("app", "android.archs", arch)
+
+ # p4a changes
+ logging.info("[DEPLOY] Using custom fork of python-for-android: "
+ "https://github.com/shyamnathp/python-for-android/tree/pyside_support")
+ self.set_value("app", "p4a.fork", "shyamnathp")
+ self.set_value("app", "p4a.branch", "pyside_support")
+ self.set_value('app', "p4a.local_recipes", str(pysidedeploy_config.recipe_dir))
+ self.set_value("app", "p4a.bootstrap", "qt")
+
+ modules = ",".join(pysidedeploy_config.modules)
+ local_libs = ",".join(pysidedeploy_config.local_libs)
+ extra_args = (f"--qt-libs={modules} --load-local-libs={local_libs}")
+ self.set_value("app", "p4a.extra_args", extra_args)
+
+ # TODO: does not work atm. Seems like a bug with buildozer
+ # change buildozer build_dir
+ # self.set_value("buildozer", "build_dir", str(build_dir.relative_to(Path.cwd())))
+
+ # change final apk/aab path
+ self.set_value("buildozer", "bin_dir", str(pysidedeploy_config.exe_dir.resolve()))
+
+ self.update_config()
+
+
+class Buildozer:
+ dry_run = False
+
+ @staticmethod
+ def initialize(pysidedeploy_config: Config):
+ project_dir = Path(pysidedeploy_config.project_dir)
+ buildozer_spec = project_dir / "buildozer.spec"
+ if buildozer_spec.exists():
+ logging.warning(f"[DEPLOY] buildozer.spec already present in {str(project_dir)}."
+ "Using it")
+ return
+
+ # creates buildozer.spec config file
+ command = ["buildozer", "init"]
+ run_command(command=command, dry_run=Buildozer.dry_run)
+ if not Buildozer.dry_run and not buildozer_spec.exists():
+ raise RuntimeError(f"buildozer.spec not found in {Path.cwd()}")
+ BuildozerConfig(buildozer_spec, pysidedeploy_config, Buildozer.dry_run)
+
+ @staticmethod
+ def create_executable(mode: str):
+ # build the application in release mode
+ command = ["buildozer", "android", mode]
+ run_command(command=command, dry_run=Buildozer.dry_run)
diff --git a/sources/pyside-tools/deploy_lib/android/recipes/PySide6/__init__.tmpl.py b/sources/pyside-tools/deploy_lib/android/recipes/PySide6/__init__.tmpl.py
new file mode 100644
index 000000000..289f2d62b
--- /dev/null
+++ b/sources/pyside-tools/deploy_lib/android/recipes/PySide6/__init__.tmpl.py
@@ -0,0 +1,43 @@
+# Copyright (C) 2023 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
+
+from pythonforandroid.recipe import PythonRecipe
+from pythonforandroid.logger import info
+import zipfile
+import shutil
+from pathlib import Path
+
+
+class PySideRecipe(PythonRecipe):
+ version = '{{ version }}'
+ wheel_path = '{{ wheel_path }}'
+ depends = ["shiboken6"]
+ call_hostpython_via_targetpython = False
+ install_in_hostpython = False
+
+ def build_arch(self, arch):
+ """Unzip the wheel and copy into site-packages of target"""
+
+ info("Installing {} into site-packages".format(self.name))
+ with zipfile.ZipFile(self.wheel_path, "r") as zip_ref:
+ info("Unzip wheels and copy into {}".format(self.ctx.get_python_install_dir(arch.arch)))
+ zip_ref.extractall(self.ctx.get_python_install_dir(arch.arch))
+
+ lib_dir = Path(f"{self.ctx.get_python_install_dir(arch.arch)}/PySide6/Qt/lib")
+ info("Copying Qt libraries to be loaded on startup")
+ shutil.copytree(lib_dir, self.ctx.get_libs_dir(arch.arch), dirs_exist_ok=True)
+
+ info("Copying libc++_shared.so from SDK to be loaded on startup")
+ libcpp_path = f"{self.ctx.ndk.sysroot_lib_dir}/{arch.command_prefix}/libc++_shared.so"
+ shutil.copyfile(libcpp_path, Path(self.ctx.get_libs_dir(arch.arch)) / "libc++_shared.so")
+
+ info("Copying Qt platform plugin to be loaded on startup from SDK to be loaded on startup")
+ shutil.copyfile(
+ Path(self.ctx.get_python_install_dir(arch.arch))
+ / "PySide6" / "Qt" / "plugins" / "platforms"
+ / f"libplugins_platforms_qtforandroid_{arch.arch}.so",
+ Path(self.ctx.get_libs_dir(arch.arch)) / f"libplugins_platforms_qtforandroid_{arch.arch}.so",
+ )
+
+
+recipe = PySideRecipe()
diff --git a/sources/pyside-tools/deploy_lib/android/recipes/shiboken6/__init__.tmpl.py b/sources/pyside-tools/deploy_lib/android/recipes/shiboken6/__init__.tmpl.py
new file mode 100644
index 000000000..afe094cbd
--- /dev/null
+++ b/sources/pyside-tools/deploy_lib/android/recipes/shiboken6/__init__.tmpl.py
@@ -0,0 +1,24 @@
+# Copyright (C) 2023 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
+
+from pythonforandroid.recipe import PythonRecipe
+from pythonforandroid.logger import info
+import zipfile
+
+
+class ShibokenRecipe(PythonRecipe):
+ version = '{{ version }}'
+ wheel_path = '{{ wheel_path }}'
+
+ call_hostpython_via_targetpython = False
+ install_in_hostpython = False
+
+ def build_arch(self, arch):
+ ''' Unzip the wheel and copy into site-packages of target'''
+ info('Installing {} into site-packages'.format(self.name))
+ with zipfile.ZipFile(self.wheel_path, 'r') as zip_ref:
+ info('Unzip wheels and copy into {}'.format(self.ctx.get_python_install_dir(arch.arch)))
+ zip_ref.extractall(self.ctx.get_python_install_dir(arch.arch))
+
+
+recipe = ShibokenRecipe()
diff --git a/sources/pyside-tools/deploy_lib/config.py b/sources/pyside-tools/deploy_lib/config.py
index 2575ab063..781c2155d 100644
--- a/sources/pyside-tools/deploy_lib/config.py
+++ b/sources/pyside-tools/deploy_lib/config.py
@@ -19,7 +19,7 @@ class BaseConfig:
def __init__(self, config_file: Path, dry_run: bool, comment_prefixes: str = "/") -> None:
self.config_file = config_file
- self.parser = ConfigParser(comment_prefixes="/", allow_no_value=True)
+ self.parser = ConfigParser(comment_prefixes=comment_prefixes, allow_no_value=True)
if not self.config_file.exists():
if not dry_run:
logging.info(f"[DEPLOY] Creating config file {self.config_file}")
@@ -62,7 +62,8 @@ class Config(BaseConfig):
creation
"""
- def __init__(self, config_file: Path, source_file: Path, python_exe: Path, dry_run: bool):
+ def __init__(self, config_file: Path, source_file: Path, python_exe: Path, dry_run: bool,
+ android_data, is_android: bool):
super().__init__(config_file, dry_run)
self._dry_run = dry_run
@@ -81,6 +82,7 @@ class Config(BaseConfig):
)
self.title = self.get_value("app", "title")
+
self.project_dir = None
if self.get_value("app", "project_dir"):
self.project_dir = Path(self.get_value("app", "project_dir")).absolute()
@@ -113,6 +115,50 @@ class Config(BaseConfig):
else:
self._find_and_set_excluded_qml_plugins()
+ # Android
+ if is_android:
+ if android_data.wheel_pyside:
+ self.wheel_pyside = android_data.wheel_pyside
+ else:
+ wheel_pyside_temp = self.get_value("qt", "wheel_pyside")
+ self.wheel_pyside = Path(wheel_pyside_temp) if wheel_pyside_temp else None
+
+ if android_data.wheel_shiboken:
+ self.wheel_shiboken = android_data.wheel_shiboken
+ else:
+ wheel_shiboken_temp = self.get_value("qt", "wheel_shiboken")
+ self.wheel_shiboken = Path(wheel_shiboken_temp) if wheel_shiboken_temp else None
+
+ if android_data.ndk_path:
+ self.ndk_path = android_data.ndk_path
+ else:
+ ndk_path_temp = self.get_value("buildozer", "ndk_path")
+ self.ndk_path = Path(ndk_path_temp) if ndk_path_temp else None
+
+ if android_data.sdk_path:
+ self.sdk_path = android_data.sdk_path
+ else:
+ sdk_path_temp = self.get_value("buildozer", "sdk_path")
+ self.sdk_path = Path(sdk_path_temp) if sdk_path_temp else None
+
+ recipe_dir_temp = self.get_value("buildozer", "recipe_dir")
+ self.recipe_dir = Path(recipe_dir_temp) if recipe_dir_temp else None
+
+ jars_dir_temp = self.get_value("buildozer", "jars_dir")
+ self.jars_dir = Path(jars_dir_temp) if jars_dir_temp else None
+
+ self._modules = []
+ if self.get_value("buildozer", "modules"):
+ self.modules = self.get_value("buildozer", "modules").split(",")
+
+ self.arch = self.get_value("buildozer", "arch")
+
+ self._local_libs = []
+ if self.get_value("buildozer", "local_libs"):
+ self.local_libs = self.get_value("buildozer", "local_libs").split(",")
+
+ self._mode = self.get_value("buildozer", "mode")
+
def set_or_fetch(self, config_property_val, config_property_key, config_property_group="app"):
"""
Write to config_file if 'config_property_key' is known without config_file
@@ -164,7 +210,7 @@ class Config(BaseConfig):
return self._source_file
@source_file.setter
- def source_file(self, source_file):
+ def source_file(self, source_file: Path):
self._source_file = source_file
@property
@@ -172,7 +218,7 @@ class Config(BaseConfig):
return self._python_path
@python_path.setter
- def python_path(self, python_path):
+ def python_path(self, python_path: Path):
self._python_path = python_path
@property
@@ -184,13 +230,113 @@ class Config(BaseConfig):
self._excluded_qml_plugins = excluded_qml_plugins
@property
+ def recipe_dir(self):
+ return self._recipe_dir
+
+ @recipe_dir.setter
+ def recipe_dir(self, recipe_dir: Path):
+ self._recipe_dir = recipe_dir.resolve() if recipe_dir else None
+ if self._recipe_dir:
+ self.set_value("buildozer", "recipe_dir", str(self._recipe_dir))
+
+ def recipes_exist(self):
+ if not self._recipe_dir:
+ return False
+
+ pyside_recipe_dir = Path(self.recipe_dir) / "PySide6"
+ shiboken_recipe_dir = Path(self.recipe_dir) / "shiboken6"
+
+ return pyside_recipe_dir.is_dir() and shiboken_recipe_dir.is_dir()
+
+ @property
+ def jars_dir(self) -> Path:
+ return self._jars_dir
+
+ @jars_dir.setter
+ def jars_dir(self, jars_dir: Path):
+ self._jars_dir = jars_dir.resolve() if jars_dir else None
+ if self._jars_dir:
+ self.set_value("buildozer", "jars_dir", str(self._jars_dir))
+
+ @property
+ def modules(self):
+ return self._modules
+
+ @modules.setter
+ def modules(self, modules):
+ self._modules = modules
+ self.set_value("buildozer", "modules", ",".join(modules))
+
+ @property
+ def local_libs(self):
+ return self._local_libs
+
+ @local_libs.setter
+ def local_libs(self, local_libs):
+ self._local_libs = local_libs
+ self.set_value("buildozer", "local_libs", ",".join(local_libs))
+
+ @property
+ def ndk_path(self):
+ return self._ndk_path
+
+ @ndk_path.setter
+ def ndk_path(self, ndk_path: Path):
+ self._ndk_path = ndk_path.resolve() if ndk_path else None
+ if self._ndk_path:
+ self.set_value("buildozer", "ndk_path", str(self._ndk_path))
+
+ @property
+ def sdk_path(self) -> Path:
+ return self._sdk_path
+
+ @sdk_path.setter
+ def sdk_path(self, sdk_path: Path):
+ self._sdk_path = sdk_path.resolve() if sdk_path else None
+ if self._sdk_path:
+ self.set_value("buildozer", "sdk_path", str(self._sdk_path))
+
+ @property
+ def arch(self):
+ return self._arch
+
+ @arch.setter
+ def arch(self, arch):
+ self._arch = arch
+ self.set_value("buildozer", "arch", arch)
+
+ @property
+ def mode(self):
+ return self._mode
+
+ @property
def exe_dir(self):
return self._exe_dir
@exe_dir.setter
- def exe_dir(self, exe_dir):
+ def exe_dir(self, exe_dir: Path):
self._exe_dir = exe_dir
+ @property
+ def wheel_pyside(self) -> Path:
+ return self._wheel_pyside
+
+ @wheel_pyside.setter
+ def wheel_pyside(self, wheel_pyside: Path):
+ self._wheel_pyside = wheel_pyside.resolve() if wheel_pyside else None
+ if self._wheel_pyside:
+ self.set_value("qt", "wheel_pyside", str(self._wheel_pyside))
+
+ @property
+ def wheel_shiboken(self) -> Path:
+ return self._wheel_shiboken
+
+ @wheel_shiboken.setter
+ def wheel_shiboken(self, wheel_shiboken: Path):
+ self._wheel_shiboken = wheel_shiboken.resolve() if wheel_shiboken else None
+ if self._wheel_shiboken:
+ self.set_value("qt", "wheel_shiboken", str(self._wheel_shiboken))
+
def _find_and_set_qml_files(self):
"""Fetches all the qml_files in the folder and sets them if the
field qml_files is empty in the config_dir"""
diff --git a/sources/pyside-tools/deploy_lib/default.spec b/sources/pyside-tools/deploy_lib/default.spec
index 17027e898..fdfb5397d 100644
--- a/sources/pyside-tools/deploy_lib/default.spec
+++ b/sources/pyside-tools/deploy_lib/default.spec
@@ -26,6 +26,9 @@ python_path =
# zstandard: provides final executable size optimization
packages = nuitka==1.5.4,ordered_set,zstandard
+# buildozer: for deploying Android application
+android_packages = buildozer==1.5.0,cython==0.29.33
+
[qt]
# Comma separated path to QML files required
@@ -35,8 +38,43 @@ qml_files =
# excluded qml plugin binaries
excluded_qml_plugins =
+# path to PySide wheel
+wheel_pyside =
+
+# path to Shiboken wheel
+wheel_shiboken =
+
[nuitka]
# (str) specify any extra nuitka arguments
# eg: extra_args = --show-modules --follow-stdlib
extra_args = --quiet --noinclude-qt-translations=True
+
+[buildozer]
+
+# build mode
+# possible options: [release, debug]
+# release creates an aab, while debug creates an apk
+mode = debug
+
+# contrains path to PySide6 and shiboken6 recipe dir
+recipe_dir =
+
+# path to extra Qt Android jars to be loaded by the application
+jars_dir =
+
+# if empty uses default ndk path downloaded by buildozer
+ndk_path =
+
+# if empty uses default sdk path downloaded by buildozer
+sdk_path =
+
+# modules used. Comma separated
+modules =
+
+# other libraries to be loaded. Comma separated.
+local_libs = plugins_platforms_qtforandroid
+
+# architecture of deployed platform
+# possible values: ["aarch64", "armv7a", "i686", "x86_64"]
+arch =
diff --git a/sources/pyside-tools/deploy_lib/deploy_util.py b/sources/pyside-tools/deploy_lib/deploy_util.py
index 843a21f5a..de545e3a5 100644
--- a/sources/pyside-tools/deploy_lib/deploy_util.py
+++ b/sources/pyside-tools/deploy_lib/deploy_util.py
@@ -19,7 +19,7 @@ def config_option_exists():
return False
-def cleanup(generated_files_path: Path, config: Config):
+def cleanup(generated_files_path: Path, config: Config, is_android: bool = False):
"""
Cleanup the generated build folders/files
"""
@@ -29,9 +29,20 @@ def cleanup(generated_files_path: Path, config: Config):
elif not config.dry_run:
logging.info(f"[DEPLOY] {generated_files_path} does not exist")
+ if is_android:
+ buildozer_spec: Path = config.project_dir / "buildozer.spec"
+ if buildozer_spec.exists():
+ buildozer_spec.unlink()
+ logging.info(f"[DEPLOY] {str(buildozer_spec)} removed")
+
+ buildozer_build: Path = config.project_dir / ".buildozer"
+ if buildozer_build.exists():
+ shutil.rmtree(buildozer_build)
+ logging.info(f"[DEPLOY] {str(buildozer_build)} removed")
+
def get_config(python_exe: Path, dry_run: bool = False, config_file: Path = None, main_file:
- Path = None):
+ Path = None, android_data = None, is_android: bool = False):
"""
Sets up a new deployment configuration or use an existing config file
"""
@@ -42,7 +53,7 @@ def get_config(python_exe: Path, dry_run: bool = False, config_file: Path = None
config_file = Path.cwd() / "pysidedeploy.spec"
config = Config(config_file=config_file, source_file=main_file, python_exe=python_exe,
- dry_run=dry_run)
+ dry_run=dry_run, android_data=android_data, is_android=is_android)
return config
@@ -70,7 +81,7 @@ def setup_python(dry_run: bool, force: bool, init: bool):
def install_python_dependencies(config: Config, python: PythonExecutable, init: bool,
- packages: str):
+ packages: str, is_android: bool = False):
"""
Installs the python package dependencies for the target deployment platform
"""
@@ -80,13 +91,14 @@ def install_python_dependencies(config: Config, python: PythonExecutable, init:
packages = config.get_value("python", packages).split(",")
python.install(packages=packages)
# nuitka requires patchelf to make patchelf rpath changes for some Qt files
- if sys.platform.startswith("linux"):
+ if sys.platform.startswith("linux") and not is_android:
python.install(packages=["patchelf"])
def finalize(generated_files_path: Path, config: Config):
"""
Copy the executable into the final location
+ For Android deployment, this is done through buildozer
"""
generated_exec_path = generated_files_path / (config.source_file.stem + EXE_FORMAT)
if generated_exec_path.exists() and config.exe_dir:
diff --git a/sources/pyside-tools/deploy_lib/python_helper.py b/sources/pyside-tools/deploy_lib/python_helper.py
index 2fc0236bc..e86ce2e1c 100644
--- a/sources/pyside-tools/deploy_lib/python_helper.py
+++ b/sources/pyside-tools/deploy_lib/python_helper.py
@@ -49,7 +49,6 @@ class PythonExecutable:
package_version = package_info[1]
else:
raise ValueError(f"{package} should be of the format 'package_name'=='version'")
-
if not self.is_installed(package=package_name):
logging.info(f"[DEPLOY] Installing package: {package}")
run_command(