Skip to content

CI: migrate aarch64 Android to Cuttlefish - #5524

Open
10ne1 wants to merge 2 commits into
rust-lang:mainfrom
10ne1:pr-android-cuttlefish-aarch64
Open

CI: migrate aarch64 Android to Cuttlefish#5524
10ne1 wants to merge 2 commits into
rust-lang:mainfrom
10ne1:pr-android-cuttlefish-aarch64

Conversation

@10ne1

@10ne1 10ne1 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Hello, this moves the aarch64 Android CI from the decade old legacy emulator (Android 7) to the modern Android 17 matching the existing x86_64 test infra.

Deliberately not included, migrating these is left for future PRs:

  1. arm-linux-androideabi needs more investigation.
  2. aosp_cf_arm64_only_phone is using a 64-bit-only image, so moving it needs a different image variant and its own investigation.

Once the above 2 are also migrated, the legacy emulator logic can be removed entirely.

Running aarch64 tests is still pretty slow because it runs via emulation (no KVM on github arm runners), but it is booting a full modern Android and especially unblocks PRs like #4459.

The approaches compared, time-wise:

  • qemu-user (PR ci: Run aarch64-linux-android tests with qemu-user #5281): 4m2s. Fastest by far, but it is a userspace ABI shim against the bionic libs, not a running Android, so there is no kernel and no real device behind the syscalls.
  • legacy SDK emulator (current main): 11m40s - 14m28s. A full system, but an Android 7 / API 24 image from 2016 which is buggy and blocks PRs like Add mallopt and related constants for Android #4459.
  • Cuttlefish through qemu (this PR), full Android 17, ABI arm64-v8a: ~36m, of which 14--21m is the boot depending on load and only ~6m is actually running tests.
  • Cuttlefish on x86_64 with crosvm + KVM (current main, for reference):
    ~6m10s. This is roughly what aarch64 would cost if github's arm64 runners exposed /dev/kvm. The gap is the price of TCG.

Credit goes to @maurer for pointing out that Cuttlefish can boot an arm64 guest on an x86_64 host: it drops -accel and swaps -cpu host for -cpu max when the guest arch does not match the host, so -vm_manager=qemu_cli runs the device under TCG.

This worked nicely so we don't need an extra layer like android-emulator-runner which unfortunately only works with images up to 2022.

Please see more details in the commit messages.

cc @tgross35

Replace the 2016-era SDK emulator (slow full-system emulation of an
Android 7 image, no KVM) with the same Cuttlefish setup x86_64 already
uses, so both Android 64-bit targets test against a current official
build instead of one target running a decade-old image.

GitHub's arm64 runners expose no /dev/kvm, so the device cannot be
accelerated by matching the host architecture. It does not have to be:
Cuttlefish boots a foreign-architecture guest through qemu, dropping
-accel and swapping -cpu host for -cpu max when the target does not match
the host (IsHostCompatible() in host/libs/vm_manager/qemu_manager.cpp), so
the guest runs under TCG on an ordinary x86_64 runner. Selecting that is
the -vm_manager=qemu_cli flag; crosvm, the default, requires host and guest
to share an architecture.

The device images are arm64 but every host tool running them must be
x86_64, and cvd fetch otherwise takes the host package from the target
named by --default_build, which would land arm64 binaries the runner
cannot execute. --host_package_build pins those to the x86_64 build of the
same drop.

The image moves from ubuntu:26.04 to debian:13, following the x86_64 one,
which upstream switched to keep the base in step with the Cuttlefish packages
it installs (6ae84e4). The two are the same setup now, so putting them on
different distributions would only invite drift.

launch_cvd's timeout grows from 10m to 45m because a TCG guest boots far
slower than an accelerated one: measured arm64 boots ranged from 16 to 22
minutes for the same configuration, against roughly one for x86_64. The
timeout only bounds a hang.

The job's own budget has to grow too: test_tier2 allows 25 minutes, which the
boot alone can spend, so the matrix entry raises it to 50 and every other
tier2 target keeps the existing 25. A full run lands around 37 minutes.

cuttlefish-entrypoint.sh moves to ci/ because it is architecture-neutral
and both targets now use it; nothing in it changes.

The clang wrapper stays at the retired emulator's runtime API level (24),
matching how x86_64-linux-android is pinned, so the version-gated test
skips resolve exactly as before: this migration changes the test
infrastructure, not the coverage. Raising both is a separate change.

Assisted-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
launch_cvd -daemon prints nothing between "Launcher Build ID" and a booted
guest. On x86_64 that gap is about a minute, but the emulated arm64 guest
spends around twenty minutes there, so the job looks hung, and a boot that
genuinely hangs produces no evidence at all: the timeout fires and the
container goes away with the logs inside it.

Follow the device's launcher and kernel logs in the background for the
duration of the launch. This is also where the logs actually are: cvd keeps
the runtime under a per-group directory beneath /var/tmp/cvd rather than
under the fetch directory, so they are not simply sitting in $CUTTLEFISH_DIR
to be read afterwards.

Enabling the serial console (-console=true) would not do this. It attaches
an interactive console device, which under -daemon is just a pty in the
instance directory; the kernel messages worth seeing are written to
kernel.log either way.

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

rustbot commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Some changes occurred in an Android module

cc @maurer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants