Skip to content

Commit 97449e9

Browse files
committed
Merge branch 'main' into release/v2
* main: Prepare for release 2.18.0. Add sample workflow for AVD snapshot caching. Add `force-avd-creation` to Configurations table in README.md. Add force-avd-creation to skip avd creation if avd with same name exists. Update workflow to test snapshot caching. Add accompanist to adoption list Gradle 7.1. Bump glob-parent from 5.1.1 to 5.1.2 Bump ws from 7.3.1 to 7.4.6
2 parents 226f262 + fe99eb1 commit 97449e9

File tree

15 files changed

+239
-65
lines changed

15 files changed

+239
-65
lines changed

.github/workflows/workflow.yml

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,21 +64,52 @@ jobs:
6464
~/.gradle/caches
6565
~/.gradle/wrapper
6666
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
67+
- uses: actions/cache@v2
68+
id: avd-cache
69+
with:
70+
path: |
71+
~/.android/avd/*
72+
~/.android/adb*
73+
~/.android/debug.keystore
74+
key: avd-${{ matrix.api-level }}-${{ matrix.os }}-${{ matrix.target }}
75+
76+
- name: assemble tests
77+
run: |
78+
cd ./test-fixture/
79+
./gradlew assembleAndroidTest
80+
81+
- name: run emulator to generate snapshot for caching
82+
if: steps.avd-cache.outputs.cache-hit != 'true'
83+
uses: ./
84+
with:
85+
api-level: ${{ matrix.api-level }}
86+
target: ${{ matrix.target }}
87+
arch: x86
88+
profile: Galaxy Nexus
89+
cores: 2
90+
sdcard-path-or-size: 100M
91+
avd-name: test
92+
force-avd-creation: false
93+
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
94+
disable-animations: false
95+
working-directory: ./test-fixture/
96+
script: echo "Generated AVD snapshot for caching."
97+
6798
- name: run action
6899
uses: ./
69100
with:
70101
api-level: ${{ matrix.api-level }}
71102
target: ${{ matrix.target }}
72103
arch: x86
73-
profile: Nexus 6
104+
profile: Galaxy Nexus
74105
cores: 2
75106
sdcard-path-or-size: 100M
76107
avd-name: test
77-
emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none
108+
force-avd-creation: false
109+
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
78110
disable-animations: true
79111
working-directory: ./test-fixture/
80112
script: |
81113
echo $GITHUB_REPOSITORY
82114
adb devices
83-
./gradlew help
84115
./gradlew connectedDebugAndroidTest

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Change Log
22

3+
## v2.18.0
4+
5+
* Add `force-avd-creation` which when set to `false` will skip avd creation if avd with same name exists. This enables AVD snapshot caching which can significantly reduce emulator startup time. See [README.md](https://github.com/ReactiveCircus/android-emulator-runner/blob/main/README.md#usage) for a sample workflow. - [#159](https://github.com/ReactiveCircus/android-emulator-runner/pull/159)
6+
37
## v2.17.0
48

59
* Add option to toggle Linux hardware acceleration - [#154](https://github.com/ReactiveCircus/android-emulator-runner/pull/154) @stevestotter

README.md

Lines changed: 85 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,14 @@ jobs:
3131
test:
3232
runs-on: macos-latest
3333
steps:
34-
- name: checkout
35-
uses: actions/checkout@v2
36-
37-
- name: run tests
38-
uses: reactivecircus/android-emulator-runner@v2
39-
with:
40-
api-level: 29
41-
script: ./gradlew connectedCheck
34+
- name: checkout
35+
uses: actions/checkout@v2
36+
37+
- name: run tests
38+
uses: reactivecircus/android-emulator-runner@v2
39+
with:
40+
api-level: 29
41+
script: ./gradlew connectedCheck
4242
```
4343

4444
We can also leverage GitHub Actions's build matrix to test across multiple configurations:
@@ -52,17 +52,17 @@ jobs:
5252
api-level: [21, 23, 29]
5353
target: [default, google_apis]
5454
steps:
55-
- name: checkout
56-
uses: actions/checkout@v2
57-
58-
- name: run tests
59-
uses: reactivecircus/android-emulator-runner@v2
60-
with:
61-
api-level: ${{ matrix.api-level }}
62-
target: ${{ matrix.target }}
63-
arch: x86_64
64-
profile: Nexus 6
65-
script: ./gradlew connectedCheck
55+
- name: checkout
56+
uses: actions/checkout@v2
57+
58+
- name: run tests
59+
uses: reactivecircus/android-emulator-runner@v2
60+
with:
61+
api-level: ${{ matrix.api-level }}
62+
target: ${{ matrix.target }}
63+
arch: x86_64
64+
profile: Nexus 6
65+
script: ./gradlew connectedCheck
6666
```
6767

6868
If you need specific versions of **NDK** and **CMake** installed:
@@ -72,16 +72,70 @@ jobs:
7272
test:
7373
runs-on: macos-latest
7474
steps:
75-
- name: checkout
76-
uses: actions/checkout@v2
77-
78-
- name: run tests
79-
uses: reactivecircus/android-emulator-runner@v2
80-
with:
81-
api-level: 29
82-
ndk: 21.0.6113669
83-
cmake: 3.10.2.4988404
84-
script: ./gradlew connectedCheck
75+
- name: checkout
76+
uses: actions/checkout@v2
77+
78+
- name: run tests
79+
uses: reactivecircus/android-emulator-runner@v2
80+
with:
81+
api-level: 29
82+
ndk: 21.0.6113669
83+
cmake: 3.10.2.4988404
84+
script: ./gradlew connectedCheck
85+
```
86+
87+
We can significantly reduce emulator startup time by setting up AVD snapshot caching:
88+
89+
1. add an `actions/cache@v2` step for caching the `avd`
90+
2. add a `reactivecircus/android-emulator-runner@v2` step to generate a clean snapshot - specify `emulator-options` without `no-snapshot`
91+
3. add another `reactivecircus/android-emulator-runner@v2` step to run your tests using existing AVD / snapshot - specify `emulator-options` with `no-snapshot-save`
92+
93+
```
94+
jobs:
95+
test:
96+
runs-on: macos-latest
97+
strategy:
98+
matrix:
99+
api-level: [21, 23, 29]
100+
steps:
101+
- name: checkout
102+
uses: actions/checkout@v2
103+
104+
- name: Gradle cache
105+
uses: actions/cache@v2
106+
with:
107+
path: |
108+
~/.gradle/caches
109+
~/.gradle/wrapper
110+
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/buildSrc/**/*.kt') }}
111+
112+
- name: AVD cache
113+
uses: actions/cache@v2
114+
id: avd-cache
115+
with:
116+
path: |
117+
~/.android/avd/*
118+
~/.android/adb*
119+
key: avd-${{ matrix.api-level }}
120+
121+
- name: create AVD and generate snapshot for caching
122+
if: steps.avd-cache.outputs.cache-hit != 'true'
123+
uses: reactivecircus/android-emulator-runner@v2
124+
with:
125+
api-level: ${{ matrix.api-level }}
126+
force-avd-creation: false
127+
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
128+
disable-animations: false
129+
script: echo "Generated AVD snapshot for caching."
130+
131+
- name: run tests
132+
uses: reactivecircus/android-emulator-runner@v2
133+
with:
134+
api-level: ${{ matrix.api-level }}
135+
force-avd-creation: false
136+
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
137+
disable-animations: true
138+
script: ./gradlew connectedCheck
85139
```
86140

87141
## Configurations
@@ -95,6 +149,7 @@ jobs:
95149
| `cores` | Optional | 2 | Number of cores to use for the emulator (`hw.cpu.ncore` in config.ini). |
96150
| `sdcard-path-or-size` | Optional | N/A | 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`. |
97151
| `avd-name` | Optional | `test` | Custom AVD name used for creating the Android Virtual Device. |
152+
| `force-avd-creation` | Optional | `true` | Whether to force create the AVD by overwriting an existing AVD with the same name as `avd-name` - `true` or `false`. |
98153
| `emulator-options` | Optional | See below | Command-line options used when launching the emulator (replacing all default options) - e.g. `-no-window -no-snapshot -camera-back emulated`. |
99154
| `disable-animations` | Optional | `true` | Whether to disable animations - `true` or `false`. |
100155
| `disable-spellchecker` | Optional | `false` | Whether to disable spellchecker - `true` or `false`. |
@@ -143,5 +198,6 @@ These are some of the open-source projects using (or used) **Android Emulator Ru
143198
- [Kiwix/kiwix-android](https://github.com/kiwix/kiwix-android/blob/develop/.github/workflows)
144199
- [wikimedia/apps-android-wikipedia](https://github.com/wikimedia/apps-android-wikipedia/blob/master/.github/workflows)
145200
- [google/android-fhir](https://github.com/google/android-fhir/tree/master/.github/workflows)
201+
- [google/accompanist](https://github.com/google/accompanist/blob/main/.github/workflows)
146202

147203
If you are using **Android Emulator Runner** and want your project included in the list, please feel free to create an issue or open a pull request.

__tests__/input-validator.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,27 @@ describe('arch validator tests', () => {
8282
});
8383
});
8484

85+
describe('force-avd-creation validator tests', () => {
86+
it('Throws if force-avd-creation is not a boolean', () => {
87+
const func = () => {
88+
validator.checkForceAvdCreation('yes');
89+
};
90+
expect(func).toThrowError(`Input for input.force-avd-creation should be either 'true' or 'false'.`);
91+
});
92+
93+
it('Validates successfully if force-avd-creation is either true or false', () => {
94+
const func1 = () => {
95+
validator.checkForceAvdCreation('true');
96+
};
97+
expect(func1).not.toThrow();
98+
99+
const func2 = () => {
100+
validator.checkForceAvdCreation('false');
101+
};
102+
expect(func2).not.toThrow();
103+
});
104+
});
105+
85106
describe('disable-animations validator tests', () => {
86107
it('Throws if disable-animations is not a boolean', () => {
87108
const func = () => {

action.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,20 @@ inputs:
2424
avd-name:
2525
description: 'custom AVD name used for creating the Android Virtual Device'
2626
default: 'test'
27+
force-avd-creation:
28+
description: 'whether to force create the AVD by overwriting an existing AVD with the same name as `avd-name` - `true` or `false`'
29+
default: 'true'
2730
emulator-options:
2831
description: 'command-line options used when launching the emulator - e.g. `-no-window -no-snapshot -camera-back emulated`'
2932
default: '-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim'
3033
disable-animations:
3134
description: 'whether to disable animations - true or false'
3235
default: 'true'
3336
disable-spellchecker:
34-
description: Whether to disable spellchecker - `true` or `false`.
37+
description: 'whether to disable spellchecker - `true` or `false`'
3538
default: 'false'
3639
disable-linux-hw-accel:
37-
description: Whether to disable hardware acceleration on Linux machines - `true` or `false`.
40+
description: 'whether to disable hardware acceleration on Linux machines - `true` or `false`'
3841
default: 'true'
3942
emulator-build:
4043
description: 'build number of a specific version of the emulator binary to use - e.g. `6061023` for emulator v29.3.0.0'

lib/emulator-manager.js

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,31 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
3030
Object.defineProperty(exports, "__esModule", { value: true });
3131
exports.killEmulator = exports.launchEmulator = void 0;
3232
const exec = __importStar(require("@actions/exec"));
33+
const fs = __importStar(require("fs"));
3334
const EMULATOR_BOOT_TIMEOUT_SECONDS = 600;
3435
/**
3536
* Creates and launches a new AVD instance with the specified configurations.
3637
*/
37-
function launchEmulator(apiLevel, target, arch, profile, cores, sdcardPathOrSize, avdName, emulatorOptions, disableAnimations, disableSpellChecker, disableLinuxHardwareAcceleration) {
38+
function launchEmulator(apiLevel, target, arch, profile, cores, sdcardPathOrSize, avdName, forceAvdCreation, emulatorOptions, disableAnimations, disableSpellChecker, disableLinuxHardwareAcceleration) {
3839
return __awaiter(this, void 0, void 0, function* () {
39-
// create a new AVD
40-
const profileOption = profile.trim() !== '' ? `--device '${profile}'` : '';
41-
const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : '';
42-
console.log(`Creating AVD.`);
43-
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}"`);
40+
// create a new AVD if AVD directory does not already exist or forceAvdCreation is true
41+
const avdPath = `${process.env.ANDROID_AVD_HOME}/${avdName}.avd`;
42+
if (!fs.existsSync(avdPath) || forceAvdCreation) {
43+
const profileOption = profile.trim() !== '' ? `--device '${profile}'` : '';
44+
const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : '';
45+
console.log(`Creating AVD.`);
46+
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}"`);
47+
}
4448
if (cores) {
45-
yield exec.exec(`sh -c \\"printf 'hw.cpu.ncore=${cores}\n' >> ~/.android/avd/"${avdName}".avd"/config.ini`);
49+
yield exec.exec(`sh -c \\"printf 'hw.cpu.ncore=${cores}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`);
4650
}
47-
// start emulator
48-
console.log('Starting emulator.');
4951
//turn off hardware acceleration on Linux
5052
if (process.platform === 'linux' && disableLinuxHardwareAcceleration) {
5153
console.log('Disabling Linux hardware acceleration.');
5254
emulatorOptions += ' -accel off';
5355
}
56+
// start emulator
57+
console.log('Starting emulator.');
5458
yield exec.exec(`sh -c \\"${process.env.ANDROID_SDK_ROOT}/emulator/emulator -avd "${avdName}" ${emulatorOptions} &"`, [], {
5559
listeners: {
5660
stderr: (data) => {

lib/input-validator.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use strict";
22
Object.defineProperty(exports, "__esModule", { value: true });
3-
exports.checkEmulatorBuild = exports.checkDisableLinuxHardwareAcceleration = exports.checkDisableSpellchecker = exports.checkDisableAnimations = exports.checkArch = exports.checkTarget = exports.checkApiLevel = exports.VALID_ARCHS = exports.VALID_TARGETS = exports.MIN_API_LEVEL = void 0;
3+
exports.checkEmulatorBuild = exports.checkDisableLinuxHardwareAcceleration = exports.checkDisableSpellchecker = exports.checkDisableAnimations = exports.checkForceAvdCreation = exports.checkArch = exports.checkTarget = exports.checkApiLevel = exports.VALID_ARCHS = exports.VALID_TARGETS = exports.MIN_API_LEVEL = void 0;
44
exports.MIN_API_LEVEL = 15;
55
exports.VALID_TARGETS = ['default', 'google_apis', 'google_apis_playstore'];
66
exports.VALID_ARCHS = ['x86', 'x86_64', 'arm64-v8a'];
@@ -25,6 +25,12 @@ function checkArch(arch) {
2525
}
2626
}
2727
exports.checkArch = checkArch;
28+
function checkForceAvdCreation(forceAvdCreation) {
29+
if (!isValidBoolean(forceAvdCreation)) {
30+
throw new Error(`Input for input.force-avd-creation should be either 'true' or 'false'.`);
31+
}
32+
}
33+
exports.checkForceAvdCreation = checkForceAvdCreation;
2834
function checkDisableAnimations(disableAnimations) {
2935
if (!isValidBoolean(disableAnimations)) {
3036
throw new Error(`Input for input.disable-animations should be either 'true' or 'false'.`);

lib/main.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ function run() {
7272
// custom name used for creating the AVD
7373
const avdName = core.getInput('avd-name');
7474
console.log(`AVD name: ${avdName}`);
75+
// force AVD creation
76+
const forceAvdCreationInput = core.getInput('force-avd-creation');
77+
input_validator_1.checkForceAvdCreation(forceAvdCreationInput);
78+
const forceAvdCreation = forceAvdCreationInput === 'true';
79+
console.log(`force avd creation: ${forceAvdCreation}`);
7580
// emulator options
7681
const emulatorOptions = core.getInput('emulator-options').trim();
7782
console.log(`emulator options: ${emulatorOptions}`);
@@ -125,7 +130,7 @@ function run() {
125130
// install SDK
126131
yield sdk_installer_1.installAndroidSdk(apiLevel, target, arch, emulatorBuild, ndkVersion, cmakeVersion);
127132
// launch an emulator
128-
yield emulator_manager_1.launchEmulator(apiLevel, target, arch, profile, cores, sdcardPathOrSize, avdName, emulatorOptions, disableAnimations, disableSpellchecker, disableLinuxHardwareAcceleration);
133+
yield emulator_manager_1.launchEmulator(apiLevel, target, arch, profile, cores, sdcardPathOrSize, avdName, forceAvdCreation, emulatorOptions, disableAnimations, disableSpellchecker, disableLinuxHardwareAcceleration);
129134
// execute the custom script
130135
try {
131136
// move to custom working directory if set

lib/sdk-installer.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ function installAndroidSdk(apiLevel, target, arch, emulatorBuild, ndkVersion, cm
5757
}
5858
// add paths for commandline-tools and platform-tools
5959
core.addPath(`${cmdlineToolsPath}/latest:${cmdlineToolsPath}/latest/bin:${process.env.ANDROID_SDK_ROOT}/platform-tools`);
60+
// set standard AVD path
61+
core.exportVariable('ANDROID_AVD_HOME', `${process.env.HOME}/.android/avd`);
6062
// additional permission and license requirements for Linux
6163
const sdkPreviewLicensePath = `${process.env.ANDROID_SDK_ROOT}/licenses/android-sdk-preview-license`;
6264
if (!isOnMac && !fs.existsSync(sdkPreviewLicensePath)) {

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)