From 20eae2ba1dd1313a9b9d742d7e197060fded8086 Mon Sep 17 00:00:00 2001 From: Ryan Carrier Date: Fri, 3 May 2024 13:57:33 +1000 Subject: [PATCH 1/3] Update README to have more robust avd caching; avoiding issues like #392 and #362 --- README.md | 64 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 15a44b9a5..6e2b987e6 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A GitHub Action for installing, configuring and running hardware-accelerated And The old ARM-based emulators were slow and are no longer supported by Google. The modern Intel Atom (x86 and x86_64) emulators can be fast, but rely on two forms of hardware acceleration to reach their peak potential: [Graphics Acceleration](https://developer.android.com/studio/run/emulator-acceleration#accel-graphics), e.g. `emulator -gpu host` and [Virtual Machine(VM) Acceleration](https://developer.android.com/studio/run/emulator-acceleration#accel-vm), e.g. `emulator -accel on`. **Note:** GPU and VM Acceleration are two different and non-mutually exclusive forms of Hardware Acceleration. -This presents a challenge when running emulators on CI especially when running emulators within a docker container, because **Nested Virtualization** must be supported by the host VM which isn't the case for most cloud-based CI providers due to infrastructural limits. If you want to learn more about Emulators on CI, here's an article [Yang](https://github.com/ychescale9) wrote: [Running Android Instrumented Tests on CI](https://dev.to/ychescale9/running-android-emulators-on-ci-from-bitrise-io-to-github-actions-3j76). +This presents a challenge when running emulators on CI especially when running emulators within a docker container, because **Nested Virtualization** must be supported by the host VM which isn't the case for most cloud-based CI providers due to infrastructural limits. If you want to learn more about Emulators on CI, here's an article [Yang](https://github.com/ychescale9) wrote: [Running Android Instrumented Tests on CI](https://dev.to/ychescale9/running-android-emulators-on-ci-from-bitrise-io-to-github-actions-3j76). ## Running hardware accelerated emulators on Linux runners @@ -133,6 +133,8 @@ jobs: strategy: matrix: api-level: [21, 23, 29] + target: [default, google_apis] + arch: [x86_64] steps: - name: checkout uses: actions/checkout@v4 @@ -145,7 +147,7 @@ jobs: - name: Gradle cache uses: gradle/actions/setup-gradle@v3 - + - name: AVD cache uses: actions/cache@v4 id: avd-cache @@ -153,13 +155,15 @@ jobs: path: | ~/.android/avd/* ~/.android/adb* - key: avd-${{ matrix.api-level }} + key: avd-${{ matrix.api-level }}-${{ matrix.target }}-${{ matrix.arch }} - name: create AVD and generate snapshot for caching if: steps.avd-cache.outputs.cache-hit != 'true' uses: reactivecircus/android-emulator-runner@v2 with: api-level: ${{ matrix.api-level }} + target: ${{ matrix.target }} + arch: ${{ matrix.arch }} force-avd-creation: false emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: false @@ -169,6 +173,8 @@ jobs: uses: reactivecircus/android-emulator-runner@v2 with: api-level: ${{ matrix.api-level }} + target: ${{ matrix.target }} + arch: ${{ matrix.arch }} force-avd-creation: false emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true @@ -177,32 +183,32 @@ jobs: ## Configurations -| **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`, `playstore`, `android-wear`, `android-wear-cn`, `android-tv`, `google-tv`, `aosp_atd` or `google_atd`. Note that `aosp_atd` and `google_atd` currently require the following: `api-level: 30`, `arch: x86` or `arch: arm64-v8` and `channel: canary`. | -| `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 device`. | -| `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` | -| `heap-size` | Optional | N/A | Heap size to use for this AVD, in KB or MB, denoted with K or M. - e.g. `512M` | -| `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, or partition 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-boot-timeout` | Optional | `600` | Emulator boot timeout in seconds. If it takes longer to boot, the action would fail - e.g. `300` for 5 minutes. | -| `emulator-options` | Optional | See below | Command-line options used when launching the emulator (replacing all default options) - e.g. `-no-window -no-snapshot -camera-back emulated`. | -| `disable-animations` | Optional | `true` | Whether to disable animations - `true` or `false`. | -| `disable-spellchecker` | Optional | `false` | Whether to disable spellchecker - `true` or `false`. | -| `disable-linux-hw-accel` | Optional | `auto` | Whether to disable hardware acceleration on Linux machines - `true`, `false` or `auto`.| -| `enable-hw-keyboard` | Optional | `false` | Whether to enable hardware keyboard - `true` or `false`. | -| `emulator-build` | Optional | N/A | Build number of a specific version of the emulator binary to use e.g. `6061023` for emulator v29.3.0.0. | -| `working-directory` | Optional | `./` | A custom working directory - e.g. `./android` if your root Gradle project is under the `./android` sub-directory within your repository. Will be used for `script` & `pre-emulator-launch-script`. | -| `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` | -| `pre-emulator-launch-script` | Optional | N/A | Custom script to run after creating the AVD and before launching the emulator - e.g. `./adjust-emulator-configs.sh` | +| **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`, `playstore`, `android-wear`, `android-wear-cn`, `android-tv`, `google-tv`, `aosp_atd` or `google_atd`. Note that `aosp_atd` and `google_atd` currently require the following: `api-level: 30`, `arch: x86` or `arch: arm64-v8` and `channel: canary`. | +| `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 device`. | +| `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` | +| `heap-size` | Optional | N/A | Heap size to use for this AVD, in KB or MB, denoted with K or M. - e.g. `512M` | +| `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, or partition 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-boot-timeout` | Optional | `600` | Emulator boot timeout in seconds. If it takes longer to boot, the action would fail - e.g. `300` for 5 minutes. | +| `emulator-options` | Optional | See below | Command-line options used when launching the emulator (replacing all default options) - e.g. `-no-window -no-snapshot -camera-back emulated`. | +| `disable-animations` | Optional | `true` | Whether to disable animations - `true` or `false`. | +| `disable-spellchecker` | Optional | `false` | Whether to disable spellchecker - `true` or `false`. | +| `disable-linux-hw-accel` | Optional | `auto` | Whether to disable hardware acceleration on Linux machines - `true`, `false` or `auto`. | +| `enable-hw-keyboard` | Optional | `false` | Whether to enable hardware keyboard - `true` or `false`. | +| `emulator-build` | Optional | N/A | Build number of a specific version of the emulator binary to use e.g. `6061023` for emulator v29.3.0.0. | +| `working-directory` | Optional | `./` | A custom working directory - e.g. `./android` if your root Gradle project is under the `./android` sub-directory within your repository. Will be used for `script` & `pre-emulator-launch-script`. | +| `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` | +| `pre-emulator-launch-script` | Optional | N/A | Custom script to run after creating the AVD and before launching the emulator - e.g. `./adjust-emulator-configs.sh` | Default `emulator-options`: `-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim`. From 5980b7106a8ba33e51af25e658d6af3c167cedfa Mon Sep 17 00:00:00 2001 From: Ryan Carrier Date: Tue, 7 May 2024 08:59:15 +1000 Subject: [PATCH 2/3] revert formatting --- README.md | 56 +++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 6e2b987e6..d74f726f4 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A GitHub Action for installing, configuring and running hardware-accelerated And The old ARM-based emulators were slow and are no longer supported by Google. The modern Intel Atom (x86 and x86_64) emulators can be fast, but rely on two forms of hardware acceleration to reach their peak potential: [Graphics Acceleration](https://developer.android.com/studio/run/emulator-acceleration#accel-graphics), e.g. `emulator -gpu host` and [Virtual Machine(VM) Acceleration](https://developer.android.com/studio/run/emulator-acceleration#accel-vm), e.g. `emulator -accel on`. **Note:** GPU and VM Acceleration are two different and non-mutually exclusive forms of Hardware Acceleration. -This presents a challenge when running emulators on CI especially when running emulators within a docker container, because **Nested Virtualization** must be supported by the host VM which isn't the case for most cloud-based CI providers due to infrastructural limits. If you want to learn more about Emulators on CI, here's an article [Yang](https://github.com/ychescale9) wrote: [Running Android Instrumented Tests on CI](https://dev.to/ychescale9/running-android-emulators-on-ci-from-bitrise-io-to-github-actions-3j76). +This presents a challenge when running emulators on CI especially when running emulators within a docker container, because **Nested Virtualization** must be supported by the host VM which isn't the case for most cloud-based CI providers due to infrastructural limits. If you want to learn more about Emulators on CI, here's an article [Yang](https://github.com/ychescale9) wrote: [Running Android Instrumented Tests on CI](https://dev.to/ychescale9/running-android-emulators-on-ci-from-bitrise-io-to-github-actions-3j76). ## Running hardware accelerated emulators on Linux runners @@ -147,7 +147,7 @@ jobs: - name: Gradle cache uses: gradle/actions/setup-gradle@v3 - + - name: AVD cache uses: actions/cache@v4 id: avd-cache @@ -183,32 +183,32 @@ jobs: ## Configurations -| **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`, `playstore`, `android-wear`, `android-wear-cn`, `android-tv`, `google-tv`, `aosp_atd` or `google_atd`. Note that `aosp_atd` and `google_atd` currently require the following: `api-level: 30`, `arch: x86` or `arch: arm64-v8` and `channel: canary`. | -| `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 device`. | -| `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` | -| `heap-size` | Optional | N/A | Heap size to use for this AVD, in KB or MB, denoted with K or M. - e.g. `512M` | -| `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, or partition 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-boot-timeout` | Optional | `600` | Emulator boot timeout in seconds. If it takes longer to boot, the action would fail - e.g. `300` for 5 minutes. | -| `emulator-options` | Optional | See below | Command-line options used when launching the emulator (replacing all default options) - e.g. `-no-window -no-snapshot -camera-back emulated`. | -| `disable-animations` | Optional | `true` | Whether to disable animations - `true` or `false`. | -| `disable-spellchecker` | Optional | `false` | Whether to disable spellchecker - `true` or `false`. | -| `disable-linux-hw-accel` | Optional | `auto` | Whether to disable hardware acceleration on Linux machines - `true`, `false` or `auto`. | -| `enable-hw-keyboard` | Optional | `false` | Whether to enable hardware keyboard - `true` or `false`. | -| `emulator-build` | Optional | N/A | Build number of a specific version of the emulator binary to use e.g. `6061023` for emulator v29.3.0.0. | -| `working-directory` | Optional | `./` | A custom working directory - e.g. `./android` if your root Gradle project is under the `./android` sub-directory within your repository. Will be used for `script` & `pre-emulator-launch-script`. | -| `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` | -| `pre-emulator-launch-script` | Optional | N/A | Custom script to run after creating the AVD and before launching the emulator - e.g. `./adjust-emulator-configs.sh` | +| **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`, `playstore`, `android-wear`, `android-wear-cn`, `android-tv`, `google-tv`, `aosp_atd` or `google_atd`. Note that `aosp_atd` and `google_atd` currently require the following: `api-level: 30`, `arch: x86` or `arch: arm64-v8` and `channel: canary`. | +| `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 device`. | +| `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` | +| `heap-size` | Optional | N/A | Heap size to use for this AVD, in KB or MB, denoted with K or M. - e.g. `512M` | +| `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, or partition 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-boot-timeout` | Optional | `600` | Emulator boot timeout in seconds. If it takes longer to boot, the action would fail - e.g. `300` for 5 minutes. | +| `emulator-options` | Optional | See below | Command-line options used when launching the emulator (replacing all default options) - e.g. `-no-window -no-snapshot -camera-back emulated`. | +| `disable-animations` | Optional | `true` | Whether to disable animations - `true` or `false`. | +| `disable-spellchecker` | Optional | `false` | Whether to disable spellchecker - `true` or `false`. | +| `disable-linux-hw-accel` | Optional | `auto` | Whether to disable hardware acceleration on Linux machines - `true`, `false` or `auto`.| +| `enable-hw-keyboard` | Optional | `false` | Whether to enable hardware keyboard - `true` or `false`. | +| `emulator-build` | Optional | N/A | Build number of a specific version of the emulator binary to use e.g. `6061023` for emulator v29.3.0.0. | +| `working-directory` | Optional | `./` | A custom working directory - e.g. `./android` if your root Gradle project is under the `./android` sub-directory within your repository. Will be used for `script` & `pre-emulator-launch-script`. | +| `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` | +| `pre-emulator-launch-script` | Optional | N/A | Custom script to run after creating the AVD and before launching the emulator - e.g. `./adjust-emulator-configs.sh` | Default `emulator-options`: `-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim`. From c12d85a51ec010a15eebe32e6dc723cd1b63b101 Mon Sep 17 00:00:00 2001 From: Ryan Carrier Date: Tue, 3 Dec 2024 10:30:25 +1000 Subject: [PATCH 3/3] feat: retry launching (creating and sdk install also), check results of commands and throw/error if failure --- action-types.yml | 60 +++++++++++++------------- action.yml | 95 +++++++++++++++++++++-------------------- lib/emulator-manager.js | 16 +++++-- lib/main.js | 14 +++++- lib/post.js | 66 ++++++++++++++++++++++++++++ lib/retry.js | 28 ++++++++++++ lib/script-parser.js | 3 -- lib/sdk-installer.js | 16 ++++--- src/emulator-manager.ts | 39 +++++++++++------ src/main.ts | 54 ++++++++++++++--------- src/post.ts | 31 ++++++++++++++ src/retry.ts | 13 ++++++ src/script-parser.ts | 5 --- src/sdk-installer.ts | 25 ++++++++--- 14 files changed, 328 insertions(+), 137 deletions(-) create mode 100644 lib/post.js create mode 100644 lib/retry.js create mode 100644 src/post.ts create mode 100644 src/retry.ts diff --git a/action-types.yml b/action-types.yml index b77d4b11d..ebefb399f 100644 --- a/action-types.yml +++ b/action-types.yml @@ -16,55 +16,57 @@ inputs: arch: type: enum allowed-values: - - x86 - - x86_64 - - arm64-v8a + - x86 + - x86_64 + - arm64-v8a profile: - type: string + type: string cores: - type: integer + type: integer ram-size: - type: string + type: string heap-size: - type: string + type: string sdcard-path-or-size: - type: string + type: string disk-size: - type: string + type: string avd-name: - type: string + type: string force-avd-creation: - type: boolean + type: boolean emulator-boot-timeout: - type: integer + type: integer emulator-port: type: integer emulator-options: - type: string + type: string disable-animations: - type: boolean + type: boolean disable-spellchecker: - type: boolean + type: boolean disable-linux-hw-accel: - type: string + type: string enable-hw-keyboard: - type: boolean + type: boolean emulator-build: - type: string + type: string working-directory: - type: string + type: string ndk: - type: string + type: string cmake: - type: string + type: string channel: - type: enum - allowed-values: - - stable - - beta - - dev - - canary + type: enum + allowed-values: + - stable + - beta + - dev + - canary script: - type: string + type: string pre-emulator-launch-script: - type: string + type: string + retry-count: + type: integer diff --git a/action.yml b/action.yml index fa60d74dd..4a2749d33 100644 --- a/action.yml +++ b/action.yml @@ -1,75 +1,78 @@ -name: 'Android Emulator Runner' -description: 'Installs, configures and starts an Android Emulator directly on hardware-accelerated runners.' -author: 'Reactive Circus' +name: "Android Emulator Runner" +description: "Installs, configures and starts an Android Emulator directly on hardware-accelerated runners." +author: "Reactive Circus" branding: - icon: 'smartphone' - color: 'green' + icon: "smartphone" + color: "green" inputs: api-level: - description: 'API level of the platform and system image - e.g. 23 for Android Marshmallow, 29 for Android 10' + description: "API level of the platform and system image - e.g. 23 for Android Marshmallow, 29 for Android 10" required: true target: - description: 'target of the system image - default, google_apis, google_apis_playstore, aosp_atd, google_atd, android-wear, android-wear-cn, android-tv or google-tv' - default: 'default' + description: "target of the system image - default, google_apis, google_apis_playstore, aosp_atd, google_atd, android-wear, android-wear-cn, android-tv or google-tv" + default: "default" arch: - description: 'CPU architecture of the system image - x86, x86_64 or arm64-v8a' - default: 'x86' + description: "CPU architecture of the system image - x86, x86_64 or arm64-v8a" + default: "x86" profile: - description: 'hardware profile used for creating the AVD - e.g. `Nexus 6`' + description: "hardware profile used for creating the AVD - e.g. `Nexus 6`" cores: - description: 'the number of cores to use for the emulator' - default: 2 + description: "the number of cores to use for the emulator" + default: "2" ram-size: - description: 'size of RAM to use for this AVD, in KB or MB, denoted with K or M. - e.g. `2048M`' + description: "size of RAM to use for this AVD, in KB or MB, denoted with K or M. - e.g. `2048M`" heap-size: - description: 'size of heap to use for this AVD in MB. - e.g. `512M`' + description: "size of heap to use for this AVD in MB. - e.g. `512M`" 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`' + 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' + 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' + description: "custom AVD name used for creating the Android Virtual Device" + default: "test" force-avd-creation: - description: 'whether to force create the AVD by overwriting an existing AVD with the same name as `avd-name` - `true` or `false`' - default: 'true' + description: "whether to force create the AVD by overwriting an existing AVD with the same name as `avd-name` - `true` or `false`" + default: "true" emulator-boot-timeout: - description: 'Emulator boot timeout in seconds. If it takes longer to boot, the action would fail - e.g. `300` for 5 minutes' - default: '600' + description: "Emulator boot timeout in seconds. If it takes longer to boot, the action would fail - e.g. `300` for 5 minutes" + default: "600" emulator-port: - description: 'Port to run emulator on, allows to run multiple emulators on the same physical machine' - default: '5554' + description: "Port to run emulator on, allows to run multiple emulators on the same physical machine" + default: "5554" emulator-options: - description: 'command-line options used when launching the emulator - e.g. `-no-window -no-snapshot -camera-back emulated`' - default: '-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim' + description: "command-line options used when launching the emulator - e.g. `-no-window -no-snapshot -camera-back emulated`" + default: "-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim" disable-animations: - description: 'whether to disable animations - true or false' - default: 'true' + description: "whether to disable animations - true or false" + default: "true" disable-spellchecker: - description: 'whether to disable the Android spell checker framework, a common source of flakiness in text fields - `true` or `false`' - default: 'false' + description: "whether to disable the Android spell checker framework, a common source of flakiness in text fields - `true` or `false`" + default: "false" disable-linux-hw-accel: - description: 'whether to disable hardware acceleration on Linux machines - `true` or `false` or `auto`' - default: 'auto' + description: "whether to disable hardware acceleration on Linux machines - `true` or `false` or `auto`" + default: "auto" enable-hw-keyboard: - description: 'whether to enable hardware keyboard - `true` or `false`.' - default: 'false' + description: "whether to enable hardware keyboard - `true` or `false`." + default: "false" emulator-build: - description: 'build number of a specific version of the emulator binary to use - e.g. `6061023` for emulator v29.3.0.0' + description: "build number of a specific version of the emulator binary to use - e.g. `6061023` for emulator v29.3.0.0" working-directory: - description: 'A custom working directory - e.g. `./android` if your root Gradle project is under the `./android` sub-directory within your repository' + description: "A custom working directory - e.g. `./android` if your root Gradle project is under the `./android` sub-directory within your repository" ndk: - description: 'version of NDK to install - e.g. 21.0.6113669' + description: "version of NDK to install - e.g. 21.0.6113669" cmake: - description: 'version of CMake to install - e.g. 3.10.2.4988404' + 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' + 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 + description: "custom script to run - e.g. `./gradlew connectedCheck`" pre-emulator-launch-script: - description: 'custom script to run after creating the AVD and before launching the emulator - e.g. `./adjust-emulator-configs.sh`' + description: "custom script to run after creating the AVD and before launching the emulator - e.g. `./adjust-emulator-configs.sh`" + retry-count: + description: "number of times to retry the action in case of failure - e.g. `3`" + default: "3" runs: - using: 'node20' - main: 'lib/main.js' + using: "node20" + main: "lib/main.js" + post: "lib/post.js" diff --git a/lib/emulator-manager.js b/lib/emulator-manager.js index a74991e14..0874f568c 100644 --- a/lib/emulator-manager.js +++ b/lib/emulator-manager.js @@ -35,10 +35,11 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.killEmulator = exports.launchEmulator = void 0; const exec = __importStar(require("@actions/exec")); const fs = __importStar(require("fs")); +const retry_1 = require("./retry"); /** * Creates and launches a new AVD instance with the specified configurations. */ -function launchEmulator(apiLevel, target, arch, profile, cores, ramSize, heapSize, sdcardPathOrSize, diskSize, avdName, forceAvdCreation, emulatorBootTimeout, port, emulatorOptions, disableAnimations, disableSpellChecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard) { +function launchEmulator(apiLevel, target, arch, profile, cores, ramSize, heapSize, sdcardPathOrSize, diskSize, avdName, forceAvdCreation, emulatorBootTimeout, port, emulatorOptions, disableAnimations, disableSpellChecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard, retryCount) { return __awaiter(this, void 0, void 0, function* () { try { console.log(`::group::Launch Emulator`); @@ -48,7 +49,11 @@ function launchEmulator(apiLevel, target, arch, profile, cores, ramSize, heapSiz 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}"`); + // Don't believe this ever failed, but it seems like a strong candidate for failure... + const result = yield (0, retry_1.execWithRetry)(() => exec.exec(`sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --abi '${target}/${arch}' --package 'system-images;android-${apiLevel};${target};${arch}' ${profileOption} ${sdcardPathOrSizeOption}"`), retryCount); + if (result !== 0) { + throw new Error('Failed to create AVD.'); + } } if (cores) { yield exec.exec(`sh -c \\"printf 'hw.cpu.ncore=${cores}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`); @@ -72,7 +77,7 @@ function launchEmulator(apiLevel, target, arch, profile, cores, ramSize, heapSiz } // start emulator console.log('Starting emulator.'); - yield exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -port ${port} -avd "${avdName}" ${emulatorOptions} &"`, [], { + const result = yield (0, retry_1.execWithRetry)(() => exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -port ${port} -avd "${avdName}" ${emulatorOptions} &"`, [], { listeners: { stderr: (data) => { if (data.toString().includes('invalid command-line parameter')) { @@ -80,7 +85,10 @@ function launchEmulator(apiLevel, target, arch, profile, cores, ramSize, heapSiz } }, }, - }); + }), retryCount); + if (result !== 0) { + throw new Error('Failed to create AVD.'); + } // wait for emulator to complete booting yield waitForDevice(port, emulatorBootTimeout); yield adb(port, `shell input keyevent 82`); diff --git a/lib/main.js b/lib/main.js index 21a5d4918..f292a0a51 100644 --- a/lib/main.js +++ b/lib/main.js @@ -178,8 +178,9 @@ function run() { console.log(`${script}`); })); console.log(`::endgroup::`); + const retryCount = parseInt(core.getInput('retry-count', { required: true })); // install SDK - yield (0, sdk_installer_1.installAndroidSdk)(apiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion); + yield (0, sdk_installer_1.installAndroidSdk)(apiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion, retryCount); // execute pre emulator launch script if set if (preEmulatorLaunchScripts !== undefined) { console.log(`::group::Run pre emulator launch script`); @@ -198,7 +199,16 @@ function run() { console.log(`::endgroup::`); } // launch an emulator - yield (0, emulator_manager_1.launchEmulator)(apiLevel, target, arch, profile, cores, ramSize, heapSize, sdcardPathOrSize, diskSize, avdName, forceAvdCreation, emulatorBootTimeout, port, emulatorOptions, disableAnimations, disableSpellchecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard); + try { + yield (0, emulator_manager_1.launchEmulator)(apiLevel, target, arch, profile, cores, ramSize, heapSize, sdcardPathOrSize, diskSize, avdName, forceAvdCreation, emulatorBootTimeout, port, emulatorOptions, disableAnimations, disableSpellchecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard, retryCount); + } + catch (error) { + core.setFailed(error instanceof Error ? error.message : error); + } + if (scripts.length === 0) { + console.log('No custom script to run. Be sure to shut down the emulator in your script.'); + console.log(`(adb -s emulator-${port} emu kill)`); + } // execute the custom script try { // move to custom working directory if set diff --git a/lib/post.js b/lib/post.js new file mode 100644 index 000000000..a5d7e81d0 --- /dev/null +++ b/lib/post.js @@ -0,0 +1,66 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const core = __importStar(require("@actions/core")); +const input_validator_1 = require("./input-validator"); +const emulator_manager_1 = require("./emulator-manager"); +const exec = __importStar(require("@actions/exec")); +function post() { + return __awaiter(this, void 0, void 0, function* () { + let port = input_validator_1.MIN_PORT; + // Emulator port to use + port = parseInt(core.getInput('emulator-port'), 10); + (0, input_validator_1.checkPort)(port); + console.log(`emulator port: ${port}`); + try { + let result = ''; + yield exec.exec(`adb -s emulator-${port} shell getprop sys.boot_completed`, [], { + listeners: { + stdout: (data) => { + result += data.toString(); + }, + }, + }); + if (result.trim() === '1') { + console.log('Emulator online, killing it.'); + yield (0, emulator_manager_1.killEmulator)(port); + } + } + catch (error) { + yield (0, emulator_manager_1.killEmulator)(port); + console.warn(error instanceof Error ? error.message : error); + } + }); +} +post(); diff --git a/lib/retry.js b/lib/retry.js new file mode 100644 index 000000000..088f1eb0c --- /dev/null +++ b/lib/retry.js @@ -0,0 +1,28 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.execWithRetry = void 0; +function execWithRetry(fn, retryCount) { + return __awaiter(this, void 0, void 0, function* () { + let attempt = 0; + let result = 1; + while (attempt <= retryCount && result !== 0) { + if (attempt > 0) { + console.log(`Retry attempt ${attempt}. (exit code was ${result})`); + yield new Promise((resolve) => setTimeout(resolve, 1000)); + } + result = yield fn(); + attempt++; + } + return result; + }); +} +exports.execWithRetry = execWithRetry; diff --git a/lib/script-parser.js b/lib/script-parser.js index 16b216489..96199fc88 100644 --- a/lib/script-parser.js +++ b/lib/script-parser.js @@ -12,9 +12,6 @@ function parseScript(rawScript) { .filter((value) => { return !value.startsWith('#') && value.length > 0; }); - if (scripts.length == 0) { - throw new Error(`No valid script found.`); - } return scripts; } exports.parseScript = parseScript; diff --git a/lib/sdk-installer.js b/lib/sdk-installer.js index fe7f51385..af35fefce 100644 --- a/lib/sdk-installer.js +++ b/lib/sdk-installer.js @@ -38,6 +38,7 @@ 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 retry_1 = require("./retry"); const BUILD_TOOLS_VERSION = '35.0.0'; // SDK command-line tools 16.0 const CMDLINE_TOOLS_URL_MAC = 'https://dl.google.com/android/repository/commandlinetools-mac-12266719_latest.zip'; @@ -46,8 +47,9 @@ 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, channelId, emulatorBuild, ndkVersion, cmakeVersion) { +function installAndroidSdk(apiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion, retryCount) { return __awaiter(this, void 0, void 0, function* () { + retryCount = retryCount || 0; try { console.log(`::group::Install Android SDK`); const isOnMac = process.platform === 'darwin'; @@ -68,9 +70,9 @@ function installAndroidSdk(apiLevel, target, arch, channelId, emulatorBuild, ndk // 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"`); + yield (0, retry_1.execWithRetry)(() => exec.exec(`sh -c \\"sdkmanager --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools 'platforms;android-${apiLevel}'> /dev/null"`), retryCount); console.log('Installing latest emulator.'); - yield exec.exec(`sh -c \\"sdkmanager --install emulator --channel=${channelId} > /dev/null"`); + yield (0, retry_1.execWithRetry)(() => exec.exec(`sh -c \\"sdkmanager --install emulator --channel=${channelId} > /dev/null"`), retryCount); if (emulatorBuild) { console.log(`Installing emulator build ${emulatorBuild}.`); // TODO find out the correct download URLs for all build ids @@ -90,19 +92,19 @@ function installAndroidSdk(apiLevel, target, arch, channelId, emulatorBuild, ndk else { downloadUrlSuffix = `-${emulatorBuild}`; } - yield exec.exec(`curl -fo emulator.zip https://dl.google.com/android/repository/emulator-${isOnMac ? 'darwin' : 'linux'}${downloadUrlSuffix}.zip`); + yield (0, retry_1.execWithRetry)(() => exec.exec(`curl -fo emulator.zip https://dl.google.com/android/repository/emulator-${isOnMac ? 'darwin' : 'linux'}${downloadUrlSuffix}.zip`), retryCount); yield exec.exec(`unzip -o -q emulator.zip -d ${process.env.ANDROID_HOME}`); 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"`); + yield (0, retry_1.execWithRetry)(() => exec.exec(`sh -c \\"sdkmanager --install 'system-images;android-${apiLevel};${target};${arch}' --channel=${channelId} > /dev/null"`), retryCount); if (ndkVersion) { console.log(`Installing NDK ${ndkVersion}.`); - yield exec.exec(`sh -c \\"sdkmanager --install 'ndk;${ndkVersion}' --channel=${channelId} > /dev/null"`); + yield (0, retry_1.execWithRetry)(() => exec.exec(`sh -c \\"sdkmanager --install 'ndk;${ndkVersion}' --channel=${channelId} > /dev/null"`), retryCount); } if (cmakeVersion) { console.log(`Installing CMake ${cmakeVersion}.`); - yield exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' --channel=${channelId} > /dev/null"`); + yield (0, retry_1.execWithRetry)(() => exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' --channel=${channelId} > /dev/null"`), retryCount); } } finally { diff --git a/src/emulator-manager.ts b/src/emulator-manager.ts index aa2a67b6d..893d47298 100644 --- a/src/emulator-manager.ts +++ b/src/emulator-manager.ts @@ -1,5 +1,6 @@ import * as exec from '@actions/exec'; import * as fs from 'fs'; +import { execWithRetry } from './retry'; /** * Creates and launches a new AVD instance with the specified configurations. @@ -22,7 +23,8 @@ export async function launchEmulator( disableAnimations: boolean, disableSpellChecker: boolean, disableLinuxHardwareAcceleration: boolean, - enableHardwareKeyboard: boolean + enableHardwareKeyboard: boolean, + retryCount: number ): Promise { try { console.log(`::group::Launch Emulator`); @@ -32,9 +34,13 @@ export async function launchEmulator( 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}"` - ); + // Don't believe this ever failed, but it seems like a strong candidate for failure... + const result = await execWithRetry( + () => exec.exec( + `sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --abi '${target}/${arch}' --package 'system-images;android-${apiLevel};${target};${arch}' ${profileOption} ${sdcardPathOrSizeOption}"`), retryCount); + if (result !== 0) { + throw new Error('Failed to create AVD.'); + } } if (cores) { @@ -66,15 +72,20 @@ export async function launchEmulator( // start emulator console.log('Starting emulator.'); - await exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -port ${port} -avd "${avdName}" ${emulatorOptions} &"`, [], { - listeners: { - stderr: (data: Buffer) => { - if (data.toString().includes('invalid command-line parameter')) { - throw new Error(data.toString()); - } - }, - }, - }); + const result = await execWithRetry( + () => + exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -port ${port} -avd "${avdName}" ${emulatorOptions} &"`, [], { + listeners: { + stderr: (data: Buffer) => { + if (data.toString().includes('invalid command-line parameter')) { + throw new Error(data.toString()); + } + }, + }, + }), retryCount); + if (result !== 0) { + throw new Error('Failed to create AVD.'); + } // wait for emulator to complete booting await waitForDevice(port, emulatorBootTimeout); @@ -97,6 +108,8 @@ export async function launchEmulator( } } + + /** * Kills the running emulator on the default port. */ diff --git a/src/main.ts b/src/main.ts index a9588245e..31f8c2d29 100644 --- a/src/main.ts +++ b/src/main.ts @@ -184,8 +184,10 @@ async function run() { }); console.log(`::endgroup::`); + const retryCount: number = parseInt(core.getInput('retry-count', { required: true })); + // install SDK - await installAndroidSdk(apiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion); + await installAndroidSdk(apiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion, retryCount); // execute pre emulator launch script if set if (preEmulatorLaunchScripts !== undefined) { @@ -205,26 +207,36 @@ async function run() { } // launch an emulator - await launchEmulator( - apiLevel, - target, - arch, - profile, - cores, - ramSize, - heapSize, - sdcardPathOrSize, - diskSize, - avdName, - forceAvdCreation, - emulatorBootTimeout, - port, - emulatorOptions, - disableAnimations, - disableSpellchecker, - disableLinuxHardwareAcceleration, - enableHardwareKeyboard - ); + try { + await launchEmulator( + apiLevel, + target, + arch, + profile, + cores, + ramSize, + heapSize, + sdcardPathOrSize, + diskSize, + avdName, + forceAvdCreation, + emulatorBootTimeout, + port, + emulatorOptions, + disableAnimations, + disableSpellchecker, + disableLinuxHardwareAcceleration, + enableHardwareKeyboard, + retryCount + ); + } catch (error) { + core.setFailed(error instanceof Error ? error.message : (error as string)); + } + + if (scripts.length === 0) { + console.log('No custom script to run. Be sure to shut down the emulator in your script.'); + console.log(`(adb -s emulator-${port} emu kill)`); + } // execute the custom script try { diff --git a/src/post.ts b/src/post.ts new file mode 100644 index 000000000..6cbb73419 --- /dev/null +++ b/src/post.ts @@ -0,0 +1,31 @@ +import * as core from '@actions/core'; +import { checkPort, MIN_PORT } from './input-validator'; +import { killEmulator } from './emulator-manager'; +import * as exec from '@actions/exec'; + +async function post() { + let port: number = MIN_PORT; + // Emulator port to use + port = parseInt(core.getInput('emulator-port'), 10); + checkPort(port); + console.log(`emulator port: ${port}`); + try { + let result = ''; + await exec.exec(`adb -s emulator-${port} shell getprop sys.boot_completed`, [], { + listeners: { + stdout: (data: Buffer) => { + result += data.toString(); + }, + }, + }); + if (result.trim() === '1') { + console.log('Emulator online, killing it.'); + await killEmulator(port); + } + } catch (error) { + await killEmulator(port); + console.warn(error instanceof Error ? error.message : error); + } +} + +post(); diff --git a/src/retry.ts b/src/retry.ts new file mode 100644 index 000000000..0eb51d682 --- /dev/null +++ b/src/retry.ts @@ -0,0 +1,13 @@ +export async function execWithRetry(fn: () => Promise, retryCount: number): Promise { + let attempt = 0; + let result = 1; + while (attempt <= retryCount && result !== 0) { + if (attempt > 0) { + console.log(`Retry attempt ${attempt}. (exit code was ${result})`); + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + result = await fn(); + attempt++; + } + return result; +} diff --git a/src/script-parser.ts b/src/script-parser.ts index 813d36bd2..242056dc8 100644 --- a/src/script-parser.ts +++ b/src/script-parser.ts @@ -9,10 +9,5 @@ export function parseScript(rawScript: string): Array { .filter((value: string) => { return !value.startsWith('#') && value.length > 0; }); - - if (scripts.length == 0) { - throw new Error(`No valid script found.`); - } - return scripts; } diff --git a/src/sdk-installer.ts b/src/sdk-installer.ts index f87028a6f..4f8c4afa0 100644 --- a/src/sdk-installer.ts +++ b/src/sdk-installer.ts @@ -3,6 +3,7 @@ import * as exec from '@actions/exec'; import * as io from '@actions/io'; import * as tc from '@actions/tool-cache'; import * as fs from 'fs'; +import { execWithRetry } from './retry'; const BUILD_TOOLS_VERSION = '35.0.0'; // SDK command-line tools 16.0 @@ -13,7 +14,17 @@ 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: string, target: string, arch: string, channelId: number, emulatorBuild?: string, ndkVersion?: string, cmakeVersion?: string): Promise { +export async function installAndroidSdk( + apiLevel: string, + target: string, + arch: string, + channelId: number, + emulatorBuild?: string, + ndkVersion?: string, + cmakeVersion?: string, + retryCount?: number +): Promise { + retryCount = retryCount || 0; try { console.log(`::group::Install Android SDK`); const isOnMac = process.platform === 'darwin'; @@ -40,10 +51,10 @@ export async function installAndroidSdk(apiLevel: string, target: string, arch: 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 execWithRetry(() => exec.exec(`sh -c \\"sdkmanager --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools 'platforms;android-${apiLevel}'> /dev/null"`), retryCount); console.log('Installing latest emulator.'); - await exec.exec(`sh -c \\"sdkmanager --install emulator --channel=${channelId} > /dev/null"`); + await execWithRetry(() => exec.exec(`sh -c \\"sdkmanager --install emulator --channel=${channelId} > /dev/null"`), retryCount); if (emulatorBuild) { console.log(`Installing emulator build ${emulatorBuild}.`); @@ -61,20 +72,20 @@ export async function installAndroidSdk(apiLevel: string, target: string, arch: } else { downloadUrlSuffix = `-${emulatorBuild}`; } - await exec.exec(`curl -fo emulator.zip https://dl.google.com/android/repository/emulator-${isOnMac ? 'darwin' : 'linux'}${downloadUrlSuffix}.zip`); + await execWithRetry(() => exec.exec(`curl -fo emulator.zip https://dl.google.com/android/repository/emulator-${isOnMac ? 'darwin' : 'linux'}${downloadUrlSuffix}.zip`), retryCount); await exec.exec(`unzip -o -q emulator.zip -d ${process.env.ANDROID_HOME}`); 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"`); + await execWithRetry(() => exec.exec(`sh -c \\"sdkmanager --install 'system-images;android-${apiLevel};${target};${arch}' --channel=${channelId} > /dev/null"`), retryCount); if (ndkVersion) { console.log(`Installing NDK ${ndkVersion}.`); - await exec.exec(`sh -c \\"sdkmanager --install 'ndk;${ndkVersion}' --channel=${channelId} > /dev/null"`); + await execWithRetry(() => exec.exec(`sh -c \\"sdkmanager --install 'ndk;${ndkVersion}' --channel=${channelId} > /dev/null"`), retryCount); } if (cmakeVersion) { console.log(`Installing CMake ${cmakeVersion}.`); - await exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' --channel=${channelId} > /dev/null"`); + await execWithRetry(() => exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' --channel=${channelId} > /dev/null"`), retryCount); } } finally { console.log(`::endgroup::`);