Repository navigation
[build-tools] Start a prebuilt Android emulator device from the worker image when it matches - #4510
szdziedzic wants to merge 2 commits into
Conversation
…r image when it matches Worker images can ship an AVD that was created and booted once at image build time, together with an eas-prebuilt-avd.json manifest. eas/start_android_emulator now starts that AVD on the first attempt, instead of creating a new one, when the manifest matches the step inputs and environment. Otherwise it creates the AVD as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
✅ Thank you for adding the changelog entry! |
| // A failed attempt deletes the device, so later attempts create a new one. | ||
| if (attemptCount === 0 && prebuiltAvd.status === 'reusable') { | ||
| logger.info(`Using the prebuilt ${deviceName} from the worker image.`); | ||
| await AndroidEmulatorUtils.removeLockFilesAsync({ deviceName, env }); |
There was a problem hiding this comment.
🔴 Repeated emulator starts retain old state
When a runner starts the emulator again, prebuiltAvd still marks the previously used AVD reusable. The next session inherits its installed apps and device data instead of starting fresh.
Learn more
A matching manifest remains in the AVD directory after a successful boot. checkPrebuiltAvdAsync checks the manifest and file presence but not whether that AVD has already served a session. A later call to the start function therefore skips createAsync, which previously recreated the device with --force. Device changes from the first session persist into the next one.
Example: A workflow starts EasAndroidDevice01, installs an APK, shuts it down, and starts it again. The second start boots the old user-data image with that APK still installed rather than a clean device.
Recommended fix: Mark the prebuilt AVD as consumed before its first runtime boot, or create a per-session copy of the pristine image and boot the copy. Ensure subsequent calls on the same runner recreate or restore clean user data, including after a failed boot.
Was this helpful? React with 👍 or 👎 to provide feedback.
| try { | ||
| await fs.promises.access(`${env.HOME}/.android/avd/${deviceName}.ini`); | ||
| await fs.promises.access(path.join(avdDirectory, 'config.ini')); | ||
| } catch { | ||
| return { status: 'mismatch', reason: 'its AVD files are missing' }; | ||
| } | ||
|
|
||
| return { status: 'reusable' }; |
There was a problem hiding this comment.
🔴 Changed AVD settings pass reuse check
When the on-disk config.ini differs from the manifest, checkPrebuiltAvdAsync still returns reusable. The emulator then boots with settings that differ from the requested inputs.
Learn more
The manifest stores the configuration written before the image's first boot. The current code compares that manifest value with the expected settings, but checks only the existence of the real AVD configuration. If the emulator's initial boot or another operation changes the file, a matching manifest no longer proves that the AVD has the requested settings. startAndroidEmulatorAsync skips device creation when this check returns reusable.
Example: A manifest records hw.lcd.width=720, but the actual config.ini now contains hw.lcd.width=1080. A request for width 720 passes the manifest comparison and boots the 1080-width AVD instead of creating one configured for 720.
Recommended fix: Read and validate the actual AVD configuration before declaring it reusable. Account for any known emulator first-boot edits when comparing effective settings, and fall back to createAsync if the required settings differ.
Was this helpful? React with 👍 or 👎 to provide feedback.
| const expectedConfigIni = getConfigIniContent({ | ||
| avdManagerConfigIni: manifest.avdManagerConfigIni, | ||
| lcdWidth, | ||
| lcdHeight, | ||
| lcdDensity, | ||
| env, | ||
| }); | ||
| if (manifest.configIni !== expectedConfigIni) { | ||
| return { status: 'mismatch', reason: 'its config.ini has different settings' }; |
There was a problem hiding this comment.
🔍 Validate reuse against the actual worker image
Tests construct manifests with getConfigIniContent, the same function used to validate them. They cannot establish whether the separate image build produces a matching manifest; a real-image run can.
Was this helpful? React with 👍 or 👎 to provide feedback.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4510 +/- ##
==========================================
+ Coverage 73.07% 73.11% +0.05%
==========================================
Files 988 988
Lines 47690 47731 +41
Branches 10291 10301 +10
==========================================
+ Hits 34845 34895 +50
+ Misses 11849 11840 -9
Partials 996 996 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
eas-cli now uses changesets for changelogs (#4580). The required "Check for a changeset" check fails on PRs that edit To update this PR:
The release adds the PR link and the author to the entry. If this PR needs no changelog entry, revert the |
Why
An Android device run session spends about 1 minute in
eas/start_android_emulator. Each run creates a new AVD withavdmanager create avd --force, so every session does the first-boot setup of a new device (user data creation, package optimization).Worker images can do that setup once at image build time. This PR lets the step use such an AVD. The image side is in https://github.com/expo/turtle-v2/pull/2633.
How
~/.android/avd/EasAndroidDevice01.avdwith aneas-prebuilt-avd.jsonmanifest. The manifest hasdeviceIdentifier,systemImagePackage, the rawconfig.inifromavdmanager(avdManagerConfigIni), and the finalconfig.ini(configIni).config.inisettings fromcreateAsyncintoAndroidEmulatorUtils.getConfigIniContent.createAsyncwrites the same file as before.AndroidEmulatorUtils.checkPrebuiltAvdAsyncbuilds the expectedconfig.inifromavdManagerConfigIniwith the step's real inputs and env. It returnsreusableonly when that, the device and the system image all match. Any drift between the image build and build-tools gives a normal fresh create, not a wrong device.startAndroidEmulatorAsync, attempt 1 starts the matching AVD after it removes stale*.lockfiles (newremoveLockFilesAsync, now also used bycloneAsync). A failed attempt already deletes the AVD, so attempt 2 creates a new one as before. A mismatch is logged with its reason.eas/start_android_emulatorandeas/start_agent_device_session, because both callstartAndroidEmulatorAsync.Test Plan
yarn jest-unitforAndroidEmulatorUtils.test.ts,startAndroidEmulator.test.tsandstartAgentDeviceSession.test.ts: 44 passed, 13 of them new. They cover match, mismatch on system image, device, LCD inputs and env, a manifest that is not valid, missing AVD files, lock-file removal, and the fallback to a new AVD after a failed first attempt.tsc,oxlintandoxfmt --checkpass forpackages/build-tools.config.iniasgetConfigIniContentin 4 heap size cases.🤖 Generated with Claude Code