diff options
| author | Shyamnath Premnadh <Shyamnath.Premnadh@qt.io> | 2023-01-27 15:40:01 +0100 |
|---|---|---|
| committer | Shyamnath Premnadh <shyamnath.premnadh@qt.io> | 2023-03-09 17:42:02 +0100 |
| commit | d8707d200aa5787e834e017244290ef6554c85ce (patch) | |
| tree | fde2b6ac921e99800985e2cd522e494bf3eb8a4d /tools/cross_compile_android/main.py | |
| parent | d074f98ded062760ca6c237b17221cbba05f5d73 (diff) | |
Cross compile Qt for Python for Android
- uses a cross compiled Python to build Qt for Python wheels for
a specified Android target platform
Task-number: PYSIDE-1612
Change-Id: I3200e3cc749ef687ca62c0093065fbb23cd74cc8
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 | 106 |
1 files changed, 88 insertions, 18 deletions
diff --git a/tools/cross_compile_android/main.py b/tools/cross_compile_android/main.py index 5fc0fad0f..a3cad74ec 100644 --- a/tools/cross_compile_android/main.py +++ b/tools/cross_compile_android/main.py @@ -8,7 +8,7 @@ import tempfile import subprocess import stat import warnings - +from dataclasses import dataclass from typing import List from pathlib import Path @@ -16,9 +16,20 @@ from git import Repo, RemoteProgress from tqdm import tqdm from jinja2 import Environment, FileSystemLoader +# Note: Does not work with PyEnv. Your Host Python should contain openssl. PYTHON_VERSION = "3.10" +@dataclass +class PlatformData: + plat_name: str + api_level: str + android_abi: str + qt_plat_name: str + gcc_march: str + plat_bits: str + + def occp_exists(): ''' check if '--only-cross-compile-python' exists in command line arguments @@ -37,8 +48,12 @@ class CloneProgress(RemoteProgress): self.pbar.refresh() -def run_command(command: List[str], cwd: str = None, ignore_fail: bool = False): - ex = subprocess.call(command, cwd=cwd, shell=True) +def run_command(command: List[str], cwd: str = None, ignore_fail: bool = False, + dry_run: bool = False): + if dry_run: + print(" ".join(command)) + return + ex = subprocess.call(command, cwd=cwd) if ex != 0 and not ignore_fail: sys.exit(ex) @@ -56,10 +71,11 @@ if __name__ == "__main__": 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("--api-level", type=str, default="31", help="Android API level to use") + parser.add_argument("--ndk-path", type=str, required=True, + help="Path to Android NDK (Preferred 25b)") + parser.add_argument("--qt-install-path", type=str, required=not occp_exists(), + help="Qt installation path eg: /home/Qt/6.5.0") parser.add_argument("-occp", "--only-cross-compile-python", action="store_true", help="Only cross compiles Python for the specified Android platform") @@ -77,13 +93,23 @@ if __name__ == "__main__": android wheels are created. ''') + parser.add_argument("--dry-run", action="store_true", help="show the commands to be run") + args = parser.parse_args() logging.basicConfig(level=args.loglevel) - current_dir = Path.cwd() + pyside_setup_dir = Path(__file__).parents[2].resolve() + qt_install_path = args.qt_install_path ndk_path = args.ndk_path only_py_cross_compile = args.only_cross_compile_python python_path = args.android_python_install_path + # the same android platforms are named differently in CMake, Cpython and Qt. + # Hence, we need to distinguish them + qt_plat_name = None + android_abi = None + gcc_march = None + plat_bits = None + dry_run = args.dry_run # python path is valid, if Python for android installation exists in python_path valid_python_path = True @@ -106,11 +132,20 @@ if __name__ == "__main__": # 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 + platform_data = None if plat_name == "armv7a": - api_level = f"eabi{api_level}" + platform_data = PlatformData("armv7a", f"eabi{api_level}", "armeabi-v7a", "armv7", "armv7", + "32") + elif plat_name == "aarch64": + platform_data = PlatformData("aarch64", api_level, "arm64-v8a", "arm64_v8a", "armv8-a", "64") + elif plat_name == "i686": + platform_data = PlatformData("i686", api_level, "x86", "x86", "i686", "32") + else: # plat_name is x86_64 + platform_data = PlatformData("x86_64", api_level, "x86_64", "x86_64", "x86-64", "64") # clone cpython and checkout 3.10 with tempfile.TemporaryDirectory() as temp_dir: + environment = Environment(loader=FileSystemLoader(templates_path)) temp_dir = Path(temp_dir) logging.info(f"temp dir created at {temp_dir}") if not python_path or not valid_python_path: @@ -131,12 +166,11 @@ if __name__ == "__main__": 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, + plat_name=platform_data.plat_name, ndk_path=ndk_path, - api_level=api_level, + api_level=platform_data.api_level, android_py_install_path_prefix=android_py_install_path_prefix, ) @@ -148,23 +182,59 @@ if __name__ == "__main__": 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) + logging.info(f"Running Python cross-compile for platform {platform_data.plat_name}") + run_command(["./cross_compile.sh"], cwd=cpython_dir, dry_run=dry_run) - python_path = (f"{android_py_install_path_prefix}/Python-{plat_name}-linux-android/" + python_path = (f"{android_py_install_path_prefix}/Python-{platform_data.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") + run_command(["patchelf", "--set-soname", f"libpython{PYTHON_VERSION}.so", + f"libpython{PYTHON_VERSION}.so.1.0"], cwd=Path(python_path) / "lib") logging.info( - f"Cross compile Python for Android platform {plat_name}." + f"Cross compile Python for Android platform {platform_data.plat_name}. " f"Final installation in " f"{python_path}" ) if only_py_cross_compile: sys.exit(0) + + qfp_toolchain = temp_dir / f"toolchain_{platform_data.plat_name}.cmake" + template = environment.get_template("toolchain_default.tmpl.cmake") + content = template.render( + ndk_path=ndk_path, + api_level=platform_data.api_level, + qt_install_path=qt_install_path, + plat_name=platform_data.plat_name, + android_abi=platform_data.android_abi, + qt_plat_name=platform_data.qt_plat_name, + gcc_march=platform_data.gcc_march, + plat_bits=platform_data.plat_bits, + python_version=PYTHON_VERSION, + target_python_path=python_path + ) + + logging.info(f"Writing Qt for Python toolchain file into" + f"{qfp_toolchain}") + with open(qfp_toolchain, mode="w", encoding="utf-8") as ccompile_script: + ccompile_script.write(content) + + # give run permission to cross compile script + qfp_toolchain.chmod(qfp_toolchain.stat().st_mode | stat.S_IEXEC) + + # run the cross compile script + logging.info(f"Running Qt for Python cross-compile for platform {platform_data.plat_name}") + qfp_ccompile_cmd = [sys.executable, "setup.py", "bdist_wheel", "--parallel=9", + "--ignore-git", "--standalone", "--limited-api=yes", + f"--cmake-toolchain-file={str(qfp_toolchain.resolve())}", + f"--qt-host-path={qt_install_path}/gcc_64", + f"--plat-name=android_{platform_data.plat_name}", + f"--python-target-path={python_path}", + (f"--qt-target-path={qt_install_path}/" + f"android_{platform_data.qt_plat_name}"), + "--no-qt-tools", "--skip-docs", "--no-examples"] + run_command(qfp_ccompile_cmd, cwd=pyside_setup_dir, dry_run=dry_run) |
