diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 6196a34db..914fdd39e 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -37,7 +37,7 @@ jobs: target: google_apis arch: x86 profile: Nexus 6 - emulator-options: -no-window -no-snapshot -noaudio -no-boot-anim -camera-back none + emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none disable-animations: true script: | ./gradlew help diff --git a/README.md b/README.md index a30b370f2..0b78c408c 100644 --- a/README.md +++ b/README.md @@ -77,8 +77,9 @@ jobs: | `target` | Optional | `default` | Target of the system image - `default` or `google_apis`. | | `arch` | Optional | `x86` | CPU architecture of the system image - `x86` or `x86_64`. | | `profile` | Optional | N/A | Hardware profile used for creating the AVD - e.g. `Nexus 6`. For a list of all profiles available, run `$ANDROID_HOME/tools/bin/avdmanager list` and refer to the results under "Available Android Virtual Devices". | -| `emulator-options` | Optional | See below | Command-line options used when launching the emulator (replacing all default options) - e.g. `-no-snapshot -camera-back emulated`. | +| `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`. | | `disable-animations` | Optional | `true` | Whether to disable animations - `true` or `false`. | +| `emulator-build` | Optional | N/A | Build number of a specific version of the emulator binary to use e.g. `6061023` for emulator v29.3.0.0. | | `script` | Required | N/A | Custom script to run - e.g. to run Android instrumented tests on the emulator: `./gradlew connectedCheck` | -Default `emulator-options`: `-no-window -no-snapshot -noaudio -no-boot-anim`. +Default `emulator-options`: `-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim`. diff --git a/__tests__/input-validator.test.ts b/__tests__/input-validator.test.ts index 813684e46..baffc0f23 100644 --- a/__tests__/input-validator.test.ts +++ b/__tests__/input-validator.test.ts @@ -97,3 +97,26 @@ describe('disable-animations validator tests', () => { expect(func2).not.toThrow(); }); }); + +describe('emulator-build validator tests', () => { + it('Throws if emulator-build is not a number', () => { + const func = () => { + validator.checkEmulatorBuild('abc123'); + }; + expect(func).toThrowError(`Unexpected emulator build: 'abc123'.`); + }); + + it('Throws if emulator-build is not an integer', () => { + const func = () => { + validator.checkEmulatorBuild('123.123'); + }; + expect(func).toThrowError(`Unexpected emulator build: '123.123'.`); + }); + + it('Validates successfully with valid emulator-build', () => { + const func = () => { + validator.checkEmulatorBuild('6061023'); + }; + expect(func).not.toThrow(); + }); +}); diff --git a/action.yml b/action.yml index b5f349e2b..e71bb0123 100644 --- a/action.yml +++ b/action.yml @@ -17,11 +17,13 @@ inputs: profile: description: 'Hardware profile used for creating the AVD - e.g. `Nexus 6`.' emulator-options: - description: 'command-line options used when launching the emulator - e.g. `-no-snapshot -camera-back emulated`.' - default: '-no-window -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' + emulator-build: + description: 'build number of a specific version of the emulator binary to use e.g. `6061023` for emulator v29.3.0.0.' script: description: 'custom script to run - e.g. `./gradlew connectedCheck`' required: true diff --git a/lib/input-validator.js b/lib/input-validator.js index a05c57baf..32a59bd4e 100644 --- a/lib/input-validator.js +++ b/lib/input-validator.js @@ -30,3 +30,9 @@ function checkDisableAnimations(disableAnimations) { } } exports.checkDisableAnimations = checkDisableAnimations; +function checkEmulatorBuild(emulatorBuild) { + if (isNaN(Number(emulatorBuild)) || !Number.isInteger(Number(emulatorBuild))) { + throw new Error(`Unexpected emulator build: '${emulatorBuild}'.`); + } +} +exports.checkEmulatorBuild = checkEmulatorBuild; diff --git a/lib/main.js b/lib/main.js index 423cfee44..91e9aea5e 100644 --- a/lib/main.js +++ b/lib/main.js @@ -52,6 +52,13 @@ function run() { input_validator_1.checkDisableAnimations(disableAnimationsInput); const disableAnimations = disableAnimationsInput === 'true'; console.log(`disable animations: ${disableAnimations}`); + // emulator build + const emulatorBuildInput = core.getInput('emulator-build'); + if (emulatorBuildInput) { + input_validator_1.checkEmulatorBuild(emulatorBuildInput); + console.log(`using emulator build: ${emulatorBuildInput}`); + } + const emulatorBuild = !emulatorBuildInput ? undefined : emulatorBuildInput; // custom script to run const scriptInput = core.getInput('script', { required: true }); const scripts = script_parser_1.parseScript(scriptInput); @@ -59,9 +66,9 @@ function run() { scripts.forEach((script) => __awaiter(this, void 0, void 0, function* () { console.log(`${script}`); })); + // install SDK + yield sdk_installer_1.installAndroidSdk(apiLevel, target, arch, emulatorBuild); try { - // install SDK - yield sdk_installer_1.installAndroidSdk(apiLevel, target, arch); // launch an emulator yield emulator_manager_1.launchEmulator(apiLevel, target, arch, profile, emulatorOptions, disableAnimations); } diff --git a/lib/sdk-installer.js b/lib/sdk-installer.js index 856b1b6b3..66a43c404 100644 --- a/lib/sdk-installer.js +++ b/lib/sdk-installer.js @@ -21,11 +21,22 @@ const BUILD_TOOLS_VERSION = '29.0.2'; * Installs & updates the Android SDK for the macOS platform, including SDK platform for the chosen API level, latest build tools, platform tools, Android Emulator, * and the system image for the chosen API level, CPU arch, and target. */ -function installAndroidSdk(apiLevel, target, arch) { +function installAndroidSdk(apiLevel, target, arch, emulatorBuild) { return __awaiter(this, void 0, void 0, function* () { const sdkmangerPath = `${process.env.ANDROID_HOME}/tools/bin/sdkmanager`; - console.log('Installing latest build tools, platform tools, platform, and emulator.'); - yield exec.exec(`sh -c \\"${sdkmangerPath} --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools 'platforms;android-${apiLevel}' emulator > /dev/null"`); + console.log('Installing latest build tools, platform tools, and platform.'); + yield exec.exec(`sh -c \\"${sdkmangerPath} --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools 'platforms;android-${apiLevel}' > /dev/null"`); + if (emulatorBuild) { + console.log(`Installing emulator build ${emulatorBuild}.`); + yield exec.exec(`curl -fo emulator.zip https://dl.google.com/android/repository/emulator-darwin-${emulatorBuild}.zip`); + yield exec.exec(`rm -rf ${process.env.ANDROID_HOME}/emulator`); + yield exec.exec(`unzip -q emulator.zip -d ${process.env.ANDROID_HOME}`); + yield exec.exec(`rm -f emulator.zip`); + } + else { + console.log('Installing latest emulator.'); + yield exec.exec(`sh -c \\"${sdkmangerPath} --install emulator > /dev/null"`); + } console.log('Installing system images.'); yield exec.exec(`sh -c \\"${sdkmangerPath} --install 'system-images;android-${apiLevel};${target};${arch}' > /dev/null"`); }); diff --git a/src/input-validator.ts b/src/input-validator.ts index 1aae6088f..176f7b756 100644 --- a/src/input-validator.ts +++ b/src/input-validator.ts @@ -28,3 +28,9 @@ export function checkDisableAnimations(disableAnimations: string): void { throw new Error(`Input for input.disable-animations should be either 'true' or 'false'.`); } } + +export function checkEmulatorBuild(emulatorBuild: string): void { + if (isNaN(Number(emulatorBuild)) || !Number.isInteger(Number(emulatorBuild))) { + throw new Error(`Unexpected emulator build: '${emulatorBuild}'.`); + } +} diff --git a/src/main.ts b/src/main.ts index f00bb4512..09dc7776a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,6 @@ import * as core from '@actions/core'; import { installAndroidSdk } from './sdk-installer'; -import { checkApiLevel, checkTarget, checkArch, checkDisableAnimations } from './input-validator'; +import { checkApiLevel, checkTarget, checkArch, checkDisableAnimations, checkEmulatorBuild } from './input-validator'; import { launchEmulator, killEmulator } from './emulator-manager'; import * as exec from '@actions/exec'; import { parseScript } from './script-parser'; @@ -42,6 +42,14 @@ async function run() { const disableAnimations = disableAnimationsInput === 'true'; console.log(`disable animations: ${disableAnimations}`); + // emulator build + const emulatorBuildInput = core.getInput('emulator-build'); + if (emulatorBuildInput) { + checkEmulatorBuild(emulatorBuildInput); + console.log(`using emulator build: ${emulatorBuildInput}`); + } + const emulatorBuild = !emulatorBuildInput ? undefined : emulatorBuildInput; + // custom script to run const scriptInput = core.getInput('script', { required: true }); const scripts = parseScript(scriptInput); @@ -50,10 +58,10 @@ async function run() { console.log(`${script}`); }); - try { - // install SDK - await installAndroidSdk(apiLevel, target, arch); + // install SDK + await installAndroidSdk(apiLevel, target, arch, emulatorBuild); + try { // launch an emulator await launchEmulator(apiLevel, target, arch, profile, emulatorOptions, disableAnimations); } catch (error) { diff --git a/src/sdk-installer.ts b/src/sdk-installer.ts index 7a94d2b46..58e2e13c5 100644 --- a/src/sdk-installer.ts +++ b/src/sdk-installer.ts @@ -6,10 +6,20 @@ const BUILD_TOOLS_VERSION = '29.0.2'; * Installs & updates the Android SDK for the macOS platform, including SDK platform for the chosen API level, latest build tools, platform tools, Android Emulator, * and the system image for the chosen API level, CPU arch, and target. */ -export async function installAndroidSdk(apiLevel: number, target: string, arch: string): Promise { +export async function installAndroidSdk(apiLevel: number, target: string, arch: string, emulatorBuild?: string): Promise { const sdkmangerPath = `${process.env.ANDROID_HOME}/tools/bin/sdkmanager`; - console.log('Installing latest build tools, platform tools, platform, and emulator.'); - await exec.exec(`sh -c \\"${sdkmangerPath} --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools 'platforms;android-${apiLevel}' emulator > /dev/null"`); + console.log('Installing latest build tools, platform tools, and platform.'); + await exec.exec(`sh -c \\"${sdkmangerPath} --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools 'platforms;android-${apiLevel}' > /dev/null"`); + if (emulatorBuild) { + console.log(`Installing emulator build ${emulatorBuild}.`); + await exec.exec(`curl -fo emulator.zip https://dl.google.com/android/repository/emulator-darwin-${emulatorBuild}.zip`); + await exec.exec(`rm -rf ${process.env.ANDROID_HOME}/emulator`); + await exec.exec(`unzip -q emulator.zip -d ${process.env.ANDROID_HOME}`); + await exec.exec(`rm -f emulator.zip`); + } else { + console.log('Installing latest emulator.'); + await exec.exec(`sh -c \\"${sdkmangerPath} --install emulator > /dev/null"`); + } console.log('Installing system images.'); await exec.exec(`sh -c \\"${sdkmangerPath} --install 'system-images;android-${apiLevel};${target};${arch}' > /dev/null"`); }