aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sources/pyside-tools/deploy.py309
-rw-r--r--sources/pyside-tools/deploy/__init__.py7
-rw-r--r--sources/pyside-tools/deploy/commands.py31
-rw-r--r--sources/pyside-tools/deploy/config.py177
-rw-r--r--sources/pyside-tools/deploy/nuitka_helper.py48
-rw-r--r--sources/pyside-tools/deploy/python_helper.py82
6 files changed, 347 insertions, 307 deletions
diff --git a/sources/pyside-tools/deploy.py b/sources/pyside-tools/deploy.py
index cfa35ebcf..2dde34ad3 100644
--- a/sources/pyside-tools/deploy.py
+++ b/sources/pyside-tools/deploy.py
@@ -25,324 +25,19 @@
2. pyside6-deploy -c /path/to/config_file
"""
-import configparser
-import subprocess
import argparse
import logging
import sys
-import os
from pathlib import Path
-from configparser import ConfigParser
import shutil
-from importlib import util
import traceback
+from deploy import Config, PythonExecutable
+
MAJOR_VERSION = 6
EXE_FORMAT = ".exe" if sys.platform == "win32" else ".bin"
-def run_command(command, dry_run: bool):
- command_str = " ".join([str(cmd) for cmd in command])
- try:
- if not dry_run:
- subprocess.check_call(command, shell=(sys.platform == "win32"))
- else:
- print(command_str + "\n")
- except FileNotFoundError as error:
- logging.exception(f"[DEPLOY]: {error.filename} not found")
- raise
- except subprocess.CalledProcessError as error:
- logging.exception(
- f"[DEPLOY]: Command {command_str} failed with error {error} and return_code"
- f"{error.returncode}"
- )
- raise
- except Exception as error:
- logging.exception(f"[DEPLOY]: Command {command_str} failed with error {error}")
- raise
-
-
-class Nuitka:
- """
- Wrapper class around the nuitka executable, enabling its usage through python code
- """
-
- def __init__(self, nuitka):
- self.nuitka = nuitka
-
- def create_executable(
- self, source_file: Path, extra_args: str, qml_files: list[Path], dry_run: bool
- ):
- extra_args = extra_args.split()
- qml_args = []
- if qml_files:
- # this includes "all" the plugins
- # FIXME: adding the "qml" plugin is equivalent to "all" because of dependencies
- # Ideally it should only add the specific qml plugins. eg: quick window, quick controls
- qml_args.append("--include-qt-plugins=all")
- qml_args.extend(
- [f"--include-data-files={qml_file}=./{qml_file.name}" for qml_file in qml_files]
- )
-
- output_dir = source_file.parent / "deployment"
- if not dry_run:
- output_dir.mkdir(parents=True, exist_ok=True)
- print("[DEPLOY] Running Nuitka")
- command = self.nuitka + [
- source_file,
- "--follow-imports",
- "--onefile",
- "--enable-plugin=pyside6",
- f"--output-dir={output_dir}",
- ]
- command.extend(extra_args + qml_args)
-
- if sys.platform == "linux":
- linux_icon = str(Path(__file__).parent / "deploy" / "pyside_icon.jpg")
- command.append(f"--linux-onefile-icon={linux_icon}")
-
- run_command(command=command, dry_run=dry_run)
-
-
-class Config:
- """
- Wrapper class around config file, whose options are used to control the executable creation
- """
-
- def __init__(self, config_file: Path) -> None:
- self.config_file = config_file
- self.parser = ConfigParser(comment_prefixes="/", allow_no_value=True)
- if not self.config_file.exists():
- logging.info(f"[DEPLOY]: Creating config file {self.config_file}")
- shutil.copy(Path(__file__).parent / "deploy" / "default.spec", self.config_file)
- else:
- print(f"Using existing config file {config_file}")
- self.parser.read(self.config_file)
-
- self.project_dir = None
- if self.get_value("app", "project_dir"):
- self.project_dir = Path(self.get_value("app", "project_dir")).absolute()
-
- self.qml_files = []
- config_qml_files = self.get_value("qt", "qml_files")
- if config_qml_files and self.project_dir:
- self.qml_files = [Path(self.project_dir) / file for file in config_qml_files.split(",")]
-
- def update_config(self):
- logging.info("[DEPLOY] Creating {config_file}")
- with open(self.config_file, "w+") as config_file:
- self.parser.write(config_file, space_around_delimiters=True)
-
- def set_value(self, section: str, key: str, new_value: str):
- try:
- current_value = self.get_value(section, key)
- if current_value != new_value:
- self.parser.set(section, key, new_value)
- except configparser.NoOptionError:
- logging.warning(f"[DEPLOY]: key {key} does not exist")
- except configparser.NoSectionError:
- logging.warning(f"[DEPLOY]: section {section} does not exist")
-
- def get_value(self, section: str, key: str):
- try:
- return self.parser.get(section, key)
- except configparser.NoOptionError:
- logging.warning(f"[DEPLOY]: key {key} does not exist")
- except configparser.NoSectionError:
- logging.warning(f"[DEPLOY]: section {section} does not exist")
-
- 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
- Fetch and return from config_file if 'config_property_key' is unknown, but
- config_file exists
- Otherwise, raise an exception
- """
- if config_property_val:
- self.set_value(config_property_group, config_property_key, str(config_property_val))
- return config_property_val
- elif self.get_value(config_property_group, config_property_key):
- return self.get_value(config_property_group, config_property_key)
- else:
- logging.exception(
- f"[DEPLOY]: No {config_property_key} specified in config file or as cli option"
- )
- raise
-
- @property
- def qml_files(self):
- return self._qml_files
-
- @qml_files.setter
- def qml_files(self, qml_files):
- self._qml_files = qml_files
-
- @property
- def project_dir(self):
- return self._project_dir
-
- @project_dir.setter
- def project_dir(self, project_dir):
- self._project_dir = project_dir
-
- 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"""
-
- if self.project_dir:
- qml_files_str = self.get_value("qt", "qml_files")
- self.qml_files = []
- for file in qml_files_str.split(","):
- if file:
- self.qml_files.append(Path(self.project_dir) / file)
- else:
- qml_files_temp = None
- source_file = (
- Path(self.get_value("app", "input_file"))
- if self.get_value("app", "input_file")
- else None
- )
- python_exe = (
- Path(self.get_value("python", "python_path"))
- if self.get_value("python", "python_path")
- else None
- )
- if source_file and python_exe:
- if not self.qml_files:
- qml_files_temp = list(source_file.parent.glob("**/*.qml"))
-
- # add all QML files, excluding the ones shipped with installed PySide6
- # The QML files shipped with PySide6 gets added if venv is used,
- # because of recursive glob
- if python_exe.parent.parent == source_file.parent:
- # python venv path is inside the main source dir
- qml_files_temp = list(
- set(qml_files_temp) - set(python_exe.parent.parent.rglob("*.qml"))
- )
-
- if len(qml_files_temp) > 500:
- if "site-packages" in str(qml_files_temp[-1]):
- logging.warning(
- "You seem to include a lot of QML files from a \
- local virtual env. Are they intended?"
- )
- else:
- logging.warning(
- "You seem to include a lot of QML files. \
- Are they intended?"
- )
-
- if qml_files_temp:
- extra_qml_files = [Path(file) for file in qml_files_temp]
- self.qml_files.extend(extra_qml_files)
- self.set_value(
- "qt", "qml_files", ",".join([str(file) for file in self.qml_files])
- )
- logging.info("[DEPLOY] QML files identified and set in config_file")
-
- def find_and_set_project_dir(self):
- source_file = (
- Path(self.get_value("app", "input_file"))
- if self.get_value("app", "input_file")
- else None
- )
-
- if self.qml_files:
- paths = self.qml_files.copy()
- paths.append(source_file.absolute())
- self.project_dir = Path(os.path.commonpath(paths=paths))
-
- # update all qml paths
- logging.info("[DEPLOY] Update QML files paths to relative paths")
- qml_relative_paths = ",".join(
- [str(qml_file.relative_to(self.project_dir)) for qml_file in self.qml_files]
- )
- self.set_value("qt", "qml_files", qml_relative_paths)
- else:
- self.project_dir = source_file.parent
-
- # update input_file path
- logging.info("[DEPLOY] Update input_file path")
- self.set_value("app", "input_file", str(source_file.relative_to(self.project_dir)))
-
- logging.info("[DEPLOY] Update project_dir path")
- if self.project_dir != Path.cwd():
- self.set_value("app", "project_dir", str(self.project_dir))
- else:
- self.set_value("app", "project_dir", str(self.project_dir.relative_to(Path.cwd())))
-
-
-class PythonExecutable:
- """
- Wrapper class around Python executable
- """
-
- def __init__(self, python_path=None, create_venv=False, dry_run=False):
- self.exe = python_path if python_path else Path(sys.executable)
- self.dry_run = dry_run
- if create_venv:
- self.__create_venv()
- self.nuitka = Nuitka(nuitka=[self.exe, "-m", "nuitka"])
-
- @property
- def exe(self):
- return Path(self._exe)
-
- @exe.setter
- def exe(self, exe):
- self._exe = exe
-
- @staticmethod
- def is_venv():
- venv = os.environ.get("VIRTUAL_ENV")
- return True if venv else False
-
- def __create_venv(self):
- self.install("virtualenv")
- if not self.is_venv():
- run_command(
- command=[self.exe, "-m", "venv", Path.cwd() / "deployment" / "venv"],
- dry_run=self.dry_run,
- )
- venv_path = Path(os.environ["VIRTUAL_ENV"])
- if sys.platform == "win32":
- self.exe = venv_path / "Scripts" / "python.exe"
- elif sys.platform in ["linux", "darwin"]:
- self.exe = venv_path / "bin" / "python"
- else:
- logging.info("[DEPLOY]: You are already in virtual environment!")
-
- def install(self, packages: list = None):
- if packages:
- for package in packages:
- if not self.is_installed(package=package):
- logging.info(f"[DEPLOY]: Installing package: {package}")
- run_command(
- command=[self.exe, "-m", "pip", "install", package],
- dry_run=self.dry_run,
- )
- else:
- logging.info(f"[DEPLOY]: Upgrading package: {package}")
- run_command(
- command=[self.exe, "-m", "pip", "install", "--upgrade", package],
- dry_run=self.dry_run,
- )
-
- def is_installed(self, package):
- return bool(util.find_spec(package))
-
- def create_executable(self, source_file: Path, extra_args: str, config: Config):
- if config.qml_files:
- logging.info(f"[DEPLOY]: Included QML files: {config.qml_files}")
-
- self.nuitka.create_executable(
- source_file=source_file,
- extra_args=extra_args,
- qml_files=config.qml_files,
- dry_run=self.dry_run,
- )
-
-
def config_option_exists():
return True if any(item in sys.argv for item in ["--config-file", "-c"]) else False
diff --git a/sources/pyside-tools/deploy/__init__.py b/sources/pyside-tools/deploy/__init__.py
new file mode 100644
index 000000000..b94bc665b
--- /dev/null
+++ b/sources/pyside-tools/deploy/__init__.py
@@ -0,0 +1,7 @@
+# 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
+
+from .commands import run_command
+from .nuitka_helper import Nuitka
+from .config import Config
+from .python_helper import PythonExecutable
diff --git a/sources/pyside-tools/deploy/commands.py b/sources/pyside-tools/deploy/commands.py
new file mode 100644
index 000000000..92745367f
--- /dev/null
+++ b/sources/pyside-tools/deploy/commands.py
@@ -0,0 +1,31 @@
+# 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
+
+import subprocess
+import sys
+import logging
+
+"""
+All utility functions for deployment
+"""
+
+
+def run_command(command, dry_run: bool):
+ command_str = " ".join([str(cmd) for cmd in command])
+ try:
+ if not dry_run:
+ subprocess.check_call(command, shell=(sys.platform == "win32"))
+ else:
+ print(command_str + "\n")
+ except FileNotFoundError as error:
+ logging.exception(f"[DEPLOY]: {error.filename} not found")
+ raise
+ except subprocess.CalledProcessError as error:
+ logging.exception(
+ f"[DEPLOY]: Command {command_str} failed with error {error} and return_code"
+ f"{error.returncode}"
+ )
+ raise
+ except Exception as error:
+ logging.exception(f"[DEPLOY]: Command {command_str} failed with error {error}")
+ raise
diff --git a/sources/pyside-tools/deploy/config.py b/sources/pyside-tools/deploy/config.py
new file mode 100644
index 000000000..3e1c72abe
--- /dev/null
+++ b/sources/pyside-tools/deploy/config.py
@@ -0,0 +1,177 @@
+# 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
+
+from pathlib import Path
+import configparser
+from configparser import ConfigParser
+import shutil
+import logging
+import os
+
+
+class Config:
+ """
+ Wrapper class around config file, whose options are used to control the executable creation
+ """
+
+ def __init__(self, config_file: Path) -> None:
+ self.config_file = config_file
+ self.parser = ConfigParser(comment_prefixes="/", allow_no_value=True)
+ if not self.config_file.exists():
+ logging.info(f"[DEPLOY]: Creating config file {self.config_file}")
+ shutil.copy(Path(__file__).parent / "default.spec", self.config_file)
+ else:
+ print(f"Using existing config file {config_file}")
+ self.parser.read(self.config_file)
+
+ self.project_dir = None
+ if self.get_value("app", "project_dir"):
+ self.project_dir = Path(self.get_value("app", "project_dir")).absolute()
+
+ self.qml_files = []
+ config_qml_files = self.get_value("qt", "qml_files")
+ if config_qml_files and self.project_dir:
+ self.qml_files = [Path(self.project_dir) / file for file in config_qml_files.split(",")]
+
+ def update_config(self):
+ logging.info("[DEPLOY] Creating {config_file}")
+ with open(self.config_file, "w+") as config_file:
+ self.parser.write(config_file, space_around_delimiters=True)
+
+ def set_value(self, section: str, key: str, new_value: str):
+ try:
+ current_value = self.get_value(section, key)
+ if current_value != new_value:
+ self.parser.set(section, key, new_value)
+ except configparser.NoOptionError:
+ logging.warning(f"[DEPLOY]: key {key} does not exist")
+ except configparser.NoSectionError:
+ logging.warning(f"[DEPLOY]: section {section} does not exist")
+
+ def get_value(self, section: str, key: str):
+ try:
+ return self.parser.get(section, key)
+ except configparser.NoOptionError:
+ logging.warning(f"[DEPLOY]: key {key} does not exist")
+ except configparser.NoSectionError:
+ logging.warning(f"[DEPLOY]: section {section} does not exist")
+
+ 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
+ Fetch and return from config_file if 'config_property_key' is unknown, but
+ config_file exists
+ Otherwise, raise an exception
+ """
+ if config_property_val:
+ self.set_value(config_property_group, config_property_key, str(config_property_val))
+ return config_property_val
+ elif self.get_value(config_property_group, config_property_key):
+ return self.get_value(config_property_group, config_property_key)
+ else:
+ logging.exception(
+ f"[DEPLOY]: No {config_property_key} specified in config file or as cli option"
+ )
+ raise
+
+ @property
+ def qml_files(self):
+ return self._qml_files
+
+ @qml_files.setter
+ def qml_files(self, qml_files):
+ self._qml_files = qml_files
+
+ @property
+ def project_dir(self):
+ return self._project_dir
+
+ @project_dir.setter
+ def project_dir(self, project_dir):
+ self._project_dir = project_dir
+
+ 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"""
+
+ if self.project_dir:
+ qml_files_str = self.get_value("qt", "qml_files")
+ self.qml_files = []
+ for file in qml_files_str.split(","):
+ if file:
+ self.qml_files.append(Path(self.project_dir) / file)
+ else:
+ qml_files_temp = None
+ source_file = (
+ Path(self.get_value("app", "input_file"))
+ if self.get_value("app", "input_file")
+ else None
+ )
+ python_exe = (
+ Path(self.get_value("python", "python_path"))
+ if self.get_value("python", "python_path")
+ else None
+ )
+ if source_file and python_exe:
+ if not self.qml_files:
+ qml_files_temp = list(source_file.parent.glob("**/*.qml"))
+
+ # add all QML files, excluding the ones shipped with installed PySide6
+ # The QML files shipped with PySide6 gets added if venv is used,
+ # because of recursive glob
+ if python_exe.parent.parent == source_file.parent:
+ # python venv path is inside the main source dir
+ qml_files_temp = list(
+ set(qml_files_temp) - set(python_exe.parent.parent.rglob("*.qml"))
+ )
+
+ if len(qml_files_temp) > 500:
+ if "site-packages" in str(qml_files_temp[-1]):
+ logging.warning(
+ "You seem to include a lot of QML files from a \
+ local virtual env. Are they intended?"
+ )
+ else:
+ logging.warning(
+ "You seem to include a lot of QML files. \
+ Are they intended?"
+ )
+
+ if qml_files_temp:
+ extra_qml_files = [Path(file) for file in qml_files_temp]
+ self.qml_files.extend(extra_qml_files)
+ self.set_value(
+ "qt", "qml_files", ",".join([str(file) for file in self.qml_files])
+ )
+ logging.info("[DEPLOY] QML files identified and set in config_file")
+
+ def find_and_set_project_dir(self):
+ source_file = (
+ Path(self.get_value("app", "input_file"))
+ if self.get_value("app", "input_file")
+ else None
+ )
+
+ if self.qml_files and source_file:
+ paths = self.qml_files.copy()
+ paths.append(source_file.absolute())
+ self.project_dir = Path(os.path.commonpath(paths=paths))
+
+ # update all qml paths
+ logging.info("[DEPLOY] Update QML files paths to relative paths")
+ qml_relative_paths = ",".join(
+ [str(qml_file.relative_to(self.project_dir)) for qml_file in self.qml_files]
+ )
+ self.set_value("qt", "qml_files", qml_relative_paths)
+ else:
+ self.project_dir = source_file.parent
+
+ # update input_file path
+ logging.info("[DEPLOY] Update input_file path")
+ self.set_value("app", "input_file", str(source_file.relative_to(self.project_dir)))
+
+ logging.info("[DEPLOY] Update project_dir path")
+ if self.project_dir != Path.cwd():
+ self.set_value("app", "project_dir", str(self.project_dir))
+ else:
+ self.set_value("app", "project_dir", str(self.project_dir.relative_to(Path.cwd())))
diff --git a/sources/pyside-tools/deploy/nuitka_helper.py b/sources/pyside-tools/deploy/nuitka_helper.py
new file mode 100644
index 000000000..a6b3f79d6
--- /dev/null
+++ b/sources/pyside-tools/deploy/nuitka_helper.py
@@ -0,0 +1,48 @@
+# 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
+
+import sys
+from pathlib import Path
+from .utils import run_command
+
+
+class Nuitka:
+ """
+ Wrapper class around the nuitka executable, enabling its usage through python code
+ """
+
+ def __init__(self, nuitka):
+ self.nuitka = nuitka
+
+ def create_executable(
+ self, source_file: Path, extra_args: str, qml_files: list[Path], dry_run: bool
+ ):
+ extra_args = extra_args.split()
+ qml_args = []
+ if qml_files:
+ # this includes "all" the plugins
+ # FIXME: adding the "qml" plugin is equivalent to "all" because of dependencies
+ # Ideally it should only add the specific qml plugins. eg: quick window, quick controls
+ qml_args.append("--include-qt-plugins=all")
+ qml_args.extend(
+ [f"--include-data-files={qml_file}=./{qml_file.name}" for qml_file in qml_files]
+ )
+
+ output_dir = source_file.parent / "deployment"
+ if not dry_run:
+ output_dir.mkdir(parents=True, exist_ok=True)
+ print("[DEPLOY] Running Nuitka")
+ command = self.nuitka + [
+ source_file,
+ "--follow-imports",
+ "--onefile",
+ "--enable-plugin=pyside6",
+ f"--output-dir={output_dir}",
+ ]
+ command.extend(extra_args + qml_args)
+
+ if sys.platform == "linux":
+ linux_icon = str(Path(__file__).parent / "pyside_icon.jpg")
+ command.append(f"--linux-onefile-icon={linux_icon}")
+
+ run_command(command=command, dry_run=dry_run)
diff --git a/sources/pyside-tools/deploy/python_helper.py b/sources/pyside-tools/deploy/python_helper.py
new file mode 100644
index 000000000..3831b7dde
--- /dev/null
+++ b/sources/pyside-tools/deploy/python_helper.py
@@ -0,0 +1,82 @@
+# 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
+
+import sys
+import os
+import logging
+from importlib import util
+from pathlib import Path
+
+from . import Nuitka, run_command, Config
+
+
+class PythonExecutable:
+ """
+ Wrapper class around Python executable
+ """
+
+ def __init__(self, python_path=None, create_venv=False, dry_run=False):
+ self.exe = python_path if python_path else Path(sys.executable)
+ self.dry_run = dry_run
+ if create_venv:
+ self.__create_venv()
+ self.nuitka = Nuitka(nuitka=[self.exe, "-m", "nuitka"])
+
+ @property
+ def exe(self):
+ return Path(self._exe)
+
+ @exe.setter
+ def exe(self, exe):
+ self._exe = exe
+
+ @staticmethod
+ def is_venv():
+ venv = os.environ.get("VIRTUAL_ENV")
+ return True if venv else False
+
+ def __create_venv(self):
+ self.install("virtualenv")
+ if not self.is_venv():
+ run_command(
+ command=[self.exe, "-m", "venv", Path.cwd() / "deployment" / "venv"],
+ dry_run=self.dry_run,
+ )
+ venv_path = Path(os.environ["VIRTUAL_ENV"])
+ if sys.platform == "win32":
+ self.exe = venv_path / "Scripts" / "python.exe"
+ elif sys.platform in ["linux", "darwin"]:
+ self.exe = venv_path / "bin" / "python"
+ else:
+ logging.info("[DEPLOY]: You are already in virtual environment!")
+
+ def install(self, packages: list = None):
+ if packages:
+ for package in packages:
+ if not self.is_installed(package=package):
+ logging.info(f"[DEPLOY]: Installing package: {package}")
+ run_command(
+ command=[self.exe, "-m", "pip", "install", package],
+ dry_run=self.dry_run,
+ )
+ else:
+ logging.info(f"[DEPLOY]: Upgrading package: {package}")
+ run_command(
+ command=[self.exe, "-m", "pip", "install", "--upgrade", package],
+ dry_run=self.dry_run,
+ )
+
+ def is_installed(self, package):
+ return bool(util.find_spec(package))
+
+ def create_executable(self, source_file: Path, extra_args: str, config: Config):
+ if config.qml_files:
+ logging.info(f"[DEPLOY]: Included QML files: {config.qml_files}")
+
+ self.nuitka.create_executable(
+ source_file=source_file,
+ extra_args=extra_args,
+ qml_files=config.qml_files,
+ dry_run=self.dry_run,
+ )
+