aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside-tools/deploy_lib/android
diff options
context:
space:
mode:
authorShyamnath Premnadh <Shyamnath.Premnadh@qt.io>2023-02-08 16:58:39 +0100
committerShyamnath Premnadh <shyamnath.premnadh@qt.io>2023-03-31 13:29:32 +0200
commit95abfa776411b6d7cd4296adf63bc7abce2270b6 (patch)
tree74e51c8c6d28044a60011158215919ab223d6fa5 /sources/pyside-tools/deploy_lib/android
parent94b30c7207bea2467f0d7e41e99af27ecc749ed5 (diff)
Deployment: New pyside6-android-deploy tool
- Preliminary support for PySide6 Android deployment - Uses jinja2 to create PySide6 and shiboken6 recipes, to be used by buildozer when python_for_android builds the app distribution - Classes for Buildozer config interaction - Run deployment to android. Typical command looks like: """ pyside6-android-deploy --wheel-pyside=./PySide6-6.5.0a1-6.5.0-cp37-abi3-android_x86_64.whl --wheel-shiboken=./shiboken6-6.5.0a1-6.5.0-cp37-abi3-android_x86_64.whl --name=stringlistmodel """ - New entrypoint for pyside6-android-deploy - Helper functinos for Android Deployment - Remove unused function main_py_exists() - Added the new files to deploy.pyproject - Remove dry_run argument from install_python_dependencies() - new Python packages added in requirements.txt to enable the deploy and cross compile tool Note: python-for-android uses my local fork. This will be changed once it is merged into python-for-android dev. Task-number: PYSIDE-1612 Pick-to: 6.5 Change-Id: I7eb96fa5507a476b4e86ec0195a5e9869f0f85fd Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Diffstat (limited to 'sources/pyside-tools/deploy_lib/android')
-rw-r--r--sources/pyside-tools/deploy_lib/android/__init__.py10
-rw-r--r--sources/pyside-tools/deploy_lib/android/android_helper.py59
-rw-r--r--sources/pyside-tools/deploy_lib/android/buildozer.py84
-rw-r--r--sources/pyside-tools/deploy_lib/android/recipes/PySide6/__init__.tmpl.py43
-rw-r--r--sources/pyside-tools/deploy_lib/android/recipes/shiboken6/__init__.tmpl.py24
5 files changed, 220 insertions, 0 deletions
diff --git a/sources/pyside-tools/deploy_lib/android/__init__.py b/sources/pyside-tools/deploy_lib/android/__init__.py
new file mode 100644
index 000000000..12eb6830c
--- /dev/null
+++ b/sources/pyside-tools/deploy_lib/android/__init__.py
@@ -0,0 +1,10 @@
+# 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
+
+WIDGET_APPLICATION_MODULES = ["Core", "Gui", "Widgets"]
+QUICK_APPLICATION_MODULES = ["Core", "Gui", "Widgets", "Network", "OpenGL", "Qml", "Quick",
+ "QuickControls2"]
+
+from .android_helper import (create_recipe, extract_and_copy_jar, get_wheel_android_arch,
+ AndroidData)
+from .buildozer import Buildozer
diff --git a/sources/pyside-tools/deploy_lib/android/android_helper.py b/sources/pyside-tools/deploy_lib/android/android_helper.py
new file mode 100644
index 000000000..b0c8254ab
--- /dev/null
+++ b/sources/pyside-tools/deploy_lib/android/android_helper.py
@@ -0,0 +1,59 @@
+# 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 logging
+from pathlib import Path
+from jinja2 import Environment, FileSystemLoader
+from zipfile import ZipFile
+from dataclasses import dataclass
+
+
+@dataclass
+class AndroidData:
+ wheel_pyside: Path
+ wheel_shiboken: Path
+ ndk_path: Path
+ sdk_path: Path
+
+
+def create_recipe(version: str, component: str, wheel_path: str, generated_files_path: Path):
+ '''
+ Create python_for_android recipe for PySide6 and shiboken6
+ '''
+ rcp_tmpl_path = Path(__file__).parent / "recipes" / f"{component}"
+ environment = Environment(loader=FileSystemLoader(rcp_tmpl_path))
+ template = environment.get_template("__init__.tmpl.py")
+ content = template.render(
+ version=version,
+ wheel_path=wheel_path,
+ )
+
+ recipe_path = generated_files_path / "recipes" / f"{component}"
+ recipe_path.mkdir(parents=True, exist_ok=True)
+ logging.info(f"Writing {component} recipe into {recipe_path}")
+ with open(recipe_path / "__init__.py", mode="w", encoding="utf-8") as recipe:
+ recipe.write(content)
+
+
+def extract_and_copy_jar(wheel_path: Path, generated_files_path: Path) -> str:
+ '''
+ extracts the PySide6 wheel and copies the 'jar' folder to 'generated_files_path'.
+ These .jar files are added to the buildozer.spec file to be later use by buildozer
+ '''
+ jar_path = generated_files_path / "jar"
+ jar_path.mkdir(parents=True, exist_ok=True)
+ archive = ZipFile(wheel_path)
+ jar_files = [file for file in archive.namelist() if file.startswith("PySide6/jar")]
+ for file in jar_files:
+ archive.extract(file, jar_path)
+ return jar_path
+
+
+def get_wheel_android_arch(wheel: str):
+ wheel = Path(wheel)
+ supported_archs = ["aarch64", "armv7a", "i686", "x86_64"]
+ for arch in supported_archs:
+ if arch in wheel.stem:
+ return arch
+
+ return None
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..91d55f4ca
--- /dev/null
+++ b/sources/pyside-tools/deploy_lib/android/buildozer.py
@@ -0,0 +1,84 @@
+# 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 logging
+from pathlib import Path
+from .. import run_command, BaseConfig, Config
+
+
+class BuildozerConfig(BaseConfig):
+ def __init__(self, buildozer_spec_file: Path, pysidedeploy_config: Config, dry_run: bool):
+ super().__init__(buildozer_spec_file, dry_run, 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"
+ self.set_value("app", "source.include_exts", include_exts)
+
+ self.set_value("app", "requirements", "python3,shiboken6,PySide6")
+
+ 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.add_jars", f"{str(pysidedeploy_config.jars_dir)}/*.jar")
+
+ platform_map = {"aarch64": "arm64-v8a",
+ "armv7a": "armeabi-v7a",
+ "i686": "x86",
+ "x86_64": "x86_64"}
+ arch = platform_map[pysidedeploy_config.arch]
+ self.set_value("app", "android.archs", arch)
+
+ # p4a changes
+ logging.info("[DEPLOY] Using custom fork of python-for-android: "
+ "https://github.com/shyamnathp/python-for-android/tree/pyside_support")
+ self.set_value("app", "p4a.fork", "shyamnathp")
+ self.set_value("app", "p4a.branch", "pyside_support")
+ self.set_value('app', "p4a.local_recipes", str(pysidedeploy_config.recipe_dir))
+ self.set_value("app", "p4a.bootstrap", "qt")
+
+ modules = ",".join(pysidedeploy_config.modules)
+ local_libs = ",".join(pysidedeploy_config.local_libs)
+ extra_args = (f"--qt-libs={modules} --load-local-libs={local_libs}")
+ 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()))
+
+ self.update_config()
+
+
+class Buildozer:
+ dry_run = False
+
+ @staticmethod
+ def initialize(pysidedeploy_config: Config):
+ 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 = ["buildozer", "init"]
+ run_command(command=command, dry_run=Buildozer.dry_run)
+ if not Buildozer.dry_run and not buildozer_spec.exists():
+ raise RuntimeError(f"buildozer.spec not found in {Path.cwd()}")
+ BuildozerConfig(buildozer_spec, pysidedeploy_config, Buildozer.dry_run)
+
+ @staticmethod
+ def create_executable(mode: str):
+ # build the application in release mode
+ command = ["buildozer", "android", mode]
+ run_command(command=command, dry_run=Buildozer.dry_run)
diff --git a/sources/pyside-tools/deploy_lib/android/recipes/PySide6/__init__.tmpl.py b/sources/pyside-tools/deploy_lib/android/recipes/PySide6/__init__.tmpl.py
new file mode 100644
index 000000000..289f2d62b
--- /dev/null
+++ b/sources/pyside-tools/deploy_lib/android/recipes/PySide6/__init__.tmpl.py
@@ -0,0 +1,43 @@
+# 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 pythonforandroid.recipe import PythonRecipe
+from pythonforandroid.logger import info
+import zipfile
+import shutil
+from pathlib import Path
+
+
+class PySideRecipe(PythonRecipe):
+ version = '{{ version }}'
+ wheel_path = '{{ wheel_path }}'
+ depends = ["shiboken6"]
+ call_hostpython_via_targetpython = False
+ install_in_hostpython = False
+
+ def build_arch(self, arch):
+ """Unzip the wheel and copy into site-packages of target"""
+
+ info("Installing {} into site-packages".format(self.name))
+ with zipfile.ZipFile(self.wheel_path, "r") as zip_ref:
+ info("Unzip wheels and copy into {}".format(self.ctx.get_python_install_dir(arch.arch)))
+ zip_ref.extractall(self.ctx.get_python_install_dir(arch.arch))
+
+ lib_dir = Path(f"{self.ctx.get_python_install_dir(arch.arch)}/PySide6/Qt/lib")
+ info("Copying Qt libraries to be loaded on startup")
+ shutil.copytree(lib_dir, self.ctx.get_libs_dir(arch.arch), dirs_exist_ok=True)
+
+ info("Copying libc++_shared.so from SDK to be loaded on startup")
+ libcpp_path = f"{self.ctx.ndk.sysroot_lib_dir}/{arch.command_prefix}/libc++_shared.so"
+ shutil.copyfile(libcpp_path, Path(self.ctx.get_libs_dir(arch.arch)) / "libc++_shared.so")
+
+ info("Copying Qt platform plugin to be loaded on startup from SDK to be loaded on startup")
+ shutil.copyfile(
+ Path(self.ctx.get_python_install_dir(arch.arch))
+ / "PySide6" / "Qt" / "plugins" / "platforms"
+ / f"libplugins_platforms_qtforandroid_{arch.arch}.so",
+ Path(self.ctx.get_libs_dir(arch.arch)) / f"libplugins_platforms_qtforandroid_{arch.arch}.so",
+ )
+
+
+recipe = PySideRecipe()
diff --git a/sources/pyside-tools/deploy_lib/android/recipes/shiboken6/__init__.tmpl.py b/sources/pyside-tools/deploy_lib/android/recipes/shiboken6/__init__.tmpl.py
new file mode 100644
index 000000000..afe094cbd
--- /dev/null
+++ b/sources/pyside-tools/deploy_lib/android/recipes/shiboken6/__init__.tmpl.py
@@ -0,0 +1,24 @@
+# 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 pythonforandroid.recipe import PythonRecipe
+from pythonforandroid.logger import info
+import zipfile
+
+
+class ShibokenRecipe(PythonRecipe):
+ version = '{{ version }}'
+ wheel_path = '{{ wheel_path }}'
+
+ call_hostpython_via_targetpython = False
+ install_in_hostpython = False
+
+ def build_arch(self, arch):
+ ''' Unzip the wheel and copy into site-packages of target'''
+ info('Installing {} into site-packages'.format(self.name))
+ with zipfile.ZipFile(self.wheel_path, 'r') as zip_ref:
+ info('Unzip wheels and copy into {}'.format(self.ctx.get_python_install_dir(arch.arch)))
+ zip_ref.extractall(self.ctx.get_python_install_dir(arch.arch))
+
+
+recipe = ShibokenRecipe()