Compare commits

...

72 Commits

Author SHA1 Message Date
Yang f71c6d1f84 Merge branch 'main' into release/v2
* main:
  Prepare for release 2.20.0.
  Update manual workflow.
  Support choosing channel to download SDK components from.
  build-tools to 31.0.0; SDK cmd-line tools to 5.0. (#174)
  Fix macos-11 VM label.
  Realm JavaScript is using Android Emulator Runner (#182)
  Add MobileRT to the list of projects (#181)
  Support non-mobile targets (Wear OS, TV) (#180)
  Bump path-parse from 1.0.6 to 1.0.7
2021-08-28 20:04:58 +10:00
Yang 511d462729 Prepare for release 2.20.0. 2021-08-28 20:03:27 +10:00
Yang d83fb3d4fb Update manual workflow. 2021-08-28 19:51:59 +10:00
Yang 1427331a62 Support choosing channel to download SDK components from. 2021-08-28 19:44:11 +10:00
Yang 1ded27b164 build-tools to 31.0.0; SDK cmd-line tools to 5.0. (#174)
* Update build-tools to 31.0.0. Add API 31 emulator to workflow. Update SDK command-line tools to 5.0

* Update test fixture dependencies.

* Test API 31 with macos-11.

* Add android:exported to AndroidManifest.

* Format
2021-08-28 17:59:37 +10:00
Yang 217e44e3c5 Fix macos-11 VM label. 2021-08-27 11:39:30 +10:00
Kenneth Geisshirt 31d8d4204a Realm JavaScript is using Android Emulator Runner (#182) 2021-08-18 21:09:31 +10:00
Tiago Santos a5bc682448 Add MobileRT to the list of projects (#181) 2021-08-16 17:58:48 +10:00
Alex Vanyo 4d1a61ed38 Support non-mobile targets (Wear OS, TV) (#180)
* Add non-mobile targets to allowlist

* Add new targets to input validator tests

* Update built input-validator.js

* Update README with new targets

* Update action.yml with new targets
2021-08-14 03:25:01 +10:00
dependabot[bot] 1bf9f17bc6 Bump path-parse from 1.0.6 to 1.0.7
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-13 21:03:46 +10:00
Yang 5de26e4bd2 Merge branch 'main' into release/v2
* main:
  Prepare for release 2.19.1.
  Accept all Android SDK Licenses
  Adds Norris to the list of projects
2021-07-20 23:53:04 +10:00
Yang 5e487dbdfe Prepare for release 2.19.1. 2021-07-20 23:52:06 +10:00
Marcus Pridham 08852ad83b Accept all Android SDK Licenses
Fixes #168
2021-07-20 11:44:43 +10:00
Ubiratan Soares 25bcfbab74 Adds Norris to the list of projects 2021-07-04 21:01:19 +10:00
Yang ac874f3db5 Merge branch 'main' into release/v2
* main:
  Prepare for release 2.19.0.
  Add option to set AVD RAM size.
2021-06-26 23:49:47 +10:00
Yang 0c3db9cd7a Prepare for release 2.19.0. 2021-06-26 23:48:51 +10:00
Yang eba1fbfb59 Add option to set AVD RAM size. 2021-06-26 23:45:24 +10:00
Yang 6e775ef330 Merge branch 'main' into release/v2
* main:
  Prepare for release 2.18.1.
  Unzip specific emulator build into latest emulator build from sdkmanager. Support using modern build id for emulator-build.
2021-06-26 01:23:30 +10:00
Yang 8a4333421e Prepare for release 2.18.1. 2021-06-26 01:22:31 +10:00
Yang 50ef0172a3 Unzip specific emulator build into latest emulator build from sdkmanager. Support using modern build id for emulator-build. 2021-06-26 01:14:29 +10:00
Yang 97449e998a Merge branch 'main' into release/v2
* main:
  Prepare for release 2.18.0.
  Add sample workflow for AVD snapshot caching.
  Add `force-avd-creation` to Configurations table in README.md.
  Add force-avd-creation to skip avd creation if avd with same name exists. Update workflow to test snapshot caching.
  Add accompanist to adoption list
  Gradle 7.1.
  Bump glob-parent from 5.1.1 to 5.1.2
  Bump ws from 7.3.1 to 7.4.6
2021-06-20 15:28:07 +10:00
Yang fe99eb10f3 Prepare for release 2.18.0. 2021-06-20 15:27:11 +10:00
Yang 6fa269535d Add sample workflow for AVD snapshot caching. 2021-06-20 01:41:30 +10:00
Yang 9b7af03883 Add force-avd-creation to Configurations table in README.md. 2021-06-20 01:41:30 +10:00
Yang 854ed15ed0 Add force-avd-creation to skip avd creation if avd with same name exists. Update workflow to test snapshot caching. 2021-06-20 01:41:30 +10:00
Yang 98e515da59 Add accompanist to adoption list 2021-06-18 16:40:54 +10:00
Yang 9e81ae6235 Gradle 7.1. 2021-06-15 12:38:10 +10:00
dependabot[bot] 6f33a98fa4 Bump glob-parent from 5.1.1 to 5.1.2
Bumps [glob-parent](https://github.com/gulpjs/glob-parent) from 5.1.1 to 5.1.2.
- [Release notes](https://github.com/gulpjs/glob-parent/releases)
- [Changelog](https://github.com/gulpjs/glob-parent/blob/main/CHANGELOG.md)
- [Commits](https://github.com/gulpjs/glob-parent/compare/v5.1.1...v5.1.2)

---
updated-dependencies:
- dependency-name: glob-parent
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-11 00:53:25 +10:00
dependabot[bot] e94dda8413 Bump ws from 7.3.1 to 7.4.6
Bumps [ws](https://github.com/websockets/ws) from 7.3.1 to 7.4.6.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.3.1...7.4.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-30 10:01:46 +10:00
Yang 226f26251f Merge branch 'main' into release/v2
* main:
  Prepare for release 2.17.0.
  Update test fixture dependencies.
  Add option to toggle Linux hardware acceleration
  Bump hosted-git-info from 2.8.8 to 2.8.9
  Bump lodash from 4.17.19 to 4.17.21
2021-05-28 17:13:49 +10:00
Yang 43421164e2 Prepare for release 2.17.0. 2021-05-28 17:12:50 +10:00
Yang a3d693d819 Update test fixture dependencies. 2021-05-28 16:42:18 +10:00
Steve Stotter 5e38ea2647 Add option to toggle Linux hardware acceleration 2021-05-28 16:36:37 +10:00
dependabot[bot] 080a93d5e1 Bump hosted-git-info from 2.8.8 to 2.8.9
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-11 08:29:15 +10:00
dependabot[bot] 98e4e00ead Bump lodash from 4.17.19 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-11 08:26:54 +10:00
Yang 599839e428 Merge branch 'main' into release/v2
* main:
  Prepare for release 2.16.0.
  Add support for arm64-v8a for Apple Silicon Macs
  Add option to disable spellcheck
  Bump y18n from 4.0.0 to 4.0.1
  Update README.md
  Add google/android-fhir to the list of projects that use android-emulator-runner (#132)
  Add Wikipedia app to list of projects.
  Add kiwix-android to the list of projects
  Avoid Wrapping Script Code In Quotes
2021-05-07 18:32:02 +10:00
Yang 751bef68f6 Prepare for release 2.16.0. 2021-05-07 18:31:11 +10:00
Cihat Gündüz bbf0887eb9 Add support for arm64-v8a for Apple Silicon Macs
Fixes https://github.com/ReactiveCircus/android-emulator-runner/issues/145
2021-04-17 13:54:28 +10:00
Afzal Najam 84b9418977 Add option to disable spellcheck
Add input-validator and tests
2021-04-08 00:44:27 +10:00
dependabot[bot] a732f125bb Bump y18n from 4.0.0 to 4.0.1
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-31 16:32:08 +11:00
Yang 5201a33a90 Update README.md 2021-03-24 09:29:59 +11:00
Jing Tang cf46dc4c18 Add google/android-fhir to the list of projects that use android-emulator-runner (#132) 2021-03-24 07:51:43 +11:00
Dmitry Brant f790770d0f Add Wikipedia app to list of projects. 2021-03-18 12:14:00 +11:00
Ayush Shrivastava 505c899f12 Add kiwix-android to the list of projects
Add kiwix-android to the list of projects that use ReactiveCircus-android-emulator-runner
2021-03-10 12:22:34 +11:00
Brian Dickens 0d47f0813d Avoid Wrapping Script Code In Quotes
The exec() command only runs a program with literal argument strings.
It does not know how to do things like expanding environment variables,
or piping/redirection.  Hence the way to get "shell intelligence"
is to use exec() to run the `sh` process with the command line string
as a parameter.

But the method being used to do this was:

    exec.exec(`sh -c \\"${script}"`)

This has problems, because wrapping the script in quotes creates
issues when the script itself contains quotes.  Escaping the string
correctly is a non-trivial problem, which also would create "noise"
in the debug output which would add confusion.

http://mywiki.wooledge.org/BashFAQ/050

The easiest way to work around this here is to use the array form of
exec() to pass exactly two parameters to `sh`.  No manipulation of the
script string is needed with this approach:

    exec.exec("sh", ["-c", script])

There are more cases in the project of `sh -c` usage which should also
be changed, and likely abstracted (shellExec()?)  But this small patch
just fixes the most important case for the user-provided script.

---

Additionally, this removes the escaped backslash (`\\`) from the
start of the executed command.  That was presumably to suppress the
use of aliases:

https://unix.stackexchange.com/questions/524254/why-are-backslashes-included-in-this-shell-script

But because this is invoking a non-interactive shell session, aliases
would not apply.  Hence the extra backslash shouldn't be needed.
2021-03-09 18:49:23 +11:00
Yang d2799957d6 Merge branch 'main' into release/v2
* main:
  Prepare for release 2.15.0.
  Change default cores to 2, add cores in workflow
  Add option to modify the number of cores
2021-03-05 10:41:47 +11:00
Yang 838986e303 Prepare for release 2.15.0. 2021-03-05 10:40:10 +11:00
Afzal Najam 27a0759d85 Change default cores to 2, add cores in workflow 2021-03-03 08:41:55 +11:00
Afzal Najam d85239a9fb Add option to modify the number of cores 2021-03-03 08:41:55 +11:00
Yang 08b092e904 Merge branch 'main' into release/v2
* main:
  Prepare for release 2.14.3.
  Always add path for cmdline-tools to support macos-11.0.
  Gradle 6.8.
  Add Hash Checker app projects to consumers list
  Add LeakCanary to adopter list
2021-01-13 19:29:01 +11:00
Yang 91b2b2f06f Prepare for release 2.14.3. 2021-01-13 19:28:11 +11:00
Yang 205fbe1b31 Always add path for cmdline-tools to support macos-11.0. 2021-01-13 19:24:10 +11:00
Yang efc0dae5fa Gradle 6.8. 2021-01-09 12:41:16 +11:00
fartem 4281471e5b Add Hash Checker app projects to consumers list 2021-01-09 10:03:23 +11:00
Yang 7f22785663 Add LeakCanary to adopter list 2021-01-06 23:10:03 +11:00
Yang 94bd1ed921 Merge branch 'main' into release/v2
* main:
  Prepare for release 2.14.2.
  Support non-default API 28 images
2021-01-05 18:29:16 +11:00
Yang c3e261e13b Prepare for release 2.14.2. 2021-01-05 18:28:29 +11:00
linl33 434e949c3b Support non-default API 28 images 2021-01-05 18:21:57 +11:00
Yang 70e3f6e680 Merge branch 'main' into release/v2
* main:
  Prepare for release 2.14.1.
  Fix AVD creation hang when profile is not specified.
2020-12-28 14:00:37 +11:00
Yang 0d8602fe47 Prepare for release 2.14.1. 2020-12-28 13:59:41 +11:00
Yang 28373d3d4f Fix AVD creation hang when profile is not specified. 2020-12-28 13:13:03 +11:00
Yang 02cf805290 Merge branch 'main' into release/v2
* main:
  Prepare for release 2.14.0.
  Support specifying SD card path or size.
  Update npm packages.
  Replace usages of deprecated ANDROID_HOME with ANDROID_ROOT_SDK. Bump build tools to 30.0.3. Update test-fixture dependencies.
  Update Compose Samples URL
2020-12-27 20:48:59 +11:00
Yang 045b7d8c5e Prepare for release 2.14.0. 2020-12-27 20:46:12 +11:00
Yang b0f3c521a5 Support specifying SD card path or size. 2020-12-27 20:37:13 +11:00
Yang 308aa234e1 Update npm packages. 2020-12-25 00:24:38 +11:00
Yang d4b3926c7d Replace usages of deprecated ANDROID_HOME with ANDROID_ROOT_SDK.
Bump build tools to 30.0.3.
Update test-fixture dependencies.
2020-12-25 00:05:51 +11:00
Vitus 877a4c47c3 Update Compose Samples URL 2020-12-11 03:24:04 +11:00
Yang e08f702234 Merge branch 'main' into release/v2
* main:
  Prepare for release 2.13.0.
  Update cmdline-tools to 3.0.
  Update npm packages.
  Add Shopify/android-testify to list of projects
2020-11-14 19:05:52 +11:00
Yang 11559ab158 Prepare for release 2.13.0. 2020-11-14 19:04:56 +11:00
Yang b5ac8dfbfe Update cmdline-tools to 3.0. 2020-11-14 19:02:24 +11:00
Yang ad97647a61 Update npm packages. 2020-11-08 16:53:33 +11:00
Daniel Jette 4690ba2920 Add Shopify/android-testify to list of projects
Shopify/android-testify uses the Android Emulator Runner for GitHub Actions
2020-10-27 08:42:51 +11:00
34 changed files with 3771 additions and 583 deletions
@@ -18,6 +18,7 @@ jobs:
os: [macos-latest, ubuntu-latest]
api-level: [16, 23, 29]
target: [default, google_apis]
arch: [x86]
exclude:
- os: ubuntu-latest
api-level: 23
@@ -30,12 +31,18 @@ jobs:
- target: google_apis
api-level: 29
include:
- os: macos-latest
api-level: 30
target: google_apis
- os: macos-latest
api-level: 24
target: playstore
arch: x86
- os: macos-latest
api-level: 28
target: google_apis
arch: x86
- os: macos-11
api-level: 31
target: google_apis
arch: x86_64
steps:
- name: checkout
@@ -61,19 +68,53 @@ jobs:
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
- uses: actions/cache@v2
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
~/.android/debug.keystore
key: avd-${{ matrix.api-level }}-${{ matrix.os }}-${{ matrix.target }}
- name: assemble tests
run: |
cd ./test-fixture/
./gradlew assembleAndroidTest
- name: run emulator to generate snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: ./
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: ${{ matrix.arch }}
profile: Galaxy Nexus
cores: 2
sdcard-path-or-size: 100M
avd-name: test
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: false
working-directory: ./test-fixture/
script: echo "Generated AVD snapshot for caching."
- name: run action
uses: ./
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: x86
profile: Nexus 6
arch: ${{ matrix.arch }}
profile: Galaxy Nexus
cores: 2
ram-size: 2048M
sdcard-path-or-size: 100M
avd-name: test
emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
working-directory: ./test-fixture/
script: |
echo $GITHUB_REPOSITORY
adb devices
./gradlew help
./gradlew connectedDebugAndroidTest
+76
View File
@@ -0,0 +1,76 @@
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: 'target of the system image - default, google_apis, google_apis_playstore, android-wear, android-wear-cn, android-tv or google-tv'
required: true
default: 'default'
arch:
description: 'CPU architecture of the system image - x86, x86_64 or arm64-v8a'
default: 'x86'
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 }}
+60
View File
@@ -1,5 +1,65 @@
# Change Log
## v2.20.0
* Support non-mobile targets: `android-wear`, `android-wear-cn`, `android-tv` and `google-tv`. - [#180](https://github.com/ReactiveCircus/android-emulator-runner/pull/180) @alexvanyo.
* Update SDK command-line tools to `5.0`. - [#174](https://github.com/ReactiveCircus/android-emulator-runner/pull/174)
* Update build tools to `31.0.0`. - [#174](https://github.com/ReactiveCircus/android-emulator-runner/pull/174)
* Add option to specify the `channel` to download SDK components from: `stable` (default), `beta`, `dev` and `canary`. - [#185](https://github.com/ReactiveCircus/android-emulator-runner/pull/185)
## v2.19.1
* Accept all Android SDL Licenses to fix `sdkmanager` installation. - [#172](https://github.com/ReactiveCircus/android-emulator-runner/pull/172) @marcuspridham.
## v2.19.0
* Add option to specify `ram-size` for the AVD. - [#165](https://github.com/ReactiveCircus/android-emulator-runner/pull/165).
## v2.18.1
* Added support for setting modern emulator build ids for `emulator-build`. Not all build ids are supported until we are able to figure out at which build id the download URL pattern changed. `7425822` (version 30.7.3) is currently the last known working build id.
## v2.18.0
* Add `force-avd-creation` which when set to `false` will skip avd creation if avd with same name exists. This enables AVD snapshot caching which can significantly reduce emulator startup time. See [README.md](https://github.com/ReactiveCircus/android-emulator-runner/blob/main/README.md#usage) for a sample workflow. - [#159](https://github.com/ReactiveCircus/android-emulator-runner/pull/159)
## v2.17.0
* Add option to toggle Linux hardware acceleration - [#154](https://github.com/ReactiveCircus/android-emulator-runner/pull/154) @stevestotter
## v2.16.0
* Avoid wrapping script code in quotes - [#134](https://github.com/ReactiveCircus/android-emulator-runner/pull/134) @hostilefork
* Add option to disable spellcheck - [#143](https://github.com/ReactiveCircus/android-emulator-runner/pull/143) @AfzalivE
* Add support for arm64-v8a for Apple Silicon Macs - [#146](https://github.com/ReactiveCircus/android-emulator-runner/pull/146) @Jeehut
## v2.15.0
* Added support for specifying the number of cores to use for the emulator - [#130](https://github.com/ReactiveCircus/android-emulator-runner/pull/130).
## v2.14.3
* Support `macos-11.0` (Big Sur) runner - [#124](https://github.com/ReactiveCircus/android-emulator-runner/pull/124).
## v2.14.2
* Support API 28 system images with `google_apis` or `google_apis_playstore` target - [#117](https://github.com/ReactiveCircus/android-emulator-runner/pull/117).
## v2.14.1
* Fix hang during AVD creation when `profile` is not specified - [#113](https://github.com/ReactiveCircus/android-emulator-runner/issues/113).
## v2.14.0
* Support specifying SD card path or size via `sdcard-path-or-size`.
* Update npm packages.
* Remove usages of deprecated `$ANDROID_HOME`.
## v2.13.0
* Updated to SDK command-line tools `3.0`.
## v2.12.0
Added support for using the `playstore` system images:
+108 -38
View File
@@ -8,8 +8,8 @@ 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 require hardware acceleration (HAXM on Mac & Windows, QEMU on Linux) from the host to run fast. This presents a challenge on CI as to be able to run hardware accelerated emulators within a docker container, **KVM** must be supported by the host VM which isn't the case for cloud-based CI providers due to infrastructural limits. If you want to learn more about this, here's an article I wrote: [Running Android Instrumented Tests on CI](https://dev.to/ychescale9/running-android-emulators-on-ci-from-bitrise-io-to-github-actions-3j76).
The **macOS** VM provided by **GitHub Actions** has **HAXM** installed so we are able to create a new AVD instance, launch an emulator with hardware acceleration, and run our Android
tests directly on the VM.
The **macOS** VM provided by **GitHub Actions** has **HAXM** installed so we are able to create a new AVD instance, launch an emulator with hardware acceleration, and run our Android
tests directly on the VM. You can also achieve this on a self-hosted Linux runner, but it will need to be on a compatible instance that allows you to enable KVM - for example AWS EC2 Bare Metal instances.
This action automates the process by doing the following:
@@ -31,14 +31,14 @@ jobs:
test:
runs-on: macos-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: checkout
uses: actions/checkout@v2
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
script: ./gradlew connectedCheck
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
script: ./gradlew connectedCheck
```
We can also leverage GitHub Actions's build matrix to test across multiple configurations:
@@ -52,17 +52,17 @@ jobs:
api-level: [21, 23, 29]
target: [default, google_apis]
steps:
- name: checkout
uses: actions/checkout@v2
- name: checkout
uses: actions/checkout@v2
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: x86_64
profile: Nexus 6
script: ./gradlew connectedCheck
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: x86_64
profile: Nexus 6
script: ./gradlew connectedCheck
```
If you need specific versions of **NDK** and **CMake** installed:
@@ -72,16 +72,70 @@ jobs:
test:
runs-on: macos-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: checkout
uses: actions/checkout@v2
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
ndk: 21.0.6113669
cmake: 3.10.2.4988404
script: ./gradlew connectedCheck
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
ndk: 21.0.6113669
cmake: 3.10.2.4988404
script: ./gradlew connectedCheck
```
We can significantly reduce emulator startup time by setting up AVD snapshot caching:
1. add an `actions/cache@v2` step for caching the `avd`
2. add a `reactivecircus/android-emulator-runner@v2` step to generate a clean snapshot - specify `emulator-options` without `no-snapshot`
3. add another `reactivecircus/android-emulator-runner@v2` step to run your tests using existing AVD / snapshot - specify `emulator-options` with `no-snapshot-save`
```
jobs:
test:
runs-on: macos-latest
strategy:
matrix:
api-level: [21, 23, 29]
steps:
- name: checkout
uses: actions/checkout@v2
- name: Gradle cache
uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/buildSrc/**/*.kt') }}
- name: AVD cache
uses: actions/cache@v2
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-${{ matrix.api-level }}
- 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 }}
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: false
script: echo "Generated AVD snapshot for caching."
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: ./gradlew connectedCheck
```
## Configurations
@@ -90,22 +144,29 @@ jobs:
|-|-|-|-|
| `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` or `playstore`. |
| `arch` | Optional | `x86` | CPU architecture of the system image - `x86` or `x86_64`. Note that `x86_64` image is only available for API 21+. |
| `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). |
| `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`. |
| `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`. |
| `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 | `true` | Whether to disable hardware acceleration on Linux machines - `true` or `false`. Note that this is true by default as Github-hosted Linux runners do not support hardware acceleration. |
| `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. |
| `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`.
## Can I use this action on Linux VMs?
The short answer is yes but it's expected to be a much worse experience (on some newer API levels it might not work at all) than running it on macOS.
The short answer is yes but on Github-hosted Linux runners it's expected to be a much worse experience (on some newer API levels it might not work at all) than running it on macOS. You can get it running much faster on self-hosted Linux runners but only if the underlying instances support KVM (which most don't).
For a longer answer please refer to [this issue](https://github.com/ReactiveCircus/android-emulator-runner/issues/46).
@@ -114,23 +175,32 @@ For a longer answer please refer to [this issue](https://github.com/ReactiveCirc
These are some of the open-source projects using (or used) **Android Emulator Runner**:
- [coil-kt/coil](https://github.com/coil-kt/coil/blob/master/.github/workflows/)
- [cashapp/sqldelight](https://github.com/cashapp/sqldelight/blob/master/.github/workflows/)
- [square/workflow](https://github.com/square/workflow/blob/main/.github/workflows/)
- [square/retrofit](https://github.com/square/retrofit/blob/master/.github/workflows/)
- [coil-kt/coil](https://github.com/coil-kt/coil/blob/master/.github/workflows)
- [cashapp/sqldelight](https://github.com/cashapp/sqldelight/blob/master/.github/workflows)
- [square/workflow-kotlin](https://github.com/square/workflow-kotlin/tree/main/.github/workflows)
- [square/retrofit](https://github.com/square/retrofit/blob/master/.github/workflows)
- [natario1/CameraView](https://github.com/natario1/CameraView/tree/master/.github/workflows)
- [natario1/Transcoder](https://github.com/natario1/Transcoder/tree/master/.github/workflows)
- [chrisbanes/insetter](https://github.com/chrisbanes/insetter/tree/main/.github/workflows)
- [slackhq/keeper](https://github.com/slackhq/keeper/tree/main/.github/workflows)
- [android/compose-samples](https://github.com/android/compose-samples/blob/master/.github/workflows/ci.yaml)
- [android/compose-samples](https://github.com/android/compose-samples/tree/main/.github/workflows)
- [ReactiveCircus/streamlined](https://github.com/ReactiveCircus/streamlined/tree/main/.github/workflows)
- [ReactiveCircus/FlowBinding](https://github.com/ReactiveCircus/FlowBinding)
- [ReactiveCircus/FlowBinding](https://github.com/ReactiveCircus/FlowBinding/tree/main/.github/workflows)
- [JakeWharton/RxBinding](https://github.com/JakeWharton/RxBinding/tree/master/.github/workflows)
- [vinaygaba/Learn-Jetpack-Compose-By-Example](https://github.com/vinaygaba/Learn-Jetpack-Compose-By-Example/)
- [vinaygaba/Learn-Jetpack-Compose-By-Example](https://github.com/vinaygaba/Learn-Jetpack-Compose-By-Example/tree/master/.github/workflows)
- [ashishb/adb-enhanced](https://github.com/ashishb/adb-enhanced/tree/master/.github/workflows)
- [vgaidarji/ci-matters](https://github.com/vgaidarji/ci-matters/blob/master/.github/workflows/main.yaml)
- [simpledotorg/simple-android](https://github.com/simpledotorg/simple-android)
- [simpledotorg/simple-android](https://github.com/simpledotorg/simple-android/tree/master/.github/workflows)
- [cashapp/copper](https://github.com/cashapp/copper/blob/trunk/.github/workflows/build.yaml)
- [square/radiography](https://github.com/square/radiography/blob/main/.github/workflows/android.yml)
- [Shopify/android-testify](https://github.com/Shopify/android-testify/blob/master/.github/workflows/sample_build.yml)
- [square/leakcanary](https://github.com/square/leakcanary/tree/main/.github/workflows)
- [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)
- [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)
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.
+17
View File
@@ -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);
});
});
+115 -1
View File
@@ -55,9 +55,29 @@ describe('target validator tests', () => {
expect(func2).not.toThrow();
const func3 = () => {
validator.checkTarget('google_apis');
validator.checkTarget('google_apis_playstore');
};
expect(func3).not.toThrow();
const func4 = () => {
validator.checkTarget('android-wear');
};
expect(func4).not.toThrow();
const func5 = () => {
validator.checkTarget('android-wear-cn');
};
expect(func5).not.toThrow();
const func6 = () => {
validator.checkTarget('android-tv');
};
expect(func6).not.toThrow();
const func7 = () => {
validator.checkTarget('google-tv');
};
expect(func7).not.toThrow();
});
});
@@ -82,6 +102,58 @@ 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 = () => {
validator.checkForceAvdCreation('yes');
};
expect(func).toThrowError(`Input for input.force-avd-creation should be either 'true' or 'false'.`);
});
it('Validates successfully if force-avd-creation is either true or false', () => {
const func1 = () => {
validator.checkForceAvdCreation('true');
};
expect(func1).not.toThrow();
const func2 = () => {
validator.checkForceAvdCreation('false');
};
expect(func2).not.toThrow();
});
});
describe('disable-animations validator tests', () => {
it('Throws if disable-animations is not a boolean', () => {
const func = () => {
@@ -103,6 +175,48 @@ describe('disable-animations validator tests', () => {
});
});
describe('disable-spellchecker validator tests', () => {
it('Throws if disable-spellchecker is not a boolean', () => {
const func = () => {
validator.checkDisableSpellchecker('yes');
};
expect(func).toThrowError(`Input for input.disable-spellchecker should be either 'true' or 'false'.`);
});
it('Validates successfully if disable-spellchecker is either true or false', () => {
const func1 = () => {
validator.checkDisableSpellchecker('true');
};
expect(func1).not.toThrow();
const func2 = () => {
validator.checkDisableSpellchecker('false');
};
expect(func2).not.toThrow();
});
});
describe('disable-linux-hw-accel validator tests', () => {
it('Throws if disable-linux-hw-accel is not a boolean', () => {
const func = () => {
validator.checkDisableLinuxHardwareAcceleration('yes');
};
expect(func).toThrowError(`Input for input.disable-linux-hw-accel should be either 'true' or 'false'.`);
});
it('Validates successfully if disable-linux-hw-accel is either true or false', () => {
const func1 = () => {
validator.checkDisableLinuxHardwareAcceleration('true');
};
expect(func1).not.toThrow();
const func2 = () => {
validator.checkDisableLinuxHardwareAcceleration('false');
};
expect(func2).not.toThrow();
});
});
describe('emulator-build validator tests', () => {
it('Throws if emulator-build is not a number', () => {
const func = () => {
+22 -3
View File
@@ -2,29 +2,45 @@ name: 'Android Emulator Runner'
description: 'Installs, configures and starts an Android Emulator directly on macOS virtual machines.'
author: 'Reactive Circus'
branding:
icon: 'smartphone'
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'
required: true
target:
description: 'target of the system image - default, google_apis or playstore'
description: 'target of the system image - default, google_apis, google_apis_playstore, android-wear, android-wear-cn, android-tv or google-tv'
default: 'default'
arch:
description: 'CPU architecture of the system image - x86 or x86_64'
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`'
cores:
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`'
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`'
avd-name:
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'
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'
disable-animations:
description: 'whether to disable animations - true or false'
default: 'true'
disable-spellchecker:
description: 'whether to disable spellchecker - `true` or `false`'
default: 'false'
disable-linux-hw-accel:
description: 'whether to disable hardware acceleration on Linux machines - `true` or `false`'
default: 'true'
emulator-build:
description: 'build number of a specific version of the emulator binary to use - e.g. `6061023` for emulator v29.3.0.0'
working-directory:
@@ -33,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
+21
View File
@@ -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;
+23 -13
View File
@@ -30,28 +30,35 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
Object.defineProperty(exports, "__esModule", { value: true });
exports.killEmulator = exports.launchEmulator = void 0;
const exec = __importStar(require("@actions/exec"));
const fs = __importStar(require("fs"));
const EMULATOR_BOOT_TIMEOUT_SECONDS = 600;
/**
* Creates and launches a new AVD instance with the specified configurations.
*/
function launchEmulator(apiLevel, target, arch, profile, avdName, emulatorOptions, disableAnimations) {
function launchEmulator(apiLevel, target, arch, profile, cores, ramSize, sdcardPathOrSize, avdName, forceAvdCreation, emulatorOptions, disableAnimations, disableSpellChecker, disableLinuxHardwareAcceleration) {
return __awaiter(this, void 0, void 0, function* () {
// create a new AVD
if (profile.trim() !== '') {
console.log(`Creating AVD with custom profile ${profile}`);
yield exec.exec(`avdmanager create avd --force -n "${avdName}" --abi "${target}/${arch}" --package "system-images;android-${apiLevel};${target};${arch}" --device "${profile}"`);
// 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}"`);
}
else {
console.log(`Creating AVD without custom profile.`);
yield exec.exec(`sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --abi '${target}/${arch}' --package 'system-images;android-${apiLevel};${target};${arch}'"`);
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`);
}
//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.');
// turn off hardware acceleration on Linux
if (process.platform === 'linux') {
emulatorOptions += ' -accel off';
}
yield exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -avd "${avdName}" ${emulatorOptions} &"`, [], {
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')) {
@@ -70,6 +77,9 @@ function launchEmulator(apiLevel, target, arch, profile, avdName, emulatorOption
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`);
}
});
}
exports.launchEmulator = launchEmulator;
+32 -4
View File
@@ -1,9 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkEmulatorBuild = exports.checkDisableAnimations = 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'];
exports.VALID_ARCHS = ['x86', 'x86_64'];
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,15 +26,42 @@ 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'.`);
}
}
exports.checkForceAvdCreation = checkForceAvdCreation;
function checkDisableAnimations(disableAnimations) {
if (disableAnimations !== 'true' && disableAnimations !== 'false') {
if (!isValidBoolean(disableAnimations)) {
throw new Error(`Input for input.disable-animations should be either 'true' or 'false'.`);
}
}
exports.checkDisableAnimations = checkDisableAnimations;
function checkDisableSpellchecker(disableSpellchecker) {
if (!isValidBoolean(disableSpellchecker)) {
throw new Error(`Input for input.disable-spellchecker should be either 'true' or 'false'.`);
}
}
exports.checkDisableSpellchecker = checkDisableSpellchecker;
function checkDisableLinuxHardwareAcceleration(disableLinuxHardwareAcceleration) {
if (!isValidBoolean(disableLinuxHardwareAcceleration)) {
throw new Error(`Input for input.disable-linux-hw-accel should be either 'true' or 'false'.`);
}
}
exports.checkDisableLinuxHardwareAcceleration = checkDisableLinuxHardwareAcceleration;
function checkEmulatorBuild(emulatorBuild) {
if (isNaN(Number(emulatorBuild)) || !Number.isInteger(Number(emulatorBuild))) {
throw new Error(`Unexpected emulator build: '${emulatorBuild}'.`);
}
}
exports.checkEmulatorBuild = checkEmulatorBuild;
function isValidBoolean(value) {
return value === 'true' || value === 'false';
}
+35 -3
View File
@@ -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 {
@@ -63,9 +64,23 @@ function run() {
// Hardware profile used for creating the AVD
const profile = core.getInput('profile');
console.log(`Hardware profile: ${profile}`);
// Number of cores to use for emulator
const cores = core.getInput('cores');
console.log(`Cores: ${cores}`);
// RAM to use for AVD
const ramSize = core.getInput('ram-size');
console.log(`RAM size: ${ramSize}`);
// 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}`);
// custom name used for creating the AVD
const avdName = core.getInput('avd-name');
console.log(`AVD name: ${avdName}`);
// force AVD creation
const forceAvdCreationInput = core.getInput('force-avd-creation');
input_validator_1.checkForceAvdCreation(forceAvdCreationInput);
const forceAvdCreation = forceAvdCreationInput === 'true';
console.log(`force avd creation: ${forceAvdCreation}`);
// emulator options
const emulatorOptions = core.getInput('emulator-options').trim();
console.log(`emulator options: ${emulatorOptions}`);
@@ -74,6 +89,16 @@ function run() {
input_validator_1.checkDisableAnimations(disableAnimationsInput);
const disableAnimations = disableAnimationsInput === 'true';
console.log(`disable animations: ${disableAnimations}`);
// disable spellchecker
const disableSpellcheckerInput = core.getInput('disable-spellchecker');
input_validator_1.checkDisableSpellchecker(disableSpellcheckerInput);
const disableSpellchecker = disableSpellcheckerInput === 'true';
console.log(`disable spellchecker: ${disableSpellchecker}`);
// disable linux hardware acceleration
const disableLinuxHardwareAccelerationInput = core.getInput('disable-linux-hw-accel');
input_validator_1.checkDisableLinuxHardwareAcceleration(disableLinuxHardwareAccelerationInput);
const disableLinuxHardwareAcceleration = disableLinuxHardwareAccelerationInput === 'true';
console.log(`disable Linux hardware acceleration: ${disableLinuxHardwareAcceleration}`);
// emulator build
const emulatorBuildInput = core.getInput('emulator-build');
if (emulatorBuildInput) {
@@ -99,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);
@@ -107,9 +137,9 @@ 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, avdName, emulatorOptions, disableAnimations);
yield emulator_manager_1.launchEmulator(apiLevel, target, arch, profile, cores, ramSize, sdcardPathOrSize, avdName, forceAvdCreation, emulatorOptions, disableAnimations, disableSpellchecker, disableLinuxHardwareAcceleration);
// execute the custom script
try {
// move to custom working directory if set
@@ -117,7 +147,9 @@ function run() {
process.chdir(workingDirectory);
}
for (const script of scripts) {
yield exec.exec(`sh -c \\"${script}"`);
// use array form to avoid various quote escaping problems
// caused by exec(`sh -c "${script}"`)
yield exec.exec('sh', ['-c', script]);
}
}
catch (error) {
+25 -32
View File
@@ -32,63 +32,56 @@ exports.installAndroidSdk = void 0;
const core = __importStar(require("@actions/core"));
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 = '30.0.2';
const CMDLINE_TOOLS_URL_MAC = 'https://dl.google.com/android/repository/commandlinetools-mac-6609375_latest.zip';
const CMDLINE_TOOLS_URL_LINUX = 'https://dl.google.com/android/repository/commandlinetools-linux-6609375_latest.zip';
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';
/**
* 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) {
yield exec.exec(`sh -c \\"sudo chown $USER:$USER ${process.env.ANDROID_HOME} -R`);
yield exec.exec(`sh -c \\"sudo chown $USER:$USER ${process.env.ANDROID_SDK_ROOT} -R`);
}
const cmdlineToolsPath = `${process.env.ANDROID_HOME}/cmdline-tools`;
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;
yield io.mkdirP(`${process.env.ANDROID_HOME}/cmdline-tools`);
yield exec.exec(`curl -fo commandlinetools.zip ${sdkUrl}`);
yield exec.exec(`unzip -q commandlinetools.zip -d ${cmdlineToolsPath}`);
yield io.rmRF('commandlinetools.zip');
// add paths for commandline-tools and platform-tools
core.addPath(`${cmdlineToolsPath}/tools:${cmdlineToolsPath}/tools/bin:${process.env.ANDROID_HOME}/platform-tools`);
}
// additional permission and license requirements for Linux
const sdkPreviewLicensePath = `${process.env.ANDROID_HOME}/licenses/android-sdk-preview-license`;
if (!isOnMac && !fs.existsSync(sdkPreviewLicensePath)) {
fs.writeFileSync(sdkPreviewLicensePath, '\n84831b9409646a918e30573bab4c9c91346d8abd');
}
// license required for API 30 system images
const sdkArmDbtLicensePath = `${process.env.ANDROID_HOME}/licenses/android-sdk-arm-dbt-license`;
if (apiLevel == 30 && !fs.existsSync(sdkArmDbtLicensePath)) {
fs.writeFileSync(sdkArmDbtLicensePath, '\n859f317696f67ef3d7f30a50a5560e7834b43903');
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}.`);
yield exec.exec(`curl -fo emulator.zip https://dl.google.com/android/repository/emulator-${isOnMac ? 'darwin' : 'linux'}-${emulatorBuild}.zip`);
yield io.rmRF(`${process.env.ANDROID_HOME}/emulator`);
yield exec.exec(`unzip -q emulator.zip -d ${process.env.ANDROID_HOME}`);
// 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');
}
else {
console.log('Installing latest emulator.');
yield exec.exec(`sh -c \\"sdkmanager --install emulator > /dev/null"`);
}
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"`);
}
});
}
+7 -1
View File
@@ -1,5 +1,11 @@
## Releases
## 1.0.9
Throw HttpClientError instead of a generic Error from the \<verb>Json() helper methods when the server responds with a non-successful status code.
## 1.0.8
Fixed security issue where a redirect (e.g. 302) to another domain would pass headers. The fix was to strip the authorization header if the hostname was different. More [details in PR #27](https://github.com/actions/http-client/pull/27)
## 1.0.7
Update NPM dependencies and add 429 to the list of HttpCodes
@@ -13,4 +19,4 @@ Adds \<verb>Json() helper methods for json over http scenarios.
Started to add \<verb>Json() helper methods. Do not use this release for that. Use >= 1.0.5 since there was an issue with types.
## 1.0.1 to 1.0.3
Adds proxy support.
Adds proxy support.
+5
View File
@@ -42,6 +42,11 @@ export declare enum MediaTypes {
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/
export declare function getProxyUrl(serverUrl: string): string;
export declare class HttpClientError extends Error {
constructor(message: string, statusCode: number);
statusCode: number;
result?: any;
}
export declare class HttpClientResponse implements ifm.IHttpClientResponse {
constructor(message: http.IncomingMessage);
message: http.IncomingMessage;
+17 -13
View File
@@ -1,6 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const url = require("url");
const http = require("http");
const https = require("https");
const pm = require("./proxy");
@@ -49,7 +48,7 @@ var MediaTypes;
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/
function getProxyUrl(serverUrl) {
let proxyUrl = pm.getProxyUrl(url.parse(serverUrl));
let proxyUrl = pm.getProxyUrl(new URL(serverUrl));
return proxyUrl ? proxyUrl.href : '';
}
exports.getProxyUrl = getProxyUrl;
@@ -68,6 +67,15 @@ const HttpResponseRetryCodes = [
const RetryableHttpVerbs = ['OPTIONS', 'GET', 'DELETE', 'HEAD'];
const ExponentialBackoffCeiling = 10;
const ExponentialBackoffTimeSlice = 5;
class HttpClientError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'HttpClientError';
this.statusCode = statusCode;
Object.setPrototypeOf(this, HttpClientError.prototype);
}
}
exports.HttpClientError = HttpClientError;
class HttpClientResponse {
constructor(message) {
this.message = message;
@@ -86,7 +94,7 @@ class HttpClientResponse {
}
exports.HttpClientResponse = HttpClientResponse;
function isHttps(requestUrl) {
let parsedUrl = url.parse(requestUrl);
let parsedUrl = new URL(requestUrl);
return parsedUrl.protocol === 'https:';
}
exports.isHttps = isHttps;
@@ -191,7 +199,7 @@ class HttpClient {
if (this._disposed) {
throw new Error('Client has already been disposed.');
}
let parsedUrl = url.parse(requestUrl);
let parsedUrl = new URL(requestUrl);
let info = this._prepareRequest(verb, parsedUrl, headers);
// Only perform retries on reads since writes may not be idempotent.
let maxTries = this._allowRetries && RetryableHttpVerbs.indexOf(verb) != -1
@@ -230,7 +238,7 @@ class HttpClient {
// if there's no location to redirect to, we won't
break;
}
let parsedRedirectUrl = url.parse(redirectUrl);
let parsedRedirectUrl = new URL(redirectUrl);
if (parsedUrl.protocol == 'https:' &&
parsedUrl.protocol != parsedRedirectUrl.protocol &&
!this._allowRedirectDowngrade) {
@@ -346,7 +354,7 @@ class HttpClient {
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/
getAgent(serverUrl) {
let parsedUrl = url.parse(serverUrl);
let parsedUrl = new URL(serverUrl);
return this._getAgent(parsedUrl);
}
_prepareRequest(method, requestUrl, headers) {
@@ -419,7 +427,7 @@ class HttpClient {
maxSockets: maxSockets,
keepAlive: this._keepAlive,
proxy: {
proxyAuth: proxyUrl.auth,
proxyAuth: `${proxyUrl.username}:${proxyUrl.password}`,
host: proxyUrl.hostname,
port: proxyUrl.port
}
@@ -514,12 +522,8 @@ class HttpClient {
else {
msg = 'Failed request: (' + statusCode + ')';
}
let err = new Error(msg);
// attach statusCode and body obj (if available) to the error object
err['statusCode'] = statusCode;
if (response.result) {
err['result'] = response.result;
}
let err = new HttpClientError(msg, statusCode);
err.result = response.result;
reject(err);
}
else {
+1 -2
View File
@@ -1,6 +1,5 @@
/// <reference types="node" />
import http = require('http');
import url = require('url');
export interface IHeaders {
[key: string]: any;
}
@@ -27,7 +26,7 @@ export interface IHttpClientResponse {
}
export interface IRequestInfo {
options: http.RequestOptions;
parsedUrl: url.Url;
parsedUrl: URL;
httpModule: any;
}
export interface IRequestOptions {
+12 -12
View File
@@ -1,33 +1,33 @@
{
"_args": [
[
"@actions/http-client@1.0.8",
"/Users/yang/Desktop/Projects/android-emulator-runner"
"@actions/http-client@1.0.9",
"."
]
],
"_from": "@actions/http-client@1.0.8",
"_id": "@actions/http-client@1.0.8",
"_from": "@actions/http-client@1.0.9",
"_id": "@actions/http-client@1.0.9",
"_inBundle": false,
"_integrity": "sha512-G4JjJ6f9Hb3Zvejj+ewLLKLf99ZC+9v+yCxoYf9vSyH+WkzPLB2LuUtRMGNkooMqdugGBFStIKXOuvH1W+EctA==",
"_integrity": "sha512-0O4SsJ7q+MK0ycvXPl2e6bMXV7dxAXOGjrXS1eTF9s2S401Tp6c/P3c3Joz04QefC1J6Gt942Wl2jbm3f4mLcg==",
"_location": "/@actions/http-client",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "@actions/http-client@1.0.8",
"raw": "@actions/http-client@1.0.9",
"name": "@actions/http-client",
"escapedName": "@actions%2fhttp-client",
"scope": "@actions",
"rawSpec": "1.0.8",
"rawSpec": "1.0.9",
"saveSpec": null,
"fetchSpec": "1.0.8"
"fetchSpec": "1.0.9"
},
"_requiredBy": [
"/@actions/tool-cache"
],
"_resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.8.tgz",
"_spec": "1.0.8",
"_where": "/Users/yang/Desktop/Projects/android-emulator-runner",
"_resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.9.tgz",
"_spec": "1.0.9",
"_where": ".",
"author": {
"name": "GitHub, Inc."
},
@@ -66,5 +66,5 @@
"format-check": "prettier --check *.ts && prettier --check **/*.ts",
"test": "jest"
},
"version": "1.0.8"
"version": "1.0.9"
}
+2 -4
View File
@@ -1,4 +1,2 @@
/// <reference types="node" />
import * as url from 'url';
export declare function getProxyUrl(reqUrl: url.Url): url.Url | undefined;
export declare function checkBypass(reqUrl: url.Url): boolean;
export declare function getProxyUrl(reqUrl: URL): URL | undefined;
export declare function checkBypass(reqUrl: URL): boolean;
+1 -2
View File
@@ -1,6 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const url = require("url");
function getProxyUrl(reqUrl) {
let usingSsl = reqUrl.protocol === 'https:';
let proxyUrl;
@@ -15,7 +14,7 @@ function getProxyUrl(reqUrl) {
proxyVar = process.env['http_proxy'] || process.env['HTTP_PROXY'];
}
if (proxyVar) {
proxyUrl = url.parse(proxyVar);
proxyUrl = new URL(proxyVar);
}
return proxyUrl;
}
+9
View File
@@ -0,0 +1,9 @@
The MIT License (MIT)
Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+12 -12
View File
@@ -1,38 +1,38 @@
{
"_args": [
[
"@actions/tool-cache@1.6.0",
"@actions/tool-cache@1.6.1",
"."
]
],
"_from": "@actions/tool-cache@1.6.0",
"_id": "@actions/tool-cache@1.6.0",
"_from": "@actions/tool-cache@1.6.1",
"_id": "@actions/tool-cache@1.6.1",
"_inBundle": false,
"_integrity": "sha512-+fyEBImPD3m5I0o6DflCO0NHY180LPoX8Lo6y4Iez+V17kO8kfkH0VHxb8mUdmD6hn9dWA9Ch1JA20fXoIYUeQ==",
"_integrity": "sha512-F+vwEDwfqcHMKuSkj79pihOnsAMv23EkG76nMpc82UsnXwyQdyEsktGxrB0SNtm7pRqTXEIOoAPTgrSQclXYTg==",
"_location": "/@actions/tool-cache",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "@actions/tool-cache@1.6.0",
"raw": "@actions/tool-cache@1.6.1",
"name": "@actions/tool-cache",
"escapedName": "@actions%2ftool-cache",
"scope": "@actions",
"rawSpec": "1.6.0",
"rawSpec": "1.6.1",
"saveSpec": null,
"fetchSpec": "1.6.0"
"fetchSpec": "1.6.1"
},
"_requiredBy": [
"/"
],
"_resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-1.6.0.tgz",
"_spec": "1.6.0",
"_resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-1.6.1.tgz",
"_spec": "1.6.1",
"_where": ".",
"bugs": {
"url": "https://github.com/actions/toolkit/issues"
},
"dependencies": {
"@actions/core": "^1.2.3",
"@actions/core": "^1.2.6",
"@actions/exec": "^1.0.0",
"@actions/http-client": "^1.0.8",
"@actions/io": "^1.0.1",
@@ -54,7 +54,7 @@
"lib",
"scripts"
],
"homepage": "https://github.com/actions/toolkit/tree/master/packages/tool-cache",
"homepage": "https://github.com/actions/toolkit/tree/main/packages/tool-cache",
"keywords": [
"github",
"actions",
@@ -77,5 +77,5 @@
"tsc": "tsc"
},
"types": "lib/tool-cache.d.ts",
"version": "1.6.0"
"version": "1.6.1"
}
+2918 -343
View File
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -23,22 +23,22 @@
"dependencies": {
"@actions/core": "^1.2.6",
"@actions/exec": "^1.0.4",
"@actions/tool-cache": "^1.6.0",
"@actions/tool-cache": "^1.6.1",
"minimist": "^1.2.5"
},
"devDependencies": {
"@types/jest": "^24.9.1",
"@types/node": "^12.12.64",
"@types/node": "^12.19.11",
"@typescript-eslint/eslint-plugin": "^2.34.0",
"@typescript-eslint/parser": "^2.34.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.12.0",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-prettier": "^3.1.4",
"jest": "^25.5.4",
"eslint-plugin-prettier": "^3.3.0",
"jest": "^26.6.3",
"jest-circus": "^25.5.4",
"prettier": "^1.19.1",
"ts-jest": "^26.4.1",
"ts-jest": "^26.4.4",
"typescript": "^3.9.7"
}
}
+13
View File
@@ -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}'.`);
}
}
+43 -14
View File
@@ -1,29 +1,55 @@
import * as exec from '@actions/exec';
import * as fs from 'fs';
const EMULATOR_BOOT_TIMEOUT_SECONDS = 600;
/**
* Creates and launches a new AVD instance with the specified configurations.
*/
export async function launchEmulator(apiLevel: number, target: string, arch: string, profile: string, avdName: string, emulatorOptions: string, disableAnimations: boolean): Promise<void> {
// create a new AVD
if (profile.trim() !== '') {
console.log(`Creating AVD with custom profile ${profile}`);
await exec.exec(`avdmanager create avd --force -n "${avdName}" --abi "${target}/${arch}" --package "system-images;android-${apiLevel};${target};${arch}" --device "${profile}"`);
} else {
console.log(`Creating AVD without custom profile.`);
await exec.exec(`sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --abi '${target}/${arch}' --package 'system-images;android-${apiLevel};${target};${arch}'"`);
export async function launchEmulator(
apiLevel: number,
target: string,
arch: string,
profile: string,
cores: string,
ramSize: string,
sdcardPathOrSize: string,
avdName: string,
forceAvdCreation: boolean,
emulatorOptions: string,
disableAnimations: boolean,
disableSpellChecker: boolean,
disableLinuxHardwareAcceleration: boolean
): Promise<void> {
// 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 (ramSize) {
await exec.exec(`sh -c \\"printf 'hw.ramSize=${ramSize}\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.');
// turn off hardware acceleration on Linux
if (process.platform === 'linux') {
emulatorOptions += ' -accel off';
}
await exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -avd "${avdName}" ${emulatorOptions} &"`, [], {
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')) {
@@ -44,6 +70,9 @@ export async function launchEmulator(apiLevel: number, target: string, arch: str
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`);
}
}
/**
+32 -3
View File
@@ -1,6 +1,7 @@
export const MIN_API_LEVEL = 15;
export const VALID_TARGETS: Array<string> = ['default', 'google_apis', 'google_apis_playstore'];
export const VALID_ARCHS: Array<string> = ['x86', 'x86_64'];
export const VALID_TARGETS: Array<string> = ['default', 'google_apis', 'google_apis_playstore', 'android-wear', 'android-wear-cn', 'android-tv', 'google-tv'];
export const VALID_ARCHS: Array<string> = ['x86', 'x86_64', 'arm64-v8a'];
export const VALID_CHANNELS: Array<string> = ['stable', 'beta', 'dev', 'canary'];
export function checkApiLevel(apiLevel: string): void {
if (isNaN(Number(apiLevel)) || !Number.isInteger(Number(apiLevel))) {
@@ -23,14 +24,42 @@ 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'.`);
}
}
export function checkDisableAnimations(disableAnimations: string): void {
if (disableAnimations !== 'true' && disableAnimations !== 'false') {
if (!isValidBoolean(disableAnimations)) {
throw new Error(`Input for input.disable-animations should be either 'true' or 'false'.`);
}
}
export function checkDisableSpellchecker(disableSpellchecker: string): void {
if (!isValidBoolean(disableSpellchecker)) {
throw new Error(`Input for input.disable-spellchecker should be either 'true' or 'false'.`);
}
}
export function checkDisableLinuxHardwareAcceleration(disableLinuxHardwareAcceleration: string): void {
if (!isValidBoolean(disableLinuxHardwareAcceleration)) {
throw new Error(`Input for input.disable-linux-hw-accel should be either 'true' or 'false'.`);
}
}
export function checkEmulatorBuild(emulatorBuild: string): void {
if (isNaN(Number(emulatorBuild)) || !Number.isInteger(Number(emulatorBuild))) {
throw new Error(`Unexpected emulator build: '${emulatorBuild}'.`);
}
}
function isValidBoolean(value: string): boolean {
return value === 'true' || value === 'false';
}
+67 -4
View File
@@ -1,9 +1,20 @@
import * as core from '@actions/core';
import { installAndroidSdk } from './sdk-installer';
import { checkApiLevel, checkTarget, checkArch, checkDisableAnimations, checkEmulatorBuild } from './input-validator';
import {
checkApiLevel,
checkTarget,
checkArch,
checkDisableAnimations,
checkEmulatorBuild,
checkDisableSpellchecker,
checkDisableLinuxHardwareAcceleration,
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 {
@@ -39,10 +50,28 @@ async function run() {
const profile = core.getInput('profile');
console.log(`Hardware profile: ${profile}`);
// Number of cores to use for emulator
const cores = core.getInput('cores');
console.log(`Cores: ${cores}`);
// RAM to use for AVD
const ramSize = core.getInput('ram-size');
console.log(`RAM size: ${ramSize}`);
// 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}`);
// custom name used for creating the AVD
const avdName = core.getInput('avd-name');
console.log(`AVD name: ${avdName}`);
// force AVD creation
const forceAvdCreationInput = core.getInput('force-avd-creation');
checkForceAvdCreation(forceAvdCreationInput);
const forceAvdCreation = forceAvdCreationInput === 'true';
console.log(`force avd creation: ${forceAvdCreation}`);
// emulator options
const emulatorOptions = core.getInput('emulator-options').trim();
console.log(`emulator options: ${emulatorOptions}`);
@@ -53,6 +82,18 @@ async function run() {
const disableAnimations = disableAnimationsInput === 'true';
console.log(`disable animations: ${disableAnimations}`);
// disable spellchecker
const disableSpellcheckerInput = core.getInput('disable-spellchecker');
checkDisableSpellchecker(disableSpellcheckerInput);
const disableSpellchecker = disableSpellcheckerInput === 'true';
console.log(`disable spellchecker: ${disableSpellchecker}`);
// disable linux hardware acceleration
const disableLinuxHardwareAccelerationInput = core.getInput('disable-linux-hw-accel');
checkDisableLinuxHardwareAcceleration(disableLinuxHardwareAccelerationInput);
const disableLinuxHardwareAcceleration = disableLinuxHardwareAccelerationInput === 'true';
console.log(`disable Linux hardware acceleration: ${disableLinuxHardwareAcceleration}`);
// emulator build
const emulatorBuildInput = core.getInput('emulator-build');
if (emulatorBuildInput) {
@@ -82,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);
@@ -91,10 +138,24 @@ 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(apiLevel, target, arch, profile, avdName, emulatorOptions, disableAnimations);
await launchEmulator(
apiLevel,
target,
arch,
profile,
cores,
ramSize,
sdcardPathOrSize,
avdName,
forceAvdCreation,
emulatorOptions,
disableAnimations,
disableSpellchecker,
disableLinuxHardwareAcceleration
);
// execute the custom script
try {
@@ -103,7 +164,9 @@ async function run() {
process.chdir(workingDirectory);
}
for (const script of scripts) {
await exec.exec(`sh -c \\"${script}"`);
// use array form to avoid various quote escaping problems
// caused by exec(`sh -c "${script}"`)
await exec.exec('sh', ['-c', script]);
}
} catch (error) {
core.setFailed(error.message);
+28 -32
View File
@@ -1,70 +1,66 @@
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as io from '@actions/io';
import * as tc from '@actions/tool-cache';
import * as fs from 'fs';
const BUILD_TOOLS_VERSION = '30.0.2';
const CMDLINE_TOOLS_URL_MAC = 'https://dl.google.com/android/repository/commandlinetools-mac-6609375_latest.zip';
const CMDLINE_TOOLS_URL_LINUX = 'https://dl.google.com/android/repository/commandlinetools-linux-6609375_latest.zip';
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';
/**
* 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<void> {
export async function installAndroidSdk(apiLevel: number, target: string, arch: string, channelId: number, emulatorBuild?: string, ndkVersion?: string, cmakeVersion?: string): Promise<void> {
const isOnMac = process.platform === 'darwin';
if (!isOnMac) {
await exec.exec(`sh -c \\"sudo chown $USER:$USER ${process.env.ANDROID_HOME} -R`);
await exec.exec(`sh -c \\"sudo chown $USER:$USER ${process.env.ANDROID_SDK_ROOT} -R`);
}
const cmdlineToolsPath = `${process.env.ANDROID_HOME}/cmdline-tools`;
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;
await io.mkdirP(`${process.env.ANDROID_HOME}/cmdline-tools`);
await exec.exec(`curl -fo commandlinetools.zip ${sdkUrl}`);
await exec.exec(`unzip -q commandlinetools.zip -d ${cmdlineToolsPath}`);
await io.rmRF('commandlinetools.zip');
// add paths for commandline-tools and platform-tools
core.addPath(`${cmdlineToolsPath}/tools:${cmdlineToolsPath}/tools/bin:${process.env.ANDROID_HOME}/platform-tools`);
const downloadPath = await tc.downloadTool(sdkUrl);
await tc.extractZip(downloadPath, cmdlineToolsPath);
await io.mv(`${cmdlineToolsPath}/cmdline-tools`, `${cmdlineToolsPath}/latest`);
}
// additional permission and license requirements for Linux
const sdkPreviewLicensePath = `${process.env.ANDROID_HOME}/licenses/android-sdk-preview-license`;
if (!isOnMac && !fs.existsSync(sdkPreviewLicensePath)) {
fs.writeFileSync(sdkPreviewLicensePath, '\n84831b9409646a918e30573bab4c9c91346d8abd');
}
// add paths for commandline-tools and platform-tools
core.addPath(`${cmdlineToolsPath}/latest:${cmdlineToolsPath}/latest/bin:${process.env.ANDROID_SDK_ROOT}/platform-tools`);
// license required for API 30 system images
const sdkArmDbtLicensePath = `${process.env.ANDROID_HOME}/licenses/android-sdk-arm-dbt-license`;
if (apiLevel == 30 && !fs.existsSync(sdkArmDbtLicensePath)) {
fs.writeFileSync(sdkArmDbtLicensePath, '\n859f317696f67ef3d7f30a50a5560e7834b43903');
}
// 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"`);
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"`);
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}.`);
await exec.exec(`curl -fo emulator.zip https://dl.google.com/android/repository/emulator-${isOnMac ? 'darwin' : 'linux'}-${emulatorBuild}.zip`);
await io.rmRF(`${process.env.ANDROID_HOME}/emulator`);
await exec.exec(`unzip -q emulator.zip -d ${process.env.ANDROID_HOME}`);
// 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');
} else {
console.log('Installing latest emulator.');
await exec.exec(`sh -c \\"sdkmanager --install emulator > /dev/null"`);
}
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"`);
}
}
+7 -11
View File
@@ -1,15 +1,14 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
compileSdkVersion 31
buildToolsVersion "31.0.0"
defaultConfig {
applicationId "com.example.testapp"
minSdkVersion 15
targetSdkVersion 30
targetSdkVersion 31
versionCode 1
versionName "1.0"
@@ -26,11 +25,8 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.3.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
@@ -9,7 +9,9 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name="com.example.testapp.MainActivity">
<activity
android:name="com.example.testapp.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
@@ -10,9 +10,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:layout_gravity="center" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
+2 -2
View File
@@ -1,13 +1,13 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.4.0'
ext.kotlin_version = '1.5.30'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
classpath 'com.android.tools.build:gradle:7.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
-13
View File
@@ -1,7 +1,6 @@
org.gradle.parallel=true
org.gradle.configureondemand=true
org.gradle.caching=true
org.gradle.vfs.watch=true
# Enable Kotlin incremental compilation
kotlin.incremental=true
@@ -24,17 +23,5 @@ kapt.include.compile.classpath=false
# Use R8 instead of ProGuard for code shrinking.
android.enableR8.fullMode=true
# Enable gradle workers
android.enableGradleWorkers=true
# Enable AndroidX
android.useAndroidX=true
# Enable rudimentary R class namespacing where each library only contains
# references to the resources it declares instead of declarations plus all
# transitive dependency references.
android.namespacedRClass=true
# Only keep the single relevant constructor for types mentioned in XML files
# instead of using a parameter wildcard which keeps them all.
android.useMinimalKeepRules=true
+1 -1
View File
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip