From 5e38ea2647c69fdf7f3b234b0a6eb9fd9b496754 Mon Sep 17 00:00:00 2001 From: Steve Stotter Date: Mon, 24 May 2021 10:33:57 +0100 Subject: [PATCH] Add option to toggle Linux hardware acceleration --- README.md | 5 +++-- __tests__/input-validator.test.ts | 21 +++++++++++++++++++++ action.yml | 3 +++ lib/emulator-manager.js | 7 ++++--- lib/input-validator.js | 8 +++++++- lib/main.js | 7 ++++++- src/emulator-manager.ts | 8 +++++--- src/input-validator.ts | 6 ++++++ src/main.ts | 10 ++++++++-- 9 files changed, 63 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index dcadfa08..632f83bf 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A GitHub Action for installing, configuring and running hardware-accelerated And The old ARM-based emulators were slow and are no longer supported by Google. The modern Intel Atom (x86 and x86_64) emulators require hardware acceleration (HAXM on Mac & Windows, QEMU on Linux) from the host to run fast. This presents a challenge on CI as to be able to run hardware accelerated emulators within a docker container, **KVM** must be supported by the host VM which isn't the case for cloud-based CI providers due to infrastructural limits. If you want to learn more about this, here's an article I wrote: [Running Android Instrumented Tests on CI](https://dev.to/ychescale9/running-android-emulators-on-ci-from-bitrise-io-to-github-actions-3j76). The **macOS** VM provided by **GitHub Actions** has **HAXM** installed so we are able to create a new AVD instance, launch an emulator with hardware acceleration, and run our Android -tests directly on the VM. +tests directly on the VM. You can also achieve this on a self-hosted Linux runner, but it will need to be on a compatible instance that allows you to enable KVM - for example AWS EC2 Bare Metal instances. This action automates the process by doing the following: @@ -98,6 +98,7 @@ jobs: | `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`. | | `disable-spellchecker` | Optional | `false` | Whether to disable spellchecker - `true` or `false`. | +| `disable-linux-hw-accel` | Optional | `true` | Whether to disable hardware acceleration on Linux machines - `true` or `false`. Note that this is true by default as Github-hosted Linux runners do not support hardware acceleration. | | `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. | | `working-directory` | Optional | `./` | A custom working directory - e.g. `./android` if your root Gradle project is under the `./android` sub-directory within your repository. | | `ndk` | Optional | N/A | Version of NDK to install - e.g. `21.0.6113669` | @@ -108,7 +109,7 @@ Default `emulator-options`: `-no-window -gpu swiftshader_indirect -no-snapshot - ## Can I use this action on Linux VMs? -The short answer is yes but it's expected to be a much worse experience (on some newer API levels it might not work at all) than running it on macOS. +The short answer is yes but on Github-hosted Linux runners it's expected to be a much worse experience (on some newer API levels it might not work at all) than running it on macOS. You can get it running much faster on self-hosted Linux runners but only if the underlying instances support KVM (which most don't). For a longer answer please refer to [this issue](https://github.com/ReactiveCircus/android-emulator-runner/issues/46). diff --git a/__tests__/input-validator.test.ts b/__tests__/input-validator.test.ts index 4da366db..4a6553f7 100644 --- a/__tests__/input-validator.test.ts +++ b/__tests__/input-validator.test.ts @@ -124,6 +124,27 @@ describe('disable-spellchecker validator tests', () => { }); }); +describe('disable-linux-hw-accel validator tests', () => { + it('Throws if disable-linux-hw-accel is not a boolean', () => { + const func = () => { + validator.checkDisableLinuxHardwareAcceleration('yes'); + }; + expect(func).toThrowError(`Input for input.disable-linux-hw-accel should be either 'true' or 'false'.`); + }); + + it('Validates successfully if disable-linux-hw-accel is either true or false', () => { + const func1 = () => { + validator.checkDisableLinuxHardwareAcceleration('true'); + }; + expect(func1).not.toThrow(); + + const func2 = () => { + validator.checkDisableLinuxHardwareAcceleration('false'); + }; + expect(func2).not.toThrow(); + }); +}); + describe('emulator-build validator tests', () => { it('Throws if emulator-build is not a number', () => { const func = () => { diff --git a/action.yml b/action.yml index fb8f87b8..57530701 100644 --- a/action.yml +++ b/action.yml @@ -33,6 +33,9 @@ inputs: disable-spellchecker: description: Whether to disable spellchecker - `true` or `false`. default: 'false' + disable-linux-hw-accel: + description: Whether to disable hardware acceleration on Linux machines - `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' working-directory: diff --git a/lib/emulator-manager.js b/lib/emulator-manager.js index fe7ee90f..6ce380e0 100644 --- a/lib/emulator-manager.js +++ b/lib/emulator-manager.js @@ -34,7 +34,7 @@ const EMULATOR_BOOT_TIMEOUT_SECONDS = 600; /** * Creates and launches a new AVD instance with the specified configurations. */ -function launchEmulator(apiLevel, target, arch, profile, cores, sdcardPathOrSize, avdName, emulatorOptions, disableAnimations, disableSpellChecker) { +function launchEmulator(apiLevel, target, arch, profile, cores, sdcardPathOrSize, avdName, emulatorOptions, disableAnimations, disableSpellChecker, disableLinuxHardwareAcceleration) { return __awaiter(this, void 0, void 0, function* () { // create a new AVD const profileOption = profile.trim() !== '' ? `--device '${profile}'` : ''; @@ -46,8 +46,9 @@ function launchEmulator(apiLevel, target, arch, profile, cores, sdcardPathOrSize } // start emulator console.log('Starting emulator.'); - // turn off hardware acceleration on Linux - if (process.platform === 'linux') { + //turn off hardware acceleration on Linux + if (process.platform === 'linux' && disableLinuxHardwareAcceleration) { + console.log('Disabling Linux hardware acceleration.'); emulatorOptions += ' -accel off'; } yield exec.exec(`sh -c \\"${process.env.ANDROID_SDK_ROOT}/emulator/emulator -avd "${avdName}" ${emulatorOptions} &"`, [], { diff --git a/lib/input-validator.js b/lib/input-validator.js index 3c6f9dfd..1d2a37fd 100644 --- a/lib/input-validator.js +++ b/lib/input-validator.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.checkEmulatorBuild = exports.checkDisableSpellchecker = exports.checkDisableAnimations = exports.checkArch = exports.checkTarget = exports.checkApiLevel = exports.VALID_ARCHS = exports.VALID_TARGETS = exports.MIN_API_LEVEL = void 0; +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; exports.MIN_API_LEVEL = 15; exports.VALID_TARGETS = ['default', 'google_apis', 'google_apis_playstore']; exports.VALID_ARCHS = ['x86', 'x86_64', 'arm64-v8a']; @@ -37,6 +37,12 @@ function checkDisableSpellchecker(disableSpellchecker) { } } exports.checkDisableSpellchecker = checkDisableSpellchecker; +function checkDisableLinuxHardwareAcceleration(disableLinuxHardwareAcceleration) { + if (!isValidBoolean(disableLinuxHardwareAcceleration)) { + throw new Error(`Input for input.disable-linux-hw-accel should be either 'true' or 'false'.`); + } +} +exports.checkDisableLinuxHardwareAcceleration = checkDisableLinuxHardwareAcceleration; function checkEmulatorBuild(emulatorBuild) { if (isNaN(Number(emulatorBuild)) || !Number.isInteger(Number(emulatorBuild))) { throw new Error(`Unexpected emulator build: '${emulatorBuild}'.`); diff --git a/lib/main.js b/lib/main.js index 6f33a45c..f3a4ef86 100644 --- a/lib/main.js +++ b/lib/main.js @@ -85,6 +85,11 @@ function run() { input_validator_1.checkDisableSpellchecker(disableSpellcheckerInput); const disableSpellchecker = disableSpellcheckerInput === 'true'; console.log(`disable spellchecker: ${disableSpellchecker}`); + // disable linux hardware acceleration + const disableLinuxHardwareAccelerationInput = core.getInput('disable-linux-hw-accel'); + input_validator_1.checkDisableLinuxHardwareAcceleration(disableLinuxHardwareAccelerationInput); + const disableLinuxHardwareAcceleration = disableLinuxHardwareAccelerationInput === 'true'; + console.log(`disable Linux hardware acceleration: ${disableLinuxHardwareAcceleration}`); // emulator build const emulatorBuildInput = core.getInput('emulator-build'); if (emulatorBuildInput) { @@ -120,7 +125,7 @@ function run() { // install SDK yield sdk_installer_1.installAndroidSdk(apiLevel, target, arch, emulatorBuild, ndkVersion, cmakeVersion); // launch an emulator - yield emulator_manager_1.launchEmulator(apiLevel, target, arch, profile, cores, sdcardPathOrSize, avdName, emulatorOptions, disableAnimations, disableSpellchecker); + yield emulator_manager_1.launchEmulator(apiLevel, target, arch, profile, cores, sdcardPathOrSize, avdName, emulatorOptions, disableAnimations, disableSpellchecker, disableLinuxHardwareAcceleration); // execute the custom script try { // move to custom working directory if set diff --git a/src/emulator-manager.ts b/src/emulator-manager.ts index c945b959..026d2556 100644 --- a/src/emulator-manager.ts +++ b/src/emulator-manager.ts @@ -15,7 +15,8 @@ export async function launchEmulator( avdName: string, emulatorOptions: string, disableAnimations: boolean, - disableSpellChecker: boolean + disableSpellChecker: boolean, + disableLinuxHardwareAcceleration: boolean ): Promise { // create a new AVD const profileOption = profile.trim() !== '' ? `--device '${profile}'` : ''; @@ -32,8 +33,9 @@ export async function launchEmulator( // start emulator console.log('Starting emulator.'); - // turn off hardware acceleration on Linux - if (process.platform === 'linux') { + //turn off hardware acceleration on Linux + if (process.platform === 'linux' && disableLinuxHardwareAcceleration) { + console.log('Disabling Linux hardware acceleration.'); emulatorOptions += ' -accel off'; } diff --git a/src/input-validator.ts b/src/input-validator.ts index edf3006c..b5875d10 100644 --- a/src/input-validator.ts +++ b/src/input-validator.ts @@ -35,6 +35,12 @@ export function checkDisableSpellchecker(disableSpellchecker: string): void { } } +export function checkDisableLinuxHardwareAcceleration(disableLinuxHardwareAcceleration: string): void { + if (!isValidBoolean(disableLinuxHardwareAcceleration)) { + throw new Error(`Input for input.disable-linux-hw-accel 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 21e290f9..f73aec27 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, checkEmulatorBuild, checkDisableSpellchecker } from './input-validator'; +import { checkApiLevel, checkTarget, checkArch, checkDisableAnimations, checkEmulatorBuild, checkDisableSpellchecker, checkDisableLinuxHardwareAcceleration } from './input-validator'; import { launchEmulator, killEmulator } from './emulator-manager'; import * as exec from '@actions/exec'; import { parseScript } from './script-parser'; @@ -67,6 +67,12 @@ async function run() { const disableSpellchecker = disableSpellcheckerInput === 'true'; console.log(`disable spellchecker: ${disableSpellchecker}`); + // disable linux hardware acceleration + const disableLinuxHardwareAccelerationInput = core.getInput('disable-linux-hw-accel'); + checkDisableLinuxHardwareAcceleration(disableLinuxHardwareAccelerationInput); + const disableLinuxHardwareAcceleration = disableLinuxHardwareAccelerationInput === 'true'; + console.log(`disable Linux hardware acceleration: ${disableLinuxHardwareAcceleration}`); + // emulator build const emulatorBuildInput = core.getInput('emulator-build'); if (emulatorBuildInput) { @@ -108,7 +114,7 @@ async function run() { await installAndroidSdk(apiLevel, target, arch, emulatorBuild, ndkVersion, cmakeVersion); // launch an emulator - await launchEmulator(apiLevel, target, arch, profile, cores, sdcardPathOrSize, avdName, emulatorOptions, disableAnimations, disableSpellchecker); + await launchEmulator(apiLevel, target, arch, profile, cores, sdcardPathOrSize, avdName, emulatorOptions, disableAnimations, disableSpellchecker, disableLinuxHardwareAcceleration); // execute the custom script try {