diff options
Diffstat (limited to 'sources/pyside-tools/deploy_lib/android/buildozer.py')
-rw-r--r-- | sources/pyside-tools/deploy_lib/android/buildozer.py | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/sources/pyside-tools/deploy_lib/android/buildozer.py b/sources/pyside-tools/deploy_lib/android/buildozer.py new file mode 100644 index 000000000..828982b5b --- /dev/null +++ b/sources/pyside-tools/deploy_lib/android/buildozer.py @@ -0,0 +1,140 @@ +# 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 sys +import logging +import xml.etree.ElementTree as ET +import zipfile +from pathlib import Path +from typing import List + +from . import AndroidConfig +from .. import BaseConfig, run_command + + +class BuildozerConfig(BaseConfig): + 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) + self.set_value("app", "package.domain", + f"org.{pysidedeploy_config.title}") + + include_exts = self.get_value("app", "source.include_exts") + include_exts = f"{include_exts},qml,js" + self.set_value("app", "source.include_exts", include_exts, raise_warning=False) + + self.set_value("app", "requirements", "python3,shiboken6,PySide6") + + # android platform specific + if pysidedeploy_config.ndk_path: + self.set_value("app", "android.ndk_path", str(pysidedeploy_config.ndk_path)) + + if pysidedeploy_config.sdk_path: + self.set_value("app", "android.sdk_path", str(pysidedeploy_config.sdk_path)) + + self.set_value("app", "android.archs", pysidedeploy_config.arch) + + # p4a changes + self.set_value("app", "p4a.bootstrap", "qt") + self.set_value('app', "p4a.local_recipes", str(pysidedeploy_config.recipe_dir)) + + # add permissions + 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(pysidedeploy_config.dependency_files, + pysidedeploy_config.jars_dir) + self.set_value("app", "android.add_jars", ",".join(jars)) + + # 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) + + # TODO: does not work atm. Seems like a bug with buildozer + # change buildozer build_dir + # self.set_value("buildozer", "build_dir", str(build_dir.relative_to(Path.cwd()))) + + # change final apk/aab path + self.set_value("buildozer", "bin_dir", str(pysidedeploy_config.exe_dir.resolve())) + + # set application icon + self.set_value("app", "icon.filename", pysidedeploy_config.icon) + + self.update_config() + + def __find_permissions(self, dependency_files: List[zipfile.Path]): + permissions = set() + for dependency_file in dependency_files: + xml_content = dependency_file.read_text() + root = ET.fromstring(xml_content) + for permission in root.iter("permission"): + permissions.add(permission.attrib['name']) + return permissions + + def __find_jars(self, dependency_files: List[zipfile.Path], jars_dir: Path): + jars, init_classes = set(), set() + for dependency_file in dependency_files: + xml_content = dependency_file.read_text() + root = ET.fromstring(xml_content) + for jar in root.iter("jar"): + jar_file = jar.attrib['file'] + if jar_file.startswith("jar/"): + jar_file_name = jar_file[4:] + if (jars_dir / jar_file_name).exists(): + jars.add(str(jars_dir / jar_file_name)) + else: + logging.warning(f"[DEPLOY] Unable to include {jar_file}. " + f"{jar_file} does not exist in {jars_dir}") + continue + else: + logging.warning(f"[DEPLOY] Unable to include {jar_file}. " + "All jar file paths should begin with 'jar/'") + continue + + jar_init_class = jar.attrib.get('initClass') + if jar_init_class: + init_classes.add(jar_init_class) + + # add the jar with all the activity and service java files + # this is created from Qt for Python instead of Qt + # The initClasses for this are already taken care of by python-for-android + android_bindings_jar = jars_dir / "Qt6AndroidBindings.jar" + if android_bindings_jar.exists(): + jars.add(str(android_bindings_jar)) + else: + raise FileNotFoundError(f"{android_bindings_jar} not found in wheel") + + return jars, init_classes + + +class Buildozer: + dry_run = False + + @staticmethod + def initialize(pysidedeploy_config: AndroidConfig): + project_dir = Path(pysidedeploy_config.project_dir) + buildozer_spec = project_dir / "buildozer.spec" + if buildozer_spec.exists(): + logging.warning(f"[DEPLOY] buildozer.spec already present in {str(project_dir)}." + "Using it") + return + + # creates buildozer.spec config file + command = [sys.executable, "-m", "buildozer", "init"] + run_command(command=command, dry_run=Buildozer.dry_run) + if not Buildozer.dry_run: + if not buildozer_spec.exists(): + raise RuntimeError(f"buildozer.spec not found in {Path.cwd()}") + BuildozerConfig(buildozer_spec, pysidedeploy_config) + + @staticmethod + def create_executable(mode: str): + command = [sys.executable, "-m", "buildozer", "android", mode] + run_command(command=command, dry_run=Buildozer.dry_run) |