python_helper.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. # Copyright (C) 2022 The Qt Company Ltd.
  2. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
  3. from __future__ import annotations
  4. import logging
  5. import os
  6. import sys
  7. from importlib import util
  8. from importlib.metadata import version
  9. from pathlib import Path
  10. from . import Config, run_command
  11. class PythonExecutable:
  12. """
  13. Wrapper class around Python executable
  14. """
  15. def __init__(self, python_path: Path = None, dry_run: bool = False, init: bool = False,
  16. force: bool = False):
  17. self.dry_run = dry_run
  18. self.init = init
  19. if not python_path:
  20. response = "yes"
  21. # checking if inside virtual environment
  22. if not self.is_venv() and not force and not self.dry_run and not self.init:
  23. response = input(("You are not using a virtual environment. pyside6-deploy needs "
  24. "to install a few Python packages for deployment to work "
  25. "seamlessly. \n Proceed? [Y/n]"))
  26. if response.lower() in ["no", "n"]:
  27. print("[DEPLOY] Exiting ...")
  28. sys.exit(0)
  29. self.exe = Path(sys.executable)
  30. else:
  31. self.exe = python_path
  32. logging.info(f"[DEPLOY] Using Python at {str(self.exe)}")
  33. @property
  34. def exe(self):
  35. return Path(self._exe)
  36. @exe.setter
  37. def exe(self, exe):
  38. self._exe = exe
  39. @staticmethod
  40. def is_venv():
  41. venv = os.environ.get("VIRTUAL_ENV")
  42. return True if venv else False
  43. def is_pyenv_python(self):
  44. pyenv_root = os.environ.get("PYENV_ROOT")
  45. if pyenv_root:
  46. resolved_exe = self.exe.resolve()
  47. if str(resolved_exe).startswith(pyenv_root):
  48. return True
  49. return False
  50. def install(self, packages: list = None):
  51. _, installed_packages = run_command(command=[str(self.exe), "-m", "pip", "freeze"],
  52. dry_run=False, fetch_output=True)
  53. installed_packages = [p.decode().split('==')[0] for p in installed_packages.split()]
  54. for package in packages:
  55. package_info = package.split('==')
  56. package_components_len = len(package_info)
  57. package_name, package_version = None, None
  58. if package_components_len == 1:
  59. package_name = package_info[0]
  60. elif package_components_len == 2:
  61. package_name = package_info[0]
  62. package_version = package_info[1]
  63. else:
  64. raise ValueError(f"{package} should be of the format 'package_name'=='version'")
  65. if (package_name not in installed_packages) and (not self.is_installed(package_name)):
  66. logging.info(f"[DEPLOY] Installing package: {package}")
  67. run_command(
  68. command=[self.exe, "-m", "pip", "install", package],
  69. dry_run=self.dry_run,
  70. )
  71. elif package_version:
  72. installed_version = version(package_name)
  73. if package_version != installed_version:
  74. logging.info(f"[DEPLOY] Installing package: {package_name}"
  75. f"version: {package_version}")
  76. run_command(
  77. command=[self.exe, "-m", "pip", "install", "--force", package],
  78. dry_run=self.dry_run,
  79. )
  80. else:
  81. logging.info(f"[DEPLOY] package: {package_name}=={package_version}"
  82. " already installed")
  83. else:
  84. logging.info(f"[DEPLOY] package: {package_name} already installed")
  85. def is_installed(self, package):
  86. return bool(util.find_spec(package))
  87. def install_dependencies(self, config: Config, packages: str, is_android: bool = False):
  88. """
  89. Installs the python package dependencies for the target deployment platform
  90. """
  91. packages = config.get_value("python", packages).split(",")
  92. if not self.init:
  93. # install packages needed for deployment
  94. logging.info("[DEPLOY] Installing dependencies")
  95. self.install(packages=packages)
  96. # nuitka requires patchelf to make patchelf rpath changes for some Qt files
  97. if sys.platform.startswith("linux") and not is_android:
  98. self.install(packages=["patchelf"])
  99. elif is_android:
  100. # install only buildozer
  101. logging.info("[DEPLOY] Installing buildozer")
  102. buildozer_package_with_version = ([package for package in packages
  103. if package.startswith("buildozer")])
  104. self.install(packages=list(buildozer_package_with_version))