# 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 import zipfile from zipfile import ZipFile from dataclasses import dataclass from typing import Set, List from pathlib import Path from jinja2 import Environment, FileSystemLoader from .. import run_command @dataclass class AndroidData: """ Dataclass to store all the Android data obtained through cli """ 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, qt_modules: List[str] = None, local_libs: List[str] = None, plugins: List[str] = None): ''' Create python_for_android recipe for PySide6 and shiboken6 ''' qt_plugins = [] if plugins: #split plugins based on category for plugin in plugins: plugin_category, plugin_name = plugin.split('_', 1) qt_plugins.append((plugin_category, plugin_name)) qt_local_libs = [] if local_libs: qt_local_libs = [local_lib for local_lib in local_libs if local_lib.startswith("Qt6") ] 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, qt_modules=qt_modules, qt_local_libs=qt_local_libs, qt_plugins=qt_plugins ) recipe_path = generated_files_path / "recipes" / f"{component}" recipe_path.mkdir(parents=True, exist_ok=True) logging.info(f"[DEPLOY] Writing {component} recipe into {str(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 used later 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: Path): ''' Get android architecture from wheel ''' supported_archs = ["aarch64", "armv7a", "i686", "x86_64"] for arch in supported_archs: if arch in wheel.stem: return arch return None def get_llvm_readobj(ndk_path: Path) -> Path: ''' Return the path to llvm_readobj from the Android Ndk ''' if not ndk_path: # fetch ndk path from buildozer raise FileNotFoundError("[DEPLOY] Unable to find Ndk path. Please pass the Ndk path either" " from the CLI or from pysidedeploy.spec") # TODO: Requires change if Windows platform supports Android Deployment or if we # support host other than linux-x86_64 return (ndk_path / "toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-readobj") def find_lib_dependencies(llvm_readobj: Path, lib_path: Path, used_dependencies: Set[str] = None, dry_run: bool = False): """ Find all the Qt dependencies of a library using llvm_readobj """ if lib_path.name in used_dependencies: return command = [str(llvm_readobj), "--needed-libs", str(lib_path)] _, output = run_command(command=command, dry_run=dry_run, fetch_output=True) dependencies = set() neededlibraries_found = False for line in output.splitlines(): line = line.decode("utf-8").lstrip() if line.startswith("NeededLibraries") and not neededlibraries_found: neededlibraries_found = True if neededlibraries_found and line.startswith("libQt"): dependencies.add(line) used_dependencies.add(line) dependent_lib_path = lib_path.parent / line find_lib_dependencies(llvm_readobj, dependent_lib_path, used_dependencies, dry_run) if dependencies: logging.info(f"[DEPLOY] Following dependencies found for {lib_path.stem}: {dependencies}") else: logging.info(f"[DEPLOY] No Qt dependencies found for {lib_path.stem}") def find_qtlibs_in_wheel(wheel_pyside: Path): """ Find the path to Qt/lib folder inside the wheel. """ archive = ZipFile(wheel_pyside) qt_libs_path = wheel_pyside / "PySide6/Qt/lib" qt_libs_path = zipfile.Path(archive, at=qt_libs_path) if not qt_libs_path.exists(): for file in archive.namelist(): # the dependency files are inside the libs folder if file.endswith("android-dependencies.xml"): qt_libs_path = zipfile.Path(archive, at=file).parent # all dependency files are in the same path break if not qt_libs_path: raise FileNotFoundError("[DEPLOY] Unable to find Qt libs folder inside the wheel") return qt_libs_path