diff --git a/CHANGELOG.md b/CHANGELOG.md index ca195fe4..f9ae475c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## v2.23.0 + +* Update build tools to `32.0.0`. - [#212](https://github.com/ReactiveCircus/android-emulator-runner/pull/212) +* Update SDK command-line tools to `6.0`. - [#213](https://github.com/ReactiveCircus/android-emulator-runner/pull/213) +* Add option to specify `disk-size` for the AVD. - [#219](https://github.com/ReactiveCircus/android-emulator-runner/pull/219) @ViliusSutkus89. +* Improve logging by grouping log lines. - [#224](https://github.com/ReactiveCircus/android-emulator-runner/pull/224) @michaelkaye. + ## v2.22.0 * Add option to enable hardware keyboard. - [#209](https://github.com/ReactiveCircus/android-emulator-runner/pull/209) (upstreamed from the [Doist fork](https://github.com/Doist/android-emulator-runner/commit/4b6ca99f0d657662beca3eb0c22d8e254fbd5b31)). diff --git a/README.md b/README.md index 8de39b5b..0864cff0 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,7 @@ jobs: | `cores` | Optional | 2 | Number of cores to use for the emulator (`hw.cpu.ncore` in config.ini). | | `ram-size` | Optional | N/A | Size of RAM to use for this AVD, in KB or MB, denoted with K or M. - e.g. `2048M` | | `sdcard-path-or-size` | Optional | N/A | Path to the SD card image for this AVD or the size of a new SD card image to create for this AVD, in KB or MB, denoted with K or M. - e.g. `path/to/sdcard`, or `1000M`. | +| `disk-size` | Optional | N/A | Disk size to use for this AVD. Either in bytes or KB, MB or GB, when denoted with K, M or G. - e.g. `2048M` | | `avd-name` | Optional | `test` | Custom AVD name used for creating the Android Virtual Device. | | `force-avd-creation` | Optional | `true` | Whether to force create the AVD by overwriting an existing AVD with the same name as `avd-name` - `true` or `false`. | | `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`. | @@ -199,7 +200,7 @@ These are some of the open-source projects using (or used) **Android Emulator Ru - [hash-checker/hash-checker](https://github.com/hash-checker/hash-checker/tree/master/.github/workflows) - [hash-checker/hash-checker-lite](https://github.com/hash-checker/hash-checker-lite/tree/master/.github/workflows) - [Kiwix/kiwix-android](https://github.com/kiwix/kiwix-android/blob/develop/.github/workflows) -- [wikimedia/apps-android-wikipedia](https://github.com/wikimedia/apps-android-wikipedia/blob/master/.github/workflows) +- [wikimedia/apps-android-wikipedia](https://github.com/wikimedia/apps-android-wikipedia/blob/main/.github/workflows) - [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) diff --git a/__tests__/input-validator.test.ts b/__tests__/input-validator.test.ts index 8aeb6875..5c52a2c5 100644 --- a/__tests__/input-validator.test.ts +++ b/__tests__/input-validator.test.ts @@ -270,3 +270,54 @@ describe('emulator-build validator tests', () => { expect(func).not.toThrow(); }); }); + +describe('checkDiskSize validator tests', () => { + it('Empty size is acceptable, means default', () => { + const func = () => { + validator.checkDiskSize(''); + }; + expect(func).not.toThrow(); + }); + + it('Numbers means bytes', () => { + expect(() => { + validator.checkDiskSize('8000000000'); + }).not.toThrow(); + }); + + it('Uppercase size modifier', () => { + expect(() => { + validator.checkDiskSize('8000000K'); + }).not.toThrow(); + expect(() => { + validator.checkDiskSize('8000M'); + }).not.toThrow(); + expect(() => { + validator.checkDiskSize('8G'); + }).not.toThrow(); + }); + + it('Lowercase size modifier', () => { + expect(() => { + validator.checkDiskSize('8000000k'); + }).not.toThrow(); + expect(() => { + validator.checkDiskSize('8000m'); + }).not.toThrow(); + expect(() => { + validator.checkDiskSize('8g'); + }).not.toThrow(); + }); + + it('Modifier without a number is unacceptable', () => { + expect(() => { + validator.checkDiskSize('G'); + }).toThrowError(`Unexpected disk size: 'G'.`); + }); + + it('Double modifier is unacceptable', () => { + expect(() => { + validator.checkDiskSize('14gg'); + }).toThrowError(`Unexpected disk size: '14gg'.`); + }); +}); diff --git a/action.yml b/action.yml index 71467db5..e8b418f6 100644 --- a/action.yml +++ b/action.yml @@ -23,6 +23,8 @@ inputs: description: 'size of RAM to use for this AVD, in KB or MB, denoted with K or M. - e.g. `2048M`' sdcard-path-or-size: description: 'path to the SD card image for this AVD or the size of a new SD card image to create for this AVD, in KB or MB, denoted with K or M. - e.g. `path/to/sdcard`, or `1000M`' + disk-size: + description: 'disk size to use for this AVD. Either in bytes or KB, MB or GB, when denoted with K, M or G' avd-name: description: 'custom AVD name used for creating the Android Virtual Device' default: 'test' diff --git a/lib/emulator-manager.js b/lib/emulator-manager.js index 8dd880dc..564c812d 100644 --- a/lib/emulator-manager.js +++ b/lib/emulator-manager.js @@ -35,55 +35,64 @@ const EMULATOR_BOOT_TIMEOUT_SECONDS = 600; /** * Creates and launches a new AVD instance with the specified configurations. */ -function launchEmulator(apiLevel, target, arch, profile, cores, ramSize, sdcardPathOrSize, avdName, forceAvdCreation, emulatorOptions, disableAnimations, disableSpellChecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard) { +function launchEmulator(apiLevel, target, arch, profile, cores, ramSize, sdcardPathOrSize, diskSize, avdName, forceAvdCreation, emulatorOptions, disableAnimations, disableSpellChecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard) { return __awaiter(this, void 0, void 0, function* () { - // create a new AVD if AVD directory does not already exist or forceAvdCreation is true - const avdPath = `${process.env.ANDROID_AVD_HOME}/${avdName}.avd`; - if (!fs.existsSync(avdPath) || forceAvdCreation) { - const profileOption = profile.trim() !== '' ? `--device '${profile}'` : ''; - const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : ''; - console.log(`Creating AVD.`); - yield exec.exec(`sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --abi '${target}/${arch}' --package 'system-images;android-${apiLevel};${target};${arch}' ${profileOption} ${sdcardPathOrSizeOption}"`); - } - if (cores) { - yield exec.exec(`sh -c \\"printf 'hw.cpu.ncore=${cores}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`); - } - if (ramSize) { - yield exec.exec(`sh -c \\"printf 'hw.ramSize=${ramSize}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`); - } - if (enableHardwareKeyboard) { - yield exec.exec(`sh -c \\"printf 'hw.keyboard=yes\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`); - } - //turn off hardware acceleration on Linux - if (process.platform === 'linux' && disableLinuxHardwareAcceleration) { - console.log('Disabling Linux hardware acceleration.'); - emulatorOptions += ' -accel off'; - } - // start emulator - console.log('Starting emulator.'); - yield exec.exec(`sh -c \\"${process.env.ANDROID_SDK_ROOT}/emulator/emulator -avd "${avdName}" ${emulatorOptions} &"`, [], { - listeners: { - stderr: (data) => { - if (data.toString().includes('invalid command-line parameter')) { - throw new Error(data.toString()); + try { + console.log(`::group::Launch Emulator`); + // create a new AVD if AVD directory does not already exist or forceAvdCreation is true + const avdPath = `${process.env.ANDROID_AVD_HOME}/${avdName}.avd`; + if (!fs.existsSync(avdPath) || forceAvdCreation) { + const profileOption = profile.trim() !== '' ? `--device '${profile}'` : ''; + const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : ''; + console.log(`Creating AVD.`); + yield exec.exec(`sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --abi '${target}/${arch}' --package 'system-images;android-${apiLevel};${target};${arch}' ${profileOption} ${sdcardPathOrSizeOption}"`); + } + if (cores) { + yield exec.exec(`sh -c \\"printf 'hw.cpu.ncore=${cores}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`); + } + if (ramSize) { + yield exec.exec(`sh -c \\"printf 'hw.ramSize=${ramSize}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`); + } + if (enableHardwareKeyboard) { + yield exec.exec(`sh -c \\"printf 'hw.keyboard=yes\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`); + } + if (diskSize) { + yield exec.exec(`sh -c \\"printf 'disk.dataPartition.size=${diskSize}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`); + } + //turn off hardware acceleration on Linux + if (process.platform === 'linux' && disableLinuxHardwareAcceleration) { + console.log('Disabling Linux hardware acceleration.'); + emulatorOptions += ' -accel off'; + } + // start emulator + console.log('Starting emulator.'); + yield exec.exec(`sh -c \\"${process.env.ANDROID_SDK_ROOT}/emulator/emulator -avd "${avdName}" ${emulatorOptions} &"`, [], { + listeners: { + stderr: (data) => { + if (data.toString().includes('invalid command-line parameter')) { + throw new Error(data.toString()); + } } } + }); + // wait for emulator to complete booting + yield waitForDevice(); + yield exec.exec(`adb shell input keyevent 82`); + if (disableAnimations) { + console.log('Disabling animations.'); + yield exec.exec(`adb shell settings put global window_animation_scale 0.0`); + yield exec.exec(`adb shell settings put global transition_animation_scale 0.0`); + yield exec.exec(`adb shell settings put global animator_duration_scale 0.0`); + } + if (disableSpellChecker) { + yield exec.exec(`adb shell settings put secure spell_checker_enabled 0`); + } + if (enableHardwareKeyboard) { + yield exec.exec(`adb shell settings put secure show_ime_with_hard_keyboard 0`); } - }); - // wait for emulator to complete booting - yield waitForDevice(); - yield exec.exec(`adb shell input keyevent 82`); - if (disableAnimations) { - console.log('Disabling animations.'); - yield exec.exec(`adb shell settings put global window_animation_scale 0.0`); - yield exec.exec(`adb shell settings put global transition_animation_scale 0.0`); - yield exec.exec(`adb shell settings put global animator_duration_scale 0.0`); } - if (disableSpellChecker) { - yield exec.exec(`adb shell settings put secure spell_checker_enabled 0`); - } - if (enableHardwareKeyboard) { - yield exec.exec(`adb shell settings put secure show_ime_with_hard_keyboard 0`); + finally { + console.log(`::endgroup::`); } }); } @@ -94,11 +103,15 @@ exports.launchEmulator = launchEmulator; function killEmulator() { return __awaiter(this, void 0, void 0, function* () { try { + console.log(`::group::Terminate Emulator`); yield exec.exec(`adb -s emulator-5554 emu kill`); } catch (error) { console.log(error.message); } + finally { + console.log(`::endgroup::`); + } }); } exports.killEmulator = killEmulator; diff --git a/lib/input-validator.js b/lib/input-validator.js index 2dcda715..a7bbabe4 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.checkEnableHardwareKeyboard = 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.checkDiskSize = exports.checkEmulatorBuild = exports.checkEnableHardwareKeyboard = 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', 'aosp_atd', 'google_atd', 'google_apis_playstore', 'android-wear', 'android-wear-cn', 'android-tv', 'google-tv']; exports.VALID_ARCHS = ['x86', 'x86_64', 'arm64-v8a']; @@ -71,3 +71,20 @@ exports.checkEmulatorBuild = checkEmulatorBuild; function isValidBoolean(value) { return value === 'true' || value === 'false'; } +function checkDiskSize(diskSize) { + // Disk size can be empty - the default value + if (diskSize) { + // Can also be number of bytes + if (isNaN(Number(diskSize)) || !Number.isInteger(Number(diskSize))) { + // Disk size can have a size multiplier at the end K, M or G + const diskSizeUpperCase = diskSize.toUpperCase(); + if (diskSizeUpperCase.endsWith('K') || diskSizeUpperCase.endsWith('M') || diskSizeUpperCase.endsWith('G')) { + const diskSizeNoModifier = diskSize.slice(0, -1); + if (0 == diskSizeNoModifier.length || isNaN(Number(diskSizeNoModifier)) || !Number.isInteger(Number(diskSizeNoModifier))) { + throw new Error(`Unexpected disk size: '${diskSize}'.`); + } + } + } + } +} +exports.checkDiskSize = checkDiskSize; diff --git a/lib/main.js b/lib/main.js index 2a274461..59e6aca0 100644 --- a/lib/main.js +++ b/lib/main.js @@ -38,6 +38,7 @@ const channel_id_mapper_1 = require("./channel-id-mapper"); function run() { return __awaiter(this, void 0, void 0, function* () { try { + console.log(`::group::Configure emulator`); // only support running on macOS or Linux if (process.platform !== 'darwin') { if (process.platform === 'linux') { @@ -73,6 +74,9 @@ function run() { // SD card path or size used for creating the AVD const sdcardPathOrSize = core.getInput('sdcard-path-or-size'); console.log(`SD card path or size: ${sdcardPathOrSize}`); + const diskSize = core.getInput('disk-size'); + input_validator_1.checkDiskSize(diskSize); + console.log(`Disk size: ${diskSize}`); // custom name used for creating the AVD const avdName = core.getInput('avd-name'); console.log(`AVD name: ${avdName}`); @@ -141,10 +145,11 @@ function run() { scripts.forEach((script) => __awaiter(this, void 0, void 0, function* () { console.log(`${script}`); })); + console.log(`::endgroup::`); // install SDK 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, enableHardwareKeyboard); + yield emulator_manager_1.launchEmulator(apiLevel, target, arch, profile, cores, ramSize, sdcardPathOrSize, diskSize, avdName, forceAvdCreation, emulatorOptions, disableAnimations, disableSpellchecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard); // execute the custom script try { // move to custom working directory if set diff --git a/lib/sdk-installer.js b/lib/sdk-installer.js index 348ac87b..c9c8145c 100644 --- a/lib/sdk-installer.js +++ b/lib/sdk-installer.js @@ -34,54 +34,60 @@ const exec = __importStar(require("@actions/exec")); const io = __importStar(require("@actions/io")); const tc = __importStar(require("@actions/tool-cache")); const fs = __importStar(require("fs")); -const BUILD_TOOLS_VERSION = '31.0.0'; -const CMDLINE_TOOLS_URL_MAC = 'https://dl.google.com/android/repository/commandlinetools-mac-7583922_latest.zip'; -const CMDLINE_TOOLS_URL_LINUX = 'https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip'; +const BUILD_TOOLS_VERSION = '32.0.0'; +const CMDLINE_TOOLS_URL_MAC = 'https://dl.google.com/android/repository/commandlinetools-mac-8092744_latest.zip'; +const CMDLINE_TOOLS_URL_LINUX = 'https://dl.google.com/android/repository/commandlinetools-linux-8092744_latest.zip'; /** * 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, channelId, emulatorBuild, ndkVersion, cmakeVersion) { return __awaiter(this, void 0, void 0, function* () { - const isOnMac = process.platform === 'darwin'; - if (!isOnMac) { - yield exec.exec(`sh -c \\"sudo chown $USER:$USER ${process.env.ANDROID_SDK_ROOT} -R`); + try { + console.log(`::group::Install Android SDK`); + const isOnMac = process.platform === 'darwin'; + if (!isOnMac) { + yield exec.exec(`sh -c \\"sudo chown $USER:$USER ${process.env.ANDROID_SDK_ROOT} -R`); + } + const cmdlineToolsPath = `${process.env.ANDROID_SDK_ROOT}/cmdline-tools`; + if (!fs.existsSync(cmdlineToolsPath)) { + console.log('Installing new cmdline-tools.'); + const sdkUrl = isOnMac ? CMDLINE_TOOLS_URL_MAC : CMDLINE_TOOLS_URL_LINUX; + const downloadPath = yield tc.downloadTool(sdkUrl); + yield tc.extractZip(downloadPath, cmdlineToolsPath); + yield io.mv(`${cmdlineToolsPath}/cmdline-tools`, `${cmdlineToolsPath}/latest`); + } + // add paths for commandline-tools and platform-tools + core.addPath(`${cmdlineToolsPath}/latest:${cmdlineToolsPath}/latest/bin:${process.env.ANDROID_SDK_ROOT}/platform-tools`); + // set standard AVD path + core.exportVariable('ANDROID_AVD_HOME', `${process.env.HOME}/.android/avd`); + // accept all Android SDK licenses + yield exec.exec(`sh -c \\"yes | sdkmanager --licenses > /dev/null"`); + 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 --channel=${channelId} > /dev/null"`); + if (emulatorBuild) { + console.log(`Installing emulator build ${emulatorBuild}.`); + // TODO find out the correct download URLs for all build ids + const downloadUrlSuffix = Number(emulatorBuild.charAt(0)) > 6 ? `_x64-${emulatorBuild}` : `-${emulatorBuild}`; + yield exec.exec(`curl -fo emulator.zip https://dl.google.com/android/repository/emulator-${isOnMac ? 'darwin' : 'linux'}${downloadUrlSuffix}.zip`); + yield exec.exec(`unzip -o -q emulator.zip -d ${process.env.ANDROID_SDK_ROOT}`); + yield io.rmRF('emulator.zip'); + } + console.log('Installing system images.'); + 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}' --channel=${channelId} > /dev/null"`); + } + if (cmakeVersion) { + console.log(`Installing CMake ${cmakeVersion}.`); + yield exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' --channel=${channelId} > /dev/null"`); + } } - const cmdlineToolsPath = `${process.env.ANDROID_SDK_ROOT}/cmdline-tools`; - if (!fs.existsSync(cmdlineToolsPath)) { - console.log('Installing new cmdline-tools.'); - const sdkUrl = isOnMac ? CMDLINE_TOOLS_URL_MAC : CMDLINE_TOOLS_URL_LINUX; - const downloadPath = yield tc.downloadTool(sdkUrl); - yield tc.extractZip(downloadPath, cmdlineToolsPath); - yield io.mv(`${cmdlineToolsPath}/cmdline-tools`, `${cmdlineToolsPath}/latest`); - } - // add paths for commandline-tools and platform-tools - core.addPath(`${cmdlineToolsPath}/latest:${cmdlineToolsPath}/latest/bin:${process.env.ANDROID_SDK_ROOT}/platform-tools`); - // set standard AVD path - core.exportVariable('ANDROID_AVD_HOME', `${process.env.HOME}/.android/avd`); - // accept all Android SDK licenses - yield exec.exec(`sh -c \\"yes | sdkmanager --licenses > /dev/null"`); - 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 --channel=${channelId} > /dev/null"`); - if (emulatorBuild) { - console.log(`Installing emulator build ${emulatorBuild}.`); - // TODO find out the correct download URLs for all build ids - const downloadUrlSuffix = Number(emulatorBuild.charAt(0)) > 6 ? `_x64-${emulatorBuild}` : `-${emulatorBuild}`; - yield exec.exec(`curl -fo emulator.zip https://dl.google.com/android/repository/emulator-${isOnMac ? 'darwin' : 'linux'}${downloadUrlSuffix}.zip`); - yield exec.exec(`unzip -o -q emulator.zip -d ${process.env.ANDROID_SDK_ROOT}`); - yield io.rmRF('emulator.zip'); - } - console.log('Installing system images.'); - 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}' --channel=${channelId} > /dev/null"`); - } - if (cmakeVersion) { - console.log(`Installing CMake ${cmakeVersion}.`); - yield exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' --channel=${channelId} > /dev/null"`); + finally { + console.log(`::endgroup::`); } }); } diff --git a/src/emulator-manager.ts b/src/emulator-manager.ts index fe56ee9b..6ee89614 100644 --- a/src/emulator-manager.ts +++ b/src/emulator-manager.ts @@ -14,6 +14,7 @@ export async function launchEmulator( cores: string, ramSize: string, sdcardPathOrSize: string, + diskSize: string, avdName: string, forceAvdCreation: boolean, emulatorOptions: string, @@ -22,63 +23,72 @@ export async function launchEmulator( disableLinuxHardwareAcceleration: boolean, enableHardwareKeyboard: boolean ): Promise { - // create a new AVD if AVD directory does not already exist or forceAvdCreation is true - const avdPath = `${process.env.ANDROID_AVD_HOME}/${avdName}.avd`; - if (!fs.existsSync(avdPath) || forceAvdCreation) { - const profileOption = profile.trim() !== '' ? `--device '${profile}'` : ''; - const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : ''; - console.log(`Creating AVD.`); - await exec.exec( - `sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --abi '${target}/${arch}' --package 'system-images;android-${apiLevel};${target};${arch}' ${profileOption} ${sdcardPathOrSizeOption}"` - ); - } + try { + console.log(`::group::Launch Emulator`); + // create a new AVD if AVD directory does not already exist or forceAvdCreation is true + const avdPath = `${process.env.ANDROID_AVD_HOME}/${avdName}.avd`; + if (!fs.existsSync(avdPath) || forceAvdCreation) { + const profileOption = profile.trim() !== '' ? `--device '${profile}'` : ''; + const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : ''; + console.log(`Creating AVD.`); + await exec.exec( + `sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --abi '${target}/${arch}' --package 'system-images;android-${apiLevel};${target};${arch}' ${profileOption} ${sdcardPathOrSizeOption}"` + ); + } - if (cores) { - await exec.exec(`sh -c \\"printf 'hw.cpu.ncore=${cores}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`); - } + if (cores) { + await exec.exec(`sh -c \\"printf 'hw.cpu.ncore=${cores}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`); + } - if (ramSize) { - await exec.exec(`sh -c \\"printf 'hw.ramSize=${ramSize}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`); - } + if (ramSize) { + await exec.exec(`sh -c \\"printf 'hw.ramSize=${ramSize}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`); + } - if (enableHardwareKeyboard) { - await exec.exec(`sh -c \\"printf 'hw.keyboard=yes\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`); - } + if (enableHardwareKeyboard) { + await exec.exec(`sh -c \\"printf 'hw.keyboard=yes\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`); + } - //turn off hardware acceleration on Linux - if (process.platform === 'linux' && disableLinuxHardwareAcceleration) { - console.log('Disabling Linux hardware acceleration.'); - emulatorOptions += ' -accel off'; - } + if (diskSize) { + await exec.exec(`sh -c \\"printf 'disk.dataPartition.size=${diskSize}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`); + } - // start emulator - console.log('Starting emulator.'); + //turn off hardware acceleration on Linux + if (process.platform === 'linux' && disableLinuxHardwareAcceleration) { + console.log('Disabling Linux hardware acceleration.'); + emulatorOptions += ' -accel off'; + } - await exec.exec(`sh -c \\"${process.env.ANDROID_SDK_ROOT}/emulator/emulator -avd "${avdName}" ${emulatorOptions} &"`, [], { - listeners: { - stderr: (data: Buffer) => { - if (data.toString().includes('invalid command-line parameter')) { - throw new Error(data.toString()); + // start emulator + console.log('Starting emulator.'); + + await exec.exec(`sh -c \\"${process.env.ANDROID_SDK_ROOT}/emulator/emulator -avd "${avdName}" ${emulatorOptions} &"`, [], { + listeners: { + stderr: (data: Buffer) => { + if (data.toString().includes('invalid command-line parameter')) { + throw new Error(data.toString()); + } } } + }); + + // wait for emulator to complete booting + await waitForDevice(); + await exec.exec(`adb shell input keyevent 82`); + + if (disableAnimations) { + console.log('Disabling animations.'); + await exec.exec(`adb shell settings put global window_animation_scale 0.0`); + await exec.exec(`adb shell settings put global transition_animation_scale 0.0`); + await exec.exec(`adb shell settings put global animator_duration_scale 0.0`); } - }); - - // wait for emulator to complete booting - await waitForDevice(); - await exec.exec(`adb shell input keyevent 82`); - - if (disableAnimations) { - console.log('Disabling animations.'); - await exec.exec(`adb shell settings put global window_animation_scale 0.0`); - await exec.exec(`adb shell settings put global transition_animation_scale 0.0`); - await exec.exec(`adb shell settings put global animator_duration_scale 0.0`); - } - if (disableSpellChecker) { - await exec.exec(`adb shell settings put secure spell_checker_enabled 0`); - } - if (enableHardwareKeyboard) { - await exec.exec(`adb shell settings put secure show_ime_with_hard_keyboard 0`); + if (disableSpellChecker) { + await exec.exec(`adb shell settings put secure spell_checker_enabled 0`); + } + if (enableHardwareKeyboard) { + await exec.exec(`adb shell settings put secure show_ime_with_hard_keyboard 0`); + } + } finally { + console.log(`::endgroup::`); } } @@ -87,9 +97,12 @@ export async function launchEmulator( */ export async function killEmulator(): Promise { try { + console.log(`::group::Terminate Emulator`); await exec.exec(`adb -s emulator-5554 emu kill`); } catch (error) { console.log(error.message); + } finally { + console.log(`::endgroup::`); } } diff --git a/src/input-validator.ts b/src/input-validator.ts index ac74762d..cdafa7f1 100644 --- a/src/input-validator.ts +++ b/src/input-validator.ts @@ -69,3 +69,20 @@ export function checkEmulatorBuild(emulatorBuild: string): void { function isValidBoolean(value: string): boolean { return value === 'true' || value === 'false'; } + +export function checkDiskSize(diskSize: string): void { + // Disk size can be empty - the default value + if (diskSize) { + // Can also be number of bytes + if (isNaN(Number(diskSize)) || !Number.isInteger(Number(diskSize))) { + // Disk size can have a size multiplier at the end K, M or G + const diskSizeUpperCase = diskSize.toUpperCase(); + if (diskSizeUpperCase.endsWith('K') || diskSizeUpperCase.endsWith('M') || diskSizeUpperCase.endsWith('G')) { + const diskSizeNoModifier: string = diskSize.slice(0, -1); + if (0 == diskSizeNoModifier.length || isNaN(Number(diskSizeNoModifier)) || !Number.isInteger(Number(diskSizeNoModifier))) { + throw new Error(`Unexpected disk size: '${diskSize}'.`); + } + } + } + } +} diff --git a/src/main.ts b/src/main.ts index e167b93c..325d1f14 100644 --- a/src/main.ts +++ b/src/main.ts @@ -10,7 +10,8 @@ import { checkDisableLinuxHardwareAcceleration, checkForceAvdCreation, checkChannel, - checkEnableHardwareKeyboard + checkEnableHardwareKeyboard, + checkDiskSize } from './input-validator'; import { launchEmulator, killEmulator } from './emulator-manager'; import * as exec from '@actions/exec'; @@ -19,6 +20,7 @@ import { getChannelId } from './channel-id-mapper'; async function run() { try { + console.log(`::group::Configure emulator`); // only support running on macOS or Linux if (process.platform !== 'darwin') { if (process.platform === 'linux') { @@ -63,6 +65,10 @@ async function run() { const sdcardPathOrSize = core.getInput('sdcard-path-or-size'); console.log(`SD card path or size: ${sdcardPathOrSize}`); + const diskSize = core.getInput('disk-size'); + checkDiskSize(diskSize); + console.log(`Disk size: ${diskSize}`); + // custom name used for creating the AVD const avdName = core.getInput('avd-name'); console.log(`AVD name: ${avdName}`); @@ -143,6 +149,7 @@ async function run() { scripts.forEach(async (script: string) => { console.log(`${script}`); }); + console.log(`::endgroup::`); // install SDK await installAndroidSdk(apiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion); @@ -156,6 +163,7 @@ async function run() { cores, ramSize, sdcardPathOrSize, + diskSize, avdName, forceAvdCreation, emulatorOptions, diff --git a/src/sdk-installer.ts b/src/sdk-installer.ts index 57de8e89..8778829e 100644 --- a/src/sdk-installer.ts +++ b/src/sdk-installer.ts @@ -4,63 +4,68 @@ import * as io from '@actions/io'; import * as tc from '@actions/tool-cache'; import * as fs from 'fs'; -const BUILD_TOOLS_VERSION = '31.0.0'; -const CMDLINE_TOOLS_URL_MAC = 'https://dl.google.com/android/repository/commandlinetools-mac-7583922_latest.zip'; -const CMDLINE_TOOLS_URL_LINUX = 'https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip'; +const BUILD_TOOLS_VERSION = '32.0.0'; +const CMDLINE_TOOLS_URL_MAC = 'https://dl.google.com/android/repository/commandlinetools-mac-8092744_latest.zip'; +const CMDLINE_TOOLS_URL_LINUX = 'https://dl.google.com/android/repository/commandlinetools-linux-8092744_latest.zip'; /** * 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, channelId: number, emulatorBuild?: string, ndkVersion?: string, cmakeVersion?: string): Promise { - const isOnMac = process.platform === 'darwin'; + try { + console.log(`::group::Install Android SDK`); + const isOnMac = process.platform === 'darwin'; - if (!isOnMac) { - await exec.exec(`sh -c \\"sudo chown $USER:$USER ${process.env.ANDROID_SDK_ROOT} -R`); - } + if (!isOnMac) { + await exec.exec(`sh -c \\"sudo chown $USER:$USER ${process.env.ANDROID_SDK_ROOT} -R`); + } - const cmdlineToolsPath = `${process.env.ANDROID_SDK_ROOT}/cmdline-tools`; - if (!fs.existsSync(cmdlineToolsPath)) { - console.log('Installing new cmdline-tools.'); - const sdkUrl = isOnMac ? CMDLINE_TOOLS_URL_MAC : CMDLINE_TOOLS_URL_LINUX; - const downloadPath = await tc.downloadTool(sdkUrl); - await tc.extractZip(downloadPath, cmdlineToolsPath); - await io.mv(`${cmdlineToolsPath}/cmdline-tools`, `${cmdlineToolsPath}/latest`); - } + const cmdlineToolsPath = `${process.env.ANDROID_SDK_ROOT}/cmdline-tools`; + if (!fs.existsSync(cmdlineToolsPath)) { + console.log('Installing new cmdline-tools.'); + const sdkUrl = isOnMac ? CMDLINE_TOOLS_URL_MAC : CMDLINE_TOOLS_URL_LINUX; + const downloadPath = await tc.downloadTool(sdkUrl); + await tc.extractZip(downloadPath, cmdlineToolsPath); + await io.mv(`${cmdlineToolsPath}/cmdline-tools`, `${cmdlineToolsPath}/latest`); + } - // add paths for commandline-tools and platform-tools - core.addPath(`${cmdlineToolsPath}/latest:${cmdlineToolsPath}/latest/bin:${process.env.ANDROID_SDK_ROOT}/platform-tools`); + // add paths for commandline-tools and platform-tools + core.addPath(`${cmdlineToolsPath}/latest:${cmdlineToolsPath}/latest/bin:${process.env.ANDROID_SDK_ROOT}/platform-tools`); - // set standard AVD path - core.exportVariable('ANDROID_AVD_HOME', `${process.env.HOME}/.android/avd`); + // set standard AVD path + core.exportVariable('ANDROID_AVD_HOME', `${process.env.HOME}/.android/avd`); - // accept all Android SDK licenses - await exec.exec(`sh -c \\"yes | sdkmanager --licenses > /dev/null"`); + // accept all Android SDK licenses + await exec.exec(`sh -c \\"yes | sdkmanager --licenses > /dev/null"`); - console.log('Installing latest build tools, platform tools, and platform.'); + console.log('Installing latest build tools, platform tools, and platform.'); - await exec.exec(`sh -c \\"sdkmanager --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools 'platforms;android-${apiLevel}' > /dev/null"`); + 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 --channel=${channelId} > /dev/null"`); + console.log('Installing latest emulator.'); + await 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 - const downloadUrlSuffix = Number(emulatorBuild.charAt(0)) > 6 ? `_x64-${emulatorBuild}` : `-${emulatorBuild}`; - await exec.exec(`curl -fo emulator.zip https://dl.google.com/android/repository/emulator-${isOnMac ? 'darwin' : 'linux'}${downloadUrlSuffix}.zip`); - await exec.exec(`unzip -o -q emulator.zip -d ${process.env.ANDROID_SDK_ROOT}`); - await io.rmRF('emulator.zip'); - } - console.log('Installing system images.'); - await exec.exec(`sh -c \\"sdkmanager --install 'system-images;android-${apiLevel};${target};${arch}' --channel=${channelId} > /dev/null"`); + if (emulatorBuild) { + console.log(`Installing emulator build ${emulatorBuild}.`); + // TODO find out the correct download URLs for all build ids + const downloadUrlSuffix = Number(emulatorBuild.charAt(0)) > 6 ? `_x64-${emulatorBuild}` : `-${emulatorBuild}`; + await exec.exec(`curl -fo emulator.zip https://dl.google.com/android/repository/emulator-${isOnMac ? 'darwin' : 'linux'}${downloadUrlSuffix}.zip`); + await exec.exec(`unzip -o -q emulator.zip -d ${process.env.ANDROID_SDK_ROOT}`); + await io.rmRF('emulator.zip'); + } + console.log('Installing system images.'); + 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}' --channel=${channelId} > /dev/null"`); - } - if (cmakeVersion) { - console.log(`Installing CMake ${cmakeVersion}.`); - await exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' --channel=${channelId} > /dev/null"`); + if (ndkVersion) { + console.log(`Installing NDK ${ndkVersion}.`); + 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}' --channel=${channelId} > /dev/null"`); + } + } finally { + console.log(`::endgroup::`); } } diff --git a/test-fixture/app/build.gradle b/test-fixture/app/build.gradle index a07d7aa0..bde6caed 100644 --- a/test-fixture/app/build.gradle +++ b/test-fixture/app/build.gradle @@ -2,13 +2,13 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { - compileSdkVersion 31 - buildToolsVersion "31.0.0" + compileSdkVersion 32 + buildToolsVersion "32.0.0" defaultConfig { applicationId "com.example.testapp" minSdkVersion 15 - targetSdkVersion 31 + targetSdkVersion 32 versionCode 1 versionName "1.0"