diff options
| author | Shyamnath Premnadh <Shyamnath.Premnadh@qt.io> | 2023-01-27 12:46:24 +0100 |
|---|---|---|
| committer | Shyamnath Premnadh <Shyamnath.Premnadh@qt.io> | 2023-03-06 15:51:59 +0100 |
| commit | 0ff718cf75351ae1ea3867a1a37dd0099454b467 (patch) | |
| tree | 718ccbff6896c686a7c476e6fa1c3c238e2c2db1 /tools/cross_compile_android/main.py | |
| parent | 486168a83d29ea8d59fbb7dc45163ee61805423e (diff) | |
tools: python cross compile
- script to cross compile Python for Android
- Downloads CPython 3.9. For Python cross compilation, the host
Python should match the target Python. python_for_android
project packages the 3.9.9 interpreter along with the python
application
- Python script creates used jinja2 to create a bash script which
run ./configure on CPython with various options
Task-number: PYSIDE-1612
Change-Id: Ia50811d6f89a1ac6e8acaf5d7e9f3841a2851e3f
Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
Diffstat (limited to 'tools/cross_compile_android/main.py')
| -rw-r--r-- | tools/cross_compile_android/main.py | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/tools/cross_compile_android/main.py b/tools/cross_compile_android/main.py new file mode 100644 index 000000000..5fc0fad0f --- /dev/null +++ b/tools/cross_compile_android/main.py @@ -0,0 +1,170 @@ +# 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 argparse +import tempfile +import subprocess +import stat +import warnings + +from typing import List + +from pathlib import Path +from git import Repo, RemoteProgress +from tqdm import tqdm +from jinja2 import Environment, FileSystemLoader + +PYTHON_VERSION = "3.10" + + +def occp_exists(): + ''' + check if '--only-cross-compile-python' exists in command line arguments + ''' + return "-occp" in sys.argv or "--only-cross-compile-python" in sys.argv + + +class CloneProgress(RemoteProgress): + def __init__(self): + super().__init__() + self.pbar = tqdm() + + def update(self, op_code, cur_count, max_count=None, message=""): + self.pbar.total = max_count + self.pbar.n = cur_count + self.pbar.refresh() + + +def run_command(command: List[str], cwd: str = None, ignore_fail: bool = False): + ex = subprocess.call(command, cwd=cwd, shell=True) + if ex != 0 and not ignore_fail: + sys.exit(ex) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="This tool cross builds cpython for android and uses that Python to cross build" + "android Qt for Python wheels", + formatter_class=argparse.RawTextHelpFormatter, + ) + + parser.add_argument("-p", "--plat-name", type=str, required=True, + choices=["aarch64", "armv7a", "i686", "x86_64"], + help="Android target platform name") + + parser.add_argument("-v", "--verbose", help="run in verbose mode", action="store_const", + dest="loglevel", const=logging.INFO) + parser.add_argument("--api-level", type=str, default="27", help="Android API level to use") + parser.add_argument( + "--ndk-path", type=str, required=True, help="Path to Android NDK (Preferred 23b)" + ) + + parser.add_argument("-occp", "--only-cross-compile-python", action="store_true", + help="Only cross compiles Python for the specified Android platform") + + parser.add_argument("-apic", "--android-python-install-path", type=str, default=None, + required=occp_exists(), + help=''' + Points to the installation path of Python for the specific Android + platform. If the path given does not exist, then Python for android + is cross compiled for the specific platform and installed into this + path as <path>/Python-'plat_name'/_install. + + If this path is not given, then Python for android is cross-compiled + into a temportary directory, which is deleted when the Qt for Python + android wheels are created. + ''') + + args = parser.parse_args() + + logging.basicConfig(level=args.loglevel) + current_dir = Path.cwd() + ndk_path = args.ndk_path + only_py_cross_compile = args.only_cross_compile_python + python_path = args.android_python_install_path + + # python path is valid, if Python for android installation exists in python_path + valid_python_path = True + if python_path and Path(python_path).exists(): + expected_dirs = ["lib", "include"] + for expected_dir in expected_dirs: + if not (Path(python_path) / expected_dir).is_dir(): + valid_python_path = False + warnings.warn( + "Given target Python, given through --android-python-install-path does not" + "contain Python. New Python for android will be cross compiled and installed" + "in this directory" + ) + break + + templates_path = Path(__file__).parent / "templates" + plat_name = args.plat_name + api_level = args.api_level + + # for armv7a the API level dependent binaries like clang are named + # armv7a-linux-androideabi27-clang, as opposed to other platforms which + # are named like x86_64-linux-android27-clang + if plat_name == "armv7a": + api_level = f"eabi{api_level}" + + # clone cpython and checkout 3.10 + with tempfile.TemporaryDirectory() as temp_dir: + temp_dir = Path(temp_dir) + logging.info(f"temp dir created at {temp_dir}") + if not python_path or not valid_python_path: + cpython_dir = temp_dir / "cpython" + python_ccompile_script = cpython_dir / "cross_compile.sh" + + logging.info(f"cloning cpython {PYTHON_VERSION}") + Repo.clone_from( + "https://github.com/python/cpython.git", + cpython_dir, + progress=CloneProgress(), + branch=PYTHON_VERSION, + ) + + if not python_path: + android_py_install_path_prefix = temp_dir + else: + android_py_install_path_prefix = python_path + + # use jinja2 to create cross_compile.sh script + environment = Environment(loader=FileSystemLoader(templates_path)) + template = environment.get_template("cross_compile.tmpl.sh") + content = template.render( + plat_name=plat_name, + ndk_path=ndk_path, + api_level=api_level, + android_py_install_path_prefix=android_py_install_path_prefix, + ) + + logging.info(f"Writing Python cross compile script into {python_ccompile_script}") + with open(python_ccompile_script, mode="w", encoding="utf-8") as ccompile_script: + ccompile_script.write(content) + + # give run permission to cross compile script + python_ccompile_script.chmod(python_ccompile_script.stat().st_mode | stat.S_IEXEC) + + # run the cross compile script + logging.info(f"Running Python cross-compile for platform {plat_name}") + run_command(["./cross_compile.sh"], cwd=cpython_dir) + + python_path = (f"{android_py_install_path_prefix}/Python-{plat_name}-linux-android/" + "_install") + + # run patchelf to change the SONAME of libpython from libpython3.x.so.1.0 to + # libpython3.x.so, to match with python_for_android's Python library. Otherwise, + # the Qfp binaries won't be able to link to Python + run_command(["patchelf", "--set-soname", f"libpython{PYTHON_VERSION}.so.1.0", + f"libpython{PYTHON_VERSION}.so"], cwd=Path(python_path) / "lib") + + logging.info( + f"Cross compile Python for Android platform {plat_name}." + f"Final installation in " + f"{python_path}" + ) + + if only_py_cross_compile: + sys.exit(0) |
