* Use auditwheel on Android * Add auditwheel command to defaults * pkgconfig fixes * Pre-import ctypes before monkey-patching in _cross_venv * Set PKG_CONFIG and PKG_CONFIG_RELOCATE_PATHS variables * Initial attempt at using mzakharo/android-gfortran * Switch to using termux/ndk-toolchain-clang-with-flang * Move PKG_CONFIG variables from build_env to android_env * Simplify flang installation * Add cross build files for NumPy * Add ldpaths entry for libomp * Add `--rm` to docker command line * Make Rust and Fortran shims consistent * Update documentation * Default to API level 24 on all Python versions * Clarify comments * Cleanups * Fix tests: * Set up Android env after installing pkgconf * Add tests for successfully using an older API level * Previous commit's auditwheel failure is fixed in the auditwheel PR * Update how-it-works diagram * Add tests for repair errors * Add more repair tests * Add test for Meson and Fortran * Add test for cross build files * Improve test_api_level error message * Add xbuild-files option * use pypa/auditwheel@main * Remove dependencies which are no longer needed * Fix README * Update to auditwheel 6.7.0 * Fix compatibility with pkgconf 2.5.1.post2 * Documentation clarifications Co-authored-by: Joe Rickerby <joerick@mac.com> * Fortran shim improvements * Add Jinja variables to new_meson_project * Update run_example_ci_configs for changed new_meson_project signature * Add missing dependency to run_example_ci_configs --------- Co-authored-by: mayeut <mayeut@users.noreply.github.com> Co-authored-by: Joe Rickerby <joerick@mac.com>
122 lines
4.3 KiB
Python
122 lines
4.3 KiB
Python
# This file intentionally has no hashbang line in the source: cibuildwheel will add it
|
|
# above this comment when the file is deployed.
|
|
|
|
import os
|
|
import re
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
from tempfile import gettempdir
|
|
|
|
from filelock import FileLock
|
|
|
|
from cibuildwheel.util.file import CIBW_CACHE_PATH, download
|
|
|
|
# In the future we might pick a different Flang release depending on the NDK version,
|
|
# but so far all Python versions use the same NDK version, so there's no need.
|
|
RELEASE_URL = "https://github.com/termux/ndk-toolchain-clang-with-flang/releases/download"
|
|
RELEASE_VERSION = "r27c"
|
|
ARCHS = ["aarch64", "x86_64"]
|
|
|
|
# The compiler is built for Linux x86_64, so we use Docker on macOS.
|
|
DOCKER_IMAGE = "debian:trixie-slim"
|
|
|
|
|
|
def main() -> None:
|
|
cache_dir = CIBW_CACHE_PATH / f"flang-android-{RELEASE_VERSION}"
|
|
with FileLock(f"{cache_dir}.lock"):
|
|
if not cache_dir.exists():
|
|
download_flang(cache_dir)
|
|
|
|
run_flang(cache_dir)
|
|
|
|
|
|
def download_flang(cache_dir: Path) -> None:
|
|
tmp_dir = Path(f"{cache_dir}.tmp")
|
|
if tmp_dir.exists():
|
|
shutil.rmtree(tmp_dir)
|
|
tmp_dir.mkdir(parents=True)
|
|
|
|
for archive_name in [f"package-flang-{arch}.tar.bz2" for arch in ARCHS] + [
|
|
"package-flang-host.tar.bz2",
|
|
"package-install.tar.bz2",
|
|
]:
|
|
archive_path = tmp_dir / archive_name
|
|
download(f"{RELEASE_URL}/{RELEASE_VERSION}/{archive_name}", archive_path)
|
|
shutil.unpack_archive(archive_path, tmp_dir)
|
|
archive_path.unlink()
|
|
|
|
# Merge the extracted trees together, along with the necessary parts of the NDK. Based on
|
|
# https://github.com/kivy/python-for-android/blob/develop/pythonforandroid/recipes/fortran/__init__.py.
|
|
flang_toolchain = tmp_dir / "toolchain"
|
|
(tmp_dir / "out/install/linux-x86/clang-dev").rename(flang_toolchain)
|
|
|
|
ndk_toolchain = Path(os.environ["CC"]).parents[1]
|
|
if (clang_ver_flang := clang_ver(flang_toolchain)) != (
|
|
clang_ver_ndk := clang_ver(ndk_toolchain)
|
|
):
|
|
msg = f"Flang uses Clang {clang_ver_flang}, but NDK uses Clang {clang_ver_ndk}"
|
|
raise ValueError(msg)
|
|
|
|
clang_lib_path = f"lib/clang/{clang_ver_ndk}/lib"
|
|
shutil.rmtree(flang_toolchain / clang_lib_path)
|
|
|
|
for src, dst in [
|
|
(f"{tmp_dir}/build-{arch}-install", f"sysroot/usr/lib/{arch}-linux-android")
|
|
for arch in ARCHS
|
|
] + [
|
|
(f"{tmp_dir}/build-host-install", ""),
|
|
(f"{ndk_toolchain}/{clang_lib_path}", clang_lib_path),
|
|
(f"{ndk_toolchain}/sysroot", "sysroot"),
|
|
]:
|
|
shutil.copytree(src, flang_toolchain / dst, symlinks=True, dirs_exist_ok=True)
|
|
|
|
flang_toolchain.rename(cache_dir)
|
|
shutil.rmtree(tmp_dir)
|
|
|
|
|
|
def clang_ver(toolchain: Path) -> str:
|
|
versions = [p.name for p in (toolchain / "lib/clang").iterdir()]
|
|
assert len(versions) == 1
|
|
return versions[0]
|
|
|
|
|
|
def run_flang(cache_dir: Path) -> None:
|
|
match = re.fullmatch(r".+/(.+)-clang", os.environ["CC"])
|
|
assert match is not None
|
|
target = match[1]
|
|
|
|
# In a future Flang version the executable name will change to "flang"
|
|
# (https://blog.llvm.org/posts/2025-03-11-flang-new/).
|
|
flang_args = [f"{cache_dir}/bin/flang-new", f"--target={target}", *sys.argv[1:]]
|
|
|
|
if sys.platform == "linux":
|
|
args = flang_args
|
|
elif sys.platform == "darwin":
|
|
args = ["docker", "run", "--rm", "--platform", "linux/amd64"]
|
|
# Docker on macOS only allows certain directories to be mounted as volumes
|
|
# by default, but they include all the locations we're likely to need.
|
|
for path in [
|
|
# Location of CIBW_CACHE_PATH (containing the Fortran compiler), and
|
|
# probably the project directory.
|
|
Path.home(),
|
|
# Temporary directories created by build backends and cibuildwheel itself.
|
|
# This is controlled by the TMPDIR environment variable, which is set to
|
|
# an unpredictable subdirectory of /var/folders. /var is itself a symlink
|
|
# to /private/var.
|
|
Path(gettempdir()),
|
|
Path(gettempdir()).resolve(),
|
|
]:
|
|
args += ["-v", f"{path}:{path}"]
|
|
args += ["--workdir", str(Path.cwd())]
|
|
args += ["--entrypoint", flang_args[0], DOCKER_IMAGE, *flang_args[1:]]
|
|
else:
|
|
msg = f"unknown platform: {sys.platform}"
|
|
raise ValueError(msg)
|
|
|
|
os.execvp(args[0], args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|