From 1427331a6274c220bcf6b1160145cd7803a32418 Mon Sep 17 00:00:00 2001 From: Yang Date: Sat, 28 Aug 2021 19:15:33 +1000 Subject: [PATCH] Support choosing channel to download SDK components from. --- .github/workflows/{workflow.yml => main.yml} | 0 .github/workflows/manually.yml | 77 ++++++++++++++++++++ README.md | 5 +- __tests__/channel-id-mapper.test.ts | 17 +++++ __tests__/input-validator.test.ts | 31 ++++++++ action.yml | 3 + lib/channel-id-mapper.js | 21 ++++++ lib/input-validator.js | 9 ++- lib/main.js | 8 +- lib/sdk-installer.js | 10 +-- src/channel-id-mapper.ts | 13 ++++ src/input-validator.ts | 7 ++ src/main.ts | 12 ++- src/sdk-installer.ts | 10 +-- 14 files changed, 206 insertions(+), 17 deletions(-) rename .github/workflows/{workflow.yml => main.yml} (100%) create mode 100644 .github/workflows/manually.yml create mode 100644 __tests__/channel-id-mapper.test.ts create mode 100644 lib/channel-id-mapper.js create mode 100644 src/channel-id-mapper.ts diff --git a/.github/workflows/workflow.yml b/.github/workflows/main.yml similarity index 100% rename from .github/workflows/workflow.yml rename to .github/workflows/main.yml diff --git a/.github/workflows/manually.yml b/.github/workflows/manually.yml new file mode 100644 index 00000000..0f2ecae5 --- /dev/null +++ b/.github/workflows/manually.yml @@ -0,0 +1,77 @@ +name: Manually triggered workflow +on: + workflow_dispatch: + inputs: + os: + description: 'OS' + required: true + default: 'macos-latest' + api-level: + description: 'API level of the platform and system image' + required: true + default: '30' + target: + description: 'CPU architecture of the system image - x86, x86_64 or arm64-v8a' + required: true + default: 'default' + os: + description: 'OS' + required: true + default: 'macos-latest' + emulator-options: + description: 'command-line options used when launching the emulator' + default: '-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim' + emulator-build: + description: 'build number of a specific version of the emulator binary to use' + channel: + description: 'Channel to download the SDK components from - `stable`, `beta`, `dev`, `canary`' + default: 'stable' + script: + description: 'custom script to run - e.g. `./gradlew connectedCheck`' + required: true + default: './gradlew connectedDebugAndroidTest' + +jobs: + test: + runs-on: ${{ github.event.inputs.os }} + env: + JAVA_TOOL_OPTIONS: -Xmx4g + timeout-minutes: 15 + + steps: + - name: checkout + uses: actions/checkout@v2 + + - name: validate gradle wrapper + uses: gradle/wrapper-validation-action@v1 + + - name: build, test and lint + run: | + npm install + npm run build + npm run lint + npm test + + - name: Java 15 + uses: actions/setup-java@v1 + with: + java-version: 15 + - uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }} + + - name: run action + uses: ./ + with: + api-level: ${{ github.event.inputs.api-level }} + target: ${{ github.event.inputs.target }} + arch: ${{ github.event.inputs.arch }} + profile: Galaxy Nexus + emulator-options: ${{ github.event.inputs.emulator-options }} + emulator-build: ${{ github.event.inputs.emulator-build }} + channel: ${{ github.event.inputs.channel }} + working-directory: ./test-fixture/ + script: ${{ github.event.inputs.script }} diff --git a/README.md b/README.md index f1aacd14..30c2e45d 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ jobs: | **Input** | **Required** | **Default** | **Description** | |-|-|-|-| | `api-level` | Required | N/A | API level of the platform system image - e.g. 23 for Android Marshmallow, 29 for Android 10. **Minimum API level supported is 15**. | -| `target` | Optional | `default` | Target of the system image - `default`, `google_apis`, `google_apis_playstore`, `android-wear`, `android-wear-cn`, `android-tv` or `google-tv`. | +| `target` | Optional | `default` | Target of the system image - `default`, `google_apis` or `playstore`. | | `arch` | Optional | `x86` | CPU architecture of the system image - `x86`, `x86_64` or `arm64-v8a`. Note that `x86_64` image is only available for API 21+. `arm64-v8a` images require Android 4.2+ and are limited to fewer API levels (e.g. 30). | | `profile` | Optional | N/A | Hardware profile used for creating the AVD - e.g. `Nexus 6`. For a list of all profiles available, run `avdmanager list` and refer to the results under "Available Android Virtual Devices". | | `cores` | Optional | 2 | Number of cores to use for the emulator (`hw.cpu.ncore` in config.ini). | @@ -159,6 +159,7 @@ jobs: | `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` | | `cmake` | Optional | N/A | Version of CMake to install - e.g. `3.10.2.4988404` | +| `channel` | Optional | stable | Channel to download the SDK components from - `stable`, `beta`, `dev`, `canary` | | `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 -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim`. @@ -201,7 +202,5 @@ These are some of the open-source projects using (or used) **Android Emulator Ru - [google/android-fhir](https://github.com/google/android-fhir/tree/master/.github/workflows) - [google/accompanist](https://github.com/google/accompanist/blob/main/.github/workflows) - [dotanuki-labs/norris](https://github.com/dotanuki-labs/norris/blob/master/.github/workflows/main.yml) -- [TiagoMSSantos/MobileRT](https://github.com/TiagoMSSantos/MobileRT/blob/master/.github/workflows/android.yml) -- [realm/realm-js](https://github.com/realm/realm-js/blob/master/.github/workflows/integration-tests.yml) 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. diff --git a/__tests__/channel-id-mapper.test.ts b/__tests__/channel-id-mapper.test.ts new file mode 100644 index 00000000..bbdf7278 --- /dev/null +++ b/__tests__/channel-id-mapper.test.ts @@ -0,0 +1,17 @@ +import * as mapper from '../src/channel-id-mapper'; + +describe('channel id mapper tests', () => { + it('Throws if channelName is unknown', () => { + const func = () => { + mapper.getChannelId('unknown-channel'); + }; + expect(func).toThrowError(`Unexpected channel name: 'unknown-channel'.`); + }); + + it('Returns expected channelId from channelName', () => { + expect(mapper.getChannelId('stable')).toBe(0); + expect(mapper.getChannelId('beta')).toBe(1); + expect(mapper.getChannelId('dev')).toBe(2); + expect(mapper.getChannelId('canary')).toBe(3); + }); +}); diff --git a/__tests__/input-validator.test.ts b/__tests__/input-validator.test.ts index 7d761c59..a5a2a22d 100644 --- a/__tests__/input-validator.test.ts +++ b/__tests__/input-validator.test.ts @@ -102,6 +102,37 @@ describe('arch validator tests', () => { }); }); +describe('channel validator tests', () => { + it('Throws if channel is unknown', () => { + const func = () => { + validator.checkChannel('some-channel'); + }; + expect(func).toThrowError(`Value for input.channel 'some-channel' is unknown. Supported options: ${validator.VALID_CHANNELS}`); + }); + + it('Validates successfully with valid channel', () => { + const func1 = () => { + validator.checkChannel('stable'); + }; + expect(func1).not.toThrow(); + + const func2 = () => { + validator.checkChannel('beta'); + }; + expect(func2).not.toThrow(); + + const func3 = () => { + validator.checkChannel('dev'); + }; + expect(func3).not.toThrow(); + + const func4 = () => { + validator.checkChannel('canary'); + }; + expect(func4).not.toThrow(); + }); +}); + describe('force-avd-creation validator tests', () => { it('Throws if force-avd-creation is not a boolean', () => { const func = () => { diff --git a/action.yml b/action.yml index ffe6e01d..1ffadf86 100644 --- a/action.yml +++ b/action.yml @@ -49,6 +49,9 @@ inputs: description: 'version of NDK to install - e.g. 21.0.6113669' cmake: description: 'version of CMake to install - e.g. 3.10.2.4988404' + channel: + description: 'Channel to download the SDK components from - `stable`, `beta`, `dev`, `canary`' + default: 'stable' script: description: 'custom script to run - e.g. `./gradlew connectedCheck`' required: true diff --git a/lib/channel-id-mapper.js b/lib/channel-id-mapper.js new file mode 100644 index 00000000..befb5cfd --- /dev/null +++ b/lib/channel-id-mapper.js @@ -0,0 +1,21 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getChannelId = void 0; +function getChannelId(channelName) { + if (channelName === 'stable') { + return 0; + } + else if (channelName === 'beta') { + return 1; + } + else if (channelName === 'dev') { + return 2; + } + else if (channelName === 'canary') { + return 3; + } + else { + throw new Error(`Unexpected channel name: '${channelName}'.`); + } +} +exports.getChannelId = getChannelId; diff --git a/lib/input-validator.js b/lib/input-validator.js index 206738fb..b069cc95 100644 --- a/lib/input-validator.js +++ b/lib/input-validator.js @@ -1,9 +1,10 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -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; +exports.checkEmulatorBuild = exports.checkDisableLinuxHardwareAcceleration = exports.checkDisableSpellchecker = exports.checkDisableAnimations = exports.checkForceAvdCreation = exports.checkChannel = exports.checkArch = exports.checkTarget = exports.checkApiLevel = exports.VALID_CHANNELS = 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', 'android-wear', 'android-wear-cn', 'android-tv', 'google-tv']; exports.VALID_ARCHS = ['x86', 'x86_64', 'arm64-v8a']; +exports.VALID_CHANNELS = ['stable', 'beta', 'dev', 'canary']; function checkApiLevel(apiLevel) { if (isNaN(Number(apiLevel)) || !Number.isInteger(Number(apiLevel))) { throw new Error(`Unexpected API level: '${apiLevel}'.`); @@ -25,6 +26,12 @@ function checkArch(arch) { } } exports.checkArch = checkArch; +function checkChannel(channel) { + if (!exports.VALID_CHANNELS.includes(channel)) { + throw new Error(`Value for input.channel '${channel}' is unknown. Supported options: ${exports.VALID_CHANNELS}.`); + } +} +exports.checkChannel = checkChannel; function checkForceAvdCreation(forceAvdCreation) { if (!isValidBoolean(forceAvdCreation)) { throw new Error(`Input for input.force-avd-creation should be either 'true' or 'false'.`); diff --git a/lib/main.js b/lib/main.js index 83e37b64..b74dcc32 100644 --- a/lib/main.js +++ b/lib/main.js @@ -34,6 +34,7 @@ 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"); +const channel_id_mapper_1 = require("./channel-id-mapper"); function run() { return __awaiter(this, void 0, void 0, function* () { try { @@ -123,6 +124,11 @@ function run() { console.log(`version of CMake to install: ${cmakeInput}`); } const cmakeVersion = !cmakeInput ? undefined : cmakeInput; + // channelId (up to and including) to download the SDK packages from + const channelName = core.getInput('channel'); + input_validator_1.checkChannel(channelName); + const channelId = channel_id_mapper_1.getChannelId(channelName); + console.log(`Channel: ${channelId} (${channelName})`); // custom script to run const scriptInput = core.getInput('script', { required: true }); const scripts = script_parser_1.parseScript(scriptInput); @@ -131,7 +137,7 @@ function run() { console.log(`${script}`); })); // install SDK - yield sdk_installer_1.installAndroidSdk(apiLevel, target, arch, emulatorBuild, ndkVersion, cmakeVersion); + yield sdk_installer_1.installAndroidSdk(apiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion); // launch an emulator yield emulator_manager_1.launchEmulator(apiLevel, target, arch, profile, cores, ramSize, sdcardPathOrSize, avdName, forceAvdCreation, emulatorOptions, disableAnimations, disableSpellchecker, disableLinuxHardwareAcceleration); // execute the custom script diff --git a/lib/sdk-installer.js b/lib/sdk-installer.js index 80c02a4d..348ac87b 100644 --- a/lib/sdk-installer.js +++ b/lib/sdk-installer.js @@ -41,7 +41,7 @@ const CMDLINE_TOOLS_URL_LINUX = 'https://dl.google.com/android/repository/comman * 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, emulatorBuild, ndkVersion, cmakeVersion) { +function installAndroidSdk(apiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion) { return __awaiter(this, void 0, void 0, function* () { const isOnMac = process.platform === 'darwin'; if (!isOnMac) { @@ -64,7 +64,7 @@ function installAndroidSdk(apiLevel, target, arch, emulatorBuild, ndkVersion, cm console.log('Installing latest build tools, platform tools, and platform.'); yield exec.exec(`sh -c \\"sdkmanager --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools 'platforms;android-${apiLevel}' > /dev/null"`); console.log('Installing latest emulator.'); - yield exec.exec(`sh -c \\"sdkmanager --install emulator > /dev/null"`); + yield exec.exec(`sh -c \\"sdkmanager --install emulator --channel=${channelId} > /dev/null"`); if (emulatorBuild) { console.log(`Installing emulator build ${emulatorBuild}.`); // TODO find out the correct download URLs for all build ids @@ -74,14 +74,14 @@ function installAndroidSdk(apiLevel, target, arch, emulatorBuild, ndkVersion, cm yield io.rmRF('emulator.zip'); } console.log('Installing system images.'); - yield exec.exec(`sh -c \\"sdkmanager --install 'system-images;android-${apiLevel};${target};${arch}' > /dev/null"`); + yield exec.exec(`sh -c \\"sdkmanager --install 'system-images;android-${apiLevel};${target};${arch}' --channel=${channelId} > /dev/null"`); if (ndkVersion) { console.log(`Installing NDK ${ndkVersion}.`); - yield exec.exec(`sh -c \\"sdkmanager --install 'ndk;${ndkVersion}' > /dev/null"`); + yield exec.exec(`sh -c \\"sdkmanager --install 'ndk;${ndkVersion}' --channel=${channelId} > /dev/null"`); } if (cmakeVersion) { console.log(`Installing CMake ${cmakeVersion}.`); - yield exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' > /dev/null"`); + yield exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' --channel=${channelId} > /dev/null"`); } }); } diff --git a/src/channel-id-mapper.ts b/src/channel-id-mapper.ts new file mode 100644 index 00000000..fbc0ca3b --- /dev/null +++ b/src/channel-id-mapper.ts @@ -0,0 +1,13 @@ +export function getChannelId(channelName: string): number { + if (channelName === 'stable') { + return 0; + } else if (channelName === 'beta') { + return 1; + } else if (channelName === 'dev') { + return 2; + } else if (channelName === 'canary') { + return 3; + } else { + throw new Error(`Unexpected channel name: '${channelName}'.`); + } +} diff --git a/src/input-validator.ts b/src/input-validator.ts index ee8d26dd..fabf61b9 100644 --- a/src/input-validator.ts +++ b/src/input-validator.ts @@ -1,6 +1,7 @@ export const MIN_API_LEVEL = 15; export const VALID_TARGETS: Array = ['default', 'google_apis', 'google_apis_playstore', 'android-wear', 'android-wear-cn', 'android-tv', 'google-tv']; export const VALID_ARCHS: Array = ['x86', 'x86_64', 'arm64-v8a']; +export const VALID_CHANNELS: Array = ['stable', 'beta', 'dev', 'canary']; export function checkApiLevel(apiLevel: string): void { if (isNaN(Number(apiLevel)) || !Number.isInteger(Number(apiLevel))) { @@ -23,6 +24,12 @@ export function checkArch(arch: string): void { } } +export function checkChannel(channel: string): void { + if (!VALID_CHANNELS.includes(channel)) { + throw new Error(`Value for input.channel '${channel}' is unknown. Supported options: ${VALID_CHANNELS}.`); + } +} + export function checkForceAvdCreation(forceAvdCreation: string): void { if (!isValidBoolean(forceAvdCreation)) { throw new Error(`Input for input.force-avd-creation should be either 'true' or 'false'.`); diff --git a/src/main.ts b/src/main.ts index 80e1cee4..98df4331 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,11 +8,13 @@ import { checkEmulatorBuild, checkDisableSpellchecker, checkDisableLinuxHardwareAcceleration, - checkForceAvdCreation + checkForceAvdCreation, + checkChannel } from './input-validator'; import { launchEmulator, killEmulator } from './emulator-manager'; import * as exec from '@actions/exec'; import { parseScript } from './script-parser'; +import { getChannelId } from './channel-id-mapper'; async function run() { try { @@ -121,6 +123,12 @@ async function run() { } const cmakeVersion = !cmakeInput ? undefined : cmakeInput; + // channelId (up to and including) to download the SDK packages from + const channelName = core.getInput('channel'); + checkChannel(channelName); + const channelId = getChannelId(channelName); + console.log(`Channel: ${channelId} (${channelName})`); + // custom script to run const scriptInput = core.getInput('script', { required: true }); const scripts = parseScript(scriptInput); @@ -130,7 +138,7 @@ async function run() { }); // install SDK - await installAndroidSdk(apiLevel, target, arch, emulatorBuild, ndkVersion, cmakeVersion); + await installAndroidSdk(apiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion); // launch an emulator await launchEmulator( diff --git a/src/sdk-installer.ts b/src/sdk-installer.ts index 47971622..57de8e89 100644 --- a/src/sdk-installer.ts +++ b/src/sdk-installer.ts @@ -12,7 +12,7 @@ const CMDLINE_TOOLS_URL_LINUX = 'https://dl.google.com/android/repository/comman * 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, emulatorBuild?: string, ndkVersion?: string, cmakeVersion?: string): Promise { +export async function installAndroidSdk(apiLevel: number, target: string, arch: string, channelId: number, emulatorBuild?: string, ndkVersion?: string, cmakeVersion?: string): Promise { const isOnMac = process.platform === 'darwin'; if (!isOnMac) { @@ -42,7 +42,7 @@ export async function installAndroidSdk(apiLevel: number, target: string, arch: await exec.exec(`sh -c \\"sdkmanager --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools 'platforms;android-${apiLevel}' > /dev/null"`); console.log('Installing latest emulator.'); - await exec.exec(`sh -c \\"sdkmanager --install emulator > /dev/null"`); + await exec.exec(`sh -c \\"sdkmanager --install emulator --channel=${channelId} > /dev/null"`); if (emulatorBuild) { console.log(`Installing emulator build ${emulatorBuild}.`); @@ -53,14 +53,14 @@ export async function installAndroidSdk(apiLevel: number, target: string, arch: await io.rmRF('emulator.zip'); } console.log('Installing system images.'); - await exec.exec(`sh -c \\"sdkmanager --install 'system-images;android-${apiLevel};${target};${arch}' > /dev/null"`); + await exec.exec(`sh -c \\"sdkmanager --install 'system-images;android-${apiLevel};${target};${arch}' --channel=${channelId} > /dev/null"`); if (ndkVersion) { console.log(`Installing NDK ${ndkVersion}.`); - await exec.exec(`sh -c \\"sdkmanager --install 'ndk;${ndkVersion}' > /dev/null"`); + await exec.exec(`sh -c \\"sdkmanager --install 'ndk;${ndkVersion}' --channel=${channelId} > /dev/null"`); } if (cmakeVersion) { console.log(`Installing CMake ${cmakeVersion}.`); - await exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' > /dev/null"`); + await exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' --channel=${channelId} > /dev/null"`); } }