Skip to content

Commit 09a4ae0

Browse files
ci: Run tests against Android NDK
Co-authored-by: Eli Schwartz <[email protected]>
1 parent 0f5c8fb commit 09a4ae0

File tree

13 files changed

+186
-34
lines changed

13 files changed

+186
-34
lines changed

.github/workflows/images.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ jobs:
3737
fail-fast: false
3838
matrix:
3939
cfg:
40+
- { name: Android NDK, id: android }
4041
- { name: Arch Linux, id: arch }
4142
- { name: CUDA (on Arch), id: cuda }
4243
- { name: CUDA Cross (on Ubuntu Jammy), id: cuda-cross }

.github/workflows/nonnative.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,16 @@ jobs:
4949
- uses: actions/checkout@v4
5050
- name: Run tests
5151
run: bash -c 'source /ci/env_vars.sh; cd $GITHUB_WORKSPACE; ./run_tests.py $CI_ARGS --cross cuda-cross.json --cross-only'
52+
53+
cross-android:
54+
runs-on: ubuntu-latest
55+
strategy:
56+
matrix:
57+
cfg:
58+
- { platform: android, arch: aarch64 }
59+
- { platform: android, arch: x86_64 }
60+
container: mesonbuild/android:latest
61+
steps:
62+
- uses: actions/checkout@v4
63+
- name: Run tests
64+
run: bash -c 'source /ci/env_vars.sh; cd $GITHUB_WORKSPACE; ./run_tests.py --cross /opt/android/meson/android-${ANDROID_NDKVER}-${{ matrix.cfg.platform }}${ANDROID_TARGET}-${{ matrix.cfg.arch }}-cross.json --cross-only'

ci/ciimage/android/image.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"base_image": "debian:sid",
3+
"args": [
4+
"--cross", "/opt/android/meson/android-${ANDROID_NDKVER}-androideabi${ANDROID_TARGET}-armv7a-cross.json",
5+
"--cross", "/opt/android/meson/android-${ANDROID_NDKVER}-android${ANDROID_TARGET}-aarch64-cross.json",
6+
"--cross", "/opt/android/meson/android-${ANDROID_NDKVER}-android${ANDROID_TARGET}-i686-cross.json",
7+
"--cross", "/opt/android/meson/android-${ANDROID_NDKVER}-android${ANDROID_TARGET}-x86_64-cross.json",
8+
"--cross", "/opt/android/meson/android-${ANDROID_NDKVER}-android35-riscv64-cross.txt"
9+
],
10+
"env": {
11+
"ANDROID_HOME": "/opt/android",
12+
"ANDROID_SDKVER": "36.1.0",
13+
"ANDROID_NDKVER": "29.0.14206865",
14+
"ANDROID_TARGET": "24",
15+
"CI": "1",
16+
"MESON_CI_JOBNAME": "android-cross"
17+
},
18+
"needs_meson_in_install": true
19+
}

ci/ciimage/android/install.sh

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
source /ci/common.sh
6+
source /ci/env_vars.sh
7+
8+
export DEBIAN_FRONTEND=noninteractive
9+
export LANG='C.UTF-8'
10+
11+
apt-get -y update
12+
apt-get -y upgrade
13+
14+
pkgs=(
15+
git jq ninja-build python3-pip sdkmanager
16+
)
17+
18+
apt-get -y install "${pkgs[@]}"
19+
20+
install_minimal_python_packages
21+
22+
# cleanup
23+
apt-get -y clean
24+
apt-get -y autoclean
25+
26+
# sdk install
27+
28+
set -x
29+
30+
if [[ -z $ANDROID_HOME || -z $ANDROID_SDKVER || -z $ANDROID_NDKVER ]]; then
31+
echo "ANDROID_HOME, ANDROID_SDKVER and ANDROID_NDKVER env var must be set!"
32+
exit 1
33+
fi
34+
35+
mkdir -p ${HOME}/.android
36+
# there are currently zero user repos
37+
echo 'count=0' > ${HOME}/.android/repositories.cfg
38+
cat <<EOF >> ${HOME}/.android/sites-settings.cfg
39+
@version@=1
40+
@disabled@https\://dl.google.com/android/repository/extras/intel/addon.xml=disabled
41+
@disabled@https\://dl.google.com/android/repository/glass/addon.xml=disabled
42+
@disabled@https\://dl.google.com/android/repository/sys-img/android/sys-img.xml=disabled
43+
@disabled@https\://dl.google.com/android/repository/sys-img/android-tv/sys-img.xml=disabled
44+
@disabled@https\://dl.google.com/android/repository/sys-img/android-wear/sys-img.xml=disabled
45+
@disabled@https\://dl.google.com/android/repository/sys-img/google_apis/sys-img.xml=disabled
46+
EOF
47+
48+
ANDROID_SDKMAJOR=${ANDROID_SDKVER%%.*}
49+
50+
# accepted licenses
51+
52+
mkdir -p $ANDROID_HOME/licenses/
53+
54+
cat << EOF > $ANDROID_HOME/licenses/android-sdk-license
55+
56+
8933bad161af4178b1185d1a37fbf41ea5269c55
57+
58+
d56f5187479451eabf01fb78af6dfcb131a6481e
59+
60+
24333f8a63b6825ea9c5514f83c2829b004d1fee
61+
EOF
62+
63+
cat <<EOF > $ANDROID_HOME/licenses/android-sdk-preview-license
64+
65+
84831b9409646a918e30573bab4c9c91346d8abd
66+
EOF
67+
68+
cat <<EOF > $ANDROID_HOME/licenses/android-sdk-preview-license-old
69+
70+
79120722343a6f314e0719f863036c702b0e6b2a
71+
72+
84831b9409646a918e30573bab4c9c91346d8abd
73+
EOF
74+
75+
cat <<EOF > $ANDROID_HOME/licenses/intel-android-extra-license
76+
77+
d975f751698a77b662f1254ddbeed3901e976f5a
78+
EOF
79+
80+
sdkmanager --sdk_root "${ANDROID_HOME}" \
81+
"ndk;${ANDROID_NDKVER}"
82+
83+
kernel=$(uname -s)
84+
arch=$(uname -m)
85+
86+
tee "${ANDROID_HOME}/toolchain.cross" <<EOF
87+
[constants]
88+
toolchain='${ANDROID_HOME}/ndk/${ANDROID_NDKVER}/toolchains/llvm/prebuilt/${kernel,,}-${arch}'
89+
EOF
90+
91+
/meson_private/meson.py env2mfile --android -o "${ANDROID_HOME}/meson/"
92+
find "${ANDROID_HOME}/meson/" -exec sh -c 'for cf; do jq -nr --arg file "$cf" "{ \"file\": \$file, \"env\": [], \"tests\": [\"common\", \"failing-meson\", \"failing-build\", \"failing-test\", \"platform-android\"] }" > "${cf%%.txt}.json"; done' sh {} +

ci/ciimage/build.py

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ def __init__(self, image_dir: Path) -> None:
2828
self.base_image: str = data['base_image']
2929
self.args: T.List[str] = data.get('args', [])
3030
self.env: T.Dict[str, str] = data['env']
31+
self.needs_meson_in_install = data.get('needs_meson_in_install', False)
3132

3233
class BuilderBase():
3334
def __init__(self, data_dir: Path, temp_dir: Path) -> None:
35+
self.meson_root = data_dir.parent.parent.parent.resolve()
3436
self.data_dir = data_dir
3537
self.temp_dir = temp_dir
3638

@@ -60,6 +62,20 @@ def validate_data_dir(self) -> None:
6062
if not i.is_file():
6163
raise RuntimeError(f'{i.as_posix()} is not a regular file')
6264

65+
def copy_meson(self) -> None:
66+
shutil.copytree(
67+
self.meson_root,
68+
self.temp_dir / 'meson',
69+
symlinks=True,
70+
ignore=shutil.ignore_patterns(
71+
'.git',
72+
'*_cache',
73+
'__pycache__',
74+
# 'work area',
75+
self.temp_dir.name,
76+
),
77+
)
78+
6379
class Builder(BuilderBase):
6480
def gen_bashrc(self) -> None:
6581
out_file = self.temp_dir / 'env_vars.sh'
@@ -91,6 +107,8 @@ def gen_dockerfile(self) -> None:
91107
out_data = textwrap.dedent(f'''\
92108
FROM {self.image_def.base_image}
93109
110+
{ "ADD meson /meson_private" if self.image_def.needs_meson_in_install else "" }
111+
94112
ADD install.sh /ci/install.sh
95113
ADD common.sh /ci/common.sh
96114
ADD env_vars.sh /ci/env_vars.sh
@@ -105,6 +123,9 @@ def do_build(self) -> None:
105123
shutil.copy(str(i), str(self.temp_dir))
106124
shutil.copy(str(self.common_sh), str(self.temp_dir))
107125

126+
if self.image_def.needs_meson_in_install:
127+
self.copy_meson()
128+
108129
self.gen_bashrc()
109130
self.gen_dockerfile()
110131

@@ -139,20 +160,6 @@ def gen_dockerfile(self) -> None:
139160

140161
out_file.write_text(out_data, encoding='utf-8')
141162

142-
def copy_meson(self) -> None:
143-
shutil.copytree(
144-
self.meson_root,
145-
self.temp_dir / 'meson',
146-
symlinks=True,
147-
ignore=shutil.ignore_patterns(
148-
'.git',
149-
'*_cache',
150-
'__pycache__',
151-
# 'work area',
152-
self.temp_dir.name,
153-
),
154-
)
155-
156163
def do_test(self, tty: bool = False) -> None:
157164
self.copy_meson()
158165
self.gen_dockerfile()

mesonbuild/scripts/env2mfile.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -464,15 +464,18 @@ def __init__(self, options: T.Any):
464464
self.outdir = pathlib.Path(options.outfile)
465465

466466
def detect_android_sdk_root(self) -> None:
467-
home = pathlib.Path.home()
468-
if self.platform == 'windows':
469-
sdk_root = home / 'AppData/Local/Android/Sdk'
470-
elif self.platform == 'darwin':
471-
sdk_root = home / 'Library/Android/Sdk'
472-
elif self.platform == 'linux':
473-
sdk_root = home / 'Android/Sdk'
474-
else:
475-
sys.exit('Unsupported platform.')
467+
android_home = os.getenv('ANDROID_HOME')
468+
sdk_root = None if android_home is None else pathlib.Path(android_home)
469+
if sdk_root is None:
470+
home = pathlib.Path.home()
471+
if self.platform == 'windows':
472+
sdk_root = home / 'AppData/Local/Android/Sdk'
473+
elif self.platform == 'darwin':
474+
sdk_root = home / 'Library/Android/Sdk'
475+
elif self.platform == 'linux':
476+
sdk_root = home / 'Android/Sdk'
477+
else:
478+
sys.exit('Unsupported platform.')
476479
if not sdk_root.is_dir():
477480
sys.exit(f'Could not locate Android SDK root in {sdk_root}.')
478481
ndk_root = sdk_root / 'ndk'

run_project_tests.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
from mesonbuild import mtest
4040
from mesonbuild.compilers import compiler_from_language
4141
from mesonbuild.build import ConfigurationData
42+
from mesonbuild.envconfig import MachineInfo, detect_machine_info
43+
from mesonbuild.machinefile import parse_machine_files
4244
from mesonbuild.mesonlib import MachineChoice, Popen_safe, TemporaryDirectoryWinProof, setup_vsenv
4345
from mesonbuild.mlog import blue, bold, cyan, green, red, yellow, normal_green
4446
from mesonbuild.coredata import version as meson_version
@@ -1079,7 +1081,7 @@ def should_skip_wayland() -> bool:
10791081
return True
10801082
return False
10811083

1082-
def detect_tests_to_run(only: T.Dict[str, T.List[str]], use_tmp: bool) -> T.List[T.Tuple[str, T.List[TestDef], bool]]:
1084+
def detect_tests_to_run(only: T.Dict[str, T.List[str]], use_tmp: bool, host_machine: MachineInfo) -> T.List[T.Tuple[str, T.List[TestDef], bool]]:
10831085
"""
10841086
Parameters
10851087
----------
@@ -1123,8 +1125,7 @@ def __init__(self, category: str, subdir: str, skip: bool = False, stdout_mandat
11231125
TestCategory('platform-osx', 'osx', not mesonlib.is_osx()),
11241126
TestCategory('platform-windows', 'windows', not mesonlib.is_windows() and not mesonlib.is_cygwin()),
11251127
TestCategory('platform-linux', 'linuxlike', mesonlib.is_osx() or mesonlib.is_windows()),
1126-
# FIXME, does not actually run in CI, change to run the test if an Android cross toolchain is detected.
1127-
TestCategory('platform-android', 'android', not mesonlib.is_android()),
1128+
TestCategory('platform-android', 'android', not host_machine.is_android()),
11281129
TestCategory('java', 'java', backend is not Backend.ninja or not have_java()),
11291130
TestCategory('C#', 'csharp', skip_csharp(backend)),
11301131
TestCategory('vala', 'vala', backend is not Backend.ninja or not shutil.which(os.environ.get('VALAC', 'valac'))),
@@ -1689,6 +1690,13 @@ def setup_symlinks() -> None:
16891690
script_dir = os.path.split(__file__)[0]
16901691
if script_dir != '':
16911692
os.chdir(script_dir)
1693+
1694+
if options.cross_file is not None:
1695+
config = parse_machine_files([options.cross_file], script_dir)
1696+
host_machine = MachineInfo.from_literal(config['host_machine']) if 'host_machine' in config else detect_machine_info()
1697+
else:
1698+
host_machine = detect_machine_info()
1699+
16921700
check_meson_commands_work(options.use_tmpdir, options.extra_args)
16931701
only = collections.defaultdict(list)
16941702
for i in options.only:
@@ -1698,7 +1706,7 @@ def setup_symlinks() -> None:
16981706
except ValueError:
16991707
only[i].append('')
17001708
try:
1701-
all_tests = detect_tests_to_run(only, options.use_tmpdir)
1709+
all_tests = detect_tests_to_run(only, options.use_tmpdir, host_machine)
17021710
res = run_tests(all_tests, 'meson-test-run', options.failfast, options.extra_args, options.use_tmpdir, options.num_workers)
17031711
(passing_tests, failing_tests, skipped_tests) = res
17041712
except StopException:

run_shell_checks.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
'ci/ciimage/fedora/install.sh',
2828
'ci/ciimage/arch/install.sh',
2929
'ci/ciimage/gentoo/install.sh',
30+
'ci/ciimage/android/install.sh',
3031
'manual tests/4 standalone binaries/myapp.sh',
3132
'manual tests/4 standalone binaries/osx_bundler.sh',
3233
'manual tests/4 standalone binaries/linux_bundler.sh',

run_tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ def main():
395395
for cf in options.cross:
396396
print(mlog.bold(f'Running {cf} cross tests.'))
397397
print(flush=True)
398-
cmd = cross_test_args + ['cross/' + cf]
398+
cmd = cross_test_args + [cf] if cf.startswith("/") else ['cross/' + cf]
399399
if options.failfast:
400400
cmd += ['--failfast']
401401
if options.cross_only:

test cases/android/1 exe_type/meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ e = executable('executable', 'exe_type.c',
66
a = executable('application', 'exe_type.c',
77
android_exe_type : 'application')
88

9+
error('test: ensure android tests are ran')
10+
911
if fs.name(e.full_path()).contains('.')
1012
error('Executable with exe_type `executable` did have expected filename')
1113
endif

0 commit comments

Comments
 (0)