Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/markdown/snippets/android-crossfiles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Android cross file generator

The `env2mfile` command now supports a `--android` argument. When
specified, it tells the cross file generator to create cross files for
all Android toolchains located on the current machines.

This typically creates many files, so the `-o` argument specifies the
output directory. A typical use case goes like this:

```
meson env2mfile --android -o androidcross
meson setup --cross-file \
androidcross/android-29.0.14033849-android35-aarch64-cross.txt \
builddir
```
114 changes: 109 additions & 5 deletions mesonbuild/scripts/env2mfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from dataclasses import dataclass, field
import sys, os, subprocess, shutil
import pathlib
import shlex
import typing as T

Expand All @@ -24,11 +25,13 @@ def add_arguments(parser: 'argparse.ArgumentParser') -> None:
parser.add_argument('--gccsuffix', default="",
help='A particular gcc version suffix if necessary.')
parser.add_argument('-o', required=True, dest='outfile',
help='The output file.')
help='The output file or directory (for Android).')
parser.add_argument('--cross', default=False, action='store_true',
help='Generate a cross compilation file.')
parser.add_argument('--native', default=False, action='store_true',
help='Generate a native compilation file.')
parser.add_argument('--android', default=False, action='store_true',
help='Generate cross files for Android toolchains.')
parser.add_argument('--use-for-build', default=False, action='store_true',
help='Use _FOR_BUILD envvars.')
parser.add_argument('--system', default=None,
Expand Down Expand Up @@ -430,19 +433,120 @@ def detect_native_env(options: T.Any) -> MachineInfo:
detect_properties_from_envvars(infos, esuffix)
return infos

ANDROID_CPU_TO_MESON_CPU_FAMILY: dict[str, str] = {
'aarch64': 'aarch64',
'armv7a': 'arm',
'i686': 'x86',
'x86_64': 'x86_64',
'riscv64': 'riscv64',
}
Comment on lines +436 to +442
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

historically there were also mipsel, mips64el and arm (armv6) architectures available

Copy link

@moi15moi moi15moi Oct 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From https://developer.android.com/ndk/guides/abis#sa

Note: Historically the NDK supported ARMv5 (armeabi), and 32-bit and 64-bit MIPS, but support for these ABIs was removed in NDK r17.

So, I think it looks like this:

Triple ABI Android CPU Meson CPU family
arm-linux-androideabi armeabi arm arm
armv7a-linux-androideabi armeabi-v7a armv7a arm
aarch64-linux-android arm64-v8a aarch64 aarch64
i686-linux-android x86 i686 x86
x86_64-linux-android x86-64 x86_64 x86_64
riscv64-linux-android riscv64 riscv64 riscv64
mipsel-linux-android mips mipsel mips
mips64el-linux-android mips64 mips64el mips64

Copy link
Member Author

@jpakkane jpakkane Oct 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ones in this MR are the ones that are installed by Android Studio when choosing to install the ndk, which I think is a feasible default. If Google starts shipping new toolchains, adding support for them should be quite easy.


class AndroidDetector:
def __init__(self, options: T.Any):
import platform
self.platform = platform.system().lower()
self.options = options

if self.platform == 'windows':
self.build_machine_id = 'windows-X86_64'
self.command_suffix = '.cmd'
self.exe_suffix = '.exe'
elif self.platform == 'darwin':
self.build_machine_id = 'darwin-x86_64' # Yes, even on aarch64 for some reason
self.command_suffix = ''
self.exe_suffix = ''
elif self.platform == 'linux':
self.build_machine_id = 'linux-x86_64'
self.command_suffix = ''
self.exe_suffix = ''
else:
sys.exit('Android lookup only supported on Linux, Windows and macOS. Patches welcome.')
self.outdir = pathlib.Path(options.outfile)

def detect_android_sdk_root(self) -> None:
home = pathlib.Path.home()
if self.platform == 'windows':
sdk_root = home / 'AppData/Local/Android/Sdk'
elif self.platform == 'darwin':
sdk_root = home / 'Library/Android/Sdk'
elif self.platform == 'linux':
sdk_root = home / 'Android/Sdk'
else:
sys.exit('Unsupported platform.')
if not sdk_root.is_dir():
sys.exit(f'Could not locate Android SDK root in {sdk_root}.')
ndk_root = sdk_root / 'ndk'
if not ndk_root.is_dir():
sys.exit(f'Could not locate Android ndk in {ndk_root}')
self.ndk_root = ndk_root

def detect_toolchains(self) -> None:
self.detect_android_sdk_root()
if not self.outdir.is_dir():
self.outdir.mkdir()
for ndk in self.ndk_root.glob('*'):
if not ndk.is_dir():
continue
self.process_ndk(ndk)

def process_ndk(self, ndk: pathlib.Path) -> None:
ndk_version = ndk.parts[-1]
toolchain_root = ndk / f'toolchains/llvm/prebuilt/{self.build_machine_id}'
bindir = toolchain_root / 'bin'
if not bindir.is_dir():
sys.exit(f'Could not detect toolchain in {toolchain_root}.')
ar_path = bindir / f'llvm-ar{self.exe_suffix}'
if not ar_path.is_file():
sys.exit(f'Could not detect llvm-ar in {toolchain_root}.')
ar_str = str(ar_path).replace('\\', '/')
strip_path = bindir / f'llvm-strip{self.exe_suffix}'
if not strip_path.is_file():
sys.exit(f'Could not detect llvm-strip n {toolchain_root}.')
strip_str = str(strip_path).replace('\\', '/')
for compiler in bindir.glob('*-clang++'):
parts = compiler.parts[-1].split('-')
assert len(parts) == 4
cpu = parts[0]
assert parts[1] == 'linux'
android_version = parts[2]
cpp_compiler_str = str(compiler).replace('\\', '/')
c_compiler_str = cpp_compiler_str[:-2]
cpp_compiler_str += self.command_suffix
c_compiler_str += self.command_suffix
crossfile_name = f'android-{ndk_version}-{android_version}-{cpu}-cross.txt'
with open(pathlib.Path(self.options.outfile) / crossfile_name, 'w', encoding='utf-8') as ofile:
ofile.write('[binaries]\n')
ofile.write(f"c = '{c_compiler_str}'\n")
ofile.write(f"cpp = '{cpp_compiler_str}'\n")
ofile.write(f"ar = '{ar_str}'\n")
ofile.write(f"strip = '{strip_str}'\n")

ofile.write('\n[host_machine]\n')
ofile.write("system = 'android'\n")
ofile.write(f"cpu_family = '{ANDROID_CPU_TO_MESON_CPU_FAMILY[cpu]}'\n")
ofile.write(f"cpu = '{cpu}'\n")
ofile.write("endian = 'little'\n")


def run(options: T.Any) -> None:
if options.cross and options.native:
sys.exit('You can only specify either --cross or --native, not both.')
if not options.cross and not options.native:
sys.exit('You must specify --cross or --native.')
if (options.cross or options.native) and options.android:
sys.exit('You can not specify either --cross or --native with --android.')
if not options.cross and not options.native and not options.android:
sys.exit('You must specify --cross, --native or --android.')
mlog.notice('This functionality is experimental and subject to change.')
detect_cross = options.cross
if detect_cross:
if options.use_for_build:
sys.exit('--use-for-build only makes sense for --native, not --cross')
infos = detect_cross_env(options)
write_system_info = True
else:
write_machine_file(infos, options.outfile, write_system_info)
elif options.android is None:
infos = detect_native_env(options)
write_system_info = False
write_machine_file(infos, options.outfile, write_system_info)
write_machine_file(infos, options.outfile, write_system_info)
else:
ad = AndroidDetector(options)
ad.detect_toolchains()
Loading