aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside-tools/deploy_lib/android/buildozer.py
diff options
context:
space:
mode:
authorShyamnath Premnadh <Shyamnath.Premnadh@qt.io>2023-05-17 10:04:18 +0200
committerShyamnath Premnadh <Shyamnath.Premnadh@qt.io>2023-09-08 09:05:06 +0200
commitf14077be7a60751599e786fe647d823dc3f91ee3 (patch)
tree2bca5135d68c2ecbc393a3fa615d28aed8daa550 /sources/pyside-tools/deploy_lib/android/buildozer.py
parent9b3d266aab68e28cd230b1587f233d74af84e629 (diff)
Android Deployment: find PySide and Qt dependencies
- Use llvm-readelf to recursively find the dependencies of a dependent Qt binary. All the Qt dependencies are loaded at startup when loading the Android application. - Parse the revelant Python files of the project into ast, and find the used Python modules. Once the Python file is parsed into an ast, we find the imports of the following form: from PySide6 import Qt<module> from PySide6.Qt<module> import <classname> This is then used to identify the module used, and we try to load the binaries of this module. If the modules does not exist in Qt for Android, then an error is thrown. - The easiest way to find the relevant Python files in the project is using a .pyproject file which lists all the relevant files. If this is not there, then we find all the Python files in the project folder excluding the following folders: [".hg", ".svn", ".git", ".tox", "__pycache__", "env", "venv", "deployment",".buildozer"] - A new cli argument --extra-ignore-dirs, that lists the extra directories to ignore when searching for all the relevant python files in the project. - A new cli argument --extra-modules, that lists the extra modules to be added manually to the application incase they are not found by `pyside6-android-deploy` automatically. Adding a module using this argument means that the module binary is loaded by the Android application on startup. - sdk and ndk cli options are now mandatory to find the dependencies. These two options will be removed later when pyside6-android-deploy can automatically download them. Task-number: PYSIDE-1612 Change-Id: Ifbdc20cbc70ab0935a23157ccc8cb7fde6992df2 Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
Diffstat (limited to 'sources/pyside-tools/deploy_lib/android/buildozer.py')
-rw-r--r--sources/pyside-tools/deploy_lib/android/buildozer.py84
1 files changed, 64 insertions, 20 deletions
diff --git a/sources/pyside-tools/deploy_lib/android/buildozer.py b/sources/pyside-tools/deploy_lib/android/buildozer.py
index 45c62075c..2eeb2d261 100644
--- a/sources/pyside-tools/deploy_lib/android/buildozer.py
+++ b/sources/pyside-tools/deploy_lib/android/buildozer.py
@@ -3,12 +3,15 @@
import re
import logging
+import tempfile
import xml.etree.ElementTree as ET
+
import zipfile
from pathlib import Path
from typing import List
-from .. import MAJOR_VERSION, BaseConfig, Config, run_command
+from .. import run_command, BaseConfig, Config, MAJOR_VERSION
+from . import get_llvm_readobj, find_lib_dependencies, find_qtlibs_in_wheel
class BuildozerConfig(BaseConfig):
@@ -47,10 +50,24 @@ class BuildozerConfig(BaseConfig):
self.set_value('app', "p4a.local_recipes", str(pysidedeploy_config.recipe_dir))
self.set_value("app", "p4a.bootstrap", "qt")
- # gets the xml dependency files from Qt installation path
- dependency_files = self.__get_dependency_files(pysidedeploy_config)
+ 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
+
+ # update the config file with the extra modules
+ if extra_modules:
+ pysidedeploy_config.update_config()
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)
+
local_libs = self.__find_local_libs(dependency_files)
pysidedeploy_config.local_libs += local_libs
@@ -80,31 +97,18 @@ class BuildozerConfig(BaseConfig):
self.update_config()
- def __get_dependency_files(self, pysidedeploy_config: Config) -> List[zipfile.Path]:
+ 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}_{self.arch}"
- "-android-dependencies.xml") for module in
- pysidedeploy_config.modules]
- archive = zipfile.ZipFile(pysidedeploy_config.wheel_pyside)
-
- # find parent path to dependency files in the wheel
- dependency_parent_path = None
- for file in archive.namelist():
- if file.endswith("android-dependencies.xml"):
- dependency_parent_path = Path(file).parent
- # all dependency files are in the same path
- break
+ 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 = dependency_parent_path / dependency_file_name
- # convert from pathlib.Path to zipfile.Path
- dependency_file = zipfile.Path(archive, at=str(dependency_file))
-
+ dependency_file = self.qt_libs_path / dependency_file_name
if dependency_file.exists():
dependency_files.append(dependency_file)
@@ -180,6 +184,46 @@ class BuildozerConfig(BaseConfig):
return list(local_libs)
+ 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)
+
+ return list(dependent_modules)
+
class Buildozer:
dry_run = False