Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update README to have more better example avd caching #394

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ jobs:
strategy:
matrix:
api-level: [21, 23, 29]
target: [default, google_apis]
arch: [x86_64]
steps:
- name: checkout
uses: actions/checkout@v4
Expand All @@ -153,13 +155,15 @@ jobs:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-${{ matrix.api-level }}
key: avd-${{ matrix.api-level }}-${{ matrix.target }}-${{ matrix.arch }}

- name: create AVD and generate snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: ${{ matrix.arch }}
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: false
Expand All @@ -169,6 +173,8 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: ${{ matrix.arch }}
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
Expand Down
60 changes: 31 additions & 29 deletions action-types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,55 +16,57 @@ inputs:
arch:
type: enum
allowed-values:
- x86
- x86_64
- arm64-v8a
- x86
- x86_64
- arm64-v8a
profile:
type: string
type: string
cores:
type: integer
type: integer
ram-size:
type: string
type: string
heap-size:
type: string
type: string
sdcard-path-or-size:
type: string
type: string
disk-size:
type: string
type: string
avd-name:
type: string
type: string
force-avd-creation:
type: boolean
type: boolean
emulator-boot-timeout:
type: integer
type: integer
emulator-port:
type: integer
emulator-options:
type: string
type: string
disable-animations:
type: boolean
type: boolean
disable-spellchecker:
type: boolean
type: boolean
disable-linux-hw-accel:
type: string
type: string
enable-hw-keyboard:
type: boolean
type: boolean
emulator-build:
type: string
type: string
working-directory:
type: string
type: string
ndk:
type: string
type: string
cmake:
type: string
type: string
channel:
type: enum
allowed-values:
- stable
- beta
- dev
- canary
type: enum
allowed-values:
- stable
- beta
- dev
- canary
script:
type: string
type: string
pre-emulator-launch-script:
type: string
type: string
retry-count:
type: integer
95 changes: 49 additions & 46 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,75 +1,78 @@
name: 'Android Emulator Runner'
description: 'Installs, configures and starts an Android Emulator directly on hardware-accelerated runners.'
author: 'Reactive Circus'
name: "Android Emulator Runner"
description: "Installs, configures and starts an Android Emulator directly on hardware-accelerated runners."
author: "Reactive Circus"
branding:
icon: 'smartphone'
color: 'green'
icon: "smartphone"
color: "green"
inputs:
api-level:
description: 'API level of the platform and system image - e.g. 23 for Android Marshmallow, 29 for Android 10'
description: "API level of the platform and system image - e.g. 23 for Android Marshmallow, 29 for Android 10"
required: true
target:
description: 'target of the system image - default, google_apis, google_apis_playstore, aosp_atd, google_atd, android-wear, android-wear-cn, android-tv or google-tv'
default: 'default'
description: "target of the system image - default, google_apis, google_apis_playstore, aosp_atd, google_atd, android-wear, android-wear-cn, android-tv or google-tv"
default: "default"
arch:
description: 'CPU architecture of the system image - x86, x86_64 or arm64-v8a'
default: 'x86'
description: "CPU architecture of the system image - x86, x86_64 or arm64-v8a"
default: "x86"
profile:
description: 'hardware profile used for creating the AVD - e.g. `Nexus 6`'
description: "hardware profile used for creating the AVD - e.g. `Nexus 6`"
cores:
description: 'the number of cores to use for the emulator'
default: 2
description: "the number of cores to use for the emulator"
default: "2"
ram-size:
description: 'size of RAM to use for this AVD, in KB or MB, denoted with K or M. - e.g. `2048M`'
description: "size of RAM to use for this AVD, in KB or MB, denoted with K or M. - e.g. `2048M`"
heap-size:
description: 'size of heap to use for this AVD in MB. - e.g. `512M`'
description: "size of heap to use for this AVD in MB. - e.g. `512M`"
sdcard-path-or-size:
description: 'path to the SD card image for this AVD or the size of a new SD card image to create for this AVD, in KB or MB, denoted with K or M. - e.g. `path/to/sdcard`, or `1000M`'
description: "path to the SD card image for this AVD or the size of a new SD card image to create for this AVD, in KB or MB, denoted with K or M. - e.g. `path/to/sdcard`, or `1000M`"
disk-size:
description: 'disk size to use for this AVD. Either in bytes or KB, MB or GB, when denoted with K, M or G'
description: "disk size to use for this AVD. Either in bytes or KB, MB or GB, when denoted with K, M or G"
avd-name:
description: 'custom AVD name used for creating the Android Virtual Device'
default: 'test'
description: "custom AVD name used for creating the Android Virtual Device"
default: "test"
force-avd-creation:
description: 'whether to force create the AVD by overwriting an existing AVD with the same name as `avd-name` - `true` or `false`'
default: 'true'
description: "whether to force create the AVD by overwriting an existing AVD with the same name as `avd-name` - `true` or `false`"
default: "true"
emulator-boot-timeout:
description: 'Emulator boot timeout in seconds. If it takes longer to boot, the action would fail - e.g. `300` for 5 minutes'
default: '600'
description: "Emulator boot timeout in seconds. If it takes longer to boot, the action would fail - e.g. `300` for 5 minutes"
default: "600"
emulator-port:
description: 'Port to run emulator on, allows to run multiple emulators on the same physical machine'
default: '5554'
description: "Port to run emulator on, allows to run multiple emulators on the same physical machine"
default: "5554"
emulator-options:
description: 'command-line options used when launching the emulator - e.g. `-no-window -no-snapshot -camera-back emulated`'
default: '-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim'
description: "command-line options used when launching the emulator - e.g. `-no-window -no-snapshot -camera-back emulated`"
default: "-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim"
disable-animations:
description: 'whether to disable animations - true or false'
default: 'true'
description: "whether to disable animations - true or false"
default: "true"
disable-spellchecker:
description: 'whether to disable the Android spell checker framework, a common source of flakiness in text fields - `true` or `false`'
default: 'false'
description: "whether to disable the Android spell checker framework, a common source of flakiness in text fields - `true` or `false`"
default: "false"
disable-linux-hw-accel:
description: 'whether to disable hardware acceleration on Linux machines - `true` or `false` or `auto`'
default: 'auto'
description: "whether to disable hardware acceleration on Linux machines - `true` or `false` or `auto`"
default: "auto"
enable-hw-keyboard:
description: 'whether to enable hardware keyboard - `true` or `false`.'
default: 'false'
description: "whether to enable hardware keyboard - `true` or `false`."
default: "false"
emulator-build:
description: 'build number of a specific version of the emulator binary to use - e.g. `6061023` for emulator v29.3.0.0'
description: "build number of a specific version of the emulator binary to use - e.g. `6061023` for emulator v29.3.0.0"
working-directory:
description: 'A custom working directory - e.g. `./android` if your root Gradle project is under the `./android` sub-directory within your repository'
description: "A custom working directory - e.g. `./android` if your root Gradle project is under the `./android` sub-directory within your repository"
ndk:
description: 'version of NDK to install - e.g. 21.0.6113669'
description: "version of NDK to install - e.g. 21.0.6113669"
cmake:
description: 'version of CMake to install - e.g. 3.10.2.4988404'
description: "version of CMake to install - e.g. 3.10.2.4988404"
channel:
description: 'Channel to download the SDK components from - `stable`, `beta`, `dev`, `canary`'
default: 'stable'
description: "Channel to download the SDK components from - `stable`, `beta`, `dev`, `canary`"
default: "stable"
script:
description: 'custom script to run - e.g. `./gradlew connectedCheck`'
required: true
description: "custom script to run - e.g. `./gradlew connectedCheck`"
pre-emulator-launch-script:
description: 'custom script to run after creating the AVD and before launching the emulator - e.g. `./adjust-emulator-configs.sh`'
description: "custom script to run after creating the AVD and before launching the emulator - e.g. `./adjust-emulator-configs.sh`"
retry-count:
description: "number of times to retry the action in case of failure - e.g. `3`"
default: "3"
runs:
using: 'node20'
main: 'lib/main.js'
using: "node20"
main: "lib/main.js"
post: "lib/post.js"
16 changes: 12 additions & 4 deletions lib/emulator-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.killEmulator = exports.launchEmulator = void 0;
const exec = __importStar(require("@actions/exec"));
const fs = __importStar(require("fs"));
const retry_1 = require("./retry");
/**
* Creates and launches a new AVD instance with the specified configurations.
*/
function launchEmulator(apiLevel, target, arch, profile, cores, ramSize, heapSize, sdcardPathOrSize, diskSize, avdName, forceAvdCreation, emulatorBootTimeout, port, emulatorOptions, disableAnimations, disableSpellChecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard) {
function launchEmulator(apiLevel, target, arch, profile, cores, ramSize, heapSize, sdcardPathOrSize, diskSize, avdName, forceAvdCreation, emulatorBootTimeout, port, emulatorOptions, disableAnimations, disableSpellChecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard, retryCount) {
return __awaiter(this, void 0, void 0, function* () {
try {
console.log(`::group::Launch Emulator`);
Expand All @@ -48,7 +49,11 @@ function launchEmulator(apiLevel, target, arch, profile, cores, ramSize, heapSiz
const profileOption = profile.trim() !== '' ? `--device '${profile}'` : '';
const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : '';
console.log(`Creating AVD.`);
yield exec.exec(`sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --abi '${target}/${arch}' --package 'system-images;android-${apiLevel};${target};${arch}' ${profileOption} ${sdcardPathOrSizeOption}"`);
// Don't believe this ever failed, but it seems like a strong candidate for failure...
const result = yield (0, retry_1.execWithRetry)(() => exec.exec(`sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --abi '${target}/${arch}' --package 'system-images;android-${apiLevel};${target};${arch}' ${profileOption} ${sdcardPathOrSizeOption}"`), retryCount);
if (result !== 0) {
throw new Error('Failed to create AVD.');
}
}
if (cores) {
yield exec.exec(`sh -c \\"printf 'hw.cpu.ncore=${cores}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`);
Expand All @@ -72,15 +77,18 @@ function launchEmulator(apiLevel, target, arch, profile, cores, ramSize, heapSiz
}
// start emulator
console.log('Starting emulator.');
yield exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -port ${port} -avd "${avdName}" ${emulatorOptions} &"`, [], {
const result = yield (0, retry_1.execWithRetry)(() => exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -port ${port} -avd "${avdName}" ${emulatorOptions} &"`, [], {
listeners: {
stderr: (data) => {
if (data.toString().includes('invalid command-line parameter')) {
throw new Error(data.toString());
}
},
},
});
}), retryCount);
if (result !== 0) {
throw new Error('Failed to create AVD.');
}
// wait for emulator to complete booting
yield waitForDevice(port, emulatorBootTimeout);
yield adb(port, `shell input keyevent 82`);
Expand Down
14 changes: 12 additions & 2 deletions lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,9 @@ function run() {
console.log(`${script}`);
}));
console.log(`::endgroup::`);
const retryCount = parseInt(core.getInput('retry-count', { required: true }));
// install SDK
yield (0, sdk_installer_1.installAndroidSdk)(apiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion);
yield (0, sdk_installer_1.installAndroidSdk)(apiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion, retryCount);
// execute pre emulator launch script if set
if (preEmulatorLaunchScripts !== undefined) {
console.log(`::group::Run pre emulator launch script`);
Expand All @@ -198,7 +199,16 @@ function run() {
console.log(`::endgroup::`);
}
// launch an emulator
yield (0, emulator_manager_1.launchEmulator)(apiLevel, target, arch, profile, cores, ramSize, heapSize, sdcardPathOrSize, diskSize, avdName, forceAvdCreation, emulatorBootTimeout, port, emulatorOptions, disableAnimations, disableSpellchecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard);
try {
yield (0, emulator_manager_1.launchEmulator)(apiLevel, target, arch, profile, cores, ramSize, heapSize, sdcardPathOrSize, diskSize, avdName, forceAvdCreation, emulatorBootTimeout, port, emulatorOptions, disableAnimations, disableSpellchecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard, retryCount);
}
catch (error) {
core.setFailed(error instanceof Error ? error.message : error);
}
if (scripts.length === 0) {
console.log('No custom script to run. Be sure to shut down the emulator in your script.');
console.log(`(adb -s emulator-${port} emu kill)`);
}
// execute the custom script
try {
// move to custom working directory if set
Expand Down
66 changes: 66 additions & 0 deletions lib/post.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core"));
const input_validator_1 = require("./input-validator");
const emulator_manager_1 = require("./emulator-manager");
const exec = __importStar(require("@actions/exec"));
function post() {
return __awaiter(this, void 0, void 0, function* () {
let port = input_validator_1.MIN_PORT;
// Emulator port to use
port = parseInt(core.getInput('emulator-port'), 10);
(0, input_validator_1.checkPort)(port);
console.log(`emulator port: ${port}`);
try {
let result = '';
yield exec.exec(`adb -s emulator-${port} shell getprop sys.boot_completed`, [], {
listeners: {
stdout: (data) => {
result += data.toString();
},
},
});
if (result.trim() === '1') {
console.log('Emulator online, killing it.');
yield (0, emulator_manager_1.killEmulator)(port);
}
}
catch (error) {
yield (0, emulator_manager_1.killEmulator)(port);
console.warn(error instanceof Error ? error.message : error);
}
});
}
post();
Loading