mirror of
https://github.com/reactivecircus/android-emulator-runner.git
synced 2026-09-01 01:59:32 +00:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e53cc7208d | |||
| dc1e458fb5 | |||
| 028694f45a | |||
| 088647b700 | |||
| 91d2d14e77 | |||
| ed009f5318 | |||
| 04335bcf9e | |||
| 660ac26f5b | |||
| cad321644d | |||
| ed6254cde0 | |||
| 8acdc1106b | |||
| a917366169 | |||
| ae2c4d7c48 | |||
| 9644b1a1f9 | |||
| 4fe4b1ae37 | |||
| 6fe36e1748 | |||
| 597aa7c21b | |||
| 38164a29c1 | |||
| 969c9896d3 | |||
| 29b35ed135 | |||
| 001613e856 | |||
| 937ab7eebc | |||
| 016d4d097a | |||
| f9bdb6d84e | |||
| b68ca169d6 | |||
| 66283c0319 | |||
| f2bf410054 | |||
| 62e6348453 | |||
| 450c4c9f73 | |||
| 2548f9f3fc | |||
| 595a4563df | |||
| a173d65200 | |||
| 50d5b10c01 | |||
| c77bfe7c0b | |||
| ead704cc5a | |||
| e800ffffe6 | |||
| 8c07710601 | |||
| 3a18f50216 | |||
| 1b985f4c7a | |||
| a3dcdb348b | |||
| 81f860b51a | |||
| e93b947104 | |||
| 4b0628e9f8 | |||
| ca77a1081f | |||
| 1fef06a960 | |||
| d8da5b6410 | |||
| a0c0e7216b | |||
| 9d5d6236f4 | |||
| bc2cd2aaea | |||
| 61a61f94a9 | |||
| 426fbd2820 | |||
| e1bd48ca1d | |||
| 1e33853aad | |||
| c479bb6c0f |
@@ -9,5 +9,5 @@ jobs:
|
||||
validate-typings:
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: krzema12/github-actions-typing@v0
|
||||
- uses: actions/checkout@v7
|
||||
- uses: krzema12/github-actions-typing@v2
|
||||
|
||||
+41
-33
@@ -18,41 +18,40 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest]
|
||||
api-level: [23, 29]
|
||||
target: [default, google_apis]
|
||||
arch: [x86]
|
||||
exclude:
|
||||
- os: ubuntu-latest
|
||||
api-level: 23
|
||||
- os: ubuntu-latest
|
||||
api-level: 29
|
||||
- target: google_apis
|
||||
api-level: 16
|
||||
- target: google_apis
|
||||
api-level: 23
|
||||
- target: google_apis
|
||||
api-level: 29
|
||||
include:
|
||||
- os: macos-latest
|
||||
- os: ubuntu-22.04
|
||||
api-level: 23
|
||||
target: default
|
||||
arch: x86
|
||||
- os: ubuntu-24.04
|
||||
api-level: 24
|
||||
target: playstore
|
||||
arch: x86
|
||||
- os: macos-latest
|
||||
api-level: 30
|
||||
target: aosp_atd
|
||||
arch: x86
|
||||
- os: macos-11
|
||||
- os: macos-15-intel
|
||||
api-level: 31
|
||||
target: default
|
||||
arch: x86_64
|
||||
- os: ubuntu-24.04
|
||||
api-level: 34
|
||||
target: aosp_atd
|
||||
arch: x86_64
|
||||
- os: ubuntu-latest
|
||||
api-level: 34-ext10
|
||||
target: android-automotive
|
||||
arch: x86_64
|
||||
system-image-api-level: 34-ext9
|
||||
- os: ubuntu-latest
|
||||
api-level: 35
|
||||
target: google_apis
|
||||
arch: x86_64
|
||||
- os: ubuntu-latest
|
||||
api-level: "37.1"
|
||||
target: google_apis_playstore_ps16k
|
||||
arch: x86_64
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: validate gradle wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: build, test and lint
|
||||
run: |
|
||||
@@ -61,25 +60,32 @@ jobs:
|
||||
npm run lint
|
||||
npm test
|
||||
|
||||
- uses: actions/setup-java@v3
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 19
|
||||
java-version: 25
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v6
|
||||
id: avd-cache
|
||||
with:
|
||||
path: |
|
||||
~/.android/avd/*
|
||||
~/.android/adb*
|
||||
~/.android/debug.keystore
|
||||
key: avd-${{ matrix.api-level }}-${{ matrix.os }}-${{ matrix.target }}
|
||||
key: avd-${{ matrix.api-level }}-${{ matrix.os }}-${{ matrix.target }}-${{ matrix.arch }}
|
||||
|
||||
- uses: gradle/actions/setup-gradle@v6
|
||||
|
||||
- name: assemble tests
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
build-root-directory: test-fixture
|
||||
arguments: assembleAndroidTest
|
||||
working-directory: test-fixture
|
||||
run: ./gradlew assembleAndroidTest
|
||||
|
||||
- name: enable KVM for linux runners
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: run emulator to generate snapshot for caching
|
||||
if: steps.avd-cache.outputs.cache-hit != 'true'
|
||||
@@ -88,6 +94,7 @@ jobs:
|
||||
api-level: ${{ matrix.api-level }}
|
||||
target: ${{ matrix.target }}
|
||||
arch: ${{ matrix.arch }}
|
||||
system-image-api-level: ${{ matrix.system-image-api-level }}
|
||||
profile: Galaxy Nexus
|
||||
cores: 2
|
||||
sdcard-path-or-size: 100M
|
||||
@@ -105,6 +112,7 @@ jobs:
|
||||
api-level: ${{ matrix.api-level }}
|
||||
target: ${{ matrix.target }}
|
||||
arch: ${{ matrix.arch }}
|
||||
system-image-api-level: ${{ matrix.system-image-api-level }}
|
||||
profile: Galaxy Nexus
|
||||
cores: 2
|
||||
ram-size: 2048M
|
||||
|
||||
@@ -5,18 +5,20 @@ on:
|
||||
os:
|
||||
description: 'OS'
|
||||
required: true
|
||||
default: 'macos-latest'
|
||||
default: 'ubuntu-latest'
|
||||
api-level:
|
||||
description: 'API level of the platform and system image'
|
||||
description: 'API level of the platform and system image (if not overridden with system-image-api-level input) - e.g. 33, 35-ext15, Baklava'
|
||||
required: true
|
||||
default: '30'
|
||||
default: '34'
|
||||
system-image-api-level:
|
||||
description: 'API level of the system image - e.g. 34-ext10, 35-ext15'
|
||||
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'
|
||||
description: 'target of the system image - default, google_apis, google_apis_playstore, aosp_atd, google_atd, android-wear, android-wear-cn, android-tv, google-tv, android-automotive, android-automotive-playstore or android-desktop'
|
||||
required: true
|
||||
default: 'default'
|
||||
arch:
|
||||
description: 'CPU architecture of the system image - x86, x86_64 or arm64-v8a'
|
||||
default: 'x86'
|
||||
default: 'x86_64'
|
||||
emulator-options:
|
||||
description: 'command-line options used when launching the emulator'
|
||||
default: '-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim'
|
||||
@@ -39,10 +41,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: validate gradle wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: build, test and lint
|
||||
run: |
|
||||
@@ -51,14 +50,19 @@ jobs:
|
||||
npm run lint
|
||||
npm test
|
||||
|
||||
- uses: actions/setup-java@v3
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 19
|
||||
java-version: 25
|
||||
|
||||
- uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
gradle-home-cache-cleanup: true
|
||||
- uses: gradle/actions/setup-gradle@v6
|
||||
|
||||
- name: enable KVM for linux runners
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: run action
|
||||
uses: ./
|
||||
@@ -66,6 +70,7 @@ jobs:
|
||||
api-level: ${{ github.event.inputs.api-level }}
|
||||
target: ${{ github.event.inputs.target }}
|
||||
arch: ${{ github.event.inputs.arch }}
|
||||
system-image-api-level: ${{ github.event.inputs.system-image-api-level }}
|
||||
profile: Galaxy Nexus
|
||||
emulator-options: ${{ github.event.inputs.emulator-options }}
|
||||
emulator-build: ${{ github.event.inputs.emulator-build }}
|
||||
|
||||
@@ -1,5 +1,61 @@
|
||||
# Change Log
|
||||
|
||||
## Unreleased
|
||||
|
||||
* Update SDK command-line tools to `23.0`. - [#492](https://github.com/ReactiveCircus/android-emulator-runner/pull/492)
|
||||
|
||||
## v2.38.0
|
||||
|
||||
* Update SDK build tools to `37.0.0`. - [#475](https://github.com/ReactiveCircus/android-emulator-runner/pull/475)
|
||||
|
||||
## v2.37.0
|
||||
|
||||
* Update SDK build tools to `36.0.0`, update SDK command-line tools to `20.0`. - [#467](https://github.com/ReactiveCircus/android-emulator-runner/pull/467)
|
||||
* Run action on Node 24. - [#466](https://github.com/ReactiveCircus/android-emulator-runner/pull/466)
|
||||
* Update Node and npm packages. - [#466](https://github.com/ReactiveCircus/android-emulator-runner/pull/466)
|
||||
|
||||
## v2.36.0
|
||||
|
||||
* Remove `--abi` option to better support 16-KB-based system images (#456).
|
||||
|
||||
|
||||
## v2.35.0
|
||||
|
||||
* Optimize config.ini updates and efficiency improvements report (#436).
|
||||
* Fix `pre-emulator-launch-script` (#439).
|
||||
* Allow `google_apis_ps16k` as a valid target (#440).
|
||||
|
||||
## v2.34.0
|
||||
|
||||
* Allow arbitrary value for `api-level` such as `Baklava` and `35-ext15`. - [428](https://github.com/ReactiveCircus/android-emulator-runner/pull/428)
|
||||
* Add option to specify `system-image-api-level` to use a system image with a different API level than the platform. - [428](https://github.com/ReactiveCircus/android-emulator-runner/pull/428)
|
||||
* Support automotive and desktop targets: `android-automotive`, `android-automotive-playstore`, `android-desktop`. - [428](https://github.com/ReactiveCircus/android-emulator-runner/pull/428)
|
||||
|
||||
## v2.33.0
|
||||
|
||||
* Fix missing AVD directory in `ubuntu-24.04` runner. - [415](https://github.com/ReactiveCircus/android-emulator-runner/pull/415)
|
||||
* Update SDK build tools to `35.0.0`, update SDK command-line tools to `16.0`. - [#356](https://github.com/ReactiveCircus/android-emulator-runner/pull/356)
|
||||
* Stop changing owner of the SDK directory. - [406](https://github.com/ReactiveCircus/android-emulator-runner/pull/406) [#409](https://github.com/ReactiveCircus/android-emulator-runner/pull/409)
|
||||
* Upgrade to latest npm dependencies. - [#401](https://github.com/ReactiveCircus/android-emulator-runner/pull/401)
|
||||
|
||||
## v2.32.0
|
||||
|
||||
* Add `port` parameter for customizing the emulator port to use. - [#383](https://github.com/ReactiveCircus/android-emulator-runner/pull/383)
|
||||
|
||||
|
||||
## v2.31.0
|
||||
|
||||
* Support setting `VanillaIceCream` as `api-level`. - [#378](https://github.com/ReactiveCircus/android-emulator-runner/pull/378)
|
||||
* Install `platforms` for the specified `api-level`. - [#384](https://github.com/ReactiveCircus/android-emulator-runner/pull/384)
|
||||
|
||||
## v2.30.1
|
||||
|
||||
* Run action on Node 20. - [#371](https://github.com/ReactiveCircus/android-emulator-runner/pull/371)
|
||||
|
||||
## v2.30.0
|
||||
|
||||
* Update to Node 20. - [#369](https://github.com/ReactiveCircus/android-emulator-runner/pull/369)
|
||||
|
||||
## v2.29.0
|
||||
|
||||
* Fixed emulator download URL. - [#343](https://github.com/ReactiveCircus/android-emulator-runner/pull/343)
|
||||
|
||||
@@ -4,12 +4,24 @@
|
||||
<a href="https://github.com/ReactiveCircus/android-emulator-runner"><img alt="GitHub Actions status" src="https://github.com/ReactiveCircus/android-emulator-runner/workflows/Main%20workflow/badge.svg"></a>
|
||||
</p>
|
||||
|
||||
A GitHub Action for installing, configuring and running hardware-accelerated Android Emulators on macOS virtual machines.
|
||||
A GitHub Action for installing, configuring and running hardware-accelerated Android Emulators on Linux and macOS virtual machines.
|
||||
|
||||
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).
|
||||
|
||||
## Running hardware accelerated emulators on Linux runners
|
||||
|
||||
GitHub's [larger Linux runners support running hardware accelerated emulators](https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/). It is now recommended to use the **Ubuntu** (`ubuntu-latest`) runners which are 2-3 times faster than the **macOS** ones which are also a lot more expensive. Remember to enable KVM in your workflow before running this action:
|
||||
|
||||
```
|
||||
- name: Enable KVM group perms
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
```
|
||||
|
||||
## A note on VM Acceleration and why we don't need HAXM anymore
|
||||
|
||||
According to [this documentation](https://developer.android.com/studio/run/emulator-acceleration#vm-mac), "on Mac OS X v10.10 Yosemite and higher, the Android Emulator uses the built-in [Hypervisor.Framework](https://developer.apple.com/documentation/hypervisor) by default, and falls back to using Intel HAXM if Hypervisor.Framework fails to initialize." This means that **HAXM is only needed to achieve VM Acceleration if this default Hypervisor is not available on macOS machines.**
|
||||
@@ -34,10 +46,16 @@ A workflow that uses **android-emulator-runner** to run your instrumented tests
|
||||
```yml
|
||||
jobs:
|
||||
test:
|
||||
runs-on: macos-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Enable KVM
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: run tests
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
@@ -51,14 +69,20 @@ We can also leverage GitHub Actions's build matrix to test across multiple confi
|
||||
```yml
|
||||
jobs:
|
||||
test:
|
||||
runs-on: macos-latest
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
api-level: [21, 23, 29]
|
||||
target: [default, google_apis]
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Enable KVM
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: run tests
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
@@ -66,7 +90,7 @@ jobs:
|
||||
api-level: ${{ matrix.api-level }}
|
||||
target: ${{ matrix.target }}
|
||||
arch: x86_64
|
||||
profile: Nexus 6
|
||||
profile: pixel_7_pro
|
||||
script: ./gradlew connectedCheck
|
||||
```
|
||||
|
||||
@@ -75,10 +99,16 @@ If you need specific versions of **NDK** and **CMake** installed:
|
||||
```yml
|
||||
jobs:
|
||||
test:
|
||||
runs-on: macos-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Enable KVM
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: run tests
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
@@ -89,29 +119,60 @@ jobs:
|
||||
script: ./gradlew connectedCheck
|
||||
```
|
||||
|
||||
If you need a specific [SDK Extensions](https://developer.android.com/guide/sdk-extensions) for the system image but not the platform:
|
||||
|
||||
```yml
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Enable KVM
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: run tests
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: 34
|
||||
system-image-api-level: 34-ext9
|
||||
target: android-automotive
|
||||
script: ./gradlew connectedCheck
|
||||
```
|
||||
|
||||
We can significantly reduce emulator startup time by setting up AVD snapshot caching:
|
||||
|
||||
1. add a `gradle/gradle-build-action@v2` step for caching Gradle, more details see [#229](https://github.com/ReactiveCircus/android-emulator-runner/issues/229)
|
||||
2. add an `actions/cache@v3` step for caching the `avd`
|
||||
1. add a `gradle/actions/setup-gradle@v5` step for caching Gradle, more details see [#229](https://github.com/ReactiveCircus/android-emulator-runner/issues/229)
|
||||
2. add an `actions/cache@v5` step for caching the `avd`
|
||||
3. add a `reactivecircus/android-emulator-runner@v2` step to generate a clean snapshot - specify `emulator-options` without `no-snapshot`
|
||||
4. add another `reactivecircus/android-emulator-runner@v2` step to run your tests using existing AVD / snapshot - specify `emulator-options` with `no-snapshot-save`
|
||||
|
||||
```yml
|
||||
jobs:
|
||||
test:
|
||||
runs-on: macos-latest
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
api-level: [21, 23, 29]
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Enable KVM
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: Gradle cache
|
||||
uses: gradle/gradle-build-action@v2
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
|
||||
- name: AVD cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v5
|
||||
id: avd-cache
|
||||
with:
|
||||
path: |
|
||||
@@ -143,10 +204,11 @@ jobs:
|
||||
|
||||
| **Input** | **Required** | **Default** | **Description** |
|
||||
|-|-|-|-|
|
||||
| `api-level` | Required | N/A | API level of the platform system image - e.g. 23 for Android Marshmallow, 29 for Android 10. **Minimum API level supported is 15**. |
|
||||
| `target` | Optional | `default` | Target of the system image - `default`, `google_apis`, `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`. |
|
||||
| `api-level` | Required | N/A | API level of the platform and system image - e.g. `23`, `33`, `35-ext15`, `Baklava`. **Minimum API level supported is 15**. |
|
||||
| `system-image-api-level` | Optional | same as `api-level` | API level of the system image - e.g. `34-ext10`, `35-ext15`. |
|
||||
| `target` | Optional | `default` | Target of the system image - e.g. `default`, `google_apis`, `google_apis_ps16k`, `google_apis_playstore`, `google_apis_playstore_ps16k`, `android-wear`, `android-wear-cn`, `android-tv`, `google-tv`, `aosp_atd`, `google_atd`, `android-automotive`, `android-automotive-playstore`, `android-desktop`. Please run `sdkmanager --list` to see the available targets. |
|
||||
| `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`. |
|
||||
| `profile` | Optional | N/A | Hardware profile id used for creating the AVD - e.g. `pixel_7_pro`. 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` |
|
||||
@@ -155,6 +217,7 @@ jobs:
|
||||
| `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-port` | Optional | `5554` | Emulator port to use. Allows to run this action on multiple workers on a single machine at the same time. This input is available for the script as `EMULATOR_PORT` enviromental variable. This port is automatically used by android device related tasks in gradle |
|
||||
| `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`. |
|
||||
@@ -170,12 +233,6 @@ jobs:
|
||||
|
||||
Default `emulator-options`: `-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim`.
|
||||
|
||||
## Can I use this action on Github Hosted Linux VMs?
|
||||
|
||||
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, because of the current lack of hardware acceleration support. You can get it running much faster on self-hosted Linux runners but only if the underlying instances support KVM (which most don't). Things might be better on the newer Larger runners but they are still in Beta. It is possible to use this Action with hardware accelerated Linux VMs hosted by a third-party runner provider.
|
||||
|
||||
For a longer answer please refer to [this issue](https://github.com/ReactiveCircus/android-emulator-runner/issues/46).
|
||||
|
||||
## Who is using Android Emulator Runner?
|
||||
|
||||
These are some of the open-source projects using (or used) **Android Emulator Runner**:
|
||||
@@ -211,5 +268,10 @@ These are some of the open-source projects using (or used) **Android Emulator Ru
|
||||
- [hzi-braunschweig/SORMAS-Project](https://github.com/hzi-braunschweig/SORMAS-Project/blob/development/.github/workflows/sormas_app_ci.yml)
|
||||
- [ACRA/acra](https://github.com/ACRA/acra/blob/master/.github/workflows/test.yml)
|
||||
- [bitfireAT/davx5-ose](https://github.com/bitfireAT/davx5-ose/blob/dev-ose/.github/workflows/test-dev.yml)
|
||||
- [robolectric/robolectric](https://github.com/robolectric/robolectric/blob/master/.github/workflows/tests.yml)
|
||||
- [home-assistant/android](https://github.com/home-assistant/android/blob/master/.github/workflows/pr.yml)
|
||||
- [composablehorizons/compose-unstyled](https://github.com/composablehorizons/compose-unstyled/tree/main/.github/workflows)
|
||||
- [f3d-app/f3d-android](https://github.com/f3d-app/f3d-android/blob/master/.github/workflows/ci.yml)
|
||||
- [freeorion/freeorion](https://github.com/freeorion/freeorion/blob/master/.github/workflows/_build-android.yml)
|
||||
|
||||
If you are using **Android Emulator Runner** and want your project included in the list, please feel free to open a pull request.
|
||||
|
||||
@@ -1,103 +1,5 @@
|
||||
import * as validator from '../src/input-validator';
|
||||
|
||||
describe('api-level validator tests', () => {
|
||||
it('Throws if api-level is not a number', () => {
|
||||
const func = () => {
|
||||
validator.checkApiLevel('api');
|
||||
};
|
||||
expect(func).toThrowError(`Unexpected API level: 'api'.`);
|
||||
});
|
||||
|
||||
it('Throws if api-level is not an integer', () => {
|
||||
const func = () => {
|
||||
validator.checkApiLevel('29.1');
|
||||
};
|
||||
expect(func).toThrowError(`Unexpected API level: '29.1'.`);
|
||||
});
|
||||
|
||||
it('Throws if api-level is lower than min API supported', () => {
|
||||
const func = () => {
|
||||
validator.checkApiLevel('14');
|
||||
};
|
||||
expect(func).toThrowError(`Minimum API level supported is ${validator.MIN_API_LEVEL}.`);
|
||||
});
|
||||
|
||||
it('Validates successfully with valid api-level', () => {
|
||||
const func1 = () => {
|
||||
validator.checkApiLevel('15');
|
||||
};
|
||||
expect(func1).not.toThrow();
|
||||
|
||||
const func2 = () => {
|
||||
validator.checkApiLevel('29');
|
||||
};
|
||||
expect(func2).not.toThrow();
|
||||
const func3 = () => {
|
||||
validator.checkApiLevel('UpsideDownCake-ext5');
|
||||
};
|
||||
expect(func3).not.toThrow();
|
||||
const func4 = () => {
|
||||
validator.checkApiLevel('TiramisuPrivacySandbox');
|
||||
};
|
||||
expect(func4).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('target validator tests', () => {
|
||||
it('Throws if target is unknown', () => {
|
||||
const func = () => {
|
||||
validator.checkTarget('some-target');
|
||||
};
|
||||
expect(func).toThrowError(`Value for input.target 'some-target' is unknown. Supported options: ${validator.VALID_TARGETS}`);
|
||||
});
|
||||
|
||||
it('Validates successfully with valid target', () => {
|
||||
const func1 = () => {
|
||||
validator.checkTarget('default');
|
||||
};
|
||||
expect(func1).not.toThrow();
|
||||
|
||||
const func2 = () => {
|
||||
validator.checkTarget('google_apis');
|
||||
};
|
||||
expect(func2).not.toThrow();
|
||||
|
||||
const func3 = () => {
|
||||
validator.checkTarget('aosp_atd');
|
||||
};
|
||||
expect(func3).not.toThrow();
|
||||
|
||||
const func4 = () => {
|
||||
validator.checkTarget('google_atd');
|
||||
};
|
||||
expect(func4).not.toThrow();
|
||||
|
||||
const func5 = () => {
|
||||
validator.checkTarget('google_apis_playstore');
|
||||
};
|
||||
expect(func5).not.toThrow();
|
||||
|
||||
const func6 = () => {
|
||||
validator.checkTarget('android-wear');
|
||||
};
|
||||
expect(func6).not.toThrow();
|
||||
|
||||
const func7 = () => {
|
||||
validator.checkTarget('android-wear-cn');
|
||||
};
|
||||
expect(func7).not.toThrow();
|
||||
|
||||
const func8 = () => {
|
||||
validator.checkTarget('android-tv');
|
||||
};
|
||||
expect(func8).not.toThrow();
|
||||
|
||||
const func9 = () => {
|
||||
validator.checkTarget('google-tv');
|
||||
};
|
||||
expect(func9).not.toThrow();
|
||||
});
|
||||
});
|
||||
import { MAX_PORT, MIN_PORT } from '../src/input-validator';
|
||||
|
||||
describe('arch validator tests', () => {
|
||||
it('Throws if arch is unknown', () => {
|
||||
@@ -172,6 +74,33 @@ describe('force-avd-creation validator tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('emulator-port validator tests', () => {
|
||||
it('Validates if emulator-port is even and in range', () => {
|
||||
const func = () => {
|
||||
validator.checkPort(5554);
|
||||
};
|
||||
expect(func).not.toThrow();
|
||||
});
|
||||
it('Throws if emulator-port is lower than MIN_PORT', () => {
|
||||
const func = () => {
|
||||
validator.checkPort(MIN_PORT - 2);
|
||||
};
|
||||
expect(func).toThrow();
|
||||
});
|
||||
it('Throws if emulator-port is higher than MAX_PORT', () => {
|
||||
const func = () => {
|
||||
validator.checkPort(MAX_PORT + 2);
|
||||
};
|
||||
expect(func).toThrow();
|
||||
});
|
||||
it('Throws if emulator-port is odd', () => {
|
||||
const func = () => {
|
||||
validator.checkPort(5555);
|
||||
};
|
||||
expect(func).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('disable-animations validator tests', () => {
|
||||
it('Throws if disable-animations is not a boolean', () => {
|
||||
const func = () => {
|
||||
|
||||
+8
-1
@@ -1,6 +1,8 @@
|
||||
inputs:
|
||||
api-level:
|
||||
type: integer
|
||||
type: string
|
||||
system-image-api-level:
|
||||
type: string
|
||||
target:
|
||||
type: enum
|
||||
allowed-values:
|
||||
@@ -13,6 +15,9 @@ inputs:
|
||||
- android-wear-cn
|
||||
- android-tv
|
||||
- google-tv
|
||||
- android-automotive
|
||||
- android-automotive-playstore
|
||||
- android-desktop
|
||||
arch:
|
||||
type: enum
|
||||
allowed-values:
|
||||
@@ -37,6 +42,8 @@ inputs:
|
||||
type: boolean
|
||||
emulator-boot-timeout:
|
||||
type: integer
|
||||
emulator-port:
|
||||
type: integer
|
||||
emulator-options:
|
||||
type: string
|
||||
disable-animations:
|
||||
|
||||
+10
-4
@@ -1,15 +1,18 @@
|
||||
name: 'Android Emulator Runner'
|
||||
description: 'Installs, configures and starts an Android Emulator directly on macOS virtual machines.'
|
||||
description: 'Installs, configures and starts an Android Emulator directly on hardware-accelerated runners.'
|
||||
author: 'Reactive Circus'
|
||||
branding:
|
||||
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, 33, 35-ext15, Baklava'
|
||||
required: true
|
||||
system-image-api-level:
|
||||
description: 'API level of the system image - e.g. 34-ext10, 35-ext15. If not set the `api-level` input will be used.'
|
||||
required: false
|
||||
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'
|
||||
description: 'target of the system image - e.g. default, google_apis, google_apis_ps16k, google_apis_playstore, google_apis_playstore_16k, aosp_atd, google_atd, android-wear, android-wear-cn, android-tv, google-tv, android-automotive, android-automotive-playstore or android-desktop'
|
||||
default: 'default'
|
||||
arch:
|
||||
description: 'CPU architecture of the system image - x86, x86_64 or arm64-v8a'
|
||||
@@ -36,6 +39,9 @@ inputs:
|
||||
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'
|
||||
emulator-port:
|
||||
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'
|
||||
@@ -68,5 +74,5 @@ inputs:
|
||||
pre-emulator-launch-script:
|
||||
description: 'custom script to run after creating the AVD and before launching the emulator - e.g. `./adjust-emulator-configs.sh`'
|
||||
runs:
|
||||
using: 'node16'
|
||||
using: 'node24'
|
||||
main: 'lib/main.js'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getChannelId = void 0;
|
||||
exports.getChannelId = getChannelId;
|
||||
function getChannelId(channelName) {
|
||||
if (channelName === 'stable') {
|
||||
return 0;
|
||||
@@ -18,4 +18,3 @@ function getChannelId(channelName) {
|
||||
throw new Error(`Unexpected channel name: '${channelName}'.`);
|
||||
}
|
||||
}
|
||||
exports.getChannelId = getChannelId;
|
||||
|
||||
+131
-115
@@ -15,149 +15,165 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
||||
}) : 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());
|
||||
});
|
||||
};
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.killEmulator = exports.launchEmulator = void 0;
|
||||
exports.createAvd = createAvd;
|
||||
exports.launchEmulator = launchEmulator;
|
||||
exports.killEmulator = killEmulator;
|
||||
const exec = __importStar(require("@actions/exec"));
|
||||
const fs = __importStar(require("fs"));
|
||||
/**
|
||||
* Creates and launches a new AVD instance with the specified configurations.
|
||||
* Creates a new AVD instance with the specified configurations.
|
||||
*/
|
||||
function launchEmulator(apiLevel, target, arch, profile, cores, ramSize, heapSize, sdcardPathOrSize, diskSize, avdName, forceAvdCreation, emulatorBootTimeout, emulatorOptions, disableAnimations, disableSpellChecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
console.log(`::group::Launch Emulator`);
|
||||
// create a new AVD if AVD directory does not already exist or forceAvdCreation is true
|
||||
const avdPath = `${process.env.ANDROID_AVD_HOME}/${avdName}.avd`;
|
||||
if (!fs.existsSync(avdPath) || forceAvdCreation) {
|
||||
const profileOption = profile.trim() !== '' ? `--device '${profile}'` : '';
|
||||
const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : '';
|
||||
console.log(`Creating AVD.`);
|
||||
yield exec.exec(`sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --abi '${target}/${arch}' --package 'system-images;android-${apiLevel};${target};${arch}' ${profileOption} ${sdcardPathOrSizeOption}"`);
|
||||
}
|
||||
async function createAvd(arch, avdName, cores, diskSize, enableHardwareKeyboard, forceAvdCreation, heapSize, profile, ramSize, sdcardPathOrSize, systemImageApiLevel, target) {
|
||||
try {
|
||||
console.log(`::group::Create AVD`);
|
||||
// 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}" --package 'system-images;android-${systemImageApiLevel};${target};${arch}' ${profileOption} ${sdcardPathOrSizeOption}"`);
|
||||
}
|
||||
if (cores || ramSize || heapSize || enableHardwareKeyboard || diskSize) {
|
||||
const configEntries = [];
|
||||
if (cores) {
|
||||
yield exec.exec(`sh -c \\"printf 'hw.cpu.ncore=${cores}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`);
|
||||
configEntries.push(`hw.cpu.ncore=${cores}`);
|
||||
}
|
||||
if (ramSize) {
|
||||
yield exec.exec(`sh -c \\"printf 'hw.ramSize=${ramSize}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`);
|
||||
configEntries.push(`hw.ramSize=${ramSize}`);
|
||||
}
|
||||
if (heapSize) {
|
||||
yield exec.exec(`sh -c \\"printf 'hw.heapSize=${heapSize}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`);
|
||||
configEntries.push(`hw.heapSize=${heapSize}`);
|
||||
}
|
||||
if (enableHardwareKeyboard) {
|
||||
yield exec.exec(`sh -c \\"printf 'hw.keyboard=yes\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`);
|
||||
configEntries.push('hw.keyboard=yes');
|
||||
}
|
||||
if (diskSize) {
|
||||
yield exec.exec(`sh -c \\"printf 'disk.dataPartition.size=${diskSize}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`);
|
||||
configEntries.push(`disk.dataPartition.size=${diskSize}`);
|
||||
}
|
||||
//turn off hardware acceleration on Linux
|
||||
if (process.platform === 'linux' && disableLinuxHardwareAcceleration) {
|
||||
console.log('Disabling Linux hardware acceleration.');
|
||||
emulatorOptions += ' -accel off';
|
||||
}
|
||||
// start emulator
|
||||
console.log('Starting emulator.');
|
||||
yield exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -avd "${avdName}" ${emulatorOptions} &"`, [], {
|
||||
listeners: {
|
||||
stderr: (data) => {
|
||||
if (data.toString().includes('invalid command-line parameter')) {
|
||||
throw new Error(data.toString());
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
// wait for emulator to complete booting
|
||||
yield waitForDevice(emulatorBootTimeout);
|
||||
yield exec.exec(`adb shell input keyevent 82`);
|
||||
if (disableAnimations) {
|
||||
console.log('Disabling animations.');
|
||||
yield exec.exec(`adb shell settings put global window_animation_scale 0.0`);
|
||||
yield exec.exec(`adb shell settings put global transition_animation_scale 0.0`);
|
||||
yield exec.exec(`adb shell settings put global animator_duration_scale 0.0`);
|
||||
}
|
||||
if (disableSpellChecker) {
|
||||
yield exec.exec(`adb shell settings put secure spell_checker_enabled 0`);
|
||||
}
|
||||
if (enableHardwareKeyboard) {
|
||||
yield exec.exec(`adb shell settings put secure show_ime_with_hard_keyboard 0`);
|
||||
if (configEntries.length > 0) {
|
||||
const configContent = configEntries.join('\\n') + '\\n';
|
||||
await exec.exec(`sh -c \\"printf '${configContent}' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini"`);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
console.log(`::endgroup::`);
|
||||
}
|
||||
});
|
||||
}
|
||||
finally {
|
||||
console.log(`::endgroup::`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Launches an existing AVD instance with the specified configurations.
|
||||
*/
|
||||
async function launchEmulator(avdName, disableAnimations, disableLinuxHardwareAcceleration, disableSpellChecker, emulatorBootTimeout, emulatorOptions, enableHardwareKeyboard, port) {
|
||||
try {
|
||||
console.log(`::group::Launch Emulator`);
|
||||
// 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.');
|
||||
await 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')) {
|
||||
throw new Error(data.toString());
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
// wait for emulator to complete booting
|
||||
await waitForDevice(port, emulatorBootTimeout);
|
||||
await adb(port, `shell input keyevent 82`);
|
||||
if (disableAnimations) {
|
||||
console.log('Disabling animations.');
|
||||
await adb(port, `shell settings put global window_animation_scale 0.0`);
|
||||
await adb(port, `shell settings put global transition_animation_scale 0.0`);
|
||||
await adb(port, `shell settings put global animator_duration_scale 0.0`);
|
||||
}
|
||||
if (disableSpellChecker) {
|
||||
await adb(port, `shell settings put secure spell_checker_enabled 0`);
|
||||
}
|
||||
if (enableHardwareKeyboard) {
|
||||
await adb(port, `shell settings put secure show_ime_with_hard_keyboard 0`);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
console.log(`::endgroup::`);
|
||||
}
|
||||
}
|
||||
exports.launchEmulator = launchEmulator;
|
||||
/**
|
||||
* Kills the running emulator on the default port.
|
||||
*/
|
||||
function killEmulator() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
console.log(`::group::Terminate Emulator`);
|
||||
yield exec.exec(`adb -s emulator-5554 emu kill`);
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error instanceof Error ? error.message : error);
|
||||
}
|
||||
finally {
|
||||
console.log(`::endgroup::`);
|
||||
}
|
||||
});
|
||||
async function killEmulator(port) {
|
||||
try {
|
||||
console.log(`::group::Terminate Emulator`);
|
||||
await adb(port, `emu kill`);
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error instanceof Error ? error.message : error);
|
||||
}
|
||||
finally {
|
||||
console.log(`::endgroup::`);
|
||||
}
|
||||
}
|
||||
async function adb(port, command) {
|
||||
return await exec.exec(`adb -s emulator-${port} ${command}`);
|
||||
}
|
||||
exports.killEmulator = killEmulator;
|
||||
/**
|
||||
* Wait for emulator to boot.
|
||||
*/
|
||||
function waitForDevice(emulatorBootTimeout) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let booted = false;
|
||||
let attempts = 0;
|
||||
const retryInterval = 2; // retry every 2 seconds
|
||||
const maxAttempts = emulatorBootTimeout / 2;
|
||||
while (!booted) {
|
||||
try {
|
||||
let result = '';
|
||||
yield exec.exec(`adb shell getprop sys.boot_completed`, [], {
|
||||
listeners: {
|
||||
stdout: (data) => {
|
||||
result += data.toString();
|
||||
},
|
||||
async function waitForDevice(port, emulatorBootTimeout) {
|
||||
let booted = false;
|
||||
let attempts = 0;
|
||||
const retryInterval = 2; // retry every 2 seconds
|
||||
const maxAttempts = emulatorBootTimeout / 2;
|
||||
while (!booted) {
|
||||
try {
|
||||
let result = '';
|
||||
await exec.exec(`adb -s emulator-${port} shell getprop sys.boot_completed`, [], {
|
||||
listeners: {
|
||||
stdout: (data) => {
|
||||
result += data.toString();
|
||||
},
|
||||
});
|
||||
if (result.trim() === '1') {
|
||||
console.log('Emulator booted.');
|
||||
booted = true;
|
||||
break;
|
||||
}
|
||||
},
|
||||
});
|
||||
if (result.trim() === '1') {
|
||||
console.log('Emulator booted.');
|
||||
booted = true;
|
||||
break;
|
||||
}
|
||||
catch (error) {
|
||||
console.warn(error instanceof Error ? error.message : error);
|
||||
}
|
||||
if (attempts < maxAttempts) {
|
||||
yield delay(retryInterval * 1000);
|
||||
}
|
||||
else {
|
||||
throw new Error(`Timeout waiting for emulator to boot.`);
|
||||
}
|
||||
attempts++;
|
||||
}
|
||||
});
|
||||
catch (error) {
|
||||
console.warn(error instanceof Error ? error.message : error);
|
||||
}
|
||||
if (attempts < maxAttempts) {
|
||||
await delay(retryInterval * 1000);
|
||||
}
|
||||
else {
|
||||
throw new Error(`Timeout waiting for emulator to boot.`);
|
||||
}
|
||||
attempts++;
|
||||
}
|
||||
}
|
||||
function delay(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
+30
-27
@@ -1,75 +1,79 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.checkDiskSize = exports.checkEmulatorBuild = exports.checkEnableHardwareKeyboard = exports.checkDisableLinuxHardwareAcceleration = exports.checkDisableSpellchecker = exports.checkDisableAnimations = exports.checkForceAvdCreation = exports.checkChannel = exports.checkArch = exports.checkTarget = exports.checkApiLevel = exports.VALID_CHANNELS = exports.VALID_ARCHS = exports.VALID_TARGETS = exports.MIN_API_LEVEL = void 0;
|
||||
exports.MAX_PORT = exports.MIN_PORT = exports.VALID_CHANNELS = exports.VALID_ARCHS = exports.MIN_API_LEVEL = void 0;
|
||||
exports.playstoreTargetSubstitution = playstoreTargetSubstitution;
|
||||
exports.checkArch = checkArch;
|
||||
exports.checkChannel = checkChannel;
|
||||
exports.checkForceAvdCreation = checkForceAvdCreation;
|
||||
exports.checkPort = checkPort;
|
||||
exports.checkDisableAnimations = checkDisableAnimations;
|
||||
exports.checkDisableSpellchecker = checkDisableSpellchecker;
|
||||
exports.checkDisableLinuxHardwareAcceleration = checkDisableLinuxHardwareAcceleration;
|
||||
exports.checkEnableHardwareKeyboard = checkEnableHardwareKeyboard;
|
||||
exports.checkEmulatorBuild = checkEmulatorBuild;
|
||||
exports.checkDiskSize = checkDiskSize;
|
||||
exports.MIN_API_LEVEL = 15;
|
||||
exports.VALID_TARGETS = ['default', 'google_apis', 'aosp_atd', 'google_atd', 'google_apis_playstore', 'android-wear', 'android-wear-cn', 'android-tv', 'google-tv'];
|
||||
exports.VALID_ARCHS = ['x86', 'x86_64', 'arm64-v8a'];
|
||||
exports.VALID_CHANNELS = ['stable', 'beta', 'dev', 'canary'];
|
||||
function checkApiLevel(apiLevel) {
|
||||
if (apiLevel.startsWith('UpsideDownCake') || apiLevel === 'TiramisuPrivacySandbox')
|
||||
return;
|
||||
if (isNaN(Number(apiLevel)) || !Number.isInteger(Number(apiLevel))) {
|
||||
throw new Error(`Unexpected API level: '${apiLevel}'.`);
|
||||
}
|
||||
if (Number(apiLevel) < exports.MIN_API_LEVEL) {
|
||||
throw new Error(`Minimum API level supported is ${exports.MIN_API_LEVEL}.`);
|
||||
}
|
||||
exports.MIN_PORT = 5554;
|
||||
exports.MAX_PORT = 5584;
|
||||
function playstoreTargetSubstitution(target) {
|
||||
// "playstore" is an allowed shorthand for "google_apis_playstore" images
|
||||
// this is idempotent - return same even if run multiple times on same target
|
||||
if (target === 'playstore')
|
||||
return 'google_apis_playstore';
|
||||
if (target === 'playstore_ps16k')
|
||||
return 'google_apis_playstore_ps16k';
|
||||
return target;
|
||||
}
|
||||
exports.checkApiLevel = checkApiLevel;
|
||||
function checkTarget(target) {
|
||||
if (!exports.VALID_TARGETS.includes(target)) {
|
||||
throw new Error(`Value for input.target '${target}' is unknown. Supported options: ${exports.VALID_TARGETS}.`);
|
||||
}
|
||||
}
|
||||
exports.checkTarget = checkTarget;
|
||||
function checkArch(arch) {
|
||||
if (!exports.VALID_ARCHS.includes(arch)) {
|
||||
throw new Error(`Value for input.arch '${arch}' is unknown. Supported options: ${exports.VALID_ARCHS}.`);
|
||||
}
|
||||
}
|
||||
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 checkPort(port) {
|
||||
if (port < exports.MIN_PORT || port > exports.MAX_PORT) {
|
||||
throw new Error(`Emulator port is outside of the supported port range [${exports.MIN_PORT}, ${exports.MAX_PORT}], was ${port}`);
|
||||
}
|
||||
if (port % 2 == 1) {
|
||||
throw new Error(`Emulator port has to be even, was ${port}`);
|
||||
}
|
||||
}
|
||||
function checkDisableAnimations(disableAnimations) {
|
||||
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) || disableLinuxHardwareAcceleration === 'auto')) {
|
||||
throw new Error(`Input for input.disable-linux-hw-accel should be either 'true' or 'false' or 'auto'.`);
|
||||
}
|
||||
}
|
||||
exports.checkDisableLinuxHardwareAcceleration = checkDisableLinuxHardwareAcceleration;
|
||||
function checkEnableHardwareKeyboard(enableHardwareKeyboard) {
|
||||
if (!isValidBoolean(enableHardwareKeyboard)) {
|
||||
throw new Error(`Input for input.enable-hw-keyboard should be either 'true' or 'false'.`);
|
||||
}
|
||||
}
|
||||
exports.checkEnableHardwareKeyboard = checkEnableHardwareKeyboard;
|
||||
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';
|
||||
}
|
||||
@@ -89,4 +93,3 @@ function checkDiskSize(diskSize) {
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.checkDiskSize = checkDiskSize;
|
||||
|
||||
+189
-179
@@ -15,22 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
||||
}) : 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());
|
||||
});
|
||||
};
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const sdk_installer_1 = require("./sdk-installer");
|
||||
@@ -40,183 +41,192 @@ const exec = __importStar(require("@actions/exec"));
|
||||
const script_parser_1 = require("./script-parser");
|
||||
const channel_id_mapper_1 = require("./channel-id-mapper");
|
||||
const fs_1 = require("fs");
|
||||
function run() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
console.log(`::group::Configure emulator`);
|
||||
let linuxSupportKVM = false;
|
||||
// only support running on macOS or Linux
|
||||
if (process.platform !== 'darwin') {
|
||||
if (process.platform === 'linux') {
|
||||
try {
|
||||
(0, fs_1.accessSync)('/dev/kvm', fs_1.constants.R_OK | fs_1.constants.W_OK);
|
||||
linuxSupportKVM = true;
|
||||
}
|
||||
catch (_a) {
|
||||
console.warn(`You're running a Linux VM where hardware acceleration is not available. Please consider using a macOS VM instead to take advantage of native hardware acceleration support provided by HAXM.`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error('Unsupported virtual machine: please use either macos or ubuntu VM.');
|
||||
}
|
||||
}
|
||||
// API level of the platform and system image
|
||||
const apiLevel = core.getInput('api-level', { required: true });
|
||||
(0, input_validator_1.checkApiLevel)(apiLevel);
|
||||
console.log(`API level: ${apiLevel}`);
|
||||
// target of the system image
|
||||
const targetInput = core.getInput('target');
|
||||
const target = targetInput == 'playstore' ? 'google_apis_playstore' : targetInput;
|
||||
(0, input_validator_1.checkTarget)(target);
|
||||
console.log(`target: ${target}`);
|
||||
// CPU architecture of the system image
|
||||
const arch = core.getInput('arch');
|
||||
(0, input_validator_1.checkArch)(arch);
|
||||
console.log(`CPU architecture: ${arch}`);
|
||||
// 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}`);
|
||||
// Heap size to use for AVD
|
||||
const heapSize = core.getInput('heap-size');
|
||||
console.log(`Heap size: ${heapSize}`);
|
||||
// SD card path or size used for creating the AVD
|
||||
const sdcardPathOrSize = core.getInput('sdcard-path-or-size');
|
||||
console.log(`SD card path or size: ${sdcardPathOrSize}`);
|
||||
const diskSize = core.getInput('disk-size');
|
||||
(0, input_validator_1.checkDiskSize)(diskSize);
|
||||
console.log(`Disk size: ${diskSize}`);
|
||||
// custom name used for creating the AVD
|
||||
const avdName = core.getInput('avd-name');
|
||||
console.log(`AVD name: ${avdName}`);
|
||||
// force AVD creation
|
||||
const forceAvdCreationInput = core.getInput('force-avd-creation');
|
||||
(0, input_validator_1.checkForceAvdCreation)(forceAvdCreationInput);
|
||||
const forceAvdCreation = forceAvdCreationInput === 'true';
|
||||
console.log(`force avd creation: ${forceAvdCreation}`);
|
||||
// Emulator boot timeout seconds
|
||||
const emulatorBootTimeout = parseInt(core.getInput('emulator-boot-timeout'), 10);
|
||||
console.log(`Emulator boot timeout: ${emulatorBootTimeout}`);
|
||||
// emulator options
|
||||
const emulatorOptions = core.getInput('emulator-options').trim();
|
||||
console.log(`emulator options: ${emulatorOptions}`);
|
||||
// disable animations
|
||||
const disableAnimationsInput = core.getInput('disable-animations');
|
||||
(0, input_validator_1.checkDisableAnimations)(disableAnimationsInput);
|
||||
const disableAnimations = disableAnimationsInput === 'true';
|
||||
console.log(`disable animations: ${disableAnimations}`);
|
||||
// disable spellchecker
|
||||
const disableSpellcheckerInput = core.getInput('disable-spellchecker');
|
||||
(0, input_validator_1.checkDisableSpellchecker)(disableSpellcheckerInput);
|
||||
const disableSpellchecker = disableSpellcheckerInput === 'true';
|
||||
console.log(`disable spellchecker: ${disableSpellchecker}`);
|
||||
// disable linux hardware acceleration
|
||||
let disableLinuxHardwareAccelerationInput = core.getInput('disable-linux-hw-accel');
|
||||
(0, input_validator_1.checkDisableLinuxHardwareAcceleration)(disableLinuxHardwareAccelerationInput);
|
||||
if (disableLinuxHardwareAccelerationInput === 'auto' && process.platform === 'linux') {
|
||||
disableLinuxHardwareAccelerationInput = linuxSupportKVM ? 'false' : 'true';
|
||||
}
|
||||
const disableLinuxHardwareAcceleration = disableLinuxHardwareAccelerationInput === 'true';
|
||||
console.log(`disable Linux hardware acceleration: ${disableLinuxHardwareAcceleration}`);
|
||||
// enable hardware keyboard
|
||||
const enableHardwareKeyboardInput = core.getInput('enable-hw-keyboard');
|
||||
(0, input_validator_1.checkEnableHardwareKeyboard)(enableHardwareKeyboardInput);
|
||||
const enableHardwareKeyboard = enableHardwareKeyboardInput === 'true';
|
||||
console.log(`enable hardware keyboard: ${enableHardwareKeyboard}`);
|
||||
// emulator build
|
||||
const emulatorBuildInput = core.getInput('emulator-build');
|
||||
if (emulatorBuildInput) {
|
||||
(0, input_validator_1.checkEmulatorBuild)(emulatorBuildInput);
|
||||
console.log(`using emulator build: ${emulatorBuildInput}`);
|
||||
}
|
||||
const emulatorBuild = !emulatorBuildInput ? undefined : emulatorBuildInput;
|
||||
// custom working directory
|
||||
const workingDirectoryInput = core.getInput('working-directory');
|
||||
if (workingDirectoryInput) {
|
||||
console.log(`custom working directory: ${workingDirectoryInput}`);
|
||||
}
|
||||
const workingDirectory = !workingDirectoryInput ? undefined : workingDirectoryInput;
|
||||
// version of NDK to install
|
||||
const ndkInput = core.getInput('ndk');
|
||||
if (ndkInput) {
|
||||
console.log(`version of NDK to install: ${ndkInput}`);
|
||||
}
|
||||
const ndkVersion = !ndkInput ? undefined : ndkInput;
|
||||
// version of CMake to install
|
||||
const cmakeInput = core.getInput('cmake');
|
||||
if (cmakeInput) {
|
||||
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');
|
||||
(0, input_validator_1.checkChannel)(channelName);
|
||||
const channelId = (0, channel_id_mapper_1.getChannelId)(channelName);
|
||||
console.log(`Channel: ${channelId} (${channelName})`);
|
||||
// custom script to run
|
||||
const scriptInput = core.getInput('script', { required: true });
|
||||
const scripts = (0, script_parser_1.parseScript)(scriptInput);
|
||||
console.log(`Script:`);
|
||||
scripts.forEach((script) => __awaiter(this, void 0, void 0, function* () {
|
||||
console.log(`${script}`);
|
||||
}));
|
||||
// custom pre emulator launch script
|
||||
const preEmulatorLaunchScriptInput = core.getInput('pre-emulator-launch-script');
|
||||
const preEmulatorLaunchScripts = !preEmulatorLaunchScriptInput ? undefined : (0, script_parser_1.parseScript)(preEmulatorLaunchScriptInput);
|
||||
console.log(`Pre emulator launch script:`);
|
||||
preEmulatorLaunchScripts === null || preEmulatorLaunchScripts === void 0 ? void 0 : preEmulatorLaunchScripts.forEach((script) => __awaiter(this, void 0, void 0, function* () {
|
||||
console.log(`${script}`);
|
||||
}));
|
||||
console.log(`::endgroup::`);
|
||||
// install SDK
|
||||
yield (0, sdk_installer_1.installAndroidSdk)(apiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion);
|
||||
// execute pre emulator launch script if set
|
||||
if (preEmulatorLaunchScripts !== undefined) {
|
||||
console.log(`::group::Run pre emulator launch script`);
|
||||
async function run() {
|
||||
let port = input_validator_1.MIN_PORT;
|
||||
try {
|
||||
console.log(`::group::Configure emulator`);
|
||||
let linuxSupportKVM = false;
|
||||
// only support running on macOS or Linux
|
||||
if (process.platform !== 'darwin') {
|
||||
if (process.platform === 'linux') {
|
||||
try {
|
||||
for (const preEmulatorLaunchScript of preEmulatorLaunchScripts) {
|
||||
// use array form to avoid various quote escaping problems
|
||||
// caused by exec(`sh -c "${preEmulatorLaunchScript}"`)
|
||||
yield exec.exec('sh', ['-c', preEmulatorLaunchScript], {
|
||||
cwd: workingDirectory,
|
||||
});
|
||||
}
|
||||
(0, fs_1.accessSync)('/dev/kvm', fs_1.constants.R_OK | fs_1.constants.W_OK);
|
||||
linuxSupportKVM = true;
|
||||
}
|
||||
catch (error) {
|
||||
core.setFailed(error instanceof Error ? error.message : error);
|
||||
catch {
|
||||
console.warn(`You're running a Linux VM where hardware acceleration is not available. Please consider using a macOS VM instead to take advantage of native hardware acceleration support provided by HAXM.`);
|
||||
}
|
||||
console.log(`::endgroup::`);
|
||||
}
|
||||
// launch an emulator
|
||||
yield (0, emulator_manager_1.launchEmulator)(apiLevel, target, arch, profile, cores, ramSize, heapSize, sdcardPathOrSize, diskSize, avdName, forceAvdCreation, emulatorBootTimeout, emulatorOptions, disableAnimations, disableSpellchecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard);
|
||||
// execute the custom script
|
||||
else {
|
||||
throw new Error('Unsupported virtual machine: please use either macos or ubuntu VM.');
|
||||
}
|
||||
}
|
||||
// API level of the platform and system image
|
||||
const apiLevel = core.getInput('api-level', { required: true });
|
||||
console.log(`API level: ${apiLevel}`);
|
||||
let systemImageApiLevel = core.getInput('system-image-api-level');
|
||||
if (!systemImageApiLevel) {
|
||||
systemImageApiLevel = apiLevel;
|
||||
}
|
||||
console.log(`System image API level: ${systemImageApiLevel}`);
|
||||
// target of the system image
|
||||
const target = (0, input_validator_1.playstoreTargetSubstitution)(core.getInput('target'));
|
||||
console.log(`target: ${target}`);
|
||||
// CPU architecture of the system image
|
||||
const arch = core.getInput('arch');
|
||||
(0, input_validator_1.checkArch)(arch);
|
||||
console.log(`CPU architecture: ${arch}`);
|
||||
// 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}`);
|
||||
// Heap size to use for AVD
|
||||
const heapSize = core.getInput('heap-size');
|
||||
console.log(`Heap size: ${heapSize}`);
|
||||
// SD card path or size used for creating the AVD
|
||||
const sdcardPathOrSize = core.getInput('sdcard-path-or-size');
|
||||
console.log(`SD card path or size: ${sdcardPathOrSize}`);
|
||||
const diskSize = core.getInput('disk-size');
|
||||
(0, input_validator_1.checkDiskSize)(diskSize);
|
||||
console.log(`Disk size: ${diskSize}`);
|
||||
// custom name used for creating the AVD
|
||||
const avdName = core.getInput('avd-name');
|
||||
console.log(`AVD name: ${avdName}`);
|
||||
// force AVD creation
|
||||
const forceAvdCreationInput = core.getInput('force-avd-creation');
|
||||
(0, input_validator_1.checkForceAvdCreation)(forceAvdCreationInput);
|
||||
const forceAvdCreation = forceAvdCreationInput === 'true';
|
||||
console.log(`force avd creation: ${forceAvdCreation}`);
|
||||
// Emulator boot timeout seconds
|
||||
const emulatorBootTimeout = parseInt(core.getInput('emulator-boot-timeout'), 10);
|
||||
console.log(`Emulator boot timeout: ${emulatorBootTimeout}`);
|
||||
// Emulator port to use
|
||||
port = parseInt(core.getInput('emulator-port'), 10);
|
||||
(0, input_validator_1.checkPort)(port);
|
||||
console.log(`emulator port: ${port}`);
|
||||
// emulator options
|
||||
const emulatorOptions = core.getInput('emulator-options').trim();
|
||||
console.log(`emulator options: ${emulatorOptions}`);
|
||||
// disable animations
|
||||
const disableAnimationsInput = core.getInput('disable-animations');
|
||||
(0, input_validator_1.checkDisableAnimations)(disableAnimationsInput);
|
||||
const disableAnimations = disableAnimationsInput === 'true';
|
||||
console.log(`disable animations: ${disableAnimations}`);
|
||||
// disable spellchecker
|
||||
const disableSpellcheckerInput = core.getInput('disable-spellchecker');
|
||||
(0, input_validator_1.checkDisableSpellchecker)(disableSpellcheckerInput);
|
||||
const disableSpellchecker = disableSpellcheckerInput === 'true';
|
||||
console.log(`disable spellchecker: ${disableSpellchecker}`);
|
||||
// disable linux hardware acceleration
|
||||
let disableLinuxHardwareAccelerationInput = core.getInput('disable-linux-hw-accel');
|
||||
(0, input_validator_1.checkDisableLinuxHardwareAcceleration)(disableLinuxHardwareAccelerationInput);
|
||||
if (disableLinuxHardwareAccelerationInput === 'auto' && process.platform === 'linux') {
|
||||
disableLinuxHardwareAccelerationInput = linuxSupportKVM ? 'false' : 'true';
|
||||
}
|
||||
const disableLinuxHardwareAcceleration = disableLinuxHardwareAccelerationInput === 'true';
|
||||
console.log(`disable Linux hardware acceleration: ${disableLinuxHardwareAcceleration}`);
|
||||
// enable hardware keyboard
|
||||
const enableHardwareKeyboardInput = core.getInput('enable-hw-keyboard');
|
||||
(0, input_validator_1.checkEnableHardwareKeyboard)(enableHardwareKeyboardInput);
|
||||
const enableHardwareKeyboard = enableHardwareKeyboardInput === 'true';
|
||||
console.log(`enable hardware keyboard: ${enableHardwareKeyboard}`);
|
||||
// emulator build
|
||||
const emulatorBuildInput = core.getInput('emulator-build');
|
||||
if (emulatorBuildInput) {
|
||||
(0, input_validator_1.checkEmulatorBuild)(emulatorBuildInput);
|
||||
console.log(`using emulator build: ${emulatorBuildInput}`);
|
||||
}
|
||||
const emulatorBuild = !emulatorBuildInput ? undefined : emulatorBuildInput;
|
||||
// custom working directory
|
||||
const workingDirectoryInput = core.getInput('working-directory');
|
||||
if (workingDirectoryInput) {
|
||||
console.log(`custom working directory: ${workingDirectoryInput}`);
|
||||
}
|
||||
const workingDirectory = !workingDirectoryInput ? undefined : workingDirectoryInput;
|
||||
// version of NDK to install
|
||||
const ndkInput = core.getInput('ndk');
|
||||
if (ndkInput) {
|
||||
console.log(`version of NDK to install: ${ndkInput}`);
|
||||
}
|
||||
const ndkVersion = !ndkInput ? undefined : ndkInput;
|
||||
// version of CMake to install
|
||||
const cmakeInput = core.getInput('cmake');
|
||||
if (cmakeInput) {
|
||||
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');
|
||||
(0, input_validator_1.checkChannel)(channelName);
|
||||
const channelId = (0, channel_id_mapper_1.getChannelId)(channelName);
|
||||
console.log(`Channel: ${channelId} (${channelName})`);
|
||||
// custom script to run
|
||||
const scriptInput = core.getInput('script', { required: true });
|
||||
const scripts = (0, script_parser_1.parseScript)(scriptInput);
|
||||
console.log(`Script:`);
|
||||
scripts.forEach(async (script) => {
|
||||
console.log(`${script}`);
|
||||
});
|
||||
// custom pre emulator launch script
|
||||
const preEmulatorLaunchScriptInput = core.getInput('pre-emulator-launch-script');
|
||||
const preEmulatorLaunchScripts = !preEmulatorLaunchScriptInput ? undefined : (0, script_parser_1.parseScript)(preEmulatorLaunchScriptInput);
|
||||
console.log(`Pre emulator launch script:`);
|
||||
preEmulatorLaunchScripts?.forEach(async (script) => {
|
||||
console.log(`${script}`);
|
||||
});
|
||||
console.log(`::endgroup::`);
|
||||
// install SDK
|
||||
await (0, sdk_installer_1.installAndroidSdk)(apiLevel, systemImageApiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion);
|
||||
// create AVD
|
||||
await (0, emulator_manager_1.createAvd)(arch, avdName, cores, diskSize, enableHardwareKeyboard, forceAvdCreation, heapSize, profile, ramSize, sdcardPathOrSize, systemImageApiLevel, target);
|
||||
// execute pre emulator launch script if set
|
||||
if (preEmulatorLaunchScripts !== undefined) {
|
||||
console.log(`::group::Run pre emulator launch script`);
|
||||
try {
|
||||
// move to custom working directory if set
|
||||
if (workingDirectory) {
|
||||
process.chdir(workingDirectory);
|
||||
}
|
||||
for (const script of scripts) {
|
||||
for (const preEmulatorLaunchScript of preEmulatorLaunchScripts) {
|
||||
// use array form to avoid various quote escaping problems
|
||||
// caused by exec(`sh -c "${script}"`)
|
||||
yield exec.exec('sh', ['-c', script]);
|
||||
// caused by exec(`sh -c "${preEmulatorLaunchScript}"`)
|
||||
await exec.exec('sh', ['-c', preEmulatorLaunchScript], {
|
||||
cwd: workingDirectory,
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
core.setFailed(error instanceof Error ? error.message : error);
|
||||
}
|
||||
// finally kill the emulator
|
||||
yield (0, emulator_manager_1.killEmulator)();
|
||||
console.log(`::endgroup::`);
|
||||
}
|
||||
// launch an emulator
|
||||
await (0, emulator_manager_1.launchEmulator)(avdName, disableAnimations, disableLinuxHardwareAcceleration, disableSpellchecker, emulatorBootTimeout, emulatorOptions, enableHardwareKeyboard, port);
|
||||
// execute the custom script
|
||||
try {
|
||||
// move to custom working directory if set
|
||||
if (workingDirectory) {
|
||||
process.chdir(workingDirectory);
|
||||
}
|
||||
for (const script of scripts) {
|
||||
// use array form to avoid various quote escaping problems
|
||||
// caused by exec(`sh -c "${script}"`)
|
||||
await exec.exec('sh', ['-c', script], {
|
||||
env: { ...process.env, EMULATOR_PORT: `${port}`, ANDROID_SERIAL: `emulator-${port}` },
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// kill the emulator so the action can exit
|
||||
yield (0, emulator_manager_1.killEmulator)();
|
||||
core.setFailed(error instanceof Error ? error.message : error);
|
||||
}
|
||||
});
|
||||
// finally kill the emulator
|
||||
await (0, emulator_manager_1.killEmulator)(port);
|
||||
}
|
||||
catch (error) {
|
||||
// kill the emulator so the action can exit
|
||||
await (0, emulator_manager_1.killEmulator)(port);
|
||||
core.setFailed(error instanceof Error ? error.message : error);
|
||||
}
|
||||
}
|
||||
run();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.parseScript = void 0;
|
||||
exports.parseScript = parseScript;
|
||||
/**
|
||||
* Convert a (potentially multi-line) script to an array of single-line script(s).
|
||||
*/
|
||||
@@ -17,4 +17,3 @@ function parseScript(rawScript) {
|
||||
}
|
||||
return scripts;
|
||||
}
|
||||
exports.parseScript = parseScript;
|
||||
|
||||
+76
-79
@@ -15,101 +15,98 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
||||
}) : 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());
|
||||
});
|
||||
};
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.installAndroidSdk = void 0;
|
||||
exports.installAndroidSdk = installAndroidSdk;
|
||||
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 = '34.0.0';
|
||||
// SDK command-line tools 11.0
|
||||
const CMDLINE_TOOLS_URL_MAC = 'https://dl.google.com/android/repository/commandlinetools-mac-10406996_latest.zip';
|
||||
const CMDLINE_TOOLS_URL_LINUX = 'https://dl.google.com/android/repository/commandlinetools-linux-10406996_latest.zip';
|
||||
const BUILD_TOOLS_VERSION = '37.0.0';
|
||||
// SDK command-line tools 23.0
|
||||
const CMDLINE_TOOLS_VERSION = '16111833';
|
||||
const CMDLINE_TOOLS_URL_MAC = `https://dl.google.com/android/repository/commandlinetools-mac_x86_64-${CMDLINE_TOOLS_VERSION}_latest.zip`;
|
||||
const CMDLINE_TOOLS_URL_LINUX = `https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINE_TOOLS_VERSION}_latest.zip`;
|
||||
/**
|
||||
* Installs & updates the Android SDK for the macOS platform, including SDK platform for the chosen API level, latest build tools, platform tools, Android Emulator,
|
||||
* and the system image for the chosen API level, CPU arch, and target.
|
||||
*/
|
||||
function installAndroidSdk(apiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
console.log(`::group::Install Android SDK`);
|
||||
const isOnMac = process.platform === 'darwin';
|
||||
const isArm = process.arch === 'arm64';
|
||||
if (!isOnMac) {
|
||||
yield exec.exec(`sh -c \\"sudo chown $USER:$USER ${process.env.ANDROID_HOME} -R`);
|
||||
}
|
||||
const cmdlineToolsPath = `${process.env.ANDROID_HOME}/cmdline-tools`;
|
||||
if (!fs.existsSync(cmdlineToolsPath)) {
|
||||
console.log('Installing new cmdline-tools.');
|
||||
const sdkUrl = isOnMac ? CMDLINE_TOOLS_URL_MAC : CMDLINE_TOOLS_URL_LINUX;
|
||||
const downloadPath = yield tc.downloadTool(sdkUrl);
|
||||
yield tc.extractZip(downloadPath, cmdlineToolsPath);
|
||||
yield io.mv(`${cmdlineToolsPath}/cmdline-tools`, `${cmdlineToolsPath}/latest`);
|
||||
}
|
||||
// add paths for commandline-tools and platform-tools
|
||||
core.addPath(`${cmdlineToolsPath}/latest:${cmdlineToolsPath}/latest/bin:${process.env.ANDROID_HOME}/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 > /dev/null"`);
|
||||
console.log('Installing latest emulator.');
|
||||
yield exec.exec(`sh -c \\"sdkmanager --install emulator --channel=${channelId} > /dev/null"`);
|
||||
if (emulatorBuild) {
|
||||
console.log(`Installing emulator build ${emulatorBuild}.`);
|
||||
// TODO find out the correct download URLs for all build ids
|
||||
var downloadUrlSuffix;
|
||||
const majorBuildVersion = Number(emulatorBuild);
|
||||
if (majorBuildVersion >= 8000000) {
|
||||
if (isArm) {
|
||||
downloadUrlSuffix = `_aarch64-${emulatorBuild}`;
|
||||
}
|
||||
else {
|
||||
downloadUrlSuffix = `_x64-${emulatorBuild}`;
|
||||
}
|
||||
}
|
||||
else if (majorBuildVersion >= 7000000) {
|
||||
downloadUrlSuffix = `_x64-${emulatorBuild}`;
|
||||
async function installAndroidSdk(apiLevel, systemImageApiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion) {
|
||||
try {
|
||||
console.log(`::group::Install Android SDK`);
|
||||
const isOnMac = process.platform === 'darwin';
|
||||
const isArm = process.arch === 'arm64';
|
||||
const cmdlineToolsPath = `${process.env.ANDROID_HOME}/cmdline-tools`;
|
||||
if (!fs.existsSync(cmdlineToolsPath)) {
|
||||
console.log('Installing new cmdline-tools.');
|
||||
const sdkUrl = isOnMac ? CMDLINE_TOOLS_URL_MAC : CMDLINE_TOOLS_URL_LINUX;
|
||||
const downloadPath = await tc.downloadTool(sdkUrl);
|
||||
await tc.extractZip(downloadPath, cmdlineToolsPath);
|
||||
await io.mv(`${cmdlineToolsPath}/cmdline-tools`, `${cmdlineToolsPath}/latest`);
|
||||
}
|
||||
// add paths for commandline-tools and platform-tools
|
||||
core.addPath(`${cmdlineToolsPath}/latest:${cmdlineToolsPath}/latest/bin:${process.env.ANDROID_HOME}/platform-tools`);
|
||||
// set standard AVD path
|
||||
await io.mkdirP(`${process.env.HOME}/.android/avd`);
|
||||
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}.`);
|
||||
// TODO find out the correct download URLs for all build ids
|
||||
var downloadUrlSuffix;
|
||||
const majorBuildVersion = Number(emulatorBuild);
|
||||
if (majorBuildVersion >= 8000000) {
|
||||
if (isArm) {
|
||||
downloadUrlSuffix = `_aarch64-${emulatorBuild}`;
|
||||
}
|
||||
else {
|
||||
downloadUrlSuffix = `-${emulatorBuild}`;
|
||||
downloadUrlSuffix = `_x64-${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_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"`);
|
||||
if (ndkVersion) {
|
||||
console.log(`Installing NDK ${ndkVersion}.`);
|
||||
yield exec.exec(`sh -c \\"sdkmanager --install 'ndk;${ndkVersion}' --channel=${channelId} > /dev/null"`);
|
||||
else if (majorBuildVersion >= 7000000) {
|
||||
downloadUrlSuffix = `_x64-${emulatorBuild}`;
|
||||
}
|
||||
if (cmakeVersion) {
|
||||
console.log(`Installing CMake ${cmakeVersion}.`);
|
||||
yield exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' --channel=${channelId} > /dev/null"`);
|
||||
else {
|
||||
downloadUrlSuffix = `-${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_HOME}`);
|
||||
await io.rmRF('emulator.zip');
|
||||
}
|
||||
finally {
|
||||
console.log(`::endgroup::`);
|
||||
console.log('Installing system images.');
|
||||
await exec.exec(`sh -c \\"sdkmanager --install 'system-images;android-${systemImageApiLevel};${target};${arch}' --channel=${channelId} > /dev/null"`);
|
||||
if (ndkVersion) {
|
||||
console.log(`Installing NDK ${ndkVersion}.`);
|
||||
await exec.exec(`sh -c \\"sdkmanager --install 'ndk;${ndkVersion}' --channel=${channelId} > /dev/null"`);
|
||||
}
|
||||
});
|
||||
if (cmakeVersion) {
|
||||
console.log(`Installing CMake ${cmakeVersion}.`);
|
||||
await exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' --channel=${channelId} > /dev/null"`);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
console.log(`::endgroup::`);
|
||||
}
|
||||
}
|
||||
exports.installAndroidSdk = installAndroidSdk;
|
||||
|
||||
Generated
+4003
-3123
File diff suppressed because it is too large
Load Diff
+17
-14
@@ -2,7 +2,7 @@
|
||||
"name": "android-emulator-runner",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"description": "A GitHub Action for installing, configuring and running Android Emulators on macOS virtual machines.",
|
||||
"description": "A GitHub Action for installing, configuring and running Android Emulators on hardware-accelerated runners.",
|
||||
"main": "lib/main.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
@@ -18,6 +18,9 @@
|
||||
"node",
|
||||
"setup"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=24"
|
||||
},
|
||||
"author": "Reactive Circus",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -28,18 +31,18 @@
|
||||
"set-value": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.2.0",
|
||||
"@types/node": "^16.18.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.41.0",
|
||||
"@typescript-eslint/parser": "^5.41.0",
|
||||
"eslint": "^8.26.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"jest": "^29.2.2",
|
||||
"jest-circus": "^29.2.2",
|
||||
"prettier": "^2.7.1",
|
||||
"ts-jest": "^29.0.3",
|
||||
"typescript": "^4.8.4"
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^24.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.57.0",
|
||||
"@typescript-eslint/parser": "^8.57.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"jest": "^29.7.0",
|
||||
"jest-circus": "^29.7.0",
|
||||
"prettier": "^3.3.3",
|
||||
"ts-jest": "^29.2.5",
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
}
|
||||
|
||||
+70
-46
@@ -2,29 +2,24 @@ import * as exec from '@actions/exec';
|
||||
import * as fs from 'fs';
|
||||
|
||||
/**
|
||||
* Creates and launches a new AVD instance with the specified configurations.
|
||||
* Creates a new AVD instance with the specified configurations.
|
||||
*/
|
||||
export async function launchEmulator(
|
||||
apiLevel: string,
|
||||
target: string,
|
||||
export async function createAvd(
|
||||
arch: string,
|
||||
profile: string,
|
||||
cores: string,
|
||||
ramSize: string,
|
||||
heapSize: string,
|
||||
sdcardPathOrSize: string,
|
||||
diskSize: string,
|
||||
avdName: string,
|
||||
cores: string,
|
||||
diskSize: string,
|
||||
enableHardwareKeyboard: boolean,
|
||||
forceAvdCreation: boolean,
|
||||
emulatorBootTimeout: number,
|
||||
emulatorOptions: string,
|
||||
disableAnimations: boolean,
|
||||
disableSpellChecker: boolean,
|
||||
disableLinuxHardwareAcceleration: boolean,
|
||||
enableHardwareKeyboard: boolean
|
||||
heapSize: string,
|
||||
profile: string,
|
||||
ramSize: string,
|
||||
sdcardPathOrSize: string,
|
||||
systemImageApiLevel: string,
|
||||
target: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
console.log(`::group::Launch Emulator`);
|
||||
console.log(`::group::Create AVD`);
|
||||
// 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) {
|
||||
@@ -32,31 +27,56 @@ export async function launchEmulator(
|
||||
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}"`
|
||||
`sh -c \\"echo no | avdmanager create avd --force -n "${avdName}" --package 'system-images;android-${systemImageApiLevel};${target};${arch}' ${profileOption} ${sdcardPathOrSizeOption}"`,
|
||||
);
|
||||
}
|
||||
|
||||
if (cores) {
|
||||
await exec.exec(`sh -c \\"printf 'hw.cpu.ncore=${cores}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`);
|
||||
}
|
||||
if (cores || ramSize || heapSize || enableHardwareKeyboard || diskSize) {
|
||||
const configEntries: string[] = [];
|
||||
|
||||
if (ramSize) {
|
||||
await exec.exec(`sh -c \\"printf 'hw.ramSize=${ramSize}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`);
|
||||
}
|
||||
if (cores) {
|
||||
configEntries.push(`hw.cpu.ncore=${cores}`);
|
||||
}
|
||||
if (ramSize) {
|
||||
configEntries.push(`hw.ramSize=${ramSize}`);
|
||||
}
|
||||
if (heapSize) {
|
||||
configEntries.push(`hw.heapSize=${heapSize}`);
|
||||
}
|
||||
if (enableHardwareKeyboard) {
|
||||
configEntries.push('hw.keyboard=yes');
|
||||
}
|
||||
if (diskSize) {
|
||||
configEntries.push(`disk.dataPartition.size=${diskSize}`);
|
||||
}
|
||||
|
||||
if (heapSize) {
|
||||
await exec.exec(`sh -c \\"printf 'hw.heapSize=${heapSize}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`);
|
||||
if (configEntries.length > 0) {
|
||||
const configContent = configEntries.join('\\n') + '\\n';
|
||||
await exec.exec(`sh -c \\"printf '${configContent}' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini"`);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
console.log(`::endgroup::`);
|
||||
}
|
||||
}
|
||||
|
||||
if (enableHardwareKeyboard) {
|
||||
await exec.exec(`sh -c \\"printf 'hw.keyboard=yes\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`);
|
||||
}
|
||||
/**
|
||||
* Launches an existing AVD instance with the specified configurations.
|
||||
*/
|
||||
export async function launchEmulator(
|
||||
avdName: string,
|
||||
disableAnimations: boolean,
|
||||
disableLinuxHardwareAcceleration: boolean,
|
||||
disableSpellChecker: boolean,
|
||||
emulatorBootTimeout: number,
|
||||
emulatorOptions: string,
|
||||
enableHardwareKeyboard: boolean,
|
||||
port: number,
|
||||
): Promise<void> {
|
||||
try {
|
||||
console.log(`::group::Launch Emulator`);
|
||||
|
||||
if (diskSize) {
|
||||
await exec.exec(`sh -c \\"printf 'disk.dataPartition.size=${diskSize}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`);
|
||||
}
|
||||
|
||||
//turn off hardware acceleration on Linux
|
||||
// turn off hardware acceleration on Linux
|
||||
if (process.platform === 'linux' && disableLinuxHardwareAcceleration) {
|
||||
console.log('Disabling Linux hardware acceleration.');
|
||||
emulatorOptions += ' -accel off';
|
||||
@@ -65,7 +85,7 @@ export async function launchEmulator(
|
||||
// start emulator
|
||||
console.log('Starting emulator.');
|
||||
|
||||
await exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -avd "${avdName}" ${emulatorOptions} &"`, [], {
|
||||
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')) {
|
||||
@@ -76,20 +96,20 @@ export async function launchEmulator(
|
||||
});
|
||||
|
||||
// wait for emulator to complete booting
|
||||
await waitForDevice(emulatorBootTimeout);
|
||||
await exec.exec(`adb shell input keyevent 82`);
|
||||
await waitForDevice(port, emulatorBootTimeout);
|
||||
await adb(port, `shell input keyevent 82`);
|
||||
|
||||
if (disableAnimations) {
|
||||
console.log('Disabling animations.');
|
||||
await exec.exec(`adb shell settings put global window_animation_scale 0.0`);
|
||||
await exec.exec(`adb shell settings put global transition_animation_scale 0.0`);
|
||||
await exec.exec(`adb shell settings put global animator_duration_scale 0.0`);
|
||||
await adb(port, `shell settings put global window_animation_scale 0.0`);
|
||||
await adb(port, `shell settings put global transition_animation_scale 0.0`);
|
||||
await adb(port, `shell settings put global animator_duration_scale 0.0`);
|
||||
}
|
||||
if (disableSpellChecker) {
|
||||
await exec.exec(`adb shell settings put secure spell_checker_enabled 0`);
|
||||
await adb(port, `shell settings put secure spell_checker_enabled 0`);
|
||||
}
|
||||
if (enableHardwareKeyboard) {
|
||||
await exec.exec(`adb shell settings put secure show_ime_with_hard_keyboard 0`);
|
||||
await adb(port, `shell settings put secure show_ime_with_hard_keyboard 0`);
|
||||
}
|
||||
} finally {
|
||||
console.log(`::endgroup::`);
|
||||
@@ -99,10 +119,10 @@ export async function launchEmulator(
|
||||
/**
|
||||
* Kills the running emulator on the default port.
|
||||
*/
|
||||
export async function killEmulator(): Promise<void> {
|
||||
export async function killEmulator(port: number): Promise<void> {
|
||||
try {
|
||||
console.log(`::group::Terminate Emulator`);
|
||||
await exec.exec(`adb -s emulator-5554 emu kill`);
|
||||
await adb(port, `emu kill`);
|
||||
} catch (error) {
|
||||
console.log(error instanceof Error ? error.message : error);
|
||||
} finally {
|
||||
@@ -110,10 +130,14 @@ export async function killEmulator(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function adb(port: number, command: string): Promise<number> {
|
||||
return await exec.exec(`adb -s emulator-${port} ${command}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for emulator to boot.
|
||||
*/
|
||||
async function waitForDevice(emulatorBootTimeout: number): Promise<void> {
|
||||
async function waitForDevice(port: number, emulatorBootTimeout: number): Promise<void> {
|
||||
let booted = false;
|
||||
let attempts = 0;
|
||||
const retryInterval = 2; // retry every 2 seconds
|
||||
@@ -121,7 +145,7 @@ async function waitForDevice(emulatorBootTimeout: number): Promise<void> {
|
||||
while (!booted) {
|
||||
try {
|
||||
let result = '';
|
||||
await exec.exec(`adb shell getprop sys.boot_completed`, [], {
|
||||
await exec.exec(`adb -s emulator-${port} shell getprop sys.boot_completed`, [], {
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => {
|
||||
result += data.toString();
|
||||
|
||||
+17
-15
@@ -1,22 +1,15 @@
|
||||
export const MIN_API_LEVEL = 15;
|
||||
export const VALID_TARGETS: Array<string> = ['default', 'google_apis', 'aosp_atd', 'google_atd', '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 const MIN_PORT = 5554;
|
||||
export const MAX_PORT = 5584;
|
||||
|
||||
export function checkApiLevel(apiLevel: string): void {
|
||||
if (apiLevel.startsWith('UpsideDownCake') || apiLevel === 'TiramisuPrivacySandbox') return;
|
||||
if (isNaN(Number(apiLevel)) || !Number.isInteger(Number(apiLevel))) {
|
||||
throw new Error(`Unexpected API level: '${apiLevel}'.`);
|
||||
}
|
||||
if (Number(apiLevel) < MIN_API_LEVEL) {
|
||||
throw new Error(`Minimum API level supported is ${MIN_API_LEVEL}.`);
|
||||
}
|
||||
}
|
||||
|
||||
export function checkTarget(target: string): void {
|
||||
if (!VALID_TARGETS.includes(target)) {
|
||||
throw new Error(`Value for input.target '${target}' is unknown. Supported options: ${VALID_TARGETS}.`);
|
||||
}
|
||||
export function playstoreTargetSubstitution(target: string): string {
|
||||
// "playstore" is an allowed shorthand for "google_apis_playstore" images
|
||||
// this is idempotent - return same even if run multiple times on same target
|
||||
if (target === 'playstore') return 'google_apis_playstore';
|
||||
if (target === 'playstore_ps16k') return 'google_apis_playstore_ps16k';
|
||||
return target;
|
||||
}
|
||||
|
||||
export function checkArch(arch: string): void {
|
||||
@@ -37,6 +30,15 @@ export function checkForceAvdCreation(forceAvdCreation: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function checkPort(port: number): void {
|
||||
if (port < MIN_PORT || port > MAX_PORT) {
|
||||
throw new Error(`Emulator port is outside of the supported port range [${MIN_PORT}, ${MAX_PORT}], was ${port}`);
|
||||
}
|
||||
if (port % 2 == 1) {
|
||||
throw new Error(`Emulator port has to be even, was ${port}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function checkDisableAnimations(disableAnimations: string): void {
|
||||
if (!isValidBoolean(disableAnimations)) {
|
||||
throw new Error(`Input for input.disable-animations should be either 'true' or 'false'.`);
|
||||
|
||||
+28
-31
@@ -1,8 +1,6 @@
|
||||
import * as core from '@actions/core';
|
||||
import { installAndroidSdk } from './sdk-installer';
|
||||
import {
|
||||
checkApiLevel,
|
||||
checkTarget,
|
||||
checkArch,
|
||||
checkDisableAnimations,
|
||||
checkEmulatorBuild,
|
||||
@@ -12,14 +10,18 @@ import {
|
||||
checkChannel,
|
||||
checkEnableHardwareKeyboard,
|
||||
checkDiskSize,
|
||||
checkPort,
|
||||
playstoreTargetSubstitution,
|
||||
MIN_PORT,
|
||||
} from './input-validator';
|
||||
import { launchEmulator, killEmulator } from './emulator-manager';
|
||||
import { createAvd, launchEmulator, killEmulator } from './emulator-manager';
|
||||
import * as exec from '@actions/exec';
|
||||
import { parseScript } from './script-parser';
|
||||
import { getChannelId } from './channel-id-mapper';
|
||||
import { accessSync, constants } from 'fs';
|
||||
|
||||
async function run() {
|
||||
let port: number = MIN_PORT;
|
||||
try {
|
||||
console.log(`::group::Configure emulator`);
|
||||
let linuxSupportKVM = false;
|
||||
@@ -31,7 +33,7 @@ async function run() {
|
||||
linuxSupportKVM = true;
|
||||
} catch {
|
||||
console.warn(
|
||||
`You're running a Linux VM where hardware acceleration is not available. Please consider using a macOS VM instead to take advantage of native hardware acceleration support provided by HAXM.`
|
||||
`You're running a Linux VM where hardware acceleration is not available. Please consider using a macOS VM instead to take advantage of native hardware acceleration support provided by HAXM.`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -41,13 +43,16 @@ async function run() {
|
||||
|
||||
// API level of the platform and system image
|
||||
const apiLevel = core.getInput('api-level', { required: true });
|
||||
checkApiLevel(apiLevel);
|
||||
console.log(`API level: ${apiLevel}`);
|
||||
|
||||
let systemImageApiLevel = core.getInput('system-image-api-level');
|
||||
if (!systemImageApiLevel) {
|
||||
systemImageApiLevel = apiLevel;
|
||||
}
|
||||
console.log(`System image API level: ${systemImageApiLevel}`);
|
||||
|
||||
// target of the system image
|
||||
const targetInput = core.getInput('target');
|
||||
const target = targetInput == 'playstore' ? 'google_apis_playstore' : targetInput;
|
||||
checkTarget(target);
|
||||
const target = playstoreTargetSubstitution(core.getInput('target'));
|
||||
console.log(`target: ${target}`);
|
||||
|
||||
// CPU architecture of the system image
|
||||
@@ -93,6 +98,11 @@ async function run() {
|
||||
const emulatorBootTimeout = parseInt(core.getInput('emulator-boot-timeout'), 10);
|
||||
console.log(`Emulator boot timeout: ${emulatorBootTimeout}`);
|
||||
|
||||
// Emulator port to use
|
||||
port = parseInt(core.getInput('emulator-port'), 10);
|
||||
checkPort(port);
|
||||
console.log(`emulator port: ${port}`);
|
||||
|
||||
// emulator options
|
||||
const emulatorOptions = core.getInput('emulator-options').trim();
|
||||
console.log(`emulator options: ${emulatorOptions}`);
|
||||
@@ -177,7 +187,10 @@ async function run() {
|
||||
console.log(`::endgroup::`);
|
||||
|
||||
// install SDK
|
||||
await installAndroidSdk(apiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion);
|
||||
await installAndroidSdk(apiLevel, systemImageApiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion);
|
||||
|
||||
// create AVD
|
||||
await createAvd(arch, avdName, cores, diskSize, enableHardwareKeyboard, forceAvdCreation, heapSize, profile, ramSize, sdcardPathOrSize, systemImageApiLevel, target);
|
||||
|
||||
// execute pre emulator launch script if set
|
||||
if (preEmulatorLaunchScripts !== undefined) {
|
||||
@@ -197,25 +210,7 @@ async function run() {
|
||||
}
|
||||
|
||||
// launch an emulator
|
||||
await launchEmulator(
|
||||
apiLevel,
|
||||
target,
|
||||
arch,
|
||||
profile,
|
||||
cores,
|
||||
ramSize,
|
||||
heapSize,
|
||||
sdcardPathOrSize,
|
||||
diskSize,
|
||||
avdName,
|
||||
forceAvdCreation,
|
||||
emulatorBootTimeout,
|
||||
emulatorOptions,
|
||||
disableAnimations,
|
||||
disableSpellchecker,
|
||||
disableLinuxHardwareAcceleration,
|
||||
enableHardwareKeyboard
|
||||
);
|
||||
await launchEmulator(avdName, disableAnimations, disableLinuxHardwareAcceleration, disableSpellchecker, emulatorBootTimeout, emulatorOptions, enableHardwareKeyboard, port);
|
||||
|
||||
// execute the custom script
|
||||
try {
|
||||
@@ -226,17 +221,19 @@ async function run() {
|
||||
for (const script of scripts) {
|
||||
// use array form to avoid various quote escaping problems
|
||||
// caused by exec(`sh -c "${script}"`)
|
||||
await exec.exec('sh', ['-c', script]);
|
||||
await exec.exec('sh', ['-c', script], {
|
||||
env: { ...process.env, EMULATOR_PORT: `${port}`, ANDROID_SERIAL: `emulator-${port}` },
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
core.setFailed(error instanceof Error ? error.message : (error as string));
|
||||
}
|
||||
|
||||
// finally kill the emulator
|
||||
await killEmulator();
|
||||
await killEmulator(port);
|
||||
} catch (error) {
|
||||
// kill the emulator so the action can exit
|
||||
await killEmulator();
|
||||
await killEmulator(port);
|
||||
core.setFailed(error instanceof Error ? error.message : (error as string));
|
||||
}
|
||||
}
|
||||
|
||||
+18
-11
@@ -4,25 +4,31 @@ import * as io from '@actions/io';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import * as fs from 'fs';
|
||||
|
||||
const BUILD_TOOLS_VERSION = '34.0.0';
|
||||
// SDK command-line tools 11.0
|
||||
const CMDLINE_TOOLS_URL_MAC = 'https://dl.google.com/android/repository/commandlinetools-mac-10406996_latest.zip';
|
||||
const CMDLINE_TOOLS_URL_LINUX = 'https://dl.google.com/android/repository/commandlinetools-linux-10406996_latest.zip';
|
||||
const BUILD_TOOLS_VERSION = '37.0.0';
|
||||
// SDK command-line tools 23.0
|
||||
const CMDLINE_TOOLS_VERSION = '16111833';
|
||||
const CMDLINE_TOOLS_URL_MAC = `https://dl.google.com/android/repository/commandlinetools-mac_x86_64-${CMDLINE_TOOLS_VERSION}_latest.zip`;
|
||||
const CMDLINE_TOOLS_URL_LINUX = `https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINE_TOOLS_VERSION}_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: string, target: string, arch: string, channelId: number, emulatorBuild?: string, ndkVersion?: string, cmakeVersion?: string): Promise<void> {
|
||||
export async function installAndroidSdk(
|
||||
apiLevel: string,
|
||||
systemImageApiLevel: String,
|
||||
target: string,
|
||||
arch: string,
|
||||
channelId: number,
|
||||
emulatorBuild?: string,
|
||||
ndkVersion?: string,
|
||||
cmakeVersion?: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
console.log(`::group::Install Android SDK`);
|
||||
const isOnMac = process.platform === 'darwin';
|
||||
const isArm = process.arch === 'arm64';
|
||||
|
||||
if (!isOnMac) {
|
||||
await exec.exec(`sh -c \\"sudo chown $USER:$USER ${process.env.ANDROID_HOME} -R`);
|
||||
}
|
||||
|
||||
const cmdlineToolsPath = `${process.env.ANDROID_HOME}/cmdline-tools`;
|
||||
if (!fs.existsSync(cmdlineToolsPath)) {
|
||||
console.log('Installing new cmdline-tools.');
|
||||
@@ -36,6 +42,7 @@ export async function installAndroidSdk(apiLevel: string, target: string, arch:
|
||||
core.addPath(`${cmdlineToolsPath}/latest:${cmdlineToolsPath}/latest/bin:${process.env.ANDROID_HOME}/platform-tools`);
|
||||
|
||||
// set standard AVD path
|
||||
await io.mkdirP(`${process.env.HOME}/.android/avd`);
|
||||
core.exportVariable('ANDROID_AVD_HOME', `${process.env.HOME}/.android/avd`);
|
||||
|
||||
// accept all Android SDK licenses
|
||||
@@ -43,7 +50,7 @@ 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 > /dev/null"`);
|
||||
await exec.exec(`sh -c \\"sdkmanager --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools 'platforms;android-${apiLevel}'> /dev/null"`);
|
||||
|
||||
console.log('Installing latest emulator.');
|
||||
await exec.exec(`sh -c \\"sdkmanager --install emulator --channel=${channelId} > /dev/null"`);
|
||||
@@ -69,7 +76,7 @@ export async function installAndroidSdk(apiLevel: string, target: string, arch:
|
||||
await io.rmRF('emulator.zip');
|
||||
}
|
||||
console.log('Installing system images.');
|
||||
await exec.exec(`sh -c \\"sdkmanager --install 'system-images;android-${apiLevel};${target};${arch}' --channel=${channelId} > /dev/null"`);
|
||||
await exec.exec(`sh -c \\"sdkmanager --install 'system-images;android-${systemImageApiLevel};${target};${arch}' --channel=${channelId} > /dev/null"`);
|
||||
|
||||
if (ndkVersion) {
|
||||
console.log(`Installing NDK ${ndkVersion}.`);
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
namespace = "com.example.testapp"
|
||||
|
||||
compileSdkVersion 34
|
||||
buildToolsVersion "34.0.0"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.example.testapp"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 34
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
|
||||
compilerOptions {
|
||||
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.testapp"
|
||||
compileSdk {
|
||||
version = release(37) {
|
||||
minorApiLevel = 1
|
||||
}
|
||||
}
|
||||
buildToolsVersion = "37.0.0"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.example.testapp"
|
||||
minSdk = 23
|
||||
targetSdk = 37
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||
compilerOptions {
|
||||
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.appcompat:appcompat:1.8.0")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.3.0")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.2.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.21"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
plugins {
|
||||
id("com.android.application") version "9.3.2" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.4.10" apply false
|
||||
}
|
||||
|
||||
@@ -5,5 +5,3 @@ org.gradle.caching=true
|
||||
# Kotlin code style
|
||||
kotlin.code.style=official
|
||||
|
||||
# Enable AndroidX
|
||||
android.useAndroidX=true
|
||||
|
||||
BIN
Binary file not shown.
+3
-1
@@ -1,7 +1,9 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
|
||||
networkTimeout=10000
|
||||
retries=0
|
||||
retryBackOffMs=500
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
Vendored
+9
-10
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
# Copyright © 2015 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -15,10 +15,12 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
# gradlew start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
@@ -27,7 +29,7 @@
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
# ksh gradlew
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -84,7 +86,7 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -112,7 +114,6 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -170,7 +171,6 @@ fi
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
@@ -203,15 +203,14 @@ fi
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
Vendored
+24
-34
@@ -13,16 +13,18 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem gradlew startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||
setlocal EnableExtensions
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@@ -43,13 +45,13 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
"%COMSPEC%" /c exit 1
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
@@ -57,36 +59,24 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
"%COMSPEC%" /c exit 1
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
@rem Execute gradlew
|
||||
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
||||
@rem which allows us to clear the local environment before executing the java command
|
||||
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
:exitWithErrorLevel
|
||||
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
rootProject.name='Test Project'
|
||||
include ':app'
|
||||
@@ -0,0 +1,28 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google {
|
||||
content {
|
||||
includeGroupByRegex("androidx.*")
|
||||
includeGroupByRegex("com.android.*")
|
||||
includeGroupByRegex("com.google.*")
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "Test Project"
|
||||
include(":app")
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
google {
|
||||
content {
|
||||
includeGroupByRegex("androidx.*")
|
||||
includeGroupByRegex("com.android.*")
|
||||
includeGroupByRegex("com.google.*")
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"target": "es2022", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
@@ -45,7 +45,8 @@
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
"skipLibCheck": true
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user