|  | 
|  | 1 | +import os | 
|  | 2 | +import subprocess | 
|  | 3 | +import shutil | 
|  | 4 | +import sh | 
|  | 5 | +from pathlib import Path | 
|  | 6 | +from os.path import join | 
|  | 7 | +from pythonforandroid.recipe import Recipe | 
|  | 8 | +from pythonforandroid.recommendations import read_ndk_version | 
|  | 9 | +from pythonforandroid.logger import info, shprint, info_main | 
|  | 10 | +from pythonforandroid.util import ensure_dir | 
|  | 11 | +import hashlib | 
|  | 12 | + | 
|  | 13 | +FLANG_FILES = { | 
|  | 14 | +    "package-flang-aarch64.tar.bz2": "bf01399513e3b435224d9a9f656b72a0965a23fdd8c3c26af0f7c32f2a5f3403", | 
|  | 15 | +    "package-flang-host.tar.bz2": "3ea2c0e8125ededddf9b3f23c767b8e37816e140ac934c76ace19a168fefdf83", | 
|  | 16 | +    "package-flang-x86_64.tar.bz2": "afe7e391355c71e7b0c8ee71a3002e83e2e524ad61810238815facf3030be6e6", | 
|  | 17 | +    "package-install.tar.bz2": "169b75f6125dc7b95e1d30416147a05d135da6cbe9cc8432d48f5b8633ac38db", | 
|  | 18 | +} | 
|  | 19 | + | 
|  | 20 | + | 
|  | 21 | +class GFortranRecipe(Recipe): | 
|  | 22 | +    # flang support in NDK by @termux (on github) | 
|  | 23 | +    name = "fortran" | 
|  | 24 | +    toolchain_ver = 0 | 
|  | 25 | +    url = "https://github.com/termux/ndk-toolchain-clang-with-flang/releases/download/" | 
|  | 26 | + | 
|  | 27 | +    def match_sha256(self, file_path, expected_hash): | 
|  | 28 | +        sha256 = hashlib.sha256() | 
|  | 29 | +        with open(file_path, "rb") as f: | 
|  | 30 | +            for chunk in iter(lambda: f.read(8192), b""): | 
|  | 31 | +                sha256.update(chunk) | 
|  | 32 | +        file_hash = sha256.hexdigest() | 
|  | 33 | +        return file_hash == expected_hash | 
|  | 34 | + | 
|  | 35 | +    @property | 
|  | 36 | +    def ndk_version(self): | 
|  | 37 | +        ndk_version = read_ndk_version(self.ctx.ndk_dir) | 
|  | 38 | +        minor_to_letter = {0: ""} | 
|  | 39 | +        minor_to_letter.update( | 
|  | 40 | +            {n + 1: chr(i) for n, i in enumerate(range(ord("b"), ord("b") + 25))} | 
|  | 41 | +        ) | 
|  | 42 | +        return f"{ndk_version.major}{minor_to_letter[ndk_version.minor]}" | 
|  | 43 | + | 
|  | 44 | +    def get_cache_dir(self): | 
|  | 45 | +        dir_name = self.get_dir_name() | 
|  | 46 | +        return join(self.ctx.build_dir, "other_builds", dir_name) | 
|  | 47 | + | 
|  | 48 | +    def get_fortran_dir(self): | 
|  | 49 | +        toolchain_name = f"android-r{self.ndk_version}-api-{self.ctx.ndk_api}" | 
|  | 50 | +        return join( | 
|  | 51 | +            self.get_cache_dir(), f"{toolchain_name}-flang-v{self.toolchain_ver}" | 
|  | 52 | +        ) | 
|  | 53 | + | 
|  | 54 | +    def get_incomplete_files(self): | 
|  | 55 | +        incomplete_files = [] | 
|  | 56 | +        cache_dir = self.get_cache_dir() | 
|  | 57 | +        for file, sha256sum in FLANG_FILES.items(): | 
|  | 58 | +            _file = join(cache_dir, file) | 
|  | 59 | +            if not (os.path.exists(_file) and self.match_sha256(_file, sha256sum)): | 
|  | 60 | +                incomplete_files.append(file) | 
|  | 61 | +        return incomplete_files | 
|  | 62 | + | 
|  | 63 | +    def download_if_necessary(self): | 
|  | 64 | +        assert self.ndk_version == "28c" | 
|  | 65 | +        if len(self.get_incomplete_files()) == 0: | 
|  | 66 | +            return | 
|  | 67 | +        self.download() | 
|  | 68 | + | 
|  | 69 | +    def download(self): | 
|  | 70 | +        cache_dir = self.get_cache_dir() | 
|  | 71 | +        ensure_dir(cache_dir) | 
|  | 72 | +        for file in self.get_incomplete_files(): | 
|  | 73 | +            _file = join(cache_dir, file) | 
|  | 74 | +            if os.path.exists(_file): | 
|  | 75 | +                os.remove(_file) | 
|  | 76 | +            self.download_file(f"{self.url}r{join(self.ndk_version, file)}", _file) | 
|  | 77 | + | 
|  | 78 | +    def extract_tar(self, file_path: Path, dest: Path, strip=1): | 
|  | 79 | +        shprint( | 
|  | 80 | +            sh.tar, | 
|  | 81 | +            "xf", | 
|  | 82 | +            str(file_path), | 
|  | 83 | +            "--strip-components", | 
|  | 84 | +            str(strip), | 
|  | 85 | +            "-C", | 
|  | 86 | +            str(dest) if dest else ".", | 
|  | 87 | +        ) | 
|  | 88 | + | 
|  | 89 | +    def create_flang_wrapper(self, path: Path, target: str): | 
|  | 90 | +        script = f"""#!/usr/bin/env bash | 
|  | 91 | +if [ "$1" != "-cpp" ] && [ "$1" != "-fc1" ]; then | 
|  | 92 | +  `dirname $0`/flang-new --target={target}{self.ctx.ndk_api} -D__ANDROID_API__={self.ctx.ndk_api} "$@" | 
|  | 93 | +else | 
|  | 94 | +  `dirname $0`/flang-new "$@" | 
|  | 95 | +fi | 
|  | 96 | +""" | 
|  | 97 | +        path.write_text(script) | 
|  | 98 | +        path.chmod(0o755) | 
|  | 99 | + | 
|  | 100 | +    def unpack(self, arch): | 
|  | 101 | +        info_main("Unpacking fortran") | 
|  | 102 | + | 
|  | 103 | +        flang_folder = self.get_fortran_dir() | 
|  | 104 | +        if os.path.exists(flang_folder): | 
|  | 105 | +            info("{} is already unpacked, skipping".format(self.name)) | 
|  | 106 | +            return | 
|  | 107 | + | 
|  | 108 | +        toolchain_path = Path( | 
|  | 109 | +            join(self.ctx.ndk_dir, "toolchains/llvm/prebuilt/linux-x86_64") | 
|  | 110 | +        ) | 
|  | 111 | +        cache_dir = Path(os.path.abspath(self.get_cache_dir())) | 
|  | 112 | + | 
|  | 113 | +        # clean tmp folder | 
|  | 114 | +        tmp_folder = Path(os.path.abspath(f"{flang_folder}-tmp")) | 
|  | 115 | +        shutil.rmtree(tmp_folder, ignore_errors=True) | 
|  | 116 | +        tmp_folder.mkdir(parents=True) | 
|  | 117 | +        os.chdir(tmp_folder) | 
|  | 118 | + | 
|  | 119 | +        self.extract_tar(cache_dir / "package-install.tar.bz2", None, strip=4) | 
|  | 120 | +        self.extract_tar(cache_dir / "package-flang-host.tar.bz2", None) | 
|  | 121 | + | 
|  | 122 | +        sysroot_path = tmp_folder / "sysroot" | 
|  | 123 | +        shutil.copytree(toolchain_path / "sysroot", sysroot_path) | 
|  | 124 | + | 
|  | 125 | +        self.extract_tar( | 
|  | 126 | +            cache_dir / "package-flang-aarch64.tar.bz2", | 
|  | 127 | +            sysroot_path / "usr/lib/aarch64-linux-android", | 
|  | 128 | +        ) | 
|  | 129 | +        self.extract_tar( | 
|  | 130 | +            cache_dir / "package-flang-x86_64.tar.bz2", | 
|  | 131 | +            sysroot_path / "usr/lib/x86_64-linux-android", | 
|  | 132 | +        ) | 
|  | 133 | + | 
|  | 134 | +        # Fix lib/clang paths | 
|  | 135 | +        version_output = subprocess.check_output( | 
|  | 136 | +            [str(tmp_folder / "bin/clang"), "--version"], text=True | 
|  | 137 | +        ) | 
|  | 138 | +        clang_version = next( | 
|  | 139 | +            (line for line in version_output.splitlines() if "clang version" in line), | 
|  | 140 | +            "", | 
|  | 141 | +        ) | 
|  | 142 | +        major_ver = clang_version.split("clang version ")[-1].split(".")[0] | 
|  | 143 | + | 
|  | 144 | +        lib_path = tmp_folder / f"lib/clang/{major_ver}/lib" | 
|  | 145 | +        src_lib_path = toolchain_path / f"lib/clang/{major_ver}/lib" | 
|  | 146 | +        shutil.rmtree(lib_path, ignore_errors=True) | 
|  | 147 | +        lib_path.mkdir(parents=True) | 
|  | 148 | + | 
|  | 149 | +        for item in src_lib_path.iterdir(): | 
|  | 150 | +            shprint(sh.cp, "-r", str(item), str(lib_path)) | 
|  | 151 | + | 
|  | 152 | +        # Create flang wrappers | 
|  | 153 | +        targets = [ | 
|  | 154 | +            "aarch64-linux-android", | 
|  | 155 | +            "armv7a-linux-androideabi", | 
|  | 156 | +            "i686-linux-android", | 
|  | 157 | +            "x86_64-linux-android", | 
|  | 158 | +        ] | 
|  | 159 | + | 
|  | 160 | +        for target in targets: | 
|  | 161 | +            wrapper_path = tmp_folder / f"bin/{target}-flang" | 
|  | 162 | +            self.create_flang_wrapper(wrapper_path, target) | 
|  | 163 | +            shutil.copy( | 
|  | 164 | +                wrapper_path, tmp_folder / f"bin/{target}{self.ctx.ndk_api}-flang" | 
|  | 165 | +            ) | 
|  | 166 | + | 
|  | 167 | +        tmp_folder.rename(flang_folder) | 
|  | 168 | + | 
|  | 169 | +    @property | 
|  | 170 | +    def bin_path(self): | 
|  | 171 | +        return f"{self.get_fortran_dir()}/bin" | 
|  | 172 | + | 
|  | 173 | +    def get_host_platform(self, arch): | 
|  | 174 | +        return { | 
|  | 175 | +            "arm64-v8a": "aarch64-linux-android", | 
|  | 176 | +            "armeabi-v7a": "armv7a-linux-androideabi", | 
|  | 177 | +            "x86_64": "x86_64-linux-android", | 
|  | 178 | +            "x86": "i686-linux-android", | 
|  | 179 | +        }[arch] | 
|  | 180 | + | 
|  | 181 | +    def get_fortran_bin(self, arch): | 
|  | 182 | +        return join(self.bin_path, f"{self.get_host_platform(arch)}-flang") | 
|  | 183 | + | 
|  | 184 | +    def get_fortran_flags(self, arch): | 
|  | 185 | +        return f"--target={self.get_host_platform(arch)}{self.ctx.ndk_api} -D__ANDROID_API__={self.ctx.ndk_api}" | 
|  | 186 | + | 
|  | 187 | + | 
|  | 188 | +recipe = GFortranRecipe() | 
0 commit comments