diff --git a/emulator-run-cmd/action.yml b/emulator-run-cmd/action.yml index f9e7a639..06d2653a 100644 --- a/emulator-run-cmd/action.yml +++ b/emulator-run-cmd/action.yml @@ -1,34 +1,37 @@ -name: 'Android Emulator Action combined with a cmd execution' -description: 'Starts Android emulator via existing Android SDK installation and executes a shell command in parallel' -author: 'Malinskiy' +name: "Android Emulator Action combined with a cmd execution" +description: "Starts Android emulator via existing Android SDK installation and executes a shell command in parallel" +author: "Malinskiy" inputs: api: - description: 'api version of the emulator' - default: '25' + description: "api version of the emulator" + default: "25" abi: - description: 'abi of the emulator' - default: 'x86' + description: "abi of the emulator" + default: "x86" tag: - description: 'default or google_apis' - default: 'default' + description: "default or google_apis" + default: "default" cmd: - description: 'cmd to execute while the emulator is running' - default: '' + description: "cmd to execute while the emulator is running" + default: "" disableAnimations: - description: '' - default: 'false' + description: "" + default: "false" cmdOptions: - description: 'additional emulator options passed to the emulator start cmd' - default: '-no-snapshot-save -noaudio -no-boot-anim' + description: "additional emulator options passed to the emulator start cmd" + default: "-no-snapshot-save -noaudio -no-boot-anim" hardwareProfile: - description: 'device profile to use. see avdmanager list for possible options' - default: '' + description: "device profile to use. see avdmanager list for possible options" + default: "" bootTimeout: - description: 'Boot emulator timeout (seconds)' - default: '600' + description: "Boot emulator timeout (seconds)" + default: "600" verbose: - description: 'be verbose' - default: 'false' + description: "be verbose" + default: "false" + cmdlineToolsVersion: + description: "version of cmdline-tools package to use" + default: "latest" runs: - using: 'node20' - main: 'lib/main.js' + using: "node20" + main: "lib/main.js" diff --git a/emulator-run-cmd/src/main.ts b/emulator-run-cmd/src/main.ts index 0125267e..9ac47a81 100644 --- a/emulator-run-cmd/src/main.ts +++ b/emulator-run-cmd/src/main.ts @@ -1,131 +1,147 @@ -import * as core from '@actions/core'; -import {InputOptions} from "@actions/core/lib/core"; -import {SdkFactory} from "./sdk"; +import * as core from "@actions/core"; +import { InputOptions } from "@actions/core/lib/core"; +import { SdkFactory } from "./sdk"; import execWithResult from "./exec-with-result"; async function run() { - try { - let api = core.getInput('api', {required: false}); - if (api == null || api == "") { - console.log(`API not set. Using 25`) - api = '25' - } + try { + let api = core.getInput("api", { required: false }); + if (api == null || api == "") { + console.log(`API not set. Using 25`); + api = "25"; + } - let abi = core.getInput('abi', {required: false}); - if (abi == null || abi == "") { - console.log(`ABI not set. Using armeabi-v7a`) - abi = 'armeabi-v7a' - } + let abi = core.getInput("abi", { required: false }); + if (abi == null || abi == "") { + console.log(`ABI not set. Using armeabi-v7a`); + abi = "armeabi-v7a"; + } - let tag = core.getInput('tag', {required: false}) - if (tag !== "default" && tag !== "google_apis") { - console.log(`Unknown tag ${tag}. Using default`) - tag = 'default' - } + let tag = core.getInput("tag", { required: false }); + if (tag !== "default" && tag !== "google_apis") { + console.log(`Unknown tag ${tag}. Using default`); + tag = "default"; + } - let verbose = false - if (core.getInput('verbose') == "true") { - verbose = true - } + let verbose = false; + if (core.getInput("verbose") == "true") { + verbose = true; + } - let cmd = core.getInput('cmd', {required: true}) - if (cmd === "") { - console.error("Please specify cmd to execute in parallel with emulator") - return - } + let cmd = core.getInput("cmd", { required: true }); + if (cmd === "") { + console.error("Please specify cmd to execute in parallel with emulator"); + return; + } - let cmdOptions = core.getInput('cmdOptions') - if (cmdOptions == null) { - cmdOptions = "-no-snapshot-save -noaudio -no-boot-anim" - } + let cmdOptions = core.getInput("cmdOptions"); + if (cmdOptions == null) { + cmdOptions = "-no-snapshot-save -noaudio -no-boot-anim"; + } - let hardwareProfile = core.getInput('hardwareProfile') - if (hardwareProfile == null) { - hardwareProfile = "" - } + let hardwareProfile = core.getInput("hardwareProfile"); + if (hardwareProfile == null) { + hardwareProfile = ""; + } - let disableAnimations = false - if (core.getInput('disableAnimations') == "true") { - disableAnimations = true - } + let disableAnimations = false; + if (core.getInput("disableAnimations") == "true") { + disableAnimations = true; + } - let bootTimeout = core.getInput('bootTimeout') - if (bootTimeout == null) { - bootTimeout = '600' - } + let bootTimeout = core.getInput("bootTimeout"); + if (bootTimeout == null) { + bootTimeout = "600"; + } + + let cmdlineToolsVersion = core.getInput("cmdlineToolsVersion"); + + console.log( + `Starting emulator with API=${api}, TAG=${tag} and ABI=${abi}...`, + ); - console.log(`Starting emulator with API=${api}, TAG=${tag} and ABI=${abi}...`) - - const androidHome = process.env.ANDROID_HOME - console.log(`ANDROID_HOME is ${androidHome}`) - console.log(`PATH is ${process.env.PATH}`) - - let sdk = new SdkFactory().getAndroidSdk(); - - try { - await sdk.installEmulatorPackage(api, tag, abi, verbose) - await sdk.installPlatform(api, verbose) - - let supportsHardwareAcceleration = await sdk.verifyHardwareAcceleration(); - if (!supportsHardwareAcceleration && abi == "x86") { - core.setFailed('Hardware acceleration is not supported') - return - } - - let emulator = await sdk.createEmulator("emulator", api, tag, abi, hardwareProfile); - console.log("starting adb server") - await sdk.startAdbServer() - let booted = await emulator.start(cmdOptions, +bootTimeout); - if (!booted) { - core.setFailed("emulator boot failed") - await emulator.stop() - return - } - - //Pre-setup - await emulator.unlock() - if (disableAnimations) { - await emulator.disableAnimations() - } - await emulator.startLogcat() - - console.log("emulator started and booted") - try { - let result = await execWithResult(`${cmd}`); - let code = result.exitCode; - if (code != 0) { - core.setFailed(`process exited with code ${code}`) - } - } catch (e) { - if(e !instanceof Error) { - core.setFailed(e.message); - } else { - core.setFailed("unknown (error !instanceof Error) occurred") - } - } - - console.log("stopping emulator") - await emulator.stop() - await emulator.stopLogcat() - console.log("emulator is stopped") - } catch (error) { - console.error(error) - if(error !instanceof Error) { - core.setFailed(error.message); - } else { - core.setFailed("unknown (error !instanceof Error) occurred") - } - return + const androidHome = process.env.ANDROID_HOME; + console.log(`ANDROID_HOME is ${androidHome}`); + console.log(`PATH is ${process.env.PATH}`); + + let sdk = new SdkFactory().getAndroidSdk(); + + try { + await sdk.installEmulatorPackage( + api, + tag, + abi, + verbose, + cmdlineToolsVersion, + ); + await sdk.installPlatform(api, verbose); + + let supportsHardwareAcceleration = await sdk.verifyHardwareAcceleration(); + if (!supportsHardwareAcceleration && abi == "x86") { + core.setFailed("Hardware acceleration is not supported"); + return; + } + + let emulator = await sdk.createEmulator( + "emulator", + api, + tag, + abi, + hardwareProfile, + ); + console.log("starting adb server"); + await sdk.startAdbServer(); + let booted = await emulator.start(cmdOptions, +bootTimeout); + if (!booted) { + core.setFailed("emulator boot failed"); + await emulator.stop(); + return; + } + + //Pre-setup + await emulator.unlock(); + if (disableAnimations) { + await emulator.disableAnimations(); + } + await emulator.startLogcat(); + + console.log("emulator started and booted"); + try { + let result = await execWithResult(`${cmd}`); + let code = result.exitCode; + if (code != 0) { + core.setFailed(`process exited with code ${code}`); } - } catch (error) { - if(error !instanceof Error) { - core.setFailed(error.message); + } catch (e) { + if (e! instanceof Error) { + core.setFailed(e.message); } else { - core.setFailed("unknown (error !instanceof Error) occurred") + core.setFailed("unknown (error !instanceof Error) occurred"); } + } - return + console.log("stopping emulator"); + await emulator.stop(); + await emulator.stopLogcat(); + console.log("emulator is stopped"); + } catch (error) { + console.error(error); + if (error! instanceof Error) { + core.setFailed(error.message); + } else { + core.setFailed("unknown (error !instanceof Error) occurred"); + } + return; + } + } catch (error) { + if (error! instanceof Error) { + core.setFailed(error.message); + } else { + core.setFailed("unknown (error !instanceof Error) occurred"); } + + return; + } } run(); diff --git a/emulator-run-cmd/src/sdk.ts b/emulator-run-cmd/src/sdk.ts index 81448689..5bb580e0 100644 --- a/emulator-run-cmd/src/sdk.ts +++ b/emulator-run-cmd/src/sdk.ts @@ -1,195 +1,286 @@ import * as core from "@actions/core"; -import execWithResult, {execIgnoreFailure} from "./exec-with-result"; +import execWithResult, { execIgnoreFailure } from "./exec-with-result"; import * as fs from "fs"; -import {writeFile} from "fs"; +import { writeFile } from "fs"; import * as util from "util"; -import {exec} from "@actions/exec/lib/exec"; -import {Emulator} from "./emulator"; +import { exec } from "@actions/exec/lib/exec"; +import { Emulator } from "./emulator"; -const ANDROID_TMP_PATH = "/tmp/android-sdk.zip" +const ANDROID_TMP_PATH = "/tmp/android-sdk.zip"; -let writeFileAsync = util.promisify(writeFile) +let writeFileAsync = util.promisify(writeFile); export interface AndroidSDK { - defaultSdkUrl: string + defaultSdkUrl: string; - install(url: string): Promise; + install(url: string, cmdlineToolsVersion: string): Promise; - androidHome(): string + androidHome(): string; - emulatorCmd(): string + emulatorCmd(): string; - acceptLicense(): Promise + acceptLicense(): Promise; - installEmulatorPackage(api: string, tag: string, abi: string, verbose: boolean): Promise + installEmulatorPackage( + api: string, + tag: string, + abi: string, + verbose: boolean, + cmdlineToolsVersion: string, + ): Promise; - installPlatform(api: string, verbose: boolean): Promise + installPlatform(api: string, verbose: boolean): Promise; - createEmulator(name: string, api: string, tag: string, abi: string, hardwareProfile: string): Promise + createEmulator( + name: string, + api: string, + tag: string, + abi: string, + hardwareProfile: string, + ): Promise; - listEmulators(): Promise + listEmulators(): Promise; - listRunningEmulators(): Promise> + listRunningEmulators(): Promise>; - startAdbServer(): Promise + startAdbServer(): Promise; - verifyHardwareAcceleration(): Promise + verifyHardwareAcceleration(): Promise; } export abstract class BaseAndroidSdk implements AndroidSDK { - abstract defaultSdkUrl: string + abstract defaultSdkUrl: string; - portCounter: number = 5554 + portCounter: number = 5554; - async install(url: string): Promise { - const ANDROID_HOME = this.androidHome() + async install(url: string, cmdlineToolsVersion: String): Promise { + const ANDROID_HOME = this.androidHome(); - let sdkUrl: string = url - if (sdkUrl == null || sdkUrl == "") { - sdkUrl = this.defaultSdkUrl - } - - if (fs.existsSync(`${process.env.HOME}/.android`)) { - await execWithResult(`mv ${process.env.HOME}/.android ${process.env.HOME}/.android.backup`) - } - - await execWithResult(`curl -L ${sdkUrl} -o ${ANDROID_TMP_PATH} -s`) - await execWithResult(`unzip -q ${ANDROID_TMP_PATH} -d ${ANDROID_HOME}`) - await execWithResult(`mv ${ANDROID_HOME}/cmdline-tools ${ANDROID_HOME}/cmdline-tools-tmp`) - await execWithResult(`mkdir -p ${ANDROID_HOME}/cmdline-tools`) - await execWithResult(`mv ${ANDROID_HOME}/cmdline-tools-tmp ${ANDROID_HOME}/cmdline-tools/bootstrap-version`) - - await execWithResult(`rm ${ANDROID_TMP_PATH}`) - await execWithResult(`mkdir -p ${ANDROID_HOME}/sdk_home`) - - core.exportVariable('ANDROID_HOME', `${ANDROID_HOME}`); - core.exportVariable('ANDROID_SDK_ROOT', `${ANDROID_HOME}`); - core.exportVariable('ANDROID_SDK_HOME', `${ANDROID_HOME}/sdk_home`); - - const PATH = process.env.PATH!! - let extraPaths = `${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/cmdline-tools/bootstrap-version/bin:${ANDROID_HOME}/bin:${ANDROID_HOME}/platform-tools:${ANDROID_HOME}/platform-tools/bin` - - let PATH_WITHOUT_ANDROID = PATH.split(':').filter(entry => { - return !entry.includes("Android") - }).join(':') - - core.exportVariable('PATH', `${extraPaths}:${PATH_WITHOUT_ANDROID}`) - return true - } - - androidHome(): string { - return `${process.env.ANDROID_HOME}` - } - - emulatorCmd(): string { - return `${this.androidHome()}/emulator/emulator`; - } - - async acceptLicense(): Promise { - await execIgnoreFailure(`mkdir -p ${this.androidHome()}/licenses`) - - await writeLicenseFile(`${this.androidHome()}/licenses/android-sdk-license`, "8933bad161af4178b1185d1a37fbf41ea5269c55\n" + - "d56f5187479451eabf01fb78af6dfcb131a6481e\n" + - "24333f8a63b6825ea9c5514f83c2829b004d1fee") - await writeLicenseFile(`${this.androidHome()}/licenses/android-sdk-preview-license`, "84831b9409646a918e30573bab4c9c91346d8abd\n") - await writeLicenseFile(`${this.androidHome()}/licenses/intel-android-extra-license`, "d975f751698a77b662f1254ddbeed3901e976f5a\n") - await writeLicenseFile(`${this.androidHome()}/licenses/mips-android-sysimage-license`, "e9acab5b5fbb560a72cfaecce8946896ff6aab9d\n") - await writeLicenseFile(`${this.androidHome()}/licenses/google-gdk-license`, "33b6a2b64607f11b759f320ef9dff4ae5c47d97a\n") - await writeLicenseFile(`${this.androidHome()}/licenses/android-googletv-license`, "601085b94cd77f0b54ff86406957099ebe79c4d6\n") - await writeLicenseFile(`${this.androidHome()}/licenses/android-sdk-arm-dbt-license`, "859f317696f67ef3d7f30a50a5560e7834b43903") - } - - async installEmulatorPackage(api: string, tag: string, abi: string, verbose: boolean): Promise { - let args = "" - if (!verbose) { - args += " > /dev/null" - } - - await execIgnoreFailure(`bash -c \\\"${this.androidHome()}/cmdline-tools/latest/bin/sdkmanager emulator platform-tools 'system-images;android-${api};${tag};${abi}'${args}"`); + let sdkUrl: string = url; + if (sdkUrl == null || sdkUrl == "") { + sdkUrl = this.defaultSdkUrl; } - async installPlatform(api: string, verbose: boolean): Promise { - let args = "" - if (!verbose) { - args += " > /dev/null" - } - - await execIgnoreFailure(`bash -c \\\"${this.androidHome()}/cmdline-tools/latest/bin/sdkmanager 'platforms;android-${api}'${args}"`) + if (fs.existsSync(`${process.env.HOME}/.android`)) { + await execWithResult(`rm -rf ${process.env.HOME}/.android.backup`); + await execWithResult( + `mv ${process.env.HOME}/.android ${process.env.HOME}/.android.backup`, + ); } - async createEmulator(name: string, api: string, tag: string, abi: string, hardwareProfile: string): Promise { - let additionalOptions = "" - if (hardwareProfile != null && hardwareProfile != "") { - additionalOptions += `--device ${hardwareProfile}` - } - - await execIgnoreFailure(`bash -c \\\"echo -n no | ${this.androidHome()}/cmdline-tools/latest/bin/avdmanager create avd -n ${name} --package \\\"system-images;android-${api};${tag};${abi}\\\" --tag ${tag}\" ${additionalOptions}`) - return new Emulator(this, name, api, abi, tag, this.portCounter++, this.portCounter++) + await execWithResult(`curl -L ${sdkUrl} -o ${ANDROID_TMP_PATH} -s`); + await execWithResult(`unzip -q ${ANDROID_TMP_PATH} -d ${ANDROID_HOME}`); + await execWithResult( + `mv ${ANDROID_HOME}/cmdline-tools ${ANDROID_HOME}/cmdline-tools-tmp`, + ); + await execWithResult(`mkdir -p ${ANDROID_HOME}/cmdline-tools`); + await execWithResult( + `mv ${ANDROID_HOME}/cmdline-tools-tmp ${ANDROID_HOME}/cmdline-tools/bootstrap-version`, + ); + + await execWithResult(`rm ${ANDROID_TMP_PATH}`); + await execWithResult(`mkdir -p ${ANDROID_HOME}/sdk_home`); + + core.exportVariable("ANDROID_HOME", `${ANDROID_HOME}`); + core.exportVariable("ANDROID_SDK_ROOT", `${ANDROID_HOME}`); + core.exportVariable("ANDROID_SDK_HOME", `${ANDROID_HOME}/sdk_home`); + + const PATH = process.env.PATH!!; + let extraPaths = `${ANDROID_HOME}/cmdline-tools/${cmdlineToolsVersion}/bin:${ANDROID_HOME}/cmdline-tools/bootstrap-version/bin:${ANDROID_HOME}/bin:${ANDROID_HOME}/platform-tools:${ANDROID_HOME}/platform-tools/bin`; + + let PATH_WITHOUT_ANDROID = PATH.split(":") + .filter((entry) => { + return !entry.includes("Android"); + }) + .join(":"); + + core.exportVariable("PATH", `${extraPaths}:${PATH_WITHOUT_ANDROID}`); + + await execIgnoreFailure( + `bash -c \\\"${this.androidHome()}/cmdline-tools/bootstrap-version/bin/sdkmanager 'cmdline-tools;${cmdlineToolsVersion}'`, + ); + + return true; + } + + androidHome(): string { + return `${process.env.HOME}/android-sdk`; + } + + emulatorCmd(): string { + return `${this.androidHome()}/emulator/emulator`; + } + + async acceptLicense(): Promise { + await execIgnoreFailure(`mkdir -p ${this.androidHome()}/licenses`); + + await writeLicenseFile( + `${this.androidHome()}/licenses/android-sdk-license`, + "8933bad161af4178b1185d1a37fbf41ea5269c55\n" + + "d56f5187479451eabf01fb78af6dfcb131a6481e\n" + + "24333f8a63b6825ea9c5514f83c2829b004d1fee", + ); + await writeLicenseFile( + `${this.androidHome()}/licenses/android-sdk-preview-license`, + "84831b9409646a918e30573bab4c9c91346d8abd\n", + ); + await writeLicenseFile( + `${this.androidHome()}/licenses/intel-android-extra-license`, + "d975f751698a77b662f1254ddbeed3901e976f5a\n", + ); + await writeLicenseFile( + `${this.androidHome()}/licenses/mips-android-sysimage-license`, + "e9acab5b5fbb560a72cfaecce8946896ff6aab9d\n", + ); + await writeLicenseFile( + `${this.androidHome()}/licenses/google-gdk-license`, + "33b6a2b64607f11b759f320ef9dff4ae5c47d97a\n", + ); + await writeLicenseFile( + `${this.androidHome()}/licenses/android-googletv-license`, + "601085b94cd77f0b54ff86406957099ebe79c4d6\n", + ); + await writeLicenseFile( + `${this.androidHome()}/licenses/android-sdk-arm-dbt-license`, + "859f317696f67ef3d7f30a50a5560e7834b43903", + ); + } + + async installEmulatorPackage( + api: string, + tag: string, + abi: string, + verbose: boolean, + cmdlineToolsVersion: string, + ): Promise { + let args = ""; + if (!verbose) { + args += " > /dev/null"; } - async verifyHardwareAcceleration(): Promise { - try { - let exitCode = await exec(`${this.emulatorCmd()} -accel-check`); - return exitCode == 0 - } catch (e) { - return false - } - } + await execIgnoreFailure( + `bash -c \\\"${this.androidHome()}/cmdline-tools/bootstrap-version/bin/sdkmanager emulator 'cmdline-tools;${cmdlineToolsVersion}' platform-tools 'system-images;android-${api};${tag};${abi}'${args}"`, + ); + } - async listEmulators(): Promise { - await execIgnoreFailure(`${this.emulatorCmd()} -list-avds`) + async installPlatform(api: string, verbose: boolean): Promise { + let args = ""; + if (!verbose) { + args += " > /dev/null"; } - async listRunningEmulators(): Promise> { - let output = await execIgnoreFailure(`${this.androidHome()}/platform-tools/adb devices`) - return await this.parseDevicesOutput(output); + await execIgnoreFailure( + `bash -c \\\"${this.androidHome()}/cmdline-tools/bootstrap-version/bin/sdkmanager 'platforms;android-${api}'${args}"`, + ); + } + + async createEmulator( + name: string, + api: string, + tag: string, + abi: string, + hardwareProfile: string, + ): Promise { + let additionalOptions = ""; + if (hardwareProfile != null && hardwareProfile != "") { + additionalOptions += `--device ${hardwareProfile}`; } - async parseDevicesOutput(output: string): Promise> { - let result = new Array() - - let lines = output.split(/\r?\n/); - for (let line in lines) { - if (line.startsWith("emulator")) { - let split = line.split(" "); - let serial = split[0]; - let port = serial.split("-")[1] - let nameOutput = await execIgnoreFailure(`${this.androidHome()}/platform-tools/adb adb -s ${serial} emu avd name`) - let nameLines = nameOutput.split(/\r?\n/); - let name = nameLines[0]; - - result.fill(new Emulator(this, name, "", "", "", parseInt(port), parseInt(port) + 1)) - } - } - return result; + await execIgnoreFailure( + `bash -c \\\"echo -n no | ${this.androidHome()}/cmdline-tools/bootstrap-version/bin/avdmanager create avd -n ${name} --package \\\"system-images;android-${api};${tag};${abi}\\\" --tag ${tag}\" ${additionalOptions}`, + ); + return new Emulator( + this, + name, + api, + abi, + tag, + this.portCounter++, + this.portCounter++, + ); + } + + async verifyHardwareAcceleration(): Promise { + try { + let exitCode = await exec(`${this.emulatorCmd()} -accel-check`); + return exitCode == 0; + } catch (e) { + return false; } - - async startAdbServer(): Promise { - await execIgnoreFailure(`${this.androidHome()}/platform-tools/adb start-server`) + } + + async listEmulators(): Promise { + await execIgnoreFailure(`${this.emulatorCmd()} -list-avds`); + } + + async listRunningEmulators(): Promise> { + let output = await execIgnoreFailure( + `${this.androidHome()}/platform-tools/adb devices`, + ); + return await this.parseDevicesOutput(output); + } + + async parseDevicesOutput(output: string): Promise> { + let result = new Array(); + + let lines = output.split(/\r?\n/); + for (let line in lines) { + if (line.startsWith("emulator")) { + let split = line.split(" "); + let serial = split[0]; + let port = serial.split("-")[1]; + let nameOutput = await execIgnoreFailure( + `${this.androidHome()}/platform-tools/adb adb -s ${serial} emu avd name`, + ); + let nameLines = nameOutput.split(/\r?\n/); + let name = nameLines[0]; + + result.fill( + new Emulator( + this, + name, + "", + "", + "", + parseInt(port), + parseInt(port) + 1, + ), + ); + } } + return result; + } + + async startAdbServer(): Promise { + await execIgnoreFailure( + `${this.androidHome()}/platform-tools/adb start-server`, + ); + } } class LinuxAndroidSdk extends BaseAndroidSdk { - defaultSdkUrl = "https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip" + defaultSdkUrl = + "https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip"; } class MacOSAndroidSdk extends BaseAndroidSdk { - defaultSdkUrl = "https://dl.google.com/android/repository/commandlinetools-mac-11076708_latest.zip" + defaultSdkUrl = + "https://dl.google.com/android/repository/commandlinetools-mac-11076708_latest.zip"; } export class SdkFactory { - getAndroidSdk(): AndroidSDK { - switch (process.platform) { - case "linux": - return new LinuxAndroidSdk() - case "darwin": - return new MacOSAndroidSdk() - default: - throw new Error("Unsupported OS") - } + getAndroidSdk(): AndroidSDK { + switch (process.platform) { + case "linux": + return new LinuxAndroidSdk(); + case "darwin": + return new MacOSAndroidSdk(); + default: + throw new Error("Unsupported OS"); } + } } async function writeLicenseFile(file: string, content: string) { - await writeFileAsync(file, content) + await writeFileAsync(file, content); } diff --git a/install-sdk/action.yml b/install-sdk/action.yml index 3571c169..a946ca61 100644 --- a/install-sdk/action.yml +++ b/install-sdk/action.yml @@ -1,13 +1,16 @@ -name: 'Install Android SDK action' -description: 'Install minimal Android SDK from url' -author: 'Malinskiy' +name: "Install Android SDK action" +description: "Install minimal Android SDK from url" +author: "Malinskiy" inputs: url: - description: 'url of Android SDK zip' - default: '' + description: "url of Android SDK zip" + default: "" acceptLicense: - description: 'do you accept all current Android licenses?' - default: 'yes' + description: "do you accept all current Android licenses?" + default: "yes" + cmdlineToolsVersion: + description: "version of cmdline-tools package to use" + default: "latest" runs: - using: 'node20' - main: 'lib/main.js' + using: "node20" + main: "lib/main.js" diff --git a/install-sdk/src/main.ts b/install-sdk/src/main.ts index ed671291..113f7617 100644 --- a/install-sdk/src/main.ts +++ b/install-sdk/src/main.ts @@ -1,34 +1,36 @@ -import * as core from '@actions/core'; -import {InputOptions} from "@actions/core/lib/core"; -import {SdkFactory} from "./sdk"; +import * as core from "@actions/core"; +import { InputOptions } from "@actions/core/lib/core"; +import { SdkFactory } from "./sdk"; async function run() { - try { - let url = core.getInput('url', {required: false}); - if (url == null || url == "") { - console.log(`Android SDK URL is not set. Using default`) - } - - let acceptLicense = core.getInput('acceptLicense') - if (acceptLicense !== "yes") { - core.setFailed('You can\'t use this unless you accept the Android SDK licenses') - return - } - let sdk = new SdkFactory().getAndroidSdk(); - console.log("Accepting Android SDK licenses") - await sdk.acceptLicense() - console.log("Installing Android Sdk") - await sdk.install(url) - - } catch (error) { - if(error !instanceof Error) { - core.setFailed(error.message); - } else { - core.setFailed("unknown (error !instanceof Error) occurred") - } + try { + let url = core.getInput("url", { required: false }); + if (url == null || url == "") { + console.log(`Android SDK URL is not set. Using default`); + } - return + let acceptLicense = core.getInput("acceptLicense"); + if (acceptLicense !== "yes") { + core.setFailed( + "You can't use this unless you accept the Android SDK licenses", + ); + return; } + let cmdlineToolsVersion = core.getInput("cmdlineToolsVersion"); + let sdk = new SdkFactory().getAndroidSdk(); + console.log("Accepting Android SDK licenses"); + await sdk.acceptLicense(); + console.log("Installing Android Sdk"); + await sdk.install(url, cmdlineToolsVersion); + } catch (error) { + if (error! instanceof Error) { + core.setFailed(error.message); + } else { + core.setFailed("unknown (error !instanceof Error) occurred"); + } + + return; + } } run(); diff --git a/install-sdk/src/sdk.ts b/install-sdk/src/sdk.ts index c7a291dc..5bb580e0 100644 --- a/install-sdk/src/sdk.ts +++ b/install-sdk/src/sdk.ts @@ -1,199 +1,286 @@ import * as core from "@actions/core"; -import execWithResult, {execIgnoreFailure} from "./exec-with-result"; +import execWithResult, { execIgnoreFailure } from "./exec-with-result"; import * as fs from "fs"; -import {writeFile} from "fs"; +import { writeFile } from "fs"; import * as util from "util"; -import {exec} from "@actions/exec/lib/exec"; -import {Emulator} from "./emulator"; +import { exec } from "@actions/exec/lib/exec"; +import { Emulator } from "./emulator"; -const ANDROID_TMP_PATH = "/tmp/android-sdk.zip" +const ANDROID_TMP_PATH = "/tmp/android-sdk.zip"; -let writeFileAsync = util.promisify(writeFile) +let writeFileAsync = util.promisify(writeFile); export interface AndroidSDK { - defaultSdkUrl: string + defaultSdkUrl: string; - install(url: string): Promise; + install(url: string, cmdlineToolsVersion: string): Promise; - androidHome(): string + androidHome(): string; - emulatorCmd(): string + emulatorCmd(): string; - acceptLicense(): Promise + acceptLicense(): Promise; - installEmulatorPackage(api: string, tag: string, abi: string, verbose: boolean): Promise + installEmulatorPackage( + api: string, + tag: string, + abi: string, + verbose: boolean, + cmdlineToolsVersion: string, + ): Promise; - installPlatform(api: string, verbose: boolean): Promise + installPlatform(api: string, verbose: boolean): Promise; - createEmulator(name: string, api: string, tag: string, abi: string, hardwareProfile: string): Promise + createEmulator( + name: string, + api: string, + tag: string, + abi: string, + hardwareProfile: string, + ): Promise; - listEmulators(): Promise + listEmulators(): Promise; - listRunningEmulators(): Promise> + listRunningEmulators(): Promise>; - startAdbServer(): Promise + startAdbServer(): Promise; - verifyHardwareAcceleration(): Promise + verifyHardwareAcceleration(): Promise; } export abstract class BaseAndroidSdk implements AndroidSDK { - abstract defaultSdkUrl: string + abstract defaultSdkUrl: string; - portCounter: number = 5554 + portCounter: number = 5554; - async install(url: string): Promise { - const ANDROID_HOME = this.androidHome() + async install(url: string, cmdlineToolsVersion: String): Promise { + const ANDROID_HOME = this.androidHome(); - let sdkUrl: string = url - if (sdkUrl == null || sdkUrl == "") { - sdkUrl = this.defaultSdkUrl - } - - if (fs.existsSync(`${process.env.HOME}/.android`)) { - await execWithResult(`rm -rf ${process.env.HOME}/.android.backup`) - await execWithResult(`mv ${process.env.HOME}/.android ${process.env.HOME}/.android.backup`) - } - - await execWithResult(`curl -L ${sdkUrl} -o ${ANDROID_TMP_PATH} -s`) - await execWithResult(`unzip -q ${ANDROID_TMP_PATH} -d ${ANDROID_HOME}`) - await execWithResult(`mv ${ANDROID_HOME}/cmdline-tools ${ANDROID_HOME}/cmdline-tools-tmp`) - await execWithResult(`mkdir -p ${ANDROID_HOME}/cmdline-tools`) - await execWithResult(`mv ${ANDROID_HOME}/cmdline-tools-tmp ${ANDROID_HOME}/cmdline-tools/bootstrap-version`) - - await execWithResult(`rm ${ANDROID_TMP_PATH}`) - await execWithResult(`mkdir -p ${ANDROID_HOME}/sdk_home`) - - core.exportVariable('ANDROID_HOME', `${ANDROID_HOME}`); - core.exportVariable('ANDROID_SDK_ROOT', `${ANDROID_HOME}`); - core.exportVariable('ANDROID_SDK_HOME', `${ANDROID_HOME}/sdk_home`); - - const PATH = process.env.PATH!! - let extraPaths = `${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/cmdline-tools/bootstrap-version/bin:${ANDROID_HOME}/bin:${ANDROID_HOME}/platform-tools:${ANDROID_HOME}/platform-tools/bin` - - let PATH_WITHOUT_ANDROID = PATH.split(':').filter(entry => { - return !entry.includes("Android") - }).join(':') - - core.exportVariable('PATH', `${extraPaths}:${PATH_WITHOUT_ANDROID}`) - - await execIgnoreFailure(`bash -c \\\"${this.androidHome()}/cmdline-tools/bootstrap-version/bin/sdkmanager 'cmdline-tools;latest'`) - - return true - } - - androidHome(): string { - return `${process.env.HOME}/android-sdk` + let sdkUrl: string = url; + if (sdkUrl == null || sdkUrl == "") { + sdkUrl = this.defaultSdkUrl; } - emulatorCmd(): string { - return `${this.androidHome()}/emulator/emulator`; + if (fs.existsSync(`${process.env.HOME}/.android`)) { + await execWithResult(`rm -rf ${process.env.HOME}/.android.backup`); + await execWithResult( + `mv ${process.env.HOME}/.android ${process.env.HOME}/.android.backup`, + ); } - async acceptLicense(): Promise { - await execIgnoreFailure(`mkdir -p ${this.androidHome()}/licenses`) - - await writeLicenseFile(`${this.androidHome()}/licenses/android-sdk-license`, "8933bad161af4178b1185d1a37fbf41ea5269c55\n" + - "d56f5187479451eabf01fb78af6dfcb131a6481e\n" + - "24333f8a63b6825ea9c5514f83c2829b004d1fee") - await writeLicenseFile(`${this.androidHome()}/licenses/android-sdk-preview-license`, "84831b9409646a918e30573bab4c9c91346d8abd\n") - await writeLicenseFile(`${this.androidHome()}/licenses/intel-android-extra-license`, "d975f751698a77b662f1254ddbeed3901e976f5a\n") - await writeLicenseFile(`${this.androidHome()}/licenses/mips-android-sysimage-license`, "e9acab5b5fbb560a72cfaecce8946896ff6aab9d\n") - await writeLicenseFile(`${this.androidHome()}/licenses/google-gdk-license`, "33b6a2b64607f11b759f320ef9dff4ae5c47d97a\n") - await writeLicenseFile(`${this.androidHome()}/licenses/android-googletv-license`, "601085b94cd77f0b54ff86406957099ebe79c4d6\n") - await writeLicenseFile(`${this.androidHome()}/licenses/android-sdk-arm-dbt-license`, "859f317696f67ef3d7f30a50a5560e7834b43903") + await execWithResult(`curl -L ${sdkUrl} -o ${ANDROID_TMP_PATH} -s`); + await execWithResult(`unzip -q ${ANDROID_TMP_PATH} -d ${ANDROID_HOME}`); + await execWithResult( + `mv ${ANDROID_HOME}/cmdline-tools ${ANDROID_HOME}/cmdline-tools-tmp`, + ); + await execWithResult(`mkdir -p ${ANDROID_HOME}/cmdline-tools`); + await execWithResult( + `mv ${ANDROID_HOME}/cmdline-tools-tmp ${ANDROID_HOME}/cmdline-tools/bootstrap-version`, + ); + + await execWithResult(`rm ${ANDROID_TMP_PATH}`); + await execWithResult(`mkdir -p ${ANDROID_HOME}/sdk_home`); + + core.exportVariable("ANDROID_HOME", `${ANDROID_HOME}`); + core.exportVariable("ANDROID_SDK_ROOT", `${ANDROID_HOME}`); + core.exportVariable("ANDROID_SDK_HOME", `${ANDROID_HOME}/sdk_home`); + + const PATH = process.env.PATH!!; + let extraPaths = `${ANDROID_HOME}/cmdline-tools/${cmdlineToolsVersion}/bin:${ANDROID_HOME}/cmdline-tools/bootstrap-version/bin:${ANDROID_HOME}/bin:${ANDROID_HOME}/platform-tools:${ANDROID_HOME}/platform-tools/bin`; + + let PATH_WITHOUT_ANDROID = PATH.split(":") + .filter((entry) => { + return !entry.includes("Android"); + }) + .join(":"); + + core.exportVariable("PATH", `${extraPaths}:${PATH_WITHOUT_ANDROID}`); + + await execIgnoreFailure( + `bash -c \\\"${this.androidHome()}/cmdline-tools/bootstrap-version/bin/sdkmanager 'cmdline-tools;${cmdlineToolsVersion}'`, + ); + + return true; + } + + androidHome(): string { + return `${process.env.HOME}/android-sdk`; + } + + emulatorCmd(): string { + return `${this.androidHome()}/emulator/emulator`; + } + + async acceptLicense(): Promise { + await execIgnoreFailure(`mkdir -p ${this.androidHome()}/licenses`); + + await writeLicenseFile( + `${this.androidHome()}/licenses/android-sdk-license`, + "8933bad161af4178b1185d1a37fbf41ea5269c55\n" + + "d56f5187479451eabf01fb78af6dfcb131a6481e\n" + + "24333f8a63b6825ea9c5514f83c2829b004d1fee", + ); + await writeLicenseFile( + `${this.androidHome()}/licenses/android-sdk-preview-license`, + "84831b9409646a918e30573bab4c9c91346d8abd\n", + ); + await writeLicenseFile( + `${this.androidHome()}/licenses/intel-android-extra-license`, + "d975f751698a77b662f1254ddbeed3901e976f5a\n", + ); + await writeLicenseFile( + `${this.androidHome()}/licenses/mips-android-sysimage-license`, + "e9acab5b5fbb560a72cfaecce8946896ff6aab9d\n", + ); + await writeLicenseFile( + `${this.androidHome()}/licenses/google-gdk-license`, + "33b6a2b64607f11b759f320ef9dff4ae5c47d97a\n", + ); + await writeLicenseFile( + `${this.androidHome()}/licenses/android-googletv-license`, + "601085b94cd77f0b54ff86406957099ebe79c4d6\n", + ); + await writeLicenseFile( + `${this.androidHome()}/licenses/android-sdk-arm-dbt-license`, + "859f317696f67ef3d7f30a50a5560e7834b43903", + ); + } + + async installEmulatorPackage( + api: string, + tag: string, + abi: string, + verbose: boolean, + cmdlineToolsVersion: string, + ): Promise { + let args = ""; + if (!verbose) { + args += " > /dev/null"; } - async installEmulatorPackage(api: string, tag: string, abi: string, verbose: boolean): Promise { - let args = "" - if (!verbose) { - args += " > /dev/null" - } + await execIgnoreFailure( + `bash -c \\\"${this.androidHome()}/cmdline-tools/bootstrap-version/bin/sdkmanager emulator 'cmdline-tools;${cmdlineToolsVersion}' platform-tools 'system-images;android-${api};${tag};${abi}'${args}"`, + ); + } - await execIgnoreFailure(`bash -c \\\"${this.androidHome()}/cmdline-tools/bootstrap-version/bin/sdkmanager emulator 'cmdline-tools;latest' platform-tools 'system-images;android-${api};${tag};${abi}'${args}"`); + async installPlatform(api: string, verbose: boolean): Promise { + let args = ""; + if (!verbose) { + args += " > /dev/null"; } - async installPlatform(api: string, verbose: boolean): Promise { - let args = "" - if (!verbose) { - args += " > /dev/null" - } - - await execIgnoreFailure(`bash -c \\\"${this.androidHome()}/cmdline-tools/bootstrap-version/bin/sdkmanager 'platforms;android-${api}'${args}"`) - } - - async createEmulator(name: string, api: string, tag: string, abi: string, hardwareProfile: string): Promise { - let additionalOptions = "" - if (hardwareProfile != null && hardwareProfile != "") { - additionalOptions += `--device ${hardwareProfile}` - } - - await execIgnoreFailure(`bash -c \\\"echo -n no | ${this.androidHome()}/cmdline-tools/bootstrap-version/bin/avdmanager create avd -n ${name} --package \\\"system-images;android-${api};${tag};${abi}\\\" --tag ${tag}\" ${additionalOptions}`) - return new Emulator(this, name, api, abi, tag, this.portCounter++, this.portCounter++) - } - - async verifyHardwareAcceleration(): Promise { - try { - let exitCode = await exec(`${this.emulatorCmd()} -accel-check`); - return exitCode == 0 - } catch (e) { - return false - } + await execIgnoreFailure( + `bash -c \\\"${this.androidHome()}/cmdline-tools/bootstrap-version/bin/sdkmanager 'platforms;android-${api}'${args}"`, + ); + } + + async createEmulator( + name: string, + api: string, + tag: string, + abi: string, + hardwareProfile: string, + ): Promise { + let additionalOptions = ""; + if (hardwareProfile != null && hardwareProfile != "") { + additionalOptions += `--device ${hardwareProfile}`; } - async listEmulators(): Promise { - await execIgnoreFailure(`${this.emulatorCmd()} -list-avds`) + await execIgnoreFailure( + `bash -c \\\"echo -n no | ${this.androidHome()}/cmdline-tools/bootstrap-version/bin/avdmanager create avd -n ${name} --package \\\"system-images;android-${api};${tag};${abi}\\\" --tag ${tag}\" ${additionalOptions}`, + ); + return new Emulator( + this, + name, + api, + abi, + tag, + this.portCounter++, + this.portCounter++, + ); + } + + async verifyHardwareAcceleration(): Promise { + try { + let exitCode = await exec(`${this.emulatorCmd()} -accel-check`); + return exitCode == 0; + } catch (e) { + return false; } - - async listRunningEmulators(): Promise> { - let output = await execIgnoreFailure(`${this.androidHome()}/platform-tools/adb devices`) - return await this.parseDevicesOutput(output); - } - - async parseDevicesOutput(output: string): Promise> { - let result = new Array() - - let lines = output.split(/\r?\n/); - for (let line in lines) { - if (line.startsWith("emulator")) { - let split = line.split(" "); - let serial = split[0]; - let port = serial.split("-")[1] - let nameOutput = await execIgnoreFailure(`${this.androidHome()}/platform-tools/adb adb -s ${serial} emu avd name`) - let nameLines = nameOutput.split(/\r?\n/); - let name = nameLines[0]; - - result.fill(new Emulator(this, name, "", "", "", parseInt(port), parseInt(port) + 1)) - } - } - return result; - } - - async startAdbServer(): Promise { - await execIgnoreFailure(`${this.androidHome()}/platform-tools/adb start-server`) + } + + async listEmulators(): Promise { + await execIgnoreFailure(`${this.emulatorCmd()} -list-avds`); + } + + async listRunningEmulators(): Promise> { + let output = await execIgnoreFailure( + `${this.androidHome()}/platform-tools/adb devices`, + ); + return await this.parseDevicesOutput(output); + } + + async parseDevicesOutput(output: string): Promise> { + let result = new Array(); + + let lines = output.split(/\r?\n/); + for (let line in lines) { + if (line.startsWith("emulator")) { + let split = line.split(" "); + let serial = split[0]; + let port = serial.split("-")[1]; + let nameOutput = await execIgnoreFailure( + `${this.androidHome()}/platform-tools/adb adb -s ${serial} emu avd name`, + ); + let nameLines = nameOutput.split(/\r?\n/); + let name = nameLines[0]; + + result.fill( + new Emulator( + this, + name, + "", + "", + "", + parseInt(port), + parseInt(port) + 1, + ), + ); + } } + return result; + } + + async startAdbServer(): Promise { + await execIgnoreFailure( + `${this.androidHome()}/platform-tools/adb start-server`, + ); + } } class LinuxAndroidSdk extends BaseAndroidSdk { - defaultSdkUrl = "https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip" + defaultSdkUrl = + "https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip"; } class MacOSAndroidSdk extends BaseAndroidSdk { - defaultSdkUrl = "https://dl.google.com/android/repository/commandlinetools-mac-11076708_latest.zip" + defaultSdkUrl = + "https://dl.google.com/android/repository/commandlinetools-mac-11076708_latest.zip"; } export class SdkFactory { - getAndroidSdk(): AndroidSDK { - switch (process.platform) { - case "linux": - return new LinuxAndroidSdk() - case "darwin": - return new MacOSAndroidSdk() - default: - throw new Error("Unsupported OS") - } + getAndroidSdk(): AndroidSDK { + switch (process.platform) { + case "linux": + return new LinuxAndroidSdk(); + case "darwin": + return new MacOSAndroidSdk(); + default: + throw new Error("Unsupported OS"); } + } } async function writeLicenseFile(file: string, content: string) { - await writeFileAsync(file, content) + await writeFileAsync(file, content); }