diff options
Diffstat (limited to 'sources/pyside-tools/deploy_lib/android/buildozer.py')
-rw-r--r-- | sources/pyside-tools/deploy_lib/android/buildozer.py | 228 |
1 files changed, 11 insertions, 217 deletions
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(): |