mirror of
https://github.com/reactivecircus/android-emulator-runner.git
synced 2026-08-31 17:49:33 +00:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 677db68893 | |||
| f9c2835191 | |||
| 1036f55734 | |||
| 5a481d20f1 | |||
| f72f505164 | |||
| fabc29122a | |||
| 2cfd145246 | |||
| e1bdfefaa7 | |||
| b35b1c6e69 | |||
| acd15a6841 | |||
| a2c97ef57f | |||
| 8c71f5ed72 | |||
| 743ec40c5b | |||
| bad4154821 | |||
| bdaf04916f | |||
| 8e947e5bd6 | |||
| e1248ae048 | |||
| 8d5c4be650 | |||
| ce60a72e42 | |||
| 3c9349d876 | |||
| 949d52fd17 |
@@ -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
|
||||
|
||||
+38
-34
@@ -18,41 +18,36 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest]
|
||||
api-level: [16, 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: 35
|
||||
target: google_apis
|
||||
arch: x86_64
|
||||
- os: ubuntu-latest
|
||||
api-level: 34-ext10
|
||||
target: android-automotive
|
||||
arch: x86_64
|
||||
system-image-api-level: 34-ext9
|
||||
|
||||
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 +56,32 @@ jobs:
|
||||
npm run lint
|
||||
npm test
|
||||
|
||||
- name: Java 15
|
||||
uses: actions/setup-java@v3
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 15
|
||||
- uses: actions/cache@v3
|
||||
java-version: 25
|
||||
|
||||
- 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 +90,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 +108,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,17 +50,19 @@ jobs:
|
||||
npm run lint
|
||||
npm test
|
||||
|
||||
- name: Java 15
|
||||
uses: actions/setup-java@v3
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 15
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
|
||||
java-version: 25
|
||||
|
||||
- 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: ./
|
||||
@@ -69,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,76 @@
|
||||
# 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)
|
||||
* Upgrade to latest npm dependencies. - [#347](https://github.com/ReactiveCircus/android-emulator-runner/pull/347) [#355](https://github.com/ReactiveCircus/android-emulator-runner/pull/355)
|
||||
* Update SDK command-line tools to `11.0`. - [#356](https://github.com/ReactiveCircus/android-emulator-runner/pull/356)
|
||||
* Update SDK build tools to `34.0.0`. - [#356](https://github.com/ReactiveCircus/android-emulator-runner/pull/356)
|
||||
|
||||
## v2.28.0
|
||||
|
||||
* Add `emulator-boot-timeout` to support configuring maximum time waiting for emulator boot. - [#326](https://github.com/ReactiveCircus/android-emulator-runner/pull/326)
|
||||
* Support non-integer `api-level`. - [#317](https://github.com/ReactiveCircus/android-emulator-runner/pull/317)
|
||||
* Replace deprecated `ANDROID_SDK_ROOT` with `ANDROID_HOME`. - [304](https://github.com/ReactiveCircus/android-emulator-runner/pull/304)
|
||||
* Update SDK command-line tools to `9.0`. - [#331](https://github.com/ReactiveCircus/android-emulator-runner/pull/331)
|
||||
* Update SDK build tools to `33.0.2`. - [#331](https://github.com/ReactiveCircus/android-emulator-runner/pull/331)
|
||||
|
||||
## v2.27.0
|
||||
|
||||
* Added `pre-emulator-launch-script` to support running script after creating the AVD and before launching the emulator. - [#247](https://github.com/ReactiveCircus/android-emulator-runner/pull/247) @nilsreichardt.
|
||||
|
||||
@@ -4,19 +4,29 @@
|
||||
<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).
|
||||
|
||||
## HAXM support on Github's MacOS Runners
|
||||
## Running hardware accelerated emulators on Linux runners
|
||||
|
||||
The 10.x **macOS** VM provided by **GitHub Actions** had **HAXM** [pre-installed](https://github.com/actions/runner-images/blob/main/images/macos/macos-10.15-Readme.md). However, Github's [macOS-11](https://github.com/actions/runner-images/blob/main/images/macos/macos-11-Readme.md) and [macOS-12](https://github.com/actions/runner-images/blob/main/images/macos/macos-12-Readme.md) VMs **no longer** come pre-installed with HAXM. See [here](https://github.com/actions/runner-images/issues/183#issuecomment-610723516) and [here](https://github.com/actions/runner-images/issues/6388) for more info.
|
||||
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:
|
||||
|
||||
**To run with HAXM on the macOS-11 and macOS-12 agents, you must install HAXM before starting your emulator**. See: [this snippet](https://gist.github.com/mrk-han/a0a11ed9bed966bb8b775ff55f2a87e9) for more info. This will enable VM acceleration, but as of right now running with GPU acceleration, e.g. `emulator -gpu host` is not possible with Github's standard runners.
|
||||
```
|
||||
- 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
|
||||
```
|
||||
|
||||
For Linux, Nested virtualization is possible on a self-hosted or 3rd party runner, but the VM will need to be hosted on a compatible machine that allows you to [enable KVM](https://developer.android.com/studio/run/emulator-acceleration#vm-linux), or is already configured with - for example the AWS EC2 Bare Metal instances. **The Github-hosted Linux runners are not currently KVM compatible.**
|
||||
## 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.**
|
||||
|
||||
**Note**: Manually enabling and downloading HAXM is not recommended because it is redundant and not needed (see above), and for users of macOS 10.13 High Sierra and higher: macOS 10.13 [disables installation of kernel extensions by default](https://developer.apple.com/library/archive/technotes/tn2459/_index.html#//apple_ref/doc/uid/DTS40017658). Because Intel HAXM is a kernel extension, we would need to manually enable its installation on the base runner VM. Furthermore, manually trying to install HAXM on a Github Runner [brings up a popup](https://github.com/ReactiveCircus/android-emulator-runner/discussions/286#discussioncomment-4026120) which further hinders tests from running.
|
||||
|
||||
## Purpose
|
||||
|
||||
@@ -36,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
|
||||
@@ -53,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
|
||||
@@ -68,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
|
||||
```
|
||||
|
||||
@@ -77,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
|
||||
@@ -91,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: |
|
||||
@@ -145,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` |
|
||||
@@ -156,6 +216,8 @@ jobs:
|
||||
| `disk-size` | Optional | N/A | Disk size, or partition size to use for this AVD. Either in bytes or KB, MB or GB, when denoted with K, M or G. - e.g. `2048M` |
|
||||
| `avd-name` | Optional | `test` | Custom AVD name used for creating the Android Virtual Device. |
|
||||
| `force-avd-creation` | Optional | `true` | Whether to force create the AVD by overwriting an existing AVD with the same name as `avd-name` - `true` or `false`. |
|
||||
| `emulator-boot-timeout` | Optional | `600` | Emulator boot timeout in seconds. If it takes longer to boot, the action would fail - e.g. `300` for 5 minutes. |
|
||||
| `emulator-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`. |
|
||||
@@ -171,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**:
|
||||
@@ -210,5 +266,12 @@ These are some of the open-source projects using (or used) **Android Emulator Ru
|
||||
- [dotanuki-labs/norris](https://github.com/dotanuki-labs/norris/blob/master/.github/workflows/main.yml)
|
||||
- [tinylog-org/tinylog](https://github.com/tinylog-org/tinylog/blob/v3.0/.github/workflows/build.yaml)
|
||||
- [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,95 +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();
|
||||
});
|
||||
});
|
||||
|
||||
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', () => {
|
||||
@@ -164,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 = () => {
|
||||
|
||||
+10
-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:
|
||||
@@ -35,6 +40,10 @@ inputs:
|
||||
type: string
|
||||
force-avd-creation:
|
||||
type: boolean
|
||||
emulator-boot-timeout:
|
||||
type: integer
|
||||
emulator-port:
|
||||
type: integer
|
||||
emulator-options:
|
||||
type: string
|
||||
disable-animations:
|
||||
|
||||
+13
-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'
|
||||
@@ -33,6 +36,12 @@ inputs:
|
||||
force-avd-creation:
|
||||
description: 'whether to force create the AVD by overwriting an existing AVD with the same name as `avd-name` - `true` or `false`'
|
||||
default: 'true'
|
||||
emulator-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'
|
||||
@@ -65,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
-116
@@ -15,150 +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"));
|
||||
const EMULATOR_BOOT_TIMEOUT_SECONDS = 600;
|
||||
/**
|
||||
* 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, 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_SDK_ROOT}/emulator/emulator -avd "${avdName}" ${emulatorOptions} &"`, [], {
|
||||
listeners: {
|
||||
stderr: (data) => {
|
||||
if (data.toString().includes('invalid command-line parameter')) {
|
||||
throw new Error(data.toString());
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
// wait for emulator to complete booting
|
||||
yield waitForDevice();
|
||||
yield exec.exec(`adb shell input keyevent 82`);
|
||||
if (disableAnimations) {
|
||||
console.log('Disabling animations.');
|
||||
yield exec.exec(`adb shell settings put global window_animation_scale 0.0`);
|
||||
yield exec.exec(`adb shell settings put global transition_animation_scale 0.0`);
|
||||
yield exec.exec(`adb shell settings put global animator_duration_scale 0.0`);
|
||||
}
|
||||
if (disableSpellChecker) {
|
||||
yield exec.exec(`adb shell settings put secure spell_checker_enabled 0`);
|
||||
}
|
||||
if (enableHardwareKeyboard) {
|
||||
yield exec.exec(`adb shell settings put secure show_ime_with_hard_keyboard 0`);
|
||||
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() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let booted = false;
|
||||
let attempts = 0;
|
||||
const retryInterval = 2; // retry every 2 seconds
|
||||
const maxAttempts = EMULATOR_BOOT_TIMEOUT_SECONDS / 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
-25
@@ -1,73 +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 (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';
|
||||
}
|
||||
@@ -87,4 +93,3 @@ function checkDiskSize(diskSize) {
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.checkDiskSize = checkDiskSize;
|
||||
|
||||
+189
-177
@@ -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,181 +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 apiLevelInput = core.getInput('api-level', { required: true });
|
||||
(0, input_validator_1.checkApiLevel)(apiLevelInput);
|
||||
const apiLevel = Number(apiLevelInput);
|
||||
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 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, 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;
|
||||
|
||||
+82
-68
@@ -15,84 +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 = '33.0.0';
|
||||
const CMDLINE_TOOLS_URL_MAC = 'https://dl.google.com/android/repository/commandlinetools-mac-8512546_latest.zip';
|
||||
const CMDLINE_TOOLS_URL_LINUX = 'https://dl.google.com/android/repository/commandlinetools-linux-8512546_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';
|
||||
if (!isOnMac) {
|
||||
yield exec.exec(`sh -c \\"sudo chown $USER:$USER ${process.env.ANDROID_SDK_ROOT} -R`);
|
||||
}
|
||||
const cmdlineToolsPath = `${process.env.ANDROID_SDK_ROOT}/cmdline-tools`;
|
||||
if (!fs.existsSync(cmdlineToolsPath)) {
|
||||
console.log('Installing new cmdline-tools.');
|
||||
const sdkUrl = isOnMac ? CMDLINE_TOOLS_URL_MAC : CMDLINE_TOOLS_URL_LINUX;
|
||||
const downloadPath = yield tc.downloadTool(sdkUrl);
|
||||
yield tc.extractZip(downloadPath, cmdlineToolsPath);
|
||||
yield io.mv(`${cmdlineToolsPath}/cmdline-tools`, `${cmdlineToolsPath}/latest`);
|
||||
}
|
||||
// add paths for commandline-tools and platform-tools
|
||||
core.addPath(`${cmdlineToolsPath}/latest:${cmdlineToolsPath}/latest/bin:${process.env.ANDROID_SDK_ROOT}/platform-tools`);
|
||||
// set standard AVD path
|
||||
core.exportVariable('ANDROID_AVD_HOME', `${process.env.HOME}/.android/avd`);
|
||||
// accept all Android SDK licenses
|
||||
yield exec.exec(`sh -c \\"yes | sdkmanager --licenses > /dev/null"`);
|
||||
console.log('Installing latest build tools, platform tools, and platform.');
|
||||
yield exec.exec(`sh -c \\"sdkmanager --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools 'platforms;android-${apiLevel}' > /dev/null"`);
|
||||
console.log('Installing latest emulator.');
|
||||
yield exec.exec(`sh -c \\"sdkmanager --install emulator --channel=${channelId} > /dev/null"`);
|
||||
if (emulatorBuild) {
|
||||
console.log(`Installing emulator build ${emulatorBuild}.`);
|
||||
// TODO find out the correct download URLs for all build ids
|
||||
const downloadUrlSuffix = Number(emulatorBuild.charAt(0)) > 6 ? `_x64-${emulatorBuild}` : `-${emulatorBuild}`;
|
||||
yield exec.exec(`curl -fo emulator.zip https://dl.google.com/android/repository/emulator-${isOnMac ? 'darwin' : 'linux'}${downloadUrlSuffix}.zip`);
|
||||
yield exec.exec(`unzip -o -q emulator.zip -d ${process.env.ANDROID_SDK_ROOT}`);
|
||||
yield io.rmRF('emulator.zip');
|
||||
}
|
||||
console.log('Installing system images.');
|
||||
yield exec.exec(`sh -c \\"sdkmanager --install 'system-images;android-${apiLevel};${target};${arch}' --channel=${channelId} > /dev/null"`);
|
||||
if (ndkVersion) {
|
||||
console.log(`Installing NDK ${ndkVersion}.`);
|
||||
yield exec.exec(`sh -c \\"sdkmanager --install 'ndk;${ndkVersion}' --channel=${channelId} > /dev/null"`);
|
||||
}
|
||||
if (cmakeVersion) {
|
||||
console.log(`Installing CMake ${cmakeVersion}.`);
|
||||
yield exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' --channel=${channelId} > /dev/null"`);
|
||||
}
|
||||
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`);
|
||||
}
|
||||
finally {
|
||||
console.log(`::endgroup::`);
|
||||
// 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 = `_x64-${emulatorBuild}`;
|
||||
}
|
||||
}
|
||||
else if (majorBuildVersion >= 7000000) {
|
||||
downloadUrlSuffix = `_x64-${emulatorBuild}`;
|
||||
}
|
||||
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');
|
||||
}
|
||||
});
|
||||
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
+4944
-2999
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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base"
|
||||
]
|
||||
}
|
||||
+71
-48
@@ -1,31 +1,25 @@
|
||||
import * as exec from '@actions/exec';
|
||||
import * as fs from 'fs';
|
||||
|
||||
const EMULATOR_BOOT_TIMEOUT_SECONDS = 600;
|
||||
|
||||
/**
|
||||
* Creates and launches a new AVD instance with the specified configurations.
|
||||
* Creates a new AVD instance with the specified configurations.
|
||||
*/
|
||||
export async function launchEmulator(
|
||||
apiLevel: number,
|
||||
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,
|
||||
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) {
|
||||
@@ -33,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';
|
||||
@@ -66,7 +85,7 @@ export async function launchEmulator(
|
||||
// start emulator
|
||||
console.log('Starting emulator.');
|
||||
|
||||
await exec.exec(`sh -c \\"${process.env.ANDROID_SDK_ROOT}/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')) {
|
||||
@@ -77,20 +96,20 @@ export async function launchEmulator(
|
||||
});
|
||||
|
||||
// wait for emulator to complete booting
|
||||
await waitForDevice();
|
||||
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::`);
|
||||
@@ -100,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 {
|
||||
@@ -111,18 +130,22 @@ 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(): Promise<void> {
|
||||
async function waitForDevice(port: number, emulatorBootTimeout: number): Promise<void> {
|
||||
let booted = false;
|
||||
let attempts = 0;
|
||||
const retryInterval = 2; // retry every 2 seconds
|
||||
const maxAttempts = EMULATOR_BOOT_TIMEOUT_SECONDS / 2;
|
||||
const maxAttempts = emulatorBootTimeout / 2;
|
||||
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
-14
@@ -1,21 +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 (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 {
|
||||
@@ -36,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'.`);
|
||||
|
||||
+33
-32
@@ -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 {
|
||||
@@ -40,15 +42,17 @@ async function run() {
|
||||
}
|
||||
|
||||
// API level of the platform and system image
|
||||
const apiLevelInput = core.getInput('api-level', { required: true });
|
||||
checkApiLevel(apiLevelInput);
|
||||
const apiLevel = Number(apiLevelInput);
|
||||
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 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
|
||||
@@ -90,6 +94,15 @@ async function run() {
|
||||
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);
|
||||
checkPort(port);
|
||||
console.log(`emulator port: ${port}`);
|
||||
|
||||
// emulator options
|
||||
const emulatorOptions = core.getInput('emulator-options').trim();
|
||||
console.log(`emulator options: ${emulatorOptions}`);
|
||||
@@ -174,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) {
|
||||
@@ -194,24 +210,7 @@ async function run() {
|
||||
}
|
||||
|
||||
// launch an emulator
|
||||
await launchEmulator(
|
||||
apiLevel,
|
||||
target,
|
||||
arch,
|
||||
profile,
|
||||
cores,
|
||||
ramSize,
|
||||
heapSize,
|
||||
sdcardPathOrSize,
|
||||
diskSize,
|
||||
avdName,
|
||||
forceAvdCreation,
|
||||
emulatorOptions,
|
||||
disableAnimations,
|
||||
disableSpellchecker,
|
||||
disableLinuxHardwareAcceleration,
|
||||
enableHardwareKeyboard
|
||||
);
|
||||
await launchEmulator(avdName, disableAnimations, disableLinuxHardwareAcceleration, disableSpellchecker, emulatorBootTimeout, emulatorOptions, enableHardwareKeyboard, port);
|
||||
|
||||
// execute the custom script
|
||||
try {
|
||||
@@ -222,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));
|
||||
}
|
||||
}
|
||||
|
||||
+35
-14
@@ -4,24 +4,32 @@ import * as io from '@actions/io';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import * as fs from 'fs';
|
||||
|
||||
const BUILD_TOOLS_VERSION = '33.0.0';
|
||||
const CMDLINE_TOOLS_URL_MAC = 'https://dl.google.com/android/repository/commandlinetools-mac-8512546_latest.zip';
|
||||
const CMDLINE_TOOLS_URL_LINUX = 'https://dl.google.com/android/repository/commandlinetools-linux-8512546_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: number, 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_SDK_ROOT} -R`);
|
||||
}
|
||||
|
||||
const cmdlineToolsPath = `${process.env.ANDROID_SDK_ROOT}/cmdline-tools`;
|
||||
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;
|
||||
@@ -31,9 +39,10 @@ export async function installAndroidSdk(apiLevel: number, target: string, arch:
|
||||
}
|
||||
|
||||
// add paths for commandline-tools and platform-tools
|
||||
core.addPath(`${cmdlineToolsPath}/latest:${cmdlineToolsPath}/latest/bin:${process.env.ANDROID_SDK_ROOT}/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
|
||||
@@ -41,7 +50,7 @@ export async function installAndroidSdk(apiLevel: number, target: string, arch:
|
||||
|
||||
console.log('Installing latest build tools, platform tools, and platform.');
|
||||
|
||||
await exec.exec(`sh -c \\"sdkmanager --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools 'platforms;android-${apiLevel}' > /dev/null"`);
|
||||
await 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"`);
|
||||
@@ -49,13 +58,25 @@ export async function installAndroidSdk(apiLevel: number, target: string, arch:
|
||||
if (emulatorBuild) {
|
||||
console.log(`Installing emulator build ${emulatorBuild}.`);
|
||||
// TODO find out the correct download URLs for all build ids
|
||||
const downloadUrlSuffix = Number(emulatorBuild.charAt(0)) > 6 ? `_x64-${emulatorBuild}` : `-${emulatorBuild}`;
|
||||
var downloadUrlSuffix: string;
|
||||
const majorBuildVersion = Number(emulatorBuild);
|
||||
if (majorBuildVersion >= 8000000) {
|
||||
if (isArm) {
|
||||
downloadUrlSuffix = `_aarch64-${emulatorBuild}`;
|
||||
} else {
|
||||
downloadUrlSuffix = `_x64-${emulatorBuild}`;
|
||||
}
|
||||
} else if (majorBuildVersion >= 7000000) {
|
||||
downloadUrlSuffix = `_x64-${emulatorBuild}`;
|
||||
} 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_SDK_ROOT}`);
|
||||
await exec.exec(`unzip -o -q emulator.zip -d ${process.env.ANDROID_HOME}`);
|
||||
await io.rmRF('emulator.zip');
|
||||
}
|
||||
console.log('Installing system images.');
|
||||
await exec.exec(`sh -c \\"sdkmanager --install 'system-images;android-${apiLevel};${target};${arch}' --channel=${channelId} > /dev/null"`);
|
||||
await exec.exec(`sh -c \\"sdkmanager --install 'system-images;android-${systemImageApiLevel};${target};${arch}' --channel=${channelId} > /dev/null"`);
|
||||
|
||||
if (ndkVersion) {
|
||||
console.log(`Installing NDK ${ndkVersion}.`);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.gradle
|
||||
build/
|
||||
target/
|
||||
out/
|
||||
local.properties
|
||||
.idea/
|
||||
*.iml
|
||||
*.DS_Store
|
||||
@@ -1,32 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion "33.0.0"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.example.testapp"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 33
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.testapp"
|
||||
compileSdk = 37
|
||||
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")
|
||||
}
|
||||
Vendored
-21
@@ -1,21 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.testapp">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.7.0'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.2.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -2,26 +2,6 @@ org.gradle.parallel=true
|
||||
org.gradle.configureondemand=true
|
||||
org.gradle.caching=true
|
||||
|
||||
# Enable Kotlin incremental compilation
|
||||
kotlin.incremental=true
|
||||
|
||||
# Enable parallel tasks execution for Kotlin Gradle plugin
|
||||
kotlin.parallel.tasks.in.project=true
|
||||
|
||||
# Kotlin code style
|
||||
kotlin.code.style=official
|
||||
|
||||
# Run kapt directly using Gradle workers
|
||||
kapt.use.worker.api=true
|
||||
|
||||
# Enable incremental annotation processor for KAPT
|
||||
kapt.incremental.apt=true
|
||||
|
||||
# Turn off AP discovery in compile path to enable compile avoidance
|
||||
kapt.include.compile.classpath=false
|
||||
|
||||
# Use R8 instead of ProGuard for code shrinking.
|
||||
android.enableR8.fullMode=true
|
||||
|
||||
# Enable AndroidX
|
||||
android.useAndroidX=true
|
||||
|
||||
BIN
Binary file not shown.
+5
-2
@@ -1,6 +1,9 @@
|
||||
#Tue Nov 26 18:34:05 AEDT 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
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
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||
|
||||
Vendored
+193
-117
@@ -1,78 +1,128 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# 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.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# 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 UN*X
|
||||
##
|
||||
#
|
||||
# gradlew start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh gradlew
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# 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/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
# This is normally unused
|
||||
# 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 -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"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -81,92 +131,118 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * 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" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
Vendored
+49
-51
@@ -1,84 +1,82 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@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=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
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:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
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
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
"%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 %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
@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
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
: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