Skip to content

Commit 345b162

Browse files
ci: Run tests against Android NDK
Co-authored-by: Eli Schwartz <[email protected]>
1 parent e1b41be commit 345b162

File tree

12 files changed

+184
-34
lines changed

12 files changed

+184
-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\", \"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
@@ -1085,7 +1087,7 @@ def should_skip_wayland() -> bool:
10851087
return True
10861088
return False
10871089

1088-
def detect_tests_to_run(only: T.Dict[str, T.List[str]], use_tmp: bool) -> T.List[T.Tuple[str, T.List[TestDef], bool]]:
1090+
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]]:
10891091
"""
10901092
Parameters
10911093
----------
@@ -1129,8 +1131,7 @@ def __init__(self, category: str, subdir: str, skip: bool = False, stdout_mandat
11291131
TestCategory('platform-osx', 'osx', not mesonlib.is_osx()),
11301132
TestCategory('platform-windows', 'windows', not mesonlib.is_windows() and not mesonlib.is_cygwin()),
11311133
TestCategory('platform-linux', 'linuxlike', mesonlib.is_osx() or mesonlib.is_windows()),
1132-
# FIXME, does not actually run in CI, change to run the test if an Android cross toolchain is detected.
1133-
TestCategory('platform-android', 'android', not mesonlib.is_android()),
1134+
TestCategory('platform-android', 'android', not host_machine.is_android()),
11341135
TestCategory('java', 'java', backend is not Backend.ninja or not have_java()),
11351136
TestCategory('C#', 'csharp', skip_csharp(backend)),
11361137
TestCategory('vala', 'vala', backend is not Backend.ninja or not shutil.which(os.environ.get('VALAC', 'valac'))),
@@ -1695,6 +1696,13 @@ def setup_symlinks() -> None:
16951696
script_dir = os.path.split(__file__)[0]
16961697
if script_dir != '':
16971698
os.chdir(script_dir)
1699+
1700+
if options.cross_file is not None:
1701+
config = parse_machine_files([options.cross_file], script_dir)
1702+
host_machine = MachineInfo.from_literal(config['host_machine']) if 'host_machine' in config else detect_machine_info()
1703+
else:
1704+
host_machine = detect_machine_info()
1705+
16981706
check_meson_commands_work(options.use_tmpdir, options.extra_args)
16991707
only = collections.defaultdict(list)
17001708
for i in options.only:
@@ -1704,7 +1712,7 @@ def setup_symlinks() -> None:
17041712
except ValueError:
17051713
only[i].append('')
17061714
try:
1707-
all_tests = detect_tests_to_run(only, options.use_tmpdir)
1715+
all_tests = detect_tests_to_run(only, options.use_tmpdir, host_machine)
17081716
res = run_tests(all_tests, 'meson-test-run', options.failfast, options.extra_args, options.use_tmpdir, options.num_workers)
17091717
(passing_tests, failing_tests, skipped_tests) = res
17101718
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/common/103 has header symbol/meson.build

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ foreach comp : [cc, cpp]
1616
assert (comp.has_header_symbol('stdio.h', 'FILE'), 'FILE structure not found')
1717
assert (comp.has_header_symbol('limits.h', 'INT_MAX'), 'INT_MAX define not found')
1818
assert (not comp.has_header_symbol('limits.h', 'guint64'), 'guint64 is not defined in limits.h')
19-
assert (not comp.has_header_symbol('stdlib.h', 'FILE'), 'FILE structure is defined in stdio.h, not stdlib.h')
19+
if host_machine.system() != 'android'
20+
# Bionics malloc.h includes stdio.h
21+
assert (not comp.has_header_symbol('stdlib.h', 'FILE'), 'FILE structure is defined in stdio.h, not stdlib.h')
22+
endif
2023
assert (not comp.has_header_symbol('stdlol.h', 'printf'), 'stdlol.h shouldn\'t exist')
2124
assert (not comp.has_header_symbol('stdlol.h', 'int'), 'shouldn\'t be able to find "int" with invalid header')
2225
endforeach

0 commit comments

Comments
 (0)