aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside-tools
diff options
context:
space:
mode:
authorShyamnath Premnadh <Shyamnath.Premnadh@qt.io>2023-10-24 16:10:56 +0200
committerShyamnath Premnadh <Shyamnath.Premnadh@qt.io>2024-02-29 10:32:54 +0100
commitec6a0f8baef5c6d4e80e650e11a498756e6055e6 (patch)
treed4f0c4ba59ae239a8841ac68683da58e5e0dd440 /sources/pyside-tools
parent489899819f4b417f8bc8923b1eb03728bed4bb5e (diff)
Deployment: Refactoring
- Functions in buildozer.py for finding the local_libs, plugin and Qt module dependencies of the application are related to the overall config of the application and not buildozer. Hence, these functions are moved to android_config.py. - `ALL_PYSIDE_MODULES` moved to a function under deploy_lib/__init__.py and `platform_map` moved to deploy_lib/android/__init__.py. - Enable the user to pass both arm64-v8a and aarch64 as the architecture type. Same for all the other architecures that are synonymous. - `verify_and_set_recipe_dir()` is now called explicitly from android_deploy.py due to `cleanup()` deleting the recipe directories during config initialization. - New property `dependency_files` for AndroidConfig class. - Fix --dry-run for Android Deployment. - Adapt tests. Pick-to: 6.6 Task-number: PYSIDE-1612 Change-Id: Icdf14001ae2b07dc8614af3f458f9cad11eafdac Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io> Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
Diffstat (limited to 'sources/pyside-tools')
-rw-r--r--sources/pyside-tools/android_deploy.py1
-rw-r--r--sources/pyside-tools/deploy_lib/__init__.py13
-rw-r--r--sources/pyside-tools/deploy_lib/android/__init__.py15
-rw-r--r--sources/pyside-tools/deploy_lib/android/android_config.py234
-rw-r--r--sources/pyside-tools/deploy_lib/android/android_helper.py9
-rw-r--r--sources/pyside-tools/deploy_lib/android/buildozer.py228
-rw-r--r--sources/pyside-tools/deploy_lib/config.py19
7 files changed, 271 insertions, 248 deletions
diff --git a/sources/pyside-tools/android_deploy.py b/sources/pyside-tools/android_deploy.py
index 8ef59781f..83350bc5f 100644
--- a/sources/pyside-tools/android_deploy.py
+++ b/sources/pyside-tools/android_deploy.py
@@ -130,6 +130,7 @@ def main(name: str = None, pyside_wheel: Path = None, shiboken_wheel: Path = Non
# this cannot be done when config file is initialized because cleanup() removes it
# so this can only be done after the cleanup()
config.find_and_set_jars_dir()
+ config.verify_and_set_recipe_dir()
# TODO: include qml files from pysidedeploy.spec rather than from extensions
# buildozer currently includes all the files with .qml extension
diff --git a/sources/pyside-tools/deploy_lib/__init__.py b/sources/pyside-tools/deploy_lib/__init__.py
index 8c5d8b4ef..40a7535db 100644
--- a/sources/pyside-tools/deploy_lib/__init__.py
+++ b/sources/pyside-tools/deploy_lib/__init__.py
@@ -17,7 +17,18 @@ else:
DEFAULT_APP_ICON = str((Path(__file__).parent / f"pyside_icon{IMAGE_FORMAT}").resolve())
-from .commands import run_command
+
+def get_all_pyside_modules():
+ """
+ Returns all the modules installed with PySide6
+ """
+ # They all start with `Qt` as the prefix. Removing this prefix and getting the actual
+ # module name
+ import PySide6
+ return [module[2:] for module in PySide6.__all__]
+
+
+from .commands import run_command, run_qmlimportscanner
from .nuitka_helper import Nuitka
from .python_helper import PythonExecutable, find_pyside_modules
from .config import BaseConfig, Config
diff --git a/sources/pyside-tools/deploy_lib/android/__init__.py b/sources/pyside-tools/deploy_lib/android/__init__.py
index 59cd510c5..c3027762c 100644
--- a/sources/pyside-tools/deploy_lib/android/__init__.py
+++ b/sources/pyside-tools/deploy_lib/android/__init__.py
@@ -1,7 +1,16 @@
# 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 .android_helper import (create_recipe, extract_and_copy_jar,
- get_wheel_android_arch, AndroidData, get_llvm_readobj,
- find_lib_dependencies, find_qtlibs_in_wheel)
+# maps instruction set to Android platform names
+platform_map = {"aarch64": "arm64-v8a",
+ "armv7a": "armeabi-v7a",
+ "i686": "x86",
+ "x86_64": "x86_64",
+ "arm64-v8a": "arm64-v8a",
+ "armeabi-v7a": "armeabi-v7a",
+ "x86": "x86"}
+
+from .android_helper import (create_recipe, extract_and_copy_jar, get_wheel_android_arch,
+ AndroidData, get_llvm_readobj, find_lib_dependencies,
+ find_qtlibs_in_wheel)
from .android_config import AndroidConfig
diff --git a/sources/pyside-tools/deploy_lib/android/android_config.py b/sources/pyside-tools/deploy_lib/android/android_config.py
index 442672a23..1ea99411f 100644
--- a/sources/pyside-tools/deploy_lib/android/android_config.py
+++ b/sources/pyside-tools/deploy_lib/android/android_config.py
@@ -1,12 +1,19 @@
# 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 re
+import tempfile
import logging
+import zipfile
+import xml.etree.ElementTree as ET
from typing import List
from pathlib import Path
+from pkginfo import Wheel
-from . import extract_and_copy_jar, get_wheel_android_arch
-from .. import Config, find_pyside_modules
+from . import (extract_and_copy_jar, get_wheel_android_arch, find_lib_dependencies,
+ get_llvm_readobj, find_qtlibs_in_wheel, platform_map, create_recipe)
+from .. import (Config, find_pyside_modules, run_qmlimportscanner, get_all_pyside_modules,
+ MAJOR_VERSION)
ANDROID_NDK_VERSION = "25c"
ANDROID_DEPLOY_CACHE = Path.home() / ".pyside6_android_deploy"
@@ -86,28 +93,52 @@ class AndroidConfig(Config):
if jars_dir_temp and Path(jars_dir_temp).resolve().exists():
self.jars_dir = Path(jars_dir_temp).resolve()
+ self._arch = None
+ if self.get_value("buildozer", "arch"):
+ self.arch = self.get_value("buildozer", "arch")
+ else:
+ self._find_and_set_arch()
+
+ # maps to correct platform name incase the instruction set was specified
+ self._arch = platform_map[self.arch]
+
+ self._mode = self.get_value("buildozer", "mode")
+
+ self.qt_libs_path: zipfile.Path = find_qtlibs_in_wheel(wheel_pyside=self.wheel_pyside)
+ logging.info(f"[DEPLOY] Qt libs path inside wheel: {str(self.qt_libs_path)}")
+
self._modules = []
if self.get_value("buildozer", "modules"):
self.modules = self.get_value("buildozer", "modules").split(",")
else:
self._find_and_set_pysidemodules()
self._find_and_set_qtquick_modules()
+ self.modules += self._find_dependent_qt_modules()
+ # remove duplicates
+ self.modules = list(set(self.modules))
- self._arch = None
- if self.get_value("buildozer", "arch"):
- self.arch = self.get_value("buildozer", "arch")
- else:
- self._find_and_set_arch()
+ # gets the xml dependency files from Qt installation path
+ self._dependency_files = []
+ self._find_and_set_dependency_files()
+
+ self._qt_plugins = []
+ if self.get_value("android", "plugins"):
+ self._qt_plugins = self.get_value("android", "plugins").split(",")
self._local_libs = []
if self.get_value("buildozer", "local_libs"):
self.local_libs = self.get_value("buildozer", "local_libs").split(",")
- self._qt_plugins = []
- if self.get_value("android", "plugins"):
- self._qt_plugins = self.get_value("android", "plugins").split(",")
+ dependent_plugins = []
+ # the local_libs can also store dependent plugins
+ local_libs, dependent_plugins = self._find_local_libs()
+ self._find_plugin_dependencies(dependent_plugins)
+ self.qt_plugins += dependent_plugins
+ self.local_libs += local_libs
- self._mode = self.get_value("buildozer", "mode")
+ recipe_dir_temp = self.get_value("buildozer", "recipe_dir")
+ if recipe_dir_temp:
+ self.recipe_dir = Path(recipe_dir_temp)
@property
def qt_plugins(self):
@@ -218,6 +249,14 @@ class AndroidConfig(Config):
if self._wheel_shiboken:
self.set_value("android", "wheel_shiboken", str(self._wheel_shiboken))
+ @property
+ def dependency_files(self):
+ return self._dependency_files
+
+ @dependency_files.setter
+ def dependency_files(self, dependency_files):
+ self._dependency_files = dependency_files
+
def _find_and_set_pysidemodules(self):
self.modules = find_pyside_modules(project_dir=self.project_dir,
extra_ignore_dirs=self.extra_ignore_dirs,
@@ -246,6 +285,9 @@ class AndroidConfig(Config):
"""Identify if QtQuick is used in QML files and add them as dependency
"""
extra_modules = []
+ if not self.qml_modules:
+ self.qml_modules = set(run_qmlimportscanner(qml_files=self.qml_files,
+ dry_run=self.dry_run))
if "QtQuick" in self.qml_modules:
extra_modules.append("Quick")
@@ -254,3 +296,173 @@ class AndroidConfig(Config):
extra_modules.append("QuickControls2")
self.modules += extra_modules
+
+ def _find_dependent_qt_modules(self):
+ """
+ Given pysidedeploy_config.modules, find all the other dependent Qt modules. This is
+ done by using llvm-readobj (readelf) to find the dependent libraries from the module
+ library.
+ """
+ dependent_modules = set()
+ all_dependencies = set()
+ lib_pattern = re.compile(f"libQt6(?P<mod_name>.*)_{self.arch}")
+
+ llvm_readobj = get_llvm_readobj(self.ndk_path)
+ if not llvm_readobj.exists():
+ raise FileNotFoundError(f"[DEPLOY] {llvm_readobj} does not exist."
+ "Finding Qt dependencies failed")
+
+ archive = zipfile.ZipFile(self.wheel_pyside)
+ lib_path_suffix = Path(str(self.qt_libs_path)).relative_to(self.wheel_pyside)
+
+ with tempfile.TemporaryDirectory() as tmpdir:
+ archive.extractall(tmpdir)
+ qt_libs_tmpdir = Path(tmpdir) / lib_path_suffix
+ # find the lib folder where Qt libraries are stored
+ for module_name in sorted(self.modules):
+ qt_module_path = qt_libs_tmpdir / f"libQt6{module_name}_{self.arch}.so"
+ if not qt_module_path.exists():
+ raise FileNotFoundError(f"[DEPLOY] libQt6{module_name}_{self.arch}.so not found"
+ " inside the wheel")
+ find_lib_dependencies(llvm_readobj=llvm_readobj, lib_path=qt_module_path,
+ dry_run=self.dry_run,
+ used_dependencies=all_dependencies)
+
+ for dependency in all_dependencies:
+ match = lib_pattern.search(dependency)
+ if match:
+ module = match.group("mod_name")
+ if module not in self.modules:
+ dependent_modules.add(module)
+
+ # check if the PySide6 binary for the Qt module actually exists
+ # eg: libQt6QmlModels.so exists and it includes QML types. Hence, it makes no
+ dependent_modules = [module for module in dependent_modules if module in
+ get_all_pyside_modules()]
+ dependent_modules_str = ",".join(dependent_modules)
+ logging.info("[DEPLOY] The following extra dependencies were found:"
+ f" {dependent_modules_str}")
+
+ return dependent_modules
+
+ def _find_and_set_dependency_files(self) -> List[zipfile.Path]:
+ """
+ Based on `modules`, returns the Qt6{module}_{arch}-android-dependencies.xml file, which
+ contains the various dependencies of the module, like permissions, plugins etc
+ """
+ needed_dependency_files = [(f"Qt{MAJOR_VERSION}{module}_{self.arch}"
+ "-android-dependencies.xml") for module in self.modules]
+
+ for dependency_file_name in needed_dependency_files:
+ dependency_file = self.qt_libs_path / dependency_file_name
+ if dependency_file.exists():
+ self._dependency_files.append(dependency_file)
+
+ logging.info("[DEPLOY] The following dependency files were found: "
+ f"{*self._dependency_files,}")
+
+ def _find_local_libs(self):
+ local_libs = set()
+ plugins = set()
+ lib_pattern = re.compile(f"lib(?P<lib_name>.*)_{self.arch}")
+ for dependency_file in self._dependency_files:
+ xml_content = dependency_file.read_text()
+ root = ET.fromstring(xml_content)
+ for local_lib in root.iter("lib"):
+
+ if 'file' not in local_lib.attrib:
+ if 'name' not in local_lib.attrib:
+ logging.warning("[DEPLOY] Invalid android dependency file"
+ f" {str(dependency_file)}")
+ continue
+
+ file = local_lib.attrib['file']
+ if file.endswith(".so"):
+ # file_name starts with lib and ends with the platform name
+ # eg: lib<lib_name>_x86_64.so
+ file_name = Path(file).stem
+
+ if file_name.startswith("libplugins_platforms_qtforandroid"):
+ # the platform library is a requisite and is already added from the
+ # configuration file
+ continue
+
+ # we only need lib_name, because lib and arch gets re-added by
+ # python-for-android
+ match = lib_pattern.search(file_name)
+ if match:
+ lib_name = match.group("lib_name")
+ local_libs.add(lib_name)
+ if lib_name.startswith("plugins"):
+ plugin_name = lib_name.split('plugins_', 1)[1]
+ plugins.add(plugin_name)
+
+ return list(local_libs), list(plugins)
+
+ def _find_plugin_dependencies(self, dependent_plugins: List[str]):
+ # The `bundled` element in the dependency xml files points to the folder where
+ # additional dependencies for the application exists. Inspecting the depenency files
+ # in android, this always points to the specific Qt plugin dependency folder.
+ # eg: for application using Qt Multimedia, this looks like:
+ # <bundled file="./plugins/multimedia" />
+ # The code recusively checks all these dependent folders and adds the necessary plugins
+ # as dependencies
+ lib_pattern = re.compile(f"libplugins_(?P<plugin_name>.*)_{self.arch}.so")
+ for dependency_file in self._dependency_files:
+ xml_content = dependency_file.read_text()
+ root = ET.fromstring(xml_content)
+ for bundled_element in root.iter("bundled"):
+ # the attribute 'file' can be misleading, but it always points to the plugin
+ # folder on inspecting the dependency files
+ if 'file' not in bundled_element.attrib:
+ logging.warning("[DEPLOY] Invalid Android dependency file"
+ f" {str(dependency_file)}")
+ continue
+
+ # from "./plugins/multimedia" to absolute path in wheel
+ plugin_module_folder = bundled_element.attrib['file']
+ # they all should start with `./plugins`
+ if plugin_module_folder.startswith("./plugins"):
+ plugin_module_folder = plugin_module_folder.partition("./plugins/")[2]
+ else:
+ continue
+
+ absolute_plugin_module_folder = (self.qt_libs_path.parent / "plugins"
+ / plugin_module_folder)
+
+ if not absolute_plugin_module_folder.is_dir():
+ logging.warning(f"[DEPLOY] Qt plugin folder '{plugin_module_folder}' does not"
+ " exist or is not a directory for this Android platform")
+ continue
+
+ for plugin in absolute_plugin_module_folder.iterdir():
+ plugin_name = plugin.name
+ if plugin_name.endswith(".so") and plugin_name.startswith("libplugins"):
+ # we only need part of plugin_name, because `lib` prefix and `arch` suffix
+ # gets re-added by python-for-android
+ match = lib_pattern.search(plugin_name)
+ if match:
+ plugin_infix_name = match.group("plugin_name")
+ if plugin_infix_name not in dependent_plugins:
+ dependent_plugins.append(plugin_infix_name)
+
+ def verify_and_set_recipe_dir(self):
+ # create recipes
+ # https://python-for-android.readthedocs.io/en/latest/recipes/
+ # These recipes are manually added through buildozer.spec file to be used by
+ # python_for_android while building the distribution
+
+ if not self.recipes_exist() and not self.dry_run:
+ logging.info("[DEPLOY] Creating p4a recipes for PySide6 and shiboken6")
+ version = Wheel(self.wheel_pyside).version
+ create_recipe(version=version, component=f"PySide{MAJOR_VERSION}",
+ wheel_path=self.wheel_pyside,
+ generated_files_path=self.generated_files_path,
+ qt_modules=self.modules,
+ local_libs=self.local_libs,
+ plugins=self.qt_plugins)
+ create_recipe(version=version, component=f"shiboken{MAJOR_VERSION}",
+ wheel_path=self.wheel_shiboken,
+ generated_files_path=self.generated_files_path)
+ self.recipe_dir = ((self.generated_files_path
+ / "recipes").resolve())
diff --git a/sources/pyside-tools/deploy_lib/android/android_helper.py b/sources/pyside-tools/deploy_lib/android/android_helper.py
index 230343647..7d2f5d575 100644
--- a/sources/pyside-tools/deploy_lib/android/android_helper.py
+++ b/sources/pyside-tools/deploy_lib/android/android_helper.py
@@ -102,8 +102,15 @@ def find_lib_dependencies(llvm_readobj: Path, lib_path: Path, used_dependencies:
if lib_path.name in used_dependencies:
return
+ used_dependencies.add(lib_path.name)
+
command = [str(llvm_readobj), "--needed-libs", str(lib_path)]
- _, output = run_command(command=command, dry_run=dry_run, fetch_output=True)
+
+ # even if dry_run is given, we need to run the actual command to see all the dependencies
+ # for which llvm-readelf is run.
+ if dry_run:
+ _, output = run_command(command=command, dry_run=dry_run, fetch_output=True)
+ _, output = run_command(command=command, dry_run=False, fetch_output=True)
dependencies = set()
neededlibraries_found = False
diff --git a/sources/pyside-tools/deploy_lib/android/buildozer.py b/sources/pyside-tools/deploy_lib/android/buildozer.py
index 3c188b1c0..828982b5b 100644
--- a/sources/pyside-tools/deploy_lib/android/buildozer.py
+++ b/sources/pyside-tools/deploy_lib/android/buildozer.py
@@ -3,27 +3,17 @@
import sys
import logging
-import re
-import tempfile
import xml.etree.ElementTree as ET
import zipfile
-import PySide6
from pathlib import Path
from typing import List
-from pkginfo import Wheel
-
-from .. import MAJOR_VERSION, BaseConfig, Config, run_command
-from . import (create_recipe, find_lib_dependencies, find_qtlibs_in_wheel,
- get_llvm_readobj)
-
-# They all start with `Qt` as the prefix. Removing this prefix and getting the actual
-# module name
-ALL_PYSIDE_MODULES = [module[2:] for module in PySide6.__all__]
+from . import AndroidConfig
+from .. import BaseConfig, run_command
class BuildozerConfig(BaseConfig):
- def __init__(self, buildozer_spec_file: Path, pysidedeploy_config: Config):
+ def __init__(self, buildozer_spec_file: Path, pysidedeploy_config: AndroidConfig):
super().__init__(buildozer_spec_file, comment_prefixes="#")
self.set_value("app", "title", pysidedeploy_config.title)
self.set_value("app", "package.name", pysidedeploy_config.title)
@@ -43,70 +33,26 @@ class BuildozerConfig(BaseConfig):
if pysidedeploy_config.sdk_path:
self.set_value("app", "android.sdk_path", str(pysidedeploy_config.sdk_path))
- platform_map = {"aarch64": "arm64-v8a",
- "armv7a": "armeabi-v7a",
- "i686": "x86",
- "x86_64": "x86_64"}
- self.arch = platform_map[pysidedeploy_config.arch]
- self.set_value("app", "android.archs", self.arch)
+ self.set_value("app", "android.archs", pysidedeploy_config.arch)
# p4a changes
self.set_value("app", "p4a.bootstrap", "qt")
-
- self.qt_libs_path: zipfile.Path = (
- find_qtlibs_in_wheel(wheel_pyside=pysidedeploy_config.wheel_pyside))
- logging.info(f"[DEPLOY] Found Qt libs path inside wheel: {str(self.qt_libs_path)}")
-
- extra_modules = self.__find_dependent_qt_modules(pysidedeploy_config)
- logging.info(f"[DEPLOY] Other dependent modules to be added: {extra_modules}")
- pysidedeploy_config.modules = pysidedeploy_config.modules + extra_modules
- modules = ",".join(pysidedeploy_config.modules)
-
- # gets the xml dependency files from Qt installation path
- dependency_files = self.__get_dependency_files(modules=pysidedeploy_config.modules,
- arch=self.arch)
-
- dependent_plugins = []
- # the local_libs can also store dependent plugins
- local_libs, dependent_plugins = self.__find_local_libs(dependency_files)
- pysidedeploy_config.local_libs += local_libs
-
- self.__find_plugin_dependencies(dependency_files, dependent_plugins)
- pysidedeploy_config.qt_plugins += dependent_plugins
-
- local_libs = ",".join(pysidedeploy_config.local_libs)
-
- # create recipes
- # https://python-for-android.readthedocs.io/en/latest/recipes/
- # These recipes are manually added through buildozer.spec file to be used by
- # python_for_android while building the distribution
- if not pysidedeploy_config.recipes_exist() and not pysidedeploy_config.dry_run:
- logging.info("[DEPLOY] Creating p4a recipes for PySide6 and shiboken6")
- version = Wheel(pysidedeploy_config.wheel_pyside).version
- create_recipe(version=version, component=f"PySide{MAJOR_VERSION}",
- wheel_path=pysidedeploy_config.wheel_pyside,
- generated_files_path=pysidedeploy_config.generated_files_path,
- qt_modules=pysidedeploy_config.modules,
- local_libs=pysidedeploy_config.local_libs,
- plugins=pysidedeploy_config.qt_plugins)
- create_recipe(version=version, component=f"shiboken{MAJOR_VERSION}",
- wheel_path=pysidedeploy_config.wheel_shiboken,
- generated_files_path=pysidedeploy_config.generated_files_path)
- pysidedeploy_config.recipe_dir = ((pysidedeploy_config.generated_files_path
- / "recipes").resolve())
self.set_value('app', "p4a.local_recipes", str(pysidedeploy_config.recipe_dir))
# add permissions
- permissions = self.__find_permissions(dependency_files)
+ permissions = self.__find_permissions(pysidedeploy_config.dependency_files)
permissions = ", ".join(permissions)
self.set_value("app", "android.permissions", permissions)
# add jars and initClasses for the jars
- jars, init_classes = self.__find_jars(dependency_files, pysidedeploy_config.jars_dir)
+ jars, init_classes = self.__find_jars(pysidedeploy_config.dependency_files,
+ pysidedeploy_config.jars_dir)
self.set_value("app", "android.add_jars", ",".join(jars))
- init_classes = ",".join(init_classes)
# extra arguments specific to Qt
+ modules = ",".join(pysidedeploy_config.modules)
+ local_libs = ",".join(pysidedeploy_config.local_libs)
+ init_classes = ",".join(init_classes)
extra_args = (f"--qt-libs={modules} --load-local-libs={local_libs}"
f" --init-classes={init_classes}")
self.set_value("app", "p4a.extra_args", extra_args)
@@ -123,25 +69,6 @@ class BuildozerConfig(BaseConfig):
self.update_config()
- def __get_dependency_files(self, modules: List[str], arch: str) -> List[zipfile.Path]:
- """
- Based on pysidedeploy_config.modules, returns the
- Qt6{module}_{arch}-android-dependencies.xml file, which contains the various
- dependencies of the module, like permissions, plugins etc
- """
- dependency_files = []
- needed_dependency_files = [(f"Qt{MAJOR_VERSION}{module}_{arch}"
- "-android-dependencies.xml") for module in modules]
-
- for dependency_file_name in needed_dependency_files:
- dependency_file = self.qt_libs_path / dependency_file_name
- if dependency_file.exists():
- dependency_files.append(dependency_file)
-
- logging.info(f"[DEPLOY] The following dependency files were found: {*dependency_files,}")
-
- return dependency_files
-
def __find_permissions(self, dependency_files: List[zipfile.Path]):
permissions = set()
for dependency_file in dependency_files:
@@ -186,145 +113,12 @@ class BuildozerConfig(BaseConfig):
return jars, init_classes
- def __find_local_libs(self, dependency_files: List[zipfile.Path]):
- local_libs = set()
- plugins = set()
- lib_pattern = re.compile(f"lib(?P<lib_name>.*)_{self.arch}")
- for dependency_file in dependency_files:
- xml_content = dependency_file.read_text()
- root = ET.fromstring(xml_content)
- for local_lib in root.iter("lib"):
-
- if 'file' not in local_lib.attrib:
- if 'name' not in local_lib.attrib:
- logging.warning("[DEPLOY] Invalid android dependency file"
- f" {str(dependency_file)}")
- continue
-
- file = local_lib.attrib['file']
- if file.endswith(".so"):
- # file_name starts with lib and ends with the platform name
- # eg: lib<lib_name>_x86_64.so
- file_name = Path(file).stem
-
- if file_name.startswith("libplugins_platforms_qtforandroid"):
- # the platform library is a requisite and is already added from the
- # configuration file
- continue
-
- # we only need lib_name, because lib and arch gets re-added by
- # python-for-android
- match = lib_pattern.search(file_name)
- if match:
- lib_name = match.group("lib_name")
- local_libs.add(lib_name)
- if lib_name.startswith("plugins"):
- plugin_name = lib_name.split('plugins_', 1)[1]
- plugins.add(plugin_name)
-
- return list(local_libs), list(plugins)
-
- def __find_plugin_dependencies(self, dependency_files: List[zipfile.Path],
- dependent_plugins: List[str]):
- # The `bundled` element in the dependency xml files points to the folder where
- # additional dependencies for the application exists. Inspecting the depenency files
- # in android, this always points to the specific Qt plugin dependency folder.
- # eg: for application using Qt Multimedia, this looks like:
- # <bundled file="./plugins/multimedia" />
- # The code recusively checks all these dependent folders and adds the necessary plugins
- # as dependencies
- lib_pattern = re.compile(f"libplugins_(?P<plugin_name>.*)_{self.arch}.so")
- for dependency_file in dependency_files:
- xml_content = dependency_file.read_text()
- root = ET.fromstring(xml_content)
- for bundled_element in root.iter("bundled"):
- # the attribute 'file' can be misleading, but it always points to the plugin
- # folder on inspecting the dependency files
- if 'file' not in bundled_element.attrib:
- logging.warning("[DEPLOY] Invalid Android dependency file"
- f" {str(dependency_file)}")
- continue
-
- # from "./plugins/multimedia" to absolute path in wheel
- plugin_module_folder = bundled_element.attrib['file']
- # they all should start with `./plugins`
- if plugin_module_folder.startswith("./plugins"):
- plugin_module_folder = plugin_module_folder.partition("./plugins/")[2]
- else:
- continue
-
- absolute_plugin_module_folder = (self.qt_libs_path.parent / "plugins"
- / plugin_module_folder)
-
- if not absolute_plugin_module_folder.is_dir():
- logging.warning(f"[DEPLOY] Qt plugin folder '{plugin_module_folder}' does not"
- " exist or is not a directory for this Android platform")
- continue
-
- for plugin in absolute_plugin_module_folder.iterdir():
- plugin_name = plugin.name
- if plugin_name.endswith(".so") and plugin_name.startswith("libplugins"):
- # we only need part of plugin_name, because `lib` prefix and `arch` suffix
- # gets re-added by python-for-android
- match = lib_pattern.search(plugin_name)
- if match:
- plugin_infix_name = match.group("plugin_name")
- if plugin_infix_name not in dependent_plugins:
- dependent_plugins.append(plugin_infix_name)
-
- def __find_dependent_qt_modules(self, pysidedeploy_config: Config):
- """
- Given pysidedeploy_config.modules, find all the other dependent Qt modules. This is
- done by using llvm-readobj (readelf) to find the dependent libraries from the module
- library.
- """
- dependent_modules = set()
- all_dependencies = set()
- lib_pattern = re.compile(f"libQt6(?P<mod_name>.*)_{self.arch}")
-
- llvm_readobj = get_llvm_readobj(pysidedeploy_config.ndk_path)
- if not llvm_readobj.exists():
- raise FileNotFoundError(f"[DEPLOY] {llvm_readobj} does not exist."
- "Finding Qt dependencies failed")
-
- archive = zipfile.ZipFile(pysidedeploy_config.wheel_pyside)
- lib_path_suffix = Path(str(self.qt_libs_path)).relative_to(pysidedeploy_config.wheel_pyside)
-
- with tempfile.TemporaryDirectory() as tmpdir:
- archive.extractall(tmpdir)
- qt_libs_tmpdir = Path(tmpdir) / lib_path_suffix
- # find the lib folder where Qt libraries are stored
- for module_name in pysidedeploy_config.modules:
- qt_module_path = qt_libs_tmpdir / f"libQt6{module_name}_{self.arch}.so"
- if not qt_module_path.exists():
- raise FileNotFoundError(f"[DEPLOY] libQt6{module_name}_{self.arch}.so not found"
- " inside the wheel")
- find_lib_dependencies(llvm_readobj=llvm_readobj, lib_path=qt_module_path,
- dry_run=pysidedeploy_config.dry_run,
- used_dependencies=all_dependencies)
-
- for dependency in all_dependencies:
- match = lib_pattern.search(dependency)
- if match:
- module = match.group("mod_name")
- if module not in pysidedeploy_config.modules:
- dependent_modules.add(module)
-
- # check if the PySide6 binary for the Qt module actually exists
- # eg: libQt6QmlModels.so exists and it includes QML types. Hence, it makes no
- dependent_modules = [module for module in dependent_modules if module in ALL_PYSIDE_MODULES]
- dependent_modules_str = ",".join(dependent_modules)
- logging.info("[DEPLOY] The following extra dependencies were found:"
- f" {dependent_modules_str}")
-
- return list(dependent_modules)
-
class Buildozer:
dry_run = False
@staticmethod
- def initialize(pysidedeploy_config: Config):
+ def initialize(pysidedeploy_config: AndroidConfig):
project_dir = Path(pysidedeploy_config.project_dir)
buildozer_spec = project_dir / "buildozer.spec"
if buildozer_spec.exists():
diff --git a/sources/pyside-tools/deploy_lib/config.py b/sources/pyside-tools/deploy_lib/config.py
index 5dbaa68eb..61b2ebec1 100644
--- a/sources/pyside-tools/deploy_lib/config.py
+++ b/sources/pyside-tools/deploy_lib/config.py
@@ -8,7 +8,6 @@ from configparser import ConfigParser
from pathlib import Path
from project import ProjectData
-
from .commands import run_qmlimportscanner
from . import DEFAULT_APP_ICON
@@ -233,27 +232,17 @@ class Config(BaseConfig):
self.qml_files = qml_files
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 self.source_file and self.python_path:
if not self.qml_files:
- qml_files_temp = list(source_file.parent.glob("**/*.qml"))
+ qml_files_temp = list(self.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:
+ if self.python_path.parent.parent == self.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"))
+ set(qml_files_temp) - set(self.python_path.parent.parent.rglob("*.qml"))
)
if len(qml_files_temp) > 500: