diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 64b1203be..17a06d7c7 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -4,6 +4,8 @@ on: push: branches: - master + paths-ignore: + - '**.md' jobs: test: @@ -32,6 +34,8 @@ jobs: target: google_apis arch: x86_64 profile: Nexus 6 - emulator-options: -no-window -no-snapshot -noaudio -no-boot-anim -camera-back emulated + emulator-options: -no-window -no-snapshot -noaudio -no-boot-anim -camera-back none disable-animations: true - script: adb devices -l + script: | + adb reconnect + adb devices -l diff --git a/CHANGELOG.md b/CHANGELOG.md index b92d18a08..c49ef05a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## v2.1.0 + +* Added support for multi-line script. + ## v2.0.0 * Added action input `emulator-options` for providing command-line options used when launching the emulator. Default value is `-no-window -no-snapshot -noaudio -no-boot-anim`. diff --git a/README.md b/README.md index e63cec2f0..a30b370f2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A GitHub Action for installing, configuring and running Android Emulators on macOS virtual machines. -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. +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 **masOS** 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. diff --git a/__tests__/script-parser.test.ts b/__tests__/script-parser.test.ts new file mode 100644 index 000000000..6bb4cdef9 --- /dev/null +++ b/__tests__/script-parser.test.ts @@ -0,0 +1,33 @@ +import * as parser from '../src/script-parser'; + +describe('script parser tests', () => { + it('Scripts are trimmed', () => { + const script = ` command \n`; + expect(parser.parseScript(script)).toEqual(['command']); + }); + + it('Commented lines are filtered out', () => { + const script = ` + # command1 + command2 + + # command3 + command4 + `; + expect(parser.parseScript(script)).toEqual(['command2', 'command4']); + }); + + it('Throws if parsed scripts array is empty', () => { + const func = () => { + const script = ` + # command1 + + # command2 + + `; + const result = parser.parseScript(script); + console.log(`Result: ${result}`); + }; + expect(func).toThrowError(`No valid script found.`); + }); +}); diff --git a/lib/emulator-manager.js b/lib/emulator-manager.js index d6fa8e02f..d2ef920a5 100644 --- a/lib/emulator-manager.js +++ b/lib/emulator-manager.js @@ -66,7 +66,7 @@ function killEmulator() { yield exec.exec(`${ADB_PATH} -s emulator-5554 emu kill`); } catch (error) { - console.log('No emulator running on port 5554'); + console.log(error.message); } }); } @@ -76,7 +76,6 @@ exports.killEmulator = killEmulator; */ function waitForDevice() { return __awaiter(this, void 0, void 0, function* () { - const adbPath = `${process.env.ANDROID_HOME}/platform-tools/adb`; let booted = false; let attempts = 0; const retryInterval = 2; // retry every 2 seconds @@ -84,7 +83,7 @@ function waitForDevice() { while (!booted) { try { let result = ''; - yield exec.exec(`${adbPath} shell getprop sys.boot_completed`, [], { + yield exec.exec(`${ADB_PATH} shell getprop sys.boot_completed`, [], { listeners: { stdout: (data) => { result += data.toString(); diff --git a/lib/main.js b/lib/main.js index 57bb7e8da..644450cf5 100644 --- a/lib/main.js +++ b/lib/main.js @@ -20,6 +20,7 @@ const sdk_installer_1 = require("./sdk-installer"); const input_validator_1 = require("./input-validator"); const emulator_manager_1 = require("./emulator-manager"); const exec = __importStar(require("@actions/exec")); +const script_parser_1 = require("./script-parser"); function run() { return __awaiter(this, void 0, void 0, function* () { try { @@ -39,7 +40,7 @@ function run() { // CPU architecture of the system image const arch = core.getInput('arch'); input_validator_1.checkArch(arch); - console.log(`CPI architecture: ${arch}`); + console.log(`CPU architecture: ${arch}`); // Hardware profile used for creating the AVD const profile = core.getInput('profile'); console.log(`Hardware profile: ${profile}`); @@ -51,19 +52,31 @@ function run() { input_validator_1.checkDisableAnimations(disableAnimationsInput); const disableAnimations = disableAnimationsInput === 'true'; console.log(`disable animations: ${disableAnimations}`); - // custom scrpt to run - const script = core.getInput('script', { required: true }); + // custom script to run + const scriptInput = core.getInput('script', { required: true }); + const scripts = script_parser_1.parseScript(scriptInput); + console.log(`Script:`); + scripts.forEach((script) => __awaiter(this, void 0, void 0, function* () { + console.log(`${script}`); + })); 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); - // execute the custom script - yield exec.exec(`${script}`); } catch (error) { core.setFailed(error.message); } + // execute the custom script + scripts.forEach((script) => __awaiter(this, void 0, void 0, function* () { + try { + yield exec.exec(`${script}`); + } + catch (error) { + core.setFailed(error.message); + } + })); // finally kill the emulator yield emulator_manager_1.killEmulator(); } diff --git a/lib/script-parser.js b/lib/script-parser.js new file mode 100644 index 000000000..2ea283cef --- /dev/null +++ b/lib/script-parser.js @@ -0,0 +1,19 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * Convert a (potentially multi-line) script to an array of single-line script(s). + */ +function parseScript(rawScript) { + const scripts = rawScript + .trim() + .split(/\r\n|\n|\r/) + .map((value) => value.trim()) + .filter((value) => { + return !value.startsWith('#') && value.length > 0; + }); + if (scripts.length == 0) { + throw new Error(`No valid script found.`); + } + return scripts; +} +exports.parseScript = parseScript; diff --git a/src/emulator-manager.ts b/src/emulator-manager.ts index e25ff6315..49a33f3f3 100644 --- a/src/emulator-manager.ts +++ b/src/emulator-manager.ts @@ -49,7 +49,7 @@ export async function killEmulator(): Promise { try { await exec.exec(`${ADB_PATH} -s emulator-5554 emu kill`); } catch (error) { - console.log('No emulator running on port 5554'); + console.log(error.message); } } @@ -57,7 +57,6 @@ export async function killEmulator(): Promise { * Wait for emulator to boot. */ async function waitForDevice(): Promise { - const adbPath = `${process.env.ANDROID_HOME}/platform-tools/adb`; let booted = false; let attempts = 0; const retryInterval = 2; // retry every 2 seconds @@ -65,7 +64,7 @@ async function waitForDevice(): Promise { while (!booted) { try { let result = ''; - await exec.exec(`${adbPath} shell getprop sys.boot_completed`, [], { + await exec.exec(`${ADB_PATH} shell getprop sys.boot_completed`, [], { listeners: { stdout: (data: Buffer) => { result += data.toString(); diff --git a/src/main.ts b/src/main.ts index a145cc81d..dc4e7e9e7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,6 +3,7 @@ import { installAndroidSdk } from './sdk-installer'; import { checkApiLevel, checkTarget, checkArch, checkDisableAnimations } from './input-validator'; import { launchEmulator, killEmulator } from './emulator-manager'; import * as exec from '@actions/exec'; +import { parseScript } from './script-parser'; async function run() { try { @@ -25,7 +26,7 @@ async function run() { // CPU architecture of the system image const arch = core.getInput('arch'); checkArch(arch); - console.log(`CPI architecture: ${arch}`); + console.log(`CPU architecture: ${arch}`); // Hardware profile used for creating the AVD const profile = core.getInput('profile'); @@ -41,8 +42,13 @@ async function run() { const disableAnimations = disableAnimationsInput === 'true'; console.log(`disable animations: ${disableAnimations}`); - // custom scrpt to run - const script = core.getInput('script', { required: true }); + // custom script to run + const scriptInput = core.getInput('script', { required: true }); + const scripts = parseScript(scriptInput); + console.log(`Script:`); + scripts.forEach(async (script: string) => { + console.log(`${script}`); + }); try { // install SDK @@ -50,13 +56,19 @@ async function run() { // launch an emulator await launchEmulator(apiLevel, target, arch, profile, emulatorOptions, disableAnimations); - - // execute the custom script - await exec.exec(`${script}`); } catch (error) { core.setFailed(error.message); } + // execute the custom script + scripts.forEach(async (script: string) => { + try { + await exec.exec(`${script}`); + } catch (error) { + core.setFailed(error.message); + } + }); + // finally kill the emulator await killEmulator(); } catch (error) { diff --git a/src/script-parser.ts b/src/script-parser.ts new file mode 100644 index 000000000..813d36bd2 --- /dev/null +++ b/src/script-parser.ts @@ -0,0 +1,18 @@ +/** + * Convert a (potentially multi-line) script to an array of single-line script(s). + */ +export function parseScript(rawScript: string): Array { + const scripts: Array = rawScript + .trim() + .split(/\r\n|\n|\r/) + .map((value: string) => value.trim()) + .filter((value: string) => { + return !value.startsWith('#') && value.length > 0; + }); + + if (scripts.length == 0) { + throw new Error(`No valid script found.`); + } + + return scripts; +}