From ec6a0f8baef5c6d4e80e650e11a498756e6055e6 Mon Sep 17 00:00:00 2001 From: Shyamnath Premnadh Date: Tue, 24 Oct 2023 16:10:56 +0200 Subject: 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 Reviewed-by: Cristian Maureira-Fredes --- sources/pyside-tools/android_deploy.py | 1 + sources/pyside-tools/deploy_lib/__init__.py | 13 +- .../pyside-tools/deploy_lib/android/__init__.py | 15 +- .../deploy_lib/android/android_config.py | 234 ++++++++++++++++++++- .../deploy_lib/android/android_helper.py | 9 +- .../pyside-tools/deploy_lib/android/buildozer.py | 228 +------------------- sources/pyside-tools/deploy_lib/config.py | 19 +- 7 files changed, 271 insertions(+), 248 deletions(-) (limited to 'sources/pyside-tools') 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.*)_{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.*)_{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_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: + # + # The code recusively checks all these dependent folders and adds the necessary plugins + # as dependencies + lib_pattern = re.compile(f"libplugins_(?P.*)_{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.*)_{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_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: - # - # The code recusively checks all these dependent folders and adds the necessary plugins - # as dependencies - lib_pattern = re.compile(f"libplugins_(?P.*)_{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.*)_{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: -- cgit v1.2.3