import os.path import platform import shutil from typing import Union def with_ccache(command: str) -> str: ccache = shutil.which('ccache') if ccache is None: return command else: return f'{ccache} {command}' android_abis = { 'armeabi-v7a': { 'arch': 'armv7a-linux-androideabi', 'ndk_arch': 'arm', 'cflags': '-fpic -mfpu=neon -mfloat-abi=softfp', }, 'arm64-v8a': { 'arch': 'aarch64-linux-android', 'ndk_arch': 'arm64', 'cflags': '-fpic', }, 'x86': { 'arch': 'i686-linux-android', 'ndk_arch': 'x86', 'cflags': '-fPIC -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32', }, 'x86_64': { 'arch': 'x86_64-linux-android', 'ndk_arch': 'x86_64', 'cflags': '-fPIC -m64', }, } # https://developer.android.com/ndk/guides/other_build_systems def build_arch() : platforms = { 'Linux': 'linux-x86_64', 'Windows': 'windows-x86_64', 'Darwin': 'darwin-x86_64' # Despite the x86_64 tag in the Darwin name, those are fat binaries that include M1 support. } return platforms.get(platform.system()) class AndroidNdkToolchain: def __init__(self, top_path: str, lib_path: str, tarball_path: str, src_path: str, ndk_path: str, android_abi: str, use_cxx): # select the NDK target abi_info = android_abis[android_abi] host_triplet = abi_info['arch'] arch_path = os.path.join(lib_path, host_triplet) self.tarball_path = tarball_path self.src_path = src_path self.build_path = os.path.join(arch_path, 'build') ndk_arch = abi_info['ndk_arch'] android_api_level = '24' install_prefix = os.path.join(arch_path, 'root') self.host_triplet = host_triplet self.install_prefix = install_prefix llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch()) llvm_triple = host_triplet + android_api_level common_flags = '-Os -g' common_flags += ' ' + abi_info['cflags'] llvm_bin = os.path.join(llvm_path, 'bin') self.cc = with_ccache(os.path.join(llvm_bin, 'clang')) self.cxx = with_ccache(os.path.join(llvm_bin, 'clang++')) common_flags += ' -target ' + llvm_triple common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections' self.ar = os.path.join(llvm_bin, 'llvm-ar') self.arflags = 'rcs' self.ranlib = os.path.join(llvm_bin, 'llvm-ranlib') self.nm = os.path.join(llvm_bin, 'llvm-nm') self.strip = os.path.join(llvm_bin, 'llvm-strip') self.cflags = common_flags self.cxxflags = common_flags self.cppflags = ' -isystem ' + os.path.join(install_prefix, 'include') self.ldflags = ' -L' + os.path.join(install_prefix, 'lib') + \ ' -Wl,--exclude-libs=ALL' + \ ' ' + common_flags self.ldflags = common_flags self.libs = '' self.is_arm = ndk_arch == 'arm' self.is_armv7 = self.is_arm and 'armv7' in self.cflags self.is_aarch64 = ndk_arch == 'arm64' self.is_windows = False self.is_android = True self.is_darwin = False libstdcxx_flags = '' libstdcxx_cxxflags = '' libstdcxx_ldflags = '' libstdcxx_libs = '-static-libstdc++' if self.is_armv7: # On 32 bit ARM, clang generates no ".eh_frame" section; # instead, the LLVM unwinder library is used for unwinding # the stack after a C++ exception was thrown libstdcxx_libs += ' -lunwind' if use_cxx: self.cxxflags += ' ' + libstdcxx_cxxflags self.ldflags += ' ' + libstdcxx_ldflags self.libs += ' ' + libstdcxx_libs self.env = dict(os.environ) # redirect pkg-config to use our root directory instead of the # default one on the build host bin_dir = os.path.join(install_prefix, 'bin') os.makedirs(bin_dir, exist_ok=True) self.pkg_config = shutil.copy(os.path.join(top_path, 'build', 'pkg-config.sh'), os.path.join(bin_dir, 'pkg-config')) self.env['PKG_CONFIG'] = self.pkg_config class MingwToolchain: def __init__(self, top_path: str, toolchain_path, host_triplet, x64: bool, tarball_path, src_path, build_path, install_prefix): self.host_triplet = host_triplet self.tarball_path = tarball_path self.src_path = src_path self.build_path = build_path self.install_prefix = install_prefix toolchain_bin = os.path.join(toolchain_path, 'bin') self.cc = with_ccache(os.path.join(toolchain_bin, host_triplet + '-gcc')) self.cxx = with_ccache(os.path.join(toolchain_bin, host_triplet + '-g++')) self.ar = os.path.join(toolchain_bin, host_triplet + '-ar') self.arflags = 'rcs' self.ranlib = os.path.join(toolchain_bin, host_triplet + '-ranlib') self.nm = os.path.join(toolchain_bin, host_triplet + '-nm') self.strip = os.path.join(toolchain_bin, host_triplet + '-strip') self.windres = os.path.join(toolchain_bin, host_triplet + '-windres') common_flags = '-O2 -g' if not x64: # enable SSE support which is required for LAME common_flags += ' -march=pentium3' self.cflags = common_flags self.cxxflags = common_flags self.cppflags = '-isystem ' + os.path.join(install_prefix, 'include') + \ ' -DWINVER=0x0600 -D_WIN32_WINNT=0x0600' self.ldflags = '-L' + os.path.join(install_prefix, 'lib') + \ ' -static-libstdc++ -static-libgcc' self.libs = '' # Explicitly disable _FORTIFY_SOURCE because it is broken with # mingw. This prevents some libraries such as libFLAC to # enable it. self.cppflags += ' -D_FORTIFY_SOURCE=0' self.is_arm = host_triplet.startswith('arm') self.is_armv7 = self.is_arm and 'armv7' in self.cflags self.is_aarch64 = host_triplet == 'aarch64' self.is_windows = 'mingw32' in host_triplet self.is_android = False self.is_darwin = False self.env = dict(os.environ) # redirect pkg-config to use our root directory instead of the # default one on the build host import shutil bin_dir = os.path.join(install_prefix, 'bin') os.makedirs(bin_dir, exist_ok=True) self.pkg_config = shutil.copy(os.path.join(top_path, 'build', 'pkg-config.sh'), os.path.join(bin_dir, 'pkg-config')) self.env['PKG_CONFIG'] = self.pkg_config AnyToolchain = Union[AndroidNdkToolchain, MingwToolchain]