Skip to content

ci: Run aarch64-linux-android tests with qemu-user#5281

Draft
10ne1 wants to merge 6 commits into
rust-lang:mainfrom
10ne1:android-qemu-user-aarch64
Draft

ci: Run aarch64-linux-android tests with qemu-user#5281
10ne1 wants to merge 6 commits into
rust-lang:mainfrom
10ne1:android-qemu-user-aarch64

Conversation

@10ne1

@10ne1 10ne1 commented Jul 14, 2026

Copy link
Copy Markdown

Description

This is a draft because it depends on #5269 and #5280 (only the last commit is new here, the others belong to those PRs and will drop out on rebase once they merge).

Part of #5262 (please see it for background and discussion).

This migrates aarch64-linux-android testing off the deprecated 2016-era emulator and contains just one commit for the migration itself.

Unlike the x86_64 migration it does not boot a Cuttlefish device: GitHub's arm64 runners expose no /dev/kvm and Cuttlefish cannot run without KVM. Instead, the test binaries run directly under qemu-user against the bionic linker and libraries extracted from a current official Android build, so this is a stopgap until GitHub exposes KVM on arm64 runners, at which point aarch64 can move to full Cuttlefish too.

Further PRs will be posted to raise the targeted Android API level (which widens test coverage; see below) and to look into 32-bit support.

Some observations from my testing and validation:

  • The job is pinned to the same released Android 17 build as the x86_64 Cuttlefish job, so new releases don't break CI (the pins reference each other and get bumped together).
  • The toolchain deliberately stays at API 24, matching the retired emulator's runtime, so the API-gated test skips from CI: Detect Android API from the toolchain #5280 resolve exactly as before: this PR changes the test infrastructure, not the coverage.
  • It runs on the standard x86_64 Ubuntu runners — no arm64 runners, KVM, or emulator boot involved. The job is green with 6528 libc-test checks and takes ~4 minutes total, versus ~25 for the old emulator job (when its boot didn't hang).
  • The full tier2 matrix still passes (32-bit arm stays on the legacy SDK emulator for now).
  • Together with ci: Run x86_64-linux-android tests on Cuttlefish virtual devices #5269 this fully unblocks Add mallopt and related constants for Android #4459, which crashes the old emulator; I validated earlier that its tests pass under this setup on both arches.

A libc-0.2 backport might be desired to get the new Android CI infra there as well, but I'll leave it up to the maintainers / reviewers to decide.

Sources

No library/API changes; CI-only. Infrastructure references:

Checklist

CI-only change: no src/ or libc-test/semver modifications.

  • Relevant tests in libc-test/semver have been updated (N/A)
  • No placeholder or unstable values (N/A)
  • Tested: validated in my fork on GitHub-hosted runners. This PR's own CI run exercises it directly.

AI disclosure

This PR was written with the help of Claude Code, but was not vibe-coded. I'm not a fan of vibe-coding.

I used AI to better understand the codebase myself (asked it a lot of questions), review both my code and any code generated by AI, used it especially to detect bugs and corner-cases and suggest fixes, however I very carefully reviewed & edited each code/comment lines, the commit messages and so on, until I was satisfied with the result.

Even this PR description was written entirely by myself and only reviewed by AI for errors.

10ne1 added 6 commits July 11, 2026 13:48
Add "android" to the Versions platform-version detection. Knowing it
allows version-gating the Android test skips instead of hardcoding them.

The level can appear in two macros:
  1. Old clang defines __ANDROID_API__ directly as an integer.
  2. Modern clang defines it as an alias of __ANDROID_MIN_SDK_VERSION__.

A toolchain whose target triple carries no API level defines neither
number, so nothing is parsed and the level stays undetected.

An undetected level (android == None) causes its consumers to treat it
conservatively: every API-gated skip stays active, as if the toolchain
targeted the oldest level possible.

Nothing consumes the value yet, so it has no effect on what is tested.
Next commits will add users for it.

Suggested-by: Trevor Gross <tg@trevorgross.com>
Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Convert the existing hardcoded "added in API level N" test skips into
gates on the detected API level added in the previous commit.

Items stay skipped when the toolchain builds below their introduction
level (or when no level was detected) and get tested once the toolchain
provably targets a high enough level.

There are no test coverage changes.

Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Bionic implements the termios API as static inline functions in
<termios.h> until API 28 turns them into real libc.so symbols,
so below that level the C-side function address is the header's
inline, never matching the symbol Rust links against.

Skip the function pointer identity check (the only check ctest
generates for a foreign function) for the termios family below API 28.

This surfaced when pinning the C test compilation to a real API
level (see next commit). At level 24, e.g. on arm, all thirteen
termios pointer comparisons fail.

It is ordered before the pin commit to keep commits bisectable.

Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Each CC wrapper's name (e.g. x86_64-linux-android28-clang) tells clang
which API to target: clang infers "-target" from the invoked binary
name whenever none is given explicitly.

Ours said 28 everywhere, which only reflects the NDK's link-time stub
level, not the level of the bionic that actually runs the tests: arm
emulators and the x86_64 system image android-sysimage.sh installs
(from x86_64-24_r07.zip) are all API 24.

This mismatch was invisible before because the tests were hardcoded
to skip higher API levels regardless of the toolchain; it surfaced
after extracting the real API level from the toolchain and wiring in
the dynamic skip mechanism (previous commits) instead of hardcoding.

With the toolchain matching the runtime, detection resolves and the
new gated skips activate. Raising an image's runtime later only needs
its own wrapper name bumped.

Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
The new ci/cuttlefish-setup.sh is heavily inspired by Mesa CI's
cuttlefish-runner.sh, credited in the script.

For more details, see the following issue, of which this is part of:
rust-lang#5262

In a nutshell, the libc x86_64 job never booted Android: it extracted
bionic from a 2016-era (API 24) image and ran the test binaries directly
on the Ubuntu host kernel. This makes the CI test boot the official
stock Android 17 image via Cuttlefish.

The virtual device boots inside the test container, self-contained:
Cuttlefish host packages are installed in the x86_64-linux-android
image and the container entrypoint creates the tap networking via
the deb's sysv script as root, then drops to an unprivileged user.

The container needs only the virtualization device nodes plus
CAP_NET_ADMIN, the same flags upstream uses for its own containerized
Cuttlefish CI, so nothing on the CI host is mutated and a local run
reduces to the usual ./ci/run-docker.sh with no host setup beyond KVM.

The container still cross-compiles with the NDK and drives the device
over adb, reusing the runtest-android.rs wrapper unchanged.

Device logs are uploaded as CI artifacts (again similar to Mesa).

The other Android targets stay on the legacy SDK emulator for now.

Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
…onic

Replace the 2016-era SDK emulator (slow full-system emulation of an
Android 7 image, no KVM) with direct execution of the test binaries
under qemu-user, linked against the bionic linker and libraries of
a current Android build extracted into /system.

Cuttlefish, which x86_64-linux-android now uses, is not an option
because GitHub's arm64 runners don't support KVM yet and Cuttlefish
can't run without KVM.

qemu-user doesn't need KVM and avoids the emulator boot flakiness,
so use it as a stopgap until GitHub esposes KVM on its arm64 runners,
at which point we can move aarch64 testing to full Cuttlefish too.

The clang wrapper stays at the retired emulator runtime API level (24),
so the version-gated test skips resolve exactly as before, so this
migration only changes the test infrastructure, not the coverage.

Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
@rustbot rustbot added A-CI Area: CI-related items O-android O-arm O-linux labels Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants