Compare commits

...

57 Commits

Author SHA1 Message Date
Yang ed009f5318 Command-line tools 23.0. (#492) 2026-08-26 18:30:25 +10:00
Jordan Longstaff 04335bcf9e Upgrade GHA workflows (#491) 2026-08-23 00:06:05 +10:00
Jordan Longstaff 660ac26f5b Update SDK command-line tools to version 22.0 (#483) 2026-08-17 21:44:08 +10:00
Yang cad321644d Prepare for release 2.38.0. 2026-07-05 15:04:14 +10:00
Yang ed6254cde0 Revert "Add minorApiLevel for compile SDK. Test with API 37.0."
This reverts commit 8acdc1106b.
2026-06-16 21:33:52 +10:00
Yang 8acdc1106b Add minorApiLevel for compile SDK. Test with API 37.0. 2026-06-16 21:30:19 +10:00
TÖRÖK Attila a917366169 Build tools 37.0.0 (#475) 2026-06-16 10:56:06 +10:00
O01eg ae2c4d7c48 Add FreeOrion to the list (#470) 2026-05-05 09:03:31 +10:00
Yang 9644b1a1f9 Prepare for release 2.37.0. 2026-03-14 19:49:27 +11:00
Yang 4fe4b1ae37 Build build tools 36.0.0, SDK command-line tools 12.0. (#467) 2026-03-14 19:47:22 +11:00
Yang 6fe36e1748 Update to Node 24. (#466) 2026-03-14 19:10:07 +11:00
Yang 597aa7c21b Modernize test-fixture project. (#465) 2026-03-14 17:27:13 +11:00
Yang 38164a29c1 Prepare for release 2.36.0. 2026-03-11 23:15:24 +11:00
Michael MIGLIORE 969c9896d3 Add F3D in the list of projects (#462) 2026-02-26 22:44:03 +11:00
Mike Hardy 29b35ed135 fix: remove problematic / unneeded --abi option in avd creation (#456) 2026-02-06 17:13:54 +11:00
Yang 001613e856 Replace deprecated macos-13 runner with macos-15-intel. (#460) 2026-02-06 17:13:30 +11:00
Alex Styl 937ab7eebc Add Compose Unstyled to the list of users (#457) 2025-11-28 23:18:06 +11:00
Yang 016d4d097a Prepare for release 2.35.0. 2025-11-07 12:32:15 +11:00
Vojtech Novak f9bdb6d84e docs: update AVD profile description (#452) 2025-09-11 23:07:58 +10:00
Marijn Suijten b68ca169d6 README: Fix imbalanced backtick in Configurations table (#445) 2025-07-09 20:52:11 +10:00
Mike Hardy 66283c0319 fix: allow google_apis_ps16k as a valid target (#440)
* test: remove target validator test

these appear to have too much maintenance burden as they only ever
add more over time

* fix: allow google_apis_ps16k and google_apis_playstore_ps16k as valid targets

Fixes #403

Co-authored-by: Yang <reactivecircus@gmail.com>

* fix: remove target validation entirely, any valid sdkmanager target will work

no longer requires code changes here to access new targets

---------

Co-authored-by: Yang <reactivecircus@gmail.com>
2025-07-02 11:45:42 +10:00
munrocket f2bf410054 Fix pre-emulator-launch-script (#439) 2025-06-26 00:48:55 +10:00
devin-ai-integration[bot] 62e6348453 Optimize config.ini updates and efficiency improvements report (#436)
* Optimize config.ini updates by batching shell executions

- Reduce up to 5 separate shell executions to 1 for AVD configuration
- Improves performance by eliminating redundant process spawns
- Add comprehensive efficiency report documenting all identified improvements
- All existing tests pass, no functional changes

Co-Authored-By: Yang <reactivecircus@gmail.com>

* Add compiled JS files and remove standalone efficiency report

- Include built lib/emulator-manager.js with batched config optimization
- Remove EFFICIENCY_REPORT.md as requested
- Efficiency report content will be moved to PR description

Co-Authored-By: Yang <reactivecircus@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Yang <reactivecircus@gmail.com>
2025-06-15 03:00:24 +10:00
devin-ai-integration[bot] 450c4c9f73 Fix outdated information about larger runners billing (#437)
Remove incorrect claim that larger runners are free for public repos.
Current GitHub documentation states they are not free.

Fixes #435

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Yang <reactivecircus@gmail.com>
2025-06-15 02:59:42 +10:00
Yang 2548f9f3fc Prepare for release 2.34.0. 2025-03-29 01:48:44 +11:00
Yang 595a4563df Update README.md and CHANGELOG.md. 2025-03-29 01:33:05 +11:00
Timothy a173d65200 Support API levels for SDK extensions and add missing targets for automotive and desktop. (#428) 2025-03-29 01:04:02 +11:00
LoveSy 50d5b10c01 Add Baklava support (#424)
* Add `Baklava` support

* Fix typo of `ubuntu-latest` to fix CI
2025-01-24 22:46:33 +11:00
Yang c77bfe7c0b Prepare for release 2.33.0. 2024-10-12 17:42:24 +11:00
Yang ead704cc5a Fix avd path on ubuntu-24.04 - attempt 2 (#415) 2024-10-12 17:26:36 +11:00
Yang e800ffffe6 Revert creating avd directory. (#414) 2024-10-11 13:55:44 +11:00
Yang 8c07710601 Fix SDK path issues with ubuntu-24.04 (#409) 2024-10-11 11:55:47 +11:00
Yang 3a18f50216 SDK build tools 35.0.0, command-line tools 16.0. (#408) 2024-10-07 00:50:35 +11:00
Justin Brooks 1b985f4c7a Avoid changing the owner of unnecessary files (#406)
* Avoid changing the owner of unnecessary files

Source files shouldn't matter for the purposes of this tool.
% ls -R $ANDROID_HOME/sources/android-34 | wc -l
   17750

* Remove non-existant directory

* Avoid another unnecessary chown

* Push js
2024-10-05 12:53:35 +10:00
Yang a3dcdb348b Update npm packages. (#401) 2024-07-09 16:23:18 +10:00
Yang 81f860b51a Prepare for release 2.32.0. 2024-07-09 12:53:57 +10:00
Yang e93b947104 Add missing compiled js files from #383. 2024-07-09 12:51:00 +10:00
Kamil Bąk 4b0628e9f8 Support customizing emulator port (#383)
* Add port parameter

* Fix a typo in test description

* Fix avd not being started with correct port

* Fix wrong port being used to kill an emulator if there was an exception
2024-07-02 22:42:44 +10:00
Yang ca77a1081f Prepare for release 2.31.0. 2024-05-25 00:00:37 +10:00
Josh Lesch 1fef06a960 Install platforms for the specified api level (#384)
* Install platforms for the specified api level

* Fix typo
2024-03-28 09:40:07 +11:00
LoveSy d8da5b6410 Add api level VanillaIceCream support (#378) 2024-02-18 10:14:24 +11:00
Róbert Papp a0c0e7216b Bump Gradle wrapper validation from v1 to v2 to use Node 20 (#374) 2024-01-30 08:34:22 +11:00
Róbert Papp 9d5d6236f4 Migrate to Node 20 on CI via GitHub Actions major upgrades (#372)
* Update first party actions to Node 20 majors.

* Update Gradle cache actions to Node 20 majors.
2024-01-27 23:11:47 +11:00
Yang bc2cd2aaea Prepare for release 2.30.1. 2024-01-26 23:26:22 +11:00
Róbert Papp 61a61f94a9 Run the action on Node 20 (#371) 2024-01-26 23:24:19 +11:00
Yang 426fbd2820 Prepare for release 2.30.0. 2024-01-25 23:14:27 +11:00
Yang e1bd48ca1d Node 20. (#369) 2024-01-25 23:10:17 +11:00
utzcoz 1e33853aad Add another user: robolectric/robolectric (#352)
Signed-off-by: utzcoz <utzcoz@outlook.com>
2024-01-20 16:43:05 +11:00
Yang c479bb6c0f Promote using ubuntu runner with KVM. (#366) 2024-01-19 17:38:35 +11:00
Yang 677db68893 Prepare for release 2.29.0. 2023-12-06 18:32:27 +11:00
Yang f9c2835191 SDK command-line tools 11.0. (#356)
* SDK command-line tools 11.0.

* Temporarily disable API 16 test.
2023-12-06 18:25:39 +11:00
Yang 1036f55734 Update npm packages. (#355) 2023-12-06 12:46:32 +11:00
Yang 5a481d20f1 Check in updated *.js file. 2023-12-06 11:38:49 +11:00
Yang f72f505164 Update test fixture dependencies. (#354) 2023-12-06 11:37:21 +11:00
Michael Bailey fabc29122a Upgrade to latest npm dependencies (#347) 2023-08-24 19:31:32 +10:00
olegdolgikh 2cfd145246 Fixed emulator download URL (#343)
Previous code worked in a wrong way. 
1. Getting the first char of a build number is mistake because now numbers start with 1, but whole number higher than the number which starts from 7.
2. URL was incorrect for arm mac machines. It needed to add `aarch64` suffix
2023-07-04 21:39:36 +10:00
Ricki Hirner e1bdfefaa7 Update README.md (Who is using…) (#335)
Add davx5-ose to Who is using…
2023-04-25 18:33:07 +10:00
34 changed files with 5909 additions and 3802 deletions
+2 -2
View File
@@ -9,5 +9,5 @@ jobs:
validate-typings: validate-typings:
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v7
- uses: krzema12/github-actions-typing@v0 - uses: krzema12/github-actions-typing@v2
+37 -33
View File
@@ -18,41 +18,36 @@ jobs:
timeout-minutes: 15 timeout-minutes: 15
strategy: strategy:
matrix: 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: include:
- os: macos-latest - os: ubuntu-22.04
api-level: 23
target: default
arch: x86
- os: ubuntu-24.04
api-level: 24 api-level: 24
target: playstore target: playstore
arch: x86 arch: x86
- os: macos-latest - os: macos-15-intel
api-level: 30
target: aosp_atd
arch: x86
- os: macos-11
api-level: 31 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 target: google_apis
arch: x86_64 arch: x86_64
- os: ubuntu-latest
api-level: 34-ext10
target: android-automotive
arch: x86_64
system-image-api-level: 34-ext9
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v3 uses: actions/checkout@v7
- name: validate gradle wrapper
uses: gradle/wrapper-validation-action@v1
- name: build, test and lint - name: build, test and lint
run: | run: |
@@ -61,25 +56,32 @@ jobs:
npm run lint npm run lint
npm test npm test
- uses: actions/setup-java@v3 - uses: actions/setup-java@v5
with: with:
distribution: 'zulu' distribution: 'zulu'
java-version: 19 java-version: 25
- uses: actions/cache@v3 - uses: actions/cache@v6
id: avd-cache id: avd-cache
with: with:
path: | path: |
~/.android/avd/* ~/.android/avd/*
~/.android/adb* ~/.android/adb*
~/.android/debug.keystore ~/.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 - name: assemble tests
uses: gradle/gradle-build-action@v2 working-directory: test-fixture
with: run: ./gradlew assembleAndroidTest
build-root-directory: test-fixture
arguments: 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 - name: run emulator to generate snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true' if: steps.avd-cache.outputs.cache-hit != 'true'
@@ -88,6 +90,7 @@ jobs:
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }} target: ${{ matrix.target }}
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
system-image-api-level: ${{ matrix.system-image-api-level }}
profile: Galaxy Nexus profile: Galaxy Nexus
cores: 2 cores: 2
sdcard-path-or-size: 100M sdcard-path-or-size: 100M
@@ -105,6 +108,7 @@ jobs:
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }} target: ${{ matrix.target }}
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
system-image-api-level: ${{ matrix.system-image-api-level }}
profile: Galaxy Nexus profile: Galaxy Nexus
cores: 2 cores: 2
ram-size: 2048M ram-size: 2048M
+19 -14
View File
@@ -5,18 +5,20 @@ on:
os: os:
description: 'OS' description: 'OS'
required: true required: true
default: 'macos-latest' default: 'ubuntu-latest'
api-level: 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 required: true
default: '30' default: '34'
system-image-api-level:
description: 'API level of the system image - e.g. 34-ext10, 35-ext15'
target: 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 required: true
default: 'default' default: 'default'
arch: arch:
description: 'CPU architecture of the system image - x86, x86_64 or arm64-v8a' description: 'CPU architecture of the system image - x86, x86_64 or arm64-v8a'
default: 'x86' default: 'x86_64'
emulator-options: emulator-options:
description: 'command-line options used when launching the emulator' description: 'command-line options used when launching the emulator'
default: '-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim' default: '-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim'
@@ -39,10 +41,7 @@ jobs:
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v3 uses: actions/checkout@v7
- name: validate gradle wrapper
uses: gradle/wrapper-validation-action@v1
- name: build, test and lint - name: build, test and lint
run: | run: |
@@ -51,14 +50,19 @@ jobs:
npm run lint npm run lint
npm test npm test
- uses: actions/setup-java@v3 - uses: actions/setup-java@v5
with: with:
distribution: 'zulu' distribution: 'zulu'
java-version: 19 java-version: 25
- uses: gradle/gradle-build-action@v2 - uses: gradle/actions/setup-gradle@v6
with:
gradle-home-cache-cleanup: true - 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 - name: run action
uses: ./ uses: ./
@@ -66,6 +70,7 @@ jobs:
api-level: ${{ github.event.inputs.api-level }} api-level: ${{ github.event.inputs.api-level }}
target: ${{ github.event.inputs.target }} target: ${{ github.event.inputs.target }}
arch: ${{ github.event.inputs.arch }} arch: ${{ github.event.inputs.arch }}
system-image-api-level: ${{ github.event.inputs.system-image-api-level }}
profile: Galaxy Nexus profile: Galaxy Nexus
emulator-options: ${{ github.event.inputs.emulator-options }} emulator-options: ${{ github.event.inputs.emulator-options }}
emulator-build: ${{ github.event.inputs.emulator-build }} emulator-build: ${{ github.event.inputs.emulator-build }}
+1
View File
@@ -0,0 +1 @@
24
+63
View File
@@ -1,5 +1,68 @@
# Change Log # 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 ## 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) * Add `emulator-boot-timeout` to support configuring maximum time waiting for emulator boot. - [#326](https://github.com/ReactiveCircus/android-emulator-runner/pull/326)
+86 -23
View File
@@ -4,12 +4,24 @@
<a href="https://github.com/ReactiveCircus/android-emulator-runner"><img alt="GitHub Actions status" src="https://github.com/ReactiveCircus/android-emulator-runner/workflows/Main%20workflow/badge.svg"></a> <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> </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. The old ARM-based emulators were slow and are no longer supported by Google. The modern Intel Atom (x86 and x86_64) emulators can be fast, but rely on two forms of hardware acceleration to reach their peak potential: [Graphics Acceleration](https://developer.android.com/studio/run/emulator-acceleration#accel-graphics), e.g. `emulator -gpu host` and [Virtual Machine(VM) Acceleration](https://developer.android.com/studio/run/emulator-acceleration#accel-vm), e.g. `emulator -accel on`. **Note:** GPU and VM Acceleration are two different and non-mutually exclusive forms of Hardware Acceleration.
This presents a challenge when running emulators on CI especially when running emulators within a docker container, because **Nested Virtualization** must be supported by the host VM which isn't the case for most cloud-based CI providers due to infrastructural limits. If you want to learn more about Emulators on CI, here's an article [Yang](https://github.com/ychescale9) wrote: [Running Android Instrumented Tests on CI](https://dev.to/ychescale9/running-android-emulators-on-ci-from-bitrise-io-to-github-actions-3j76). This presents a challenge when running emulators on CI especially when running emulators within a docker container, because **Nested Virtualization** must be supported by the host VM which isn't the case for most cloud-based CI providers due to infrastructural limits. If you want to learn more about Emulators on CI, here's an article [Yang](https://github.com/ychescale9) wrote: [Running Android Instrumented Tests on CI](https://dev.to/ychescale9/running-android-emulators-on-ci-from-bitrise-io-to-github-actions-3j76).
## Running hardware accelerated emulators on Linux runners
GitHub's [larger Linux runners support running hardware accelerated emulators](https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/). It is now recommended to use the **Ubuntu** (`ubuntu-latest`) runners which are 2-3 times faster than the **macOS** ones which are also a lot more expensive. Remember to enable KVM in your workflow before running this action:
```
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
```
## A note on VM Acceleration and why we don't need HAXM anymore ## 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.** According to [this documentation](https://developer.android.com/studio/run/emulator-acceleration#vm-mac), "on Mac OS X v10.10 Yosemite and higher, the Android Emulator uses the built-in [Hypervisor.Framework](https://developer.apple.com/documentation/hypervisor) by default, and falls back to using Intel HAXM if Hypervisor.Framework fails to initialize." This means that **HAXM is only needed to achieve VM Acceleration if this default Hypervisor is not available on macOS machines.**
@@ -34,10 +46,16 @@ A workflow that uses **android-emulator-runner** to run your instrumented tests
```yml ```yml
jobs: jobs:
test: test:
runs-on: macos-latest runs-on: ubuntu-latest
steps: steps:
- name: checkout - 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 - name: run tests
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2
@@ -51,14 +69,20 @@ We can also leverage GitHub Actions's build matrix to test across multiple confi
```yml ```yml
jobs: jobs:
test: test:
runs-on: macos-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
api-level: [21, 23, 29] api-level: [21, 23, 29]
target: [default, google_apis] target: [default, google_apis]
steps: steps:
- name: checkout - 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 - name: run tests
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2
@@ -66,7 +90,7 @@ jobs:
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }} target: ${{ matrix.target }}
arch: x86_64 arch: x86_64
profile: Nexus 6 profile: pixel_7_pro
script: ./gradlew connectedCheck script: ./gradlew connectedCheck
``` ```
@@ -75,10 +99,16 @@ If you need specific versions of **NDK** and **CMake** installed:
```yml ```yml
jobs: jobs:
test: test:
runs-on: macos-latest runs-on: ubuntu-latest
steps: steps:
- name: checkout - 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 - name: run tests
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2
@@ -89,29 +119,60 @@ jobs:
script: ./gradlew connectedCheck 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: 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) 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@v3` step for caching the `avd` 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` 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` 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 ```yml
jobs: jobs:
test: test:
runs-on: macos-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
api-level: [21, 23, 29] api-level: [21, 23, 29]
steps: steps:
- name: checkout - 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 - name: Gradle cache
uses: gradle/gradle-build-action@v2 uses: gradle/actions/setup-gradle@v5
- name: AVD cache - name: AVD cache
uses: actions/cache@v3 uses: actions/cache@v5
id: avd-cache id: avd-cache
with: with:
path: | path: |
@@ -143,10 +204,11 @@ jobs:
| **Input** | **Required** | **Default** | **Description** | | **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**. | | `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**. |
| `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`. | | `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). | | `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). | | `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` | | `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` | | `heap-size` | Optional | N/A | Heap size to use for this AVD, in KB or MB, denoted with K or M. - e.g. `512M` |
@@ -155,6 +217,7 @@ jobs:
| `avd-name` | Optional | `test` | Custom AVD name used for creating the Android Virtual Device. | | `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`. | | `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-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`. | | `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-animations` | Optional | `true` | Whether to disable animations - `true` or `false`. |
| `disable-spellchecker` | Optional | `false` | Whether to disable spellchecker - `true` or `false`. | | `disable-spellchecker` | Optional | `false` | Whether to disable spellchecker - `true` or `false`. |
@@ -170,12 +233,6 @@ jobs:
Default `emulator-options`: `-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim`. 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? ## Who is using Android Emulator Runner?
These are some of the open-source projects using (or used) **Android Emulator Runner**: These are some of the open-source projects using (or used) **Android Emulator Runner**:
@@ -210,5 +267,11 @@ These are some of the open-source projects using (or used) **Android Emulator Ru
- [tinylog-org/tinylog](https://github.com/tinylog-org/tinylog/blob/v3.0/.github/workflows/build.yaml) - [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) - [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) - [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. If you are using **Android Emulator Runner** and want your project included in the list, please feel free to open a pull request.
+28 -99
View File
@@ -1,103 +1,5 @@
import * as validator from '../src/input-validator'; import * as validator from '../src/input-validator';
import { MAX_PORT, MIN_PORT } from '../src/input-validator';
describe('api-level validator tests', () => {
it('Throws if api-level is not a number', () => {
const func = () => {
validator.checkApiLevel('api');
};
expect(func).toThrowError(`Unexpected API level: 'api'.`);
});
it('Throws if api-level is not an integer', () => {
const func = () => {
validator.checkApiLevel('29.1');
};
expect(func).toThrowError(`Unexpected API level: '29.1'.`);
});
it('Throws if api-level is lower than min API supported', () => {
const func = () => {
validator.checkApiLevel('14');
};
expect(func).toThrowError(`Minimum API level supported is ${validator.MIN_API_LEVEL}.`);
});
it('Validates successfully with valid api-level', () => {
const func1 = () => {
validator.checkApiLevel('15');
};
expect(func1).not.toThrow();
const func2 = () => {
validator.checkApiLevel('29');
};
expect(func2).not.toThrow();
const func3 = () => {
validator.checkApiLevel('UpsideDownCake-ext5');
};
expect(func3).not.toThrow();
const func4 = () => {
validator.checkApiLevel('TiramisuPrivacySandbox');
};
expect(func4).not.toThrow();
});
});
describe('target validator tests', () => {
it('Throws if target is unknown', () => {
const func = () => {
validator.checkTarget('some-target');
};
expect(func).toThrowError(`Value for input.target 'some-target' is unknown. Supported options: ${validator.VALID_TARGETS}`);
});
it('Validates successfully with valid target', () => {
const func1 = () => {
validator.checkTarget('default');
};
expect(func1).not.toThrow();
const func2 = () => {
validator.checkTarget('google_apis');
};
expect(func2).not.toThrow();
const func3 = () => {
validator.checkTarget('aosp_atd');
};
expect(func3).not.toThrow();
const func4 = () => {
validator.checkTarget('google_atd');
};
expect(func4).not.toThrow();
const func5 = () => {
validator.checkTarget('google_apis_playstore');
};
expect(func5).not.toThrow();
const func6 = () => {
validator.checkTarget('android-wear');
};
expect(func6).not.toThrow();
const func7 = () => {
validator.checkTarget('android-wear-cn');
};
expect(func7).not.toThrow();
const func8 = () => {
validator.checkTarget('android-tv');
};
expect(func8).not.toThrow();
const func9 = () => {
validator.checkTarget('google-tv');
};
expect(func9).not.toThrow();
});
});
describe('arch validator tests', () => { describe('arch validator tests', () => {
it('Throws if arch is unknown', () => { it('Throws if arch is unknown', () => {
@@ -172,6 +74,33 @@ describe('force-avd-creation validator tests', () => {
}); });
}); });
describe('emulator-port validator tests', () => {
it('Validates if emulator-port is even and in range', () => {
const func = () => {
validator.checkPort(5554);
};
expect(func).not.toThrow();
});
it('Throws if emulator-port is lower than MIN_PORT', () => {
const func = () => {
validator.checkPort(MIN_PORT - 2);
};
expect(func).toThrow();
});
it('Throws if emulator-port is higher than MAX_PORT', () => {
const func = () => {
validator.checkPort(MAX_PORT + 2);
};
expect(func).toThrow();
});
it('Throws if emulator-port is odd', () => {
const func = () => {
validator.checkPort(5555);
};
expect(func).toThrow();
});
});
describe('disable-animations validator tests', () => { describe('disable-animations validator tests', () => {
it('Throws if disable-animations is not a boolean', () => { it('Throws if disable-animations is not a boolean', () => {
const func = () => { const func = () => {
+8 -1
View File
@@ -1,6 +1,8 @@
inputs: inputs:
api-level: api-level:
type: integer type: string
system-image-api-level:
type: string
target: target:
type: enum type: enum
allowed-values: allowed-values:
@@ -13,6 +15,9 @@ inputs:
- android-wear-cn - android-wear-cn
- android-tv - android-tv
- google-tv - google-tv
- android-automotive
- android-automotive-playstore
- android-desktop
arch: arch:
type: enum type: enum
allowed-values: allowed-values:
@@ -37,6 +42,8 @@ inputs:
type: boolean type: boolean
emulator-boot-timeout: emulator-boot-timeout:
type: integer type: integer
emulator-port:
type: integer
emulator-options: emulator-options:
type: string type: string
disable-animations: disable-animations:
+10 -4
View File
@@ -1,15 +1,18 @@
name: 'Android Emulator Runner' 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' author: 'Reactive Circus'
branding: branding:
icon: 'smartphone' icon: 'smartphone'
color: 'green' color: 'green'
inputs: inputs:
api-level: 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 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: 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' default: 'default'
arch: arch:
description: 'CPU architecture of the system image - x86, x86_64 or arm64-v8a' description: 'CPU architecture of the system image - x86, x86_64 or arm64-v8a'
@@ -36,6 +39,9 @@ inputs:
emulator-boot-timeout: 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' description: 'Emulator boot timeout in seconds. If it takes longer to boot, the action would fail - e.g. `300` for 5 minutes'
default: '600' default: '600'
emulator-port:
description: 'Port to run emulator on, allows to run multiple emulators on the same physical machine'
default: '5554'
emulator-options: emulator-options:
description: 'command-line options used when launching the emulator - e.g. `-no-window -no-snapshot -camera-back emulated`' 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' default: '-no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim'
@@ -68,5 +74,5 @@ inputs:
pre-emulator-launch-script: pre-emulator-launch-script:
description: 'custom script to run after creating the AVD and before launching the emulator - e.g. `./adjust-emulator-configs.sh`' description: 'custom script to run after creating the AVD and before launching the emulator - e.g. `./adjust-emulator-configs.sh`'
runs: runs:
using: 'node16' using: 'node24'
main: 'lib/main.js' main: 'lib/main.js'
+1 -2
View File
@@ -1,6 +1,6 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.getChannelId = void 0; exports.getChannelId = getChannelId;
function getChannelId(channelName) { function getChannelId(channelName) {
if (channelName === 'stable') { if (channelName === 'stable') {
return 0; return 0;
@@ -18,4 +18,3 @@ function getChannelId(channelName) {
throw new Error(`Unexpected channel name: '${channelName}'.`); throw new Error(`Unexpected channel name: '${channelName}'.`);
} }
} }
exports.getChannelId = getChannelId;
+58 -42
View File
@@ -15,56 +15,76 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
}) : function(o, v) { }) : function(o, v) {
o["default"] = v; o["default"] = v;
}); });
var __importStar = (this && this.__importStar) || function (mod) { 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; if (mod && mod.__esModule) return mod;
var result = {}; var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 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); __setModuleDefault(result, mod);
return result; return result;
}; };
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { })();
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true }); 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 exec = __importStar(require("@actions/exec"));
const fs = __importStar(require("fs")); const fs = __importStar(require("fs"));
/** /**
* Creates and launches a new AVD instance with the specified configurations. * Creates a new AVD instance with the specified configurations.
*/ */
function launchEmulator(apiLevel, target, arch, profile, cores, ramSize, heapSize, sdcardPathOrSize, diskSize, avdName, forceAvdCreation, emulatorBootTimeout, emulatorOptions, disableAnimations, disableSpellChecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard) { async function createAvd(arch, avdName, cores, diskSize, enableHardwareKeyboard, forceAvdCreation, heapSize, profile, ramSize, sdcardPathOrSize, systemImageApiLevel, target) {
return __awaiter(this, void 0, void 0, function* () {
try { 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 // create a new AVD if AVD directory does not already exist or forceAvdCreation is true
const avdPath = `${process.env.ANDROID_AVD_HOME}/${avdName}.avd`; const avdPath = `${process.env.ANDROID_AVD_HOME}/${avdName}.avd`;
if (!fs.existsSync(avdPath) || forceAvdCreation) { if (!fs.existsSync(avdPath) || forceAvdCreation) {
const profileOption = profile.trim() !== '' ? `--device '${profile}'` : ''; const profileOption = profile.trim() !== '' ? `--device '${profile}'` : '';
const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : ''; const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : '';
console.log(`Creating AVD.`); 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}"`); 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) { 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) { 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) { 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) { 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) { 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}`);
} }
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::`);
}
}
/**
* 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 // turn off hardware acceleration on Linux
if (process.platform === 'linux' && disableLinuxHardwareAcceleration) { if (process.platform === 'linux' && disableLinuxHardwareAcceleration) {
console.log('Disabling Linux hardware acceleration.'); console.log('Disabling Linux hardware acceleration.');
@@ -72,7 +92,7 @@ function launchEmulator(apiLevel, target, arch, profile, cores, ramSize, heapSiz
} }
// start emulator // start emulator
console.log('Starting emulator.'); console.log('Starting emulator.');
yield exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -avd "${avdName}" ${emulatorOptions} &"`, [], { await exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -port ${port} -avd "${avdName}" ${emulatorOptions} &"`, [], {
listeners: { listeners: {
stderr: (data) => { stderr: (data) => {
if (data.toString().includes('invalid command-line parameter')) { if (data.toString().includes('invalid command-line parameter')) {
@@ -82,35 +102,32 @@ function launchEmulator(apiLevel, target, arch, profile, cores, ramSize, heapSiz
}, },
}); });
// wait for emulator to complete booting // wait for emulator to complete booting
yield waitForDevice(emulatorBootTimeout); await waitForDevice(port, emulatorBootTimeout);
yield exec.exec(`adb shell input keyevent 82`); await adb(port, `shell input keyevent 82`);
if (disableAnimations) { if (disableAnimations) {
console.log('Disabling animations.'); console.log('Disabling animations.');
yield exec.exec(`adb shell settings put global window_animation_scale 0.0`); await adb(port, `shell settings put global window_animation_scale 0.0`);
yield exec.exec(`adb shell settings put global transition_animation_scale 0.0`); await adb(port, `shell settings put global transition_animation_scale 0.0`);
yield exec.exec(`adb shell settings put global animator_duration_scale 0.0`); await adb(port, `shell settings put global animator_duration_scale 0.0`);
} }
if (disableSpellChecker) { if (disableSpellChecker) {
yield exec.exec(`adb shell settings put secure spell_checker_enabled 0`); await adb(port, `shell settings put secure spell_checker_enabled 0`);
} }
if (enableHardwareKeyboard) { if (enableHardwareKeyboard) {
yield 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 { finally {
console.log(`::endgroup::`); console.log(`::endgroup::`);
} }
});
} }
exports.launchEmulator = launchEmulator;
/** /**
* Kills the running emulator on the default port. * Kills the running emulator on the default port.
*/ */
function killEmulator() { async function killEmulator(port) {
return __awaiter(this, void 0, void 0, function* () {
try { try {
console.log(`::group::Terminate Emulator`); console.log(`::group::Terminate Emulator`);
yield exec.exec(`adb -s emulator-5554 emu kill`); await adb(port, `emu kill`);
} }
catch (error) { catch (error) {
console.log(error instanceof Error ? error.message : error); console.log(error instanceof Error ? error.message : error);
@@ -118,14 +135,14 @@ function killEmulator() {
finally { finally {
console.log(`::endgroup::`); console.log(`::endgroup::`);
} }
});
} }
exports.killEmulator = killEmulator; async function adb(port, command) {
return await exec.exec(`adb -s emulator-${port} ${command}`);
}
/** /**
* Wait for emulator to boot. * Wait for emulator to boot.
*/ */
function waitForDevice(emulatorBootTimeout) { async function waitForDevice(port, emulatorBootTimeout) {
return __awaiter(this, void 0, void 0, function* () {
let booted = false; let booted = false;
let attempts = 0; let attempts = 0;
const retryInterval = 2; // retry every 2 seconds const retryInterval = 2; // retry every 2 seconds
@@ -133,7 +150,7 @@ function waitForDevice(emulatorBootTimeout) {
while (!booted) { while (!booted) {
try { try {
let result = ''; let result = '';
yield exec.exec(`adb shell getprop sys.boot_completed`, [], { await exec.exec(`adb -s emulator-${port} shell getprop sys.boot_completed`, [], {
listeners: { listeners: {
stdout: (data) => { stdout: (data) => {
result += data.toString(); result += data.toString();
@@ -150,14 +167,13 @@ function waitForDevice(emulatorBootTimeout) {
console.warn(error instanceof Error ? error.message : error); console.warn(error instanceof Error ? error.message : error);
} }
if (attempts < maxAttempts) { if (attempts < maxAttempts) {
yield delay(retryInterval * 1000); await delay(retryInterval * 1000);
} }
else { else {
throw new Error(`Timeout waiting for emulator to boot.`); throw new Error(`Timeout waiting for emulator to boot.`);
} }
attempts++; attempts++;
} }
});
} }
function delay(ms) { function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
+30 -27
View File
@@ -1,75 +1,79 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); 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.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_ARCHS = ['x86', 'x86_64', 'arm64-v8a'];
exports.VALID_CHANNELS = ['stable', 'beta', 'dev', 'canary']; exports.VALID_CHANNELS = ['stable', 'beta', 'dev', 'canary'];
function checkApiLevel(apiLevel) { exports.MIN_PORT = 5554;
if (apiLevel.startsWith('UpsideDownCake') || apiLevel === 'TiramisuPrivacySandbox') exports.MAX_PORT = 5584;
return; function playstoreTargetSubstitution(target) {
if (isNaN(Number(apiLevel)) || !Number.isInteger(Number(apiLevel))) { // "playstore" is an allowed shorthand for "google_apis_playstore" images
throw new Error(`Unexpected API level: '${apiLevel}'.`); // 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;
} }
if (Number(apiLevel) < exports.MIN_API_LEVEL) {
throw new Error(`Minimum API level supported is ${exports.MIN_API_LEVEL}.`);
}
}
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) { function checkArch(arch) {
if (!exports.VALID_ARCHS.includes(arch)) { if (!exports.VALID_ARCHS.includes(arch)) {
throw new Error(`Value for input.arch '${arch}' is unknown. Supported options: ${exports.VALID_ARCHS}.`); throw new Error(`Value for input.arch '${arch}' is unknown. Supported options: ${exports.VALID_ARCHS}.`);
} }
} }
exports.checkArch = checkArch;
function checkChannel(channel) { function checkChannel(channel) {
if (!exports.VALID_CHANNELS.includes(channel)) { if (!exports.VALID_CHANNELS.includes(channel)) {
throw new Error(`Value for input.channel '${channel}' is unknown. Supported options: ${exports.VALID_CHANNELS}.`); throw new Error(`Value for input.channel '${channel}' is unknown. Supported options: ${exports.VALID_CHANNELS}.`);
} }
} }
exports.checkChannel = checkChannel;
function checkForceAvdCreation(forceAvdCreation) { function checkForceAvdCreation(forceAvdCreation) {
if (!isValidBoolean(forceAvdCreation)) { if (!isValidBoolean(forceAvdCreation)) {
throw new Error(`Input for input.force-avd-creation should be either 'true' or 'false'.`); 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) { function checkDisableAnimations(disableAnimations) {
if (!isValidBoolean(disableAnimations)) { if (!isValidBoolean(disableAnimations)) {
throw new Error(`Input for input.disable-animations should be either 'true' or 'false'.`); throw new Error(`Input for input.disable-animations should be either 'true' or 'false'.`);
} }
} }
exports.checkDisableAnimations = checkDisableAnimations;
function checkDisableSpellchecker(disableSpellchecker) { function checkDisableSpellchecker(disableSpellchecker) {
if (!isValidBoolean(disableSpellchecker)) { if (!isValidBoolean(disableSpellchecker)) {
throw new Error(`Input for input.disable-spellchecker should be either 'true' or 'false'.`); throw new Error(`Input for input.disable-spellchecker should be either 'true' or 'false'.`);
} }
} }
exports.checkDisableSpellchecker = checkDisableSpellchecker;
function checkDisableLinuxHardwareAcceleration(disableLinuxHardwareAcceleration) { function checkDisableLinuxHardwareAcceleration(disableLinuxHardwareAcceleration) {
if (!(isValidBoolean(disableLinuxHardwareAcceleration) || disableLinuxHardwareAcceleration === 'auto')) { if (!(isValidBoolean(disableLinuxHardwareAcceleration) || disableLinuxHardwareAcceleration === 'auto')) {
throw new Error(`Input for input.disable-linux-hw-accel should be either 'true' or 'false' or '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) { function checkEnableHardwareKeyboard(enableHardwareKeyboard) {
if (!isValidBoolean(enableHardwareKeyboard)) { if (!isValidBoolean(enableHardwareKeyboard)) {
throw new Error(`Input for input.enable-hw-keyboard should be either 'true' or 'false'.`); throw new Error(`Input for input.enable-hw-keyboard should be either 'true' or 'false'.`);
} }
} }
exports.checkEnableHardwareKeyboard = checkEnableHardwareKeyboard;
function checkEmulatorBuild(emulatorBuild) { function checkEmulatorBuild(emulatorBuild) {
if (isNaN(Number(emulatorBuild)) || !Number.isInteger(Number(emulatorBuild))) { if (isNaN(Number(emulatorBuild)) || !Number.isInteger(Number(emulatorBuild))) {
throw new Error(`Unexpected emulator build: '${emulatorBuild}'.`); throw new Error(`Unexpected emulator build: '${emulatorBuild}'.`);
} }
} }
exports.checkEmulatorBuild = checkEmulatorBuild;
function isValidBoolean(value) { function isValidBoolean(value) {
return value === 'true' || value === 'false'; return value === 'true' || value === 'false';
} }
@@ -89,4 +93,3 @@ function checkDiskSize(diskSize) {
} }
} }
} }
exports.checkDiskSize = checkDiskSize;
+39 -29
View File
@@ -15,22 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
}) : function(o, v) { }) : function(o, v) {
o["default"] = v; o["default"] = v;
}); });
var __importStar = (this && this.__importStar) || function (mod) { 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; if (mod && mod.__esModule) return mod;
var result = {}; var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 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); __setModuleDefault(result, mod);
return result; return result;
}; };
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { })();
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core")); const core = __importStar(require("@actions/core"));
const sdk_installer_1 = require("./sdk-installer"); const sdk_installer_1 = require("./sdk-installer");
@@ -40,8 +41,8 @@ const exec = __importStar(require("@actions/exec"));
const script_parser_1 = require("./script-parser"); const script_parser_1 = require("./script-parser");
const channel_id_mapper_1 = require("./channel-id-mapper"); const channel_id_mapper_1 = require("./channel-id-mapper");
const fs_1 = require("fs"); const fs_1 = require("fs");
function run() { async function run() {
return __awaiter(this, void 0, void 0, function* () { let port = input_validator_1.MIN_PORT;
try { try {
console.log(`::group::Configure emulator`); console.log(`::group::Configure emulator`);
let linuxSupportKVM = false; let linuxSupportKVM = false;
@@ -52,7 +53,7 @@ function run() {
(0, fs_1.accessSync)('/dev/kvm', fs_1.constants.R_OK | fs_1.constants.W_OK); (0, fs_1.accessSync)('/dev/kvm', fs_1.constants.R_OK | fs_1.constants.W_OK);
linuxSupportKVM = true; linuxSupportKVM = true;
} }
catch (_a) { 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.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.`);
} }
} }
@@ -62,12 +63,14 @@ function run() {
} }
// API level of the platform and system image // API level of the platform and system image
const apiLevel = core.getInput('api-level', { required: true }); const apiLevel = core.getInput('api-level', { required: true });
(0, input_validator_1.checkApiLevel)(apiLevel);
console.log(`API level: ${apiLevel}`); console.log(`API level: ${apiLevel}`);
let systemImageApiLevel = core.getInput('system-image-api-level');
if (!systemImageApiLevel) {
systemImageApiLevel = apiLevel;
}
console.log(`System image API level: ${systemImageApiLevel}`);
// target of the system image // target of the system image
const targetInput = core.getInput('target'); const target = (0, input_validator_1.playstoreTargetSubstitution)(core.getInput('target'));
const target = targetInput == 'playstore' ? 'google_apis_playstore' : targetInput;
(0, input_validator_1.checkTarget)(target);
console.log(`target: ${target}`); console.log(`target: ${target}`);
// CPU architecture of the system image // CPU architecture of the system image
const arch = core.getInput('arch'); const arch = core.getInput('arch');
@@ -102,6 +105,10 @@ function run() {
// Emulator boot timeout seconds // Emulator boot timeout seconds
const emulatorBootTimeout = parseInt(core.getInput('emulator-boot-timeout'), 10); const emulatorBootTimeout = parseInt(core.getInput('emulator-boot-timeout'), 10);
console.log(`Emulator boot timeout: ${emulatorBootTimeout}`); 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 // emulator options
const emulatorOptions = core.getInput('emulator-options').trim(); const emulatorOptions = core.getInput('emulator-options').trim();
console.log(`emulator options: ${emulatorOptions}`); console.log(`emulator options: ${emulatorOptions}`);
@@ -162,19 +169,21 @@ function run() {
const scriptInput = core.getInput('script', { required: true }); const scriptInput = core.getInput('script', { required: true });
const scripts = (0, script_parser_1.parseScript)(scriptInput); const scripts = (0, script_parser_1.parseScript)(scriptInput);
console.log(`Script:`); console.log(`Script:`);
scripts.forEach((script) => __awaiter(this, void 0, void 0, function* () { scripts.forEach(async (script) => {
console.log(`${script}`); console.log(`${script}`);
})); });
// custom pre emulator launch script // custom pre emulator launch script
const preEmulatorLaunchScriptInput = core.getInput('pre-emulator-launch-script'); const preEmulatorLaunchScriptInput = core.getInput('pre-emulator-launch-script');
const preEmulatorLaunchScripts = !preEmulatorLaunchScriptInput ? undefined : (0, script_parser_1.parseScript)(preEmulatorLaunchScriptInput); const preEmulatorLaunchScripts = !preEmulatorLaunchScriptInput ? undefined : (0, script_parser_1.parseScript)(preEmulatorLaunchScriptInput);
console.log(`Pre emulator launch script:`); console.log(`Pre emulator launch script:`);
preEmulatorLaunchScripts === null || preEmulatorLaunchScripts === void 0 ? void 0 : preEmulatorLaunchScripts.forEach((script) => __awaiter(this, void 0, void 0, function* () { preEmulatorLaunchScripts?.forEach(async (script) => {
console.log(`${script}`); console.log(`${script}`);
})); });
console.log(`::endgroup::`); console.log(`::endgroup::`);
// install SDK // install SDK
yield (0, sdk_installer_1.installAndroidSdk)(apiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion); 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 // execute pre emulator launch script if set
if (preEmulatorLaunchScripts !== undefined) { if (preEmulatorLaunchScripts !== undefined) {
console.log(`::group::Run pre emulator launch script`); console.log(`::group::Run pre emulator launch script`);
@@ -182,7 +191,7 @@ function run() {
for (const preEmulatorLaunchScript of preEmulatorLaunchScripts) { for (const preEmulatorLaunchScript of preEmulatorLaunchScripts) {
// use array form to avoid various quote escaping problems // use array form to avoid various quote escaping problems
// caused by exec(`sh -c "${preEmulatorLaunchScript}"`) // caused by exec(`sh -c "${preEmulatorLaunchScript}"`)
yield exec.exec('sh', ['-c', preEmulatorLaunchScript], { await exec.exec('sh', ['-c', preEmulatorLaunchScript], {
cwd: workingDirectory, cwd: workingDirectory,
}); });
} }
@@ -193,7 +202,7 @@ function run() {
console.log(`::endgroup::`); console.log(`::endgroup::`);
} }
// launch an emulator // launch an emulator
yield (0, emulator_manager_1.launchEmulator)(apiLevel, target, arch, profile, cores, ramSize, heapSize, sdcardPathOrSize, diskSize, avdName, forceAvdCreation, emulatorBootTimeout, emulatorOptions, disableAnimations, disableSpellchecker, disableLinuxHardwareAcceleration, enableHardwareKeyboard); await (0, emulator_manager_1.launchEmulator)(avdName, disableAnimations, disableLinuxHardwareAcceleration, disableSpellchecker, emulatorBootTimeout, emulatorOptions, enableHardwareKeyboard, port);
// execute the custom script // execute the custom script
try { try {
// move to custom working directory if set // move to custom working directory if set
@@ -203,20 +212,21 @@ function run() {
for (const script of scripts) { for (const script of scripts) {
// use array form to avoid various quote escaping problems // use array form to avoid various quote escaping problems
// caused by exec(`sh -c "${script}"`) // caused by exec(`sh -c "${script}"`)
yield exec.exec('sh', ['-c', script]); await exec.exec('sh', ['-c', script], {
env: { ...process.env, EMULATOR_PORT: `${port}`, ANDROID_SERIAL: `emulator-${port}` },
});
} }
} }
catch (error) { catch (error) {
core.setFailed(error instanceof Error ? error.message : error); core.setFailed(error instanceof Error ? error.message : error);
} }
// finally kill the emulator // finally kill the emulator
yield (0, emulator_manager_1.killEmulator)(); await (0, emulator_manager_1.killEmulator)(port);
} }
catch (error) { catch (error) {
// kill the emulator so the action can exit // kill the emulator so the action can exit
yield (0, emulator_manager_1.killEmulator)(); await (0, emulator_manager_1.killEmulator)(port);
core.setFailed(error instanceof Error ? error.message : error); core.setFailed(error instanceof Error ? error.message : error);
} }
});
} }
run(); run();
+1 -2
View File
@@ -1,6 +1,6 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); 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). * Convert a (potentially multi-line) script to an array of single-line script(s).
*/ */
@@ -17,4 +17,3 @@ function parseScript(rawScript) {
} }
return scripts; return scripts;
} }
exports.parseScript = parseScript;
+49 -36
View File
@@ -15,85 +15,98 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
}) : function(o, v) { }) : function(o, v) {
o["default"] = v; o["default"] = v;
}); });
var __importStar = (this && this.__importStar) || function (mod) { 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; if (mod && mod.__esModule) return mod;
var result = {}; var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 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); __setModuleDefault(result, mod);
return result; return result;
}; };
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { })();
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.installAndroidSdk = void 0; exports.installAndroidSdk = installAndroidSdk;
const core = __importStar(require("@actions/core")); const core = __importStar(require("@actions/core"));
const exec = __importStar(require("@actions/exec")); const exec = __importStar(require("@actions/exec"));
const io = __importStar(require("@actions/io")); const io = __importStar(require("@actions/io"));
const tc = __importStar(require("@actions/tool-cache")); const tc = __importStar(require("@actions/tool-cache"));
const fs = __importStar(require("fs")); const fs = __importStar(require("fs"));
const BUILD_TOOLS_VERSION = '33.0.2'; const BUILD_TOOLS_VERSION = '37.0.0';
// SDK command-line tools 9.0 // SDK command-line tools 23.0
const CMDLINE_TOOLS_URL_MAC = 'https://dl.google.com/android/repository/commandlinetools-mac-9477386_latest.zip'; const CMDLINE_TOOLS_VERSION = '16111833';
const CMDLINE_TOOLS_URL_LINUX = 'https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip'; 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, * 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. * and the system image for the chosen API level, CPU arch, and target.
*/ */
function installAndroidSdk(apiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion) { async function installAndroidSdk(apiLevel, systemImageApiLevel, target, arch, channelId, emulatorBuild, ndkVersion, cmakeVersion) {
return __awaiter(this, void 0, void 0, function* () {
try { try {
console.log(`::group::Install Android SDK`); console.log(`::group::Install Android SDK`);
const isOnMac = process.platform === 'darwin'; const isOnMac = process.platform === 'darwin';
if (!isOnMac) { const isArm = process.arch === 'arm64';
yield exec.exec(`sh -c \\"sudo chown $USER:$USER ${process.env.ANDROID_HOME} -R`);
}
const cmdlineToolsPath = `${process.env.ANDROID_HOME}/cmdline-tools`; const cmdlineToolsPath = `${process.env.ANDROID_HOME}/cmdline-tools`;
if (!fs.existsSync(cmdlineToolsPath)) { if (!fs.existsSync(cmdlineToolsPath)) {
console.log('Installing new cmdline-tools.'); console.log('Installing new cmdline-tools.');
const sdkUrl = isOnMac ? CMDLINE_TOOLS_URL_MAC : CMDLINE_TOOLS_URL_LINUX; const sdkUrl = isOnMac ? CMDLINE_TOOLS_URL_MAC : CMDLINE_TOOLS_URL_LINUX;
const downloadPath = yield tc.downloadTool(sdkUrl); const downloadPath = await tc.downloadTool(sdkUrl);
yield tc.extractZip(downloadPath, cmdlineToolsPath); await tc.extractZip(downloadPath, cmdlineToolsPath);
yield io.mv(`${cmdlineToolsPath}/cmdline-tools`, `${cmdlineToolsPath}/latest`); await io.mv(`${cmdlineToolsPath}/cmdline-tools`, `${cmdlineToolsPath}/latest`);
} }
// add paths for commandline-tools and platform-tools // add paths for commandline-tools and platform-tools
core.addPath(`${cmdlineToolsPath}/latest:${cmdlineToolsPath}/latest/bin:${process.env.ANDROID_HOME}/platform-tools`); core.addPath(`${cmdlineToolsPath}/latest:${cmdlineToolsPath}/latest/bin:${process.env.ANDROID_HOME}/platform-tools`);
// set standard AVD path // set standard AVD path
await io.mkdirP(`${process.env.HOME}/.android/avd`);
core.exportVariable('ANDROID_AVD_HOME', `${process.env.HOME}/.android/avd`); core.exportVariable('ANDROID_AVD_HOME', `${process.env.HOME}/.android/avd`);
// accept all Android SDK licenses // accept all Android SDK licenses
yield exec.exec(`sh -c \\"yes | sdkmanager --licenses > /dev/null"`); await exec.exec(`sh -c \\"yes | sdkmanager --licenses > /dev/null"`);
console.log('Installing latest build tools, platform tools, and platform.'); console.log('Installing latest build tools, platform tools, and platform.');
yield exec.exec(`sh -c \\"sdkmanager --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools > /dev/null"`); await exec.exec(`sh -c \\"sdkmanager --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools 'platforms;android-${apiLevel}'> /dev/null"`);
console.log('Installing latest emulator.'); console.log('Installing latest emulator.');
yield exec.exec(`sh -c \\"sdkmanager --install emulator --channel=${channelId} > /dev/null"`); await exec.exec(`sh -c \\"sdkmanager --install emulator --channel=${channelId} > /dev/null"`);
if (emulatorBuild) { if (emulatorBuild) {
console.log(`Installing emulator build ${emulatorBuild}.`); console.log(`Installing emulator build ${emulatorBuild}.`);
// TODO find out the correct download URLs for all build ids // TODO find out the correct download URLs for all build ids
const downloadUrlSuffix = Number(emulatorBuild.charAt(0)) > 6 ? `_x64-${emulatorBuild}` : `-${emulatorBuild}`; var downloadUrlSuffix;
yield exec.exec(`curl -fo emulator.zip https://dl.google.com/android/repository/emulator-${isOnMac ? 'darwin' : 'linux'}${downloadUrlSuffix}.zip`); const majorBuildVersion = Number(emulatorBuild);
yield exec.exec(`unzip -o -q emulator.zip -d ${process.env.ANDROID_HOME}`); if (majorBuildVersion >= 8000000) {
yield io.rmRF('emulator.zip'); 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.'); console.log('Installing system images.');
yield 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) { if (ndkVersion) {
console.log(`Installing NDK ${ndkVersion}.`); console.log(`Installing NDK ${ndkVersion}.`);
yield exec.exec(`sh -c \\"sdkmanager --install 'ndk;${ndkVersion}' --channel=${channelId} > /dev/null"`); await exec.exec(`sh -c \\"sdkmanager --install 'ndk;${ndkVersion}' --channel=${channelId} > /dev/null"`);
} }
if (cmakeVersion) { if (cmakeVersion) {
console.log(`Installing CMake ${cmakeVersion}.`); console.log(`Installing CMake ${cmakeVersion}.`);
yield exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' --channel=${channelId} > /dev/null"`); await exec.exec(`sh -c \\"sdkmanager --install 'cmake;${cmakeVersion}' --channel=${channelId} > /dev/null"`);
} }
} }
finally { finally {
console.log(`::endgroup::`); console.log(`::endgroup::`);
} }
});
} }
exports.installAndroidSdk = installAndroidSdk;
+4930 -2985
View File
File diff suppressed because it is too large Load Diff
+17 -14
View File
@@ -2,7 +2,7 @@
"name": "android-emulator-runner", "name": "android-emulator-runner",
"version": "0.0.0", "version": "0.0.0",
"private": true, "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", "main": "lib/main.js",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
@@ -18,6 +18,9 @@
"node", "node",
"setup" "setup"
], ],
"engines": {
"node": ">=24"
},
"author": "Reactive Circus", "author": "Reactive Circus",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -28,18 +31,18 @@
"set-value": "^4.1.0" "set-value": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.2.0", "@types/jest": "^29.5.12",
"@types/node": "^16.18.2", "@types/node": "^24.1.0",
"@typescript-eslint/eslint-plugin": "^5.41.0", "@typescript-eslint/eslint-plugin": "^8.57.0",
"@typescript-eslint/parser": "^5.41.0", "@typescript-eslint/parser": "^8.57.0",
"eslint": "^8.26.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^5.1.3",
"jest": "^29.2.2", "jest": "^29.7.0",
"jest-circus": "^29.2.2", "jest-circus": "^29.7.0",
"prettier": "^2.7.1", "prettier": "^3.3.3",
"ts-jest": "^29.0.3", "ts-jest": "^29.2.5",
"typescript": "^4.8.4" "typescript": "^5.5.4"
} }
} }
+63 -39
View File
@@ -2,29 +2,24 @@ import * as exec from '@actions/exec';
import * as fs from 'fs'; import * as fs from 'fs';
/** /**
* Creates and launches a new AVD instance with the specified configurations. * Creates a new AVD instance with the specified configurations.
*/ */
export async function launchEmulator( export async function createAvd(
apiLevel: string,
target: string,
arch: string, arch: string,
profile: string,
cores: string,
ramSize: string,
heapSize: string,
sdcardPathOrSize: string,
diskSize: string,
avdName: string, avdName: string,
cores: string,
diskSize: string,
enableHardwareKeyboard: boolean,
forceAvdCreation: boolean, forceAvdCreation: boolean,
emulatorBootTimeout: number, heapSize: string,
emulatorOptions: string, profile: string,
disableAnimations: boolean, ramSize: string,
disableSpellChecker: boolean, sdcardPathOrSize: string,
disableLinuxHardwareAcceleration: boolean, systemImageApiLevel: string,
enableHardwareKeyboard: boolean target: string,
): Promise<void> { ): Promise<void> {
try { 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 // create a new AVD if AVD directory does not already exist or forceAvdCreation is true
const avdPath = `${process.env.ANDROID_AVD_HOME}/${avdName}.avd`; const avdPath = `${process.env.ANDROID_AVD_HOME}/${avdName}.avd`;
if (!fs.existsSync(avdPath) || forceAvdCreation) { if (!fs.existsSync(avdPath) || forceAvdCreation) {
@@ -32,29 +27,54 @@ export async function launchEmulator(
const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : ''; const sdcardPathOrSizeOption = sdcardPathOrSize.trim() !== '' ? `--sdcard '${sdcardPathOrSize}'` : '';
console.log(`Creating AVD.`); console.log(`Creating AVD.`);
await exec.exec( 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 || ramSize || heapSize || enableHardwareKeyboard || diskSize) {
const configEntries: string[] = [];
if (cores) { if (cores) {
await 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) { if (ramSize) {
await 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) { if (heapSize) {
await 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) { if (enableHardwareKeyboard) {
await 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) {
configEntries.push(`disk.dataPartition.size=${diskSize}`);
} }
if (diskSize) { if (configEntries.length > 0) {
await exec.exec(`sh -c \\"printf 'disk.dataPartition.size=${diskSize}\n' >> ${process.env.ANDROID_AVD_HOME}/"${avdName}".avd"/config.ini`); 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::`);
}
}
/**
* 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`);
// turn off hardware acceleration on Linux // turn off hardware acceleration on Linux
if (process.platform === 'linux' && disableLinuxHardwareAcceleration) { if (process.platform === 'linux' && disableLinuxHardwareAcceleration) {
@@ -65,7 +85,7 @@ export async function launchEmulator(
// start emulator // start emulator
console.log('Starting emulator.'); console.log('Starting emulator.');
await exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -avd "${avdName}" ${emulatorOptions} &"`, [], { await exec.exec(`sh -c \\"${process.env.ANDROID_HOME}/emulator/emulator -port ${port} -avd "${avdName}" ${emulatorOptions} &"`, [], {
listeners: { listeners: {
stderr: (data: Buffer) => { stderr: (data: Buffer) => {
if (data.toString().includes('invalid command-line parameter')) { if (data.toString().includes('invalid command-line parameter')) {
@@ -76,20 +96,20 @@ export async function launchEmulator(
}); });
// wait for emulator to complete booting // wait for emulator to complete booting
await waitForDevice(emulatorBootTimeout); await waitForDevice(port, emulatorBootTimeout);
await exec.exec(`adb shell input keyevent 82`); await adb(port, `shell input keyevent 82`);
if (disableAnimations) { if (disableAnimations) {
console.log('Disabling animations.'); console.log('Disabling animations.');
await exec.exec(`adb shell settings put global window_animation_scale 0.0`); await adb(port, `shell settings put global window_animation_scale 0.0`);
await exec.exec(`adb shell settings put global transition_animation_scale 0.0`); await adb(port, `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 animator_duration_scale 0.0`);
} }
if (disableSpellChecker) { 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) { 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 { } finally {
console.log(`::endgroup::`); console.log(`::endgroup::`);
@@ -99,10 +119,10 @@ export async function launchEmulator(
/** /**
* Kills the running emulator on the default port. * Kills the running emulator on the default port.
*/ */
export async function killEmulator(): Promise<void> { export async function killEmulator(port: number): Promise<void> {
try { try {
console.log(`::group::Terminate Emulator`); console.log(`::group::Terminate Emulator`);
await exec.exec(`adb -s emulator-5554 emu kill`); await adb(port, `emu kill`);
} catch (error) { } catch (error) {
console.log(error instanceof Error ? error.message : error); console.log(error instanceof Error ? error.message : error);
} finally { } finally {
@@ -110,10 +130,14 @@ export async function killEmulator(): Promise<void> {
} }
} }
async function adb(port: number, command: string): Promise<number> {
return await exec.exec(`adb -s emulator-${port} ${command}`);
}
/** /**
* Wait for emulator to boot. * Wait for emulator to boot.
*/ */
async function waitForDevice(emulatorBootTimeout: number): Promise<void> { async function waitForDevice(port: number, emulatorBootTimeout: number): Promise<void> {
let booted = false; let booted = false;
let attempts = 0; let attempts = 0;
const retryInterval = 2; // retry every 2 seconds const retryInterval = 2; // retry every 2 seconds
@@ -121,7 +145,7 @@ async function waitForDevice(emulatorBootTimeout: number): Promise<void> {
while (!booted) { while (!booted) {
try { try {
let result = ''; let result = '';
await exec.exec(`adb shell getprop sys.boot_completed`, [], { await exec.exec(`adb -s emulator-${port} shell getprop sys.boot_completed`, [], {
listeners: { listeners: {
stdout: (data: Buffer) => { stdout: (data: Buffer) => {
result += data.toString(); result += data.toString();
+17 -15
View File
@@ -1,22 +1,15 @@
export const MIN_API_LEVEL = 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_ARCHS: Array<string> = ['x86', 'x86_64', 'arm64-v8a'];
export const VALID_CHANNELS: Array<string> = ['stable', 'beta', 'dev', 'canary']; 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 { export function playstoreTargetSubstitution(target: string): string {
if (apiLevel.startsWith('UpsideDownCake') || apiLevel === 'TiramisuPrivacySandbox') return; // "playstore" is an allowed shorthand for "google_apis_playstore" images
if (isNaN(Number(apiLevel)) || !Number.isInteger(Number(apiLevel))) { // this is idempotent - return same even if run multiple times on same target
throw new Error(`Unexpected API level: '${apiLevel}'.`); if (target === 'playstore') return 'google_apis_playstore';
} if (target === 'playstore_ps16k') return 'google_apis_playstore_ps16k';
if (Number(apiLevel) < MIN_API_LEVEL) { return target;
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 checkArch(arch: string): void { export function checkArch(arch: string): void {
@@ -37,6 +30,15 @@ export function checkForceAvdCreation(forceAvdCreation: string): void {
} }
} }
export function checkPort(port: number): void {
if (port < MIN_PORT || port > MAX_PORT) {
throw new Error(`Emulator port is outside of the supported port range [${MIN_PORT}, ${MAX_PORT}], was ${port}`);
}
if (port % 2 == 1) {
throw new Error(`Emulator port has to be even, was ${port}`);
}
}
export function checkDisableAnimations(disableAnimations: string): void { export function checkDisableAnimations(disableAnimations: string): void {
if (!isValidBoolean(disableAnimations)) { if (!isValidBoolean(disableAnimations)) {
throw new Error(`Input for input.disable-animations should be either 'true' or 'false'.`); throw new Error(`Input for input.disable-animations should be either 'true' or 'false'.`);
+28 -31
View File
@@ -1,8 +1,6 @@
import * as core from '@actions/core'; import * as core from '@actions/core';
import { installAndroidSdk } from './sdk-installer'; import { installAndroidSdk } from './sdk-installer';
import { import {
checkApiLevel,
checkTarget,
checkArch, checkArch,
checkDisableAnimations, checkDisableAnimations,
checkEmulatorBuild, checkEmulatorBuild,
@@ -12,14 +10,18 @@ import {
checkChannel, checkChannel,
checkEnableHardwareKeyboard, checkEnableHardwareKeyboard,
checkDiskSize, checkDiskSize,
checkPort,
playstoreTargetSubstitution,
MIN_PORT,
} from './input-validator'; } from './input-validator';
import { launchEmulator, killEmulator } from './emulator-manager'; import { createAvd, launchEmulator, killEmulator } from './emulator-manager';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
import { parseScript } from './script-parser'; import { parseScript } from './script-parser';
import { getChannelId } from './channel-id-mapper'; import { getChannelId } from './channel-id-mapper';
import { accessSync, constants } from 'fs'; import { accessSync, constants } from 'fs';
async function run() { async function run() {
let port: number = MIN_PORT;
try { try {
console.log(`::group::Configure emulator`); console.log(`::group::Configure emulator`);
let linuxSupportKVM = false; let linuxSupportKVM = false;
@@ -31,7 +33,7 @@ async function run() {
linuxSupportKVM = true; linuxSupportKVM = true;
} catch { } catch {
console.warn( 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 { } else {
@@ -41,13 +43,16 @@ async function run() {
// API level of the platform and system image // API level of the platform and system image
const apiLevel = core.getInput('api-level', { required: true }); const apiLevel = core.getInput('api-level', { required: true });
checkApiLevel(apiLevel);
console.log(`API level: ${apiLevel}`); console.log(`API level: ${apiLevel}`);
let systemImageApiLevel = core.getInput('system-image-api-level');
if (!systemImageApiLevel) {
systemImageApiLevel = apiLevel;
}
console.log(`System image API level: ${systemImageApiLevel}`);
// target of the system image // target of the system image
const targetInput = core.getInput('target'); const target = playstoreTargetSubstitution(core.getInput('target'));
const target = targetInput == 'playstore' ? 'google_apis_playstore' : targetInput;
checkTarget(target);
console.log(`target: ${target}`); console.log(`target: ${target}`);
// CPU architecture of the system image // CPU architecture of the system image
@@ -93,6 +98,11 @@ async function run() {
const emulatorBootTimeout = parseInt(core.getInput('emulator-boot-timeout'), 10); const emulatorBootTimeout = parseInt(core.getInput('emulator-boot-timeout'), 10);
console.log(`Emulator boot timeout: ${emulatorBootTimeout}`); 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 // emulator options
const emulatorOptions = core.getInput('emulator-options').trim(); const emulatorOptions = core.getInput('emulator-options').trim();
console.log(`emulator options: ${emulatorOptions}`); console.log(`emulator options: ${emulatorOptions}`);
@@ -177,7 +187,10 @@ async function run() {
console.log(`::endgroup::`); console.log(`::endgroup::`);
// install SDK // 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 // execute pre emulator launch script if set
if (preEmulatorLaunchScripts !== undefined) { if (preEmulatorLaunchScripts !== undefined) {
@@ -197,25 +210,7 @@ async function run() {
} }
// launch an emulator // launch an emulator
await launchEmulator( await launchEmulator(avdName, disableAnimations, disableLinuxHardwareAcceleration, disableSpellchecker, emulatorBootTimeout, emulatorOptions, enableHardwareKeyboard, port);
apiLevel,
target,
arch,
profile,
cores,
ramSize,
heapSize,
sdcardPathOrSize,
diskSize,
avdName,
forceAvdCreation,
emulatorBootTimeout,
emulatorOptions,
disableAnimations,
disableSpellchecker,
disableLinuxHardwareAcceleration,
enableHardwareKeyboard
);
// execute the custom script // execute the custom script
try { try {
@@ -226,17 +221,19 @@ async function run() {
for (const script of scripts) { for (const script of scripts) {
// use array form to avoid various quote escaping problems // use array form to avoid various quote escaping problems
// caused by exec(`sh -c "${script}"`) // 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) { } catch (error) {
core.setFailed(error instanceof Error ? error.message : (error as string)); core.setFailed(error instanceof Error ? error.message : (error as string));
} }
// finally kill the emulator // finally kill the emulator
await killEmulator(); await killEmulator(port);
} catch (error) { } catch (error) {
// kill the emulator so the action can exit // kill the emulator so the action can exit
await killEmulator(); await killEmulator(port);
core.setFailed(error instanceof Error ? error.message : (error as string)); core.setFailed(error instanceof Error ? error.message : (error as string));
} }
} }
+32 -12
View File
@@ -4,23 +4,30 @@ import * as io from '@actions/io';
import * as tc from '@actions/tool-cache'; import * as tc from '@actions/tool-cache';
import * as fs from 'fs'; import * as fs from 'fs';
const BUILD_TOOLS_VERSION = '33.0.2'; const BUILD_TOOLS_VERSION = '37.0.0';
// SDK command-line tools 9.0 // SDK command-line tools 23.0
const CMDLINE_TOOLS_URL_MAC = 'https://dl.google.com/android/repository/commandlinetools-mac-9477386_latest.zip'; const CMDLINE_TOOLS_VERSION = '16111833';
const CMDLINE_TOOLS_URL_LINUX = 'https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip'; 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, * 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. * and the system image for the chosen API level, CPU arch, and target.
*/ */
export async function installAndroidSdk(apiLevel: string, target: string, arch: string, channelId: number, emulatorBuild?: string, ndkVersion?: string, cmakeVersion?: string): Promise<void> { export async function installAndroidSdk(
apiLevel: string,
systemImageApiLevel: String,
target: string,
arch: string,
channelId: number,
emulatorBuild?: string,
ndkVersion?: string,
cmakeVersion?: string,
): Promise<void> {
try { try {
console.log(`::group::Install Android SDK`); console.log(`::group::Install Android SDK`);
const isOnMac = process.platform === 'darwin'; const isOnMac = process.platform === 'darwin';
const isArm = process.arch === 'arm64';
if (!isOnMac) {
await exec.exec(`sh -c \\"sudo chown $USER:$USER ${process.env.ANDROID_HOME} -R`);
}
const cmdlineToolsPath = `${process.env.ANDROID_HOME}/cmdline-tools`; const cmdlineToolsPath = `${process.env.ANDROID_HOME}/cmdline-tools`;
if (!fs.existsSync(cmdlineToolsPath)) { if (!fs.existsSync(cmdlineToolsPath)) {
@@ -35,6 +42,7 @@ export async function installAndroidSdk(apiLevel: string, target: string, arch:
core.addPath(`${cmdlineToolsPath}/latest:${cmdlineToolsPath}/latest/bin:${process.env.ANDROID_HOME}/platform-tools`); core.addPath(`${cmdlineToolsPath}/latest:${cmdlineToolsPath}/latest/bin:${process.env.ANDROID_HOME}/platform-tools`);
// set standard AVD path // set standard AVD path
await io.mkdirP(`${process.env.HOME}/.android/avd`);
core.exportVariable('ANDROID_AVD_HOME', `${process.env.HOME}/.android/avd`); core.exportVariable('ANDROID_AVD_HOME', `${process.env.HOME}/.android/avd`);
// accept all Android SDK licenses // accept all Android SDK licenses
@@ -42,7 +50,7 @@ export async function installAndroidSdk(apiLevel: string, target: string, arch:
console.log('Installing latest build tools, platform tools, and platform.'); console.log('Installing latest build tools, platform tools, and platform.');
await exec.exec(`sh -c \\"sdkmanager --install 'build-tools;${BUILD_TOOLS_VERSION}' platform-tools > /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.'); console.log('Installing latest emulator.');
await exec.exec(`sh -c \\"sdkmanager --install emulator --channel=${channelId} > /dev/null"`); await exec.exec(`sh -c \\"sdkmanager --install emulator --channel=${channelId} > /dev/null"`);
@@ -50,13 +58,25 @@ export async function installAndroidSdk(apiLevel: string, target: string, arch:
if (emulatorBuild) { if (emulatorBuild) {
console.log(`Installing emulator build ${emulatorBuild}.`); console.log(`Installing emulator build ${emulatorBuild}.`);
// TODO find out the correct download URLs for all build ids // 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(`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 exec.exec(`unzip -o -q emulator.zip -d ${process.env.ANDROID_HOME}`);
await io.rmRF('emulator.zip'); await io.rmRF('emulator.zip');
} }
console.log('Installing system images.'); 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) { if (ndkVersion) {
console.log(`Installing NDK ${ndkVersion}.`); console.log(`Installing NDK ${ndkVersion}.`);
-35
View File
@@ -1,35 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 33
buildToolsVersion "33.0.2"
defaultConfig {
applicationId "com.example.testapp"
minSdkVersion 15
targetSdkVersion 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
compilerOptions {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.6.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
+36
View File
@@ -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")
}
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
package="com.example.testapp">
<application <application
android:allowBackup="true" android:allowBackup="true"
-26
View File
@@ -1,26 +0,0 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10"
// 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
}
+5
View File
@@ -0,0 +1,5 @@
plugins {
id("com.android.application") version "9.3.2" apply false
id("org.jetbrains.kotlin.android") version "2.4.10" apply false
}
-5
View File
@@ -2,11 +2,6 @@ org.gradle.parallel=true
org.gradle.configureondemand=true org.gradle.configureondemand=true
org.gradle.caching=true org.gradle.caching=true
# Enable parallel tasks execution for Kotlin Gradle plugin
kotlin.parallel.tasks.in.project=true
# Kotlin code style # Kotlin code style
kotlin.code.style=official kotlin.code.style=official
# Enable AndroidX
android.useAndroidX=true
Binary file not shown.
+4 -1
View File
@@ -1,6 +1,9 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000 networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
+24 -20
View File
@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# #
# Copyright © 2015-2021 the original authors. # Copyright © 2015 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -15,10 +15,12 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
# Gradle start up script for POSIX generated by Gradle. # gradlew start up script for POSIX generated by Gradle.
# #
# Important for running: # Important for running:
# #
@@ -27,7 +29,7 @@
# bash, then to run this script, type that shell name before the whole # bash, then to run this script, type that shell name before the whole
# command line, like: # command line, like:
# #
# ksh Gradle # ksh gradlew
# #
# Busybox and similar reduced shells will NOT work, because this script # Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features: # requires all of these POSIX shell features:
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -83,10 +85,8 @@ done
# This is normally unused # This is normally unused
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # 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
# 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"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@@ -114,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;; NONSTOP* ) nonstop=true ;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@@ -133,18 +132,21 @@ location of your Java installation."
fi fi
else else
JAVACMD=java 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. 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 Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045 # shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
@@ -152,7 +154,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #( '' | soft) :;; #(
*) *)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045 # shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
@@ -169,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java # For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" ) JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -197,16 +198,19 @@ if "$cygwin" || "$msys" ; then
done done
fi fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
# shell script including quotes and variable substitutions, so put them in DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded. # 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 -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
org.gradle.wrapper.GradleWrapperMain \
"$@" "$@"
# Stop when "xargs" is not available. # Stop when "xargs" is not available.
+24 -34
View File
@@ -13,16 +13,18 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem gradlew startup script for Windows
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables, and ensure extensions are enabled
if "%OS%"=="Windows_NT" setlocal setlocal EnableExtensions
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@@ -43,13 +45,13 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail "%COMSPEC%" /c exit 1
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
@@ -57,36 +59,24 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail "%COMSPEC%" /c exit 1
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute gradlew
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* @rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:end :exitWithErrorLevel
@rem End local scope for the variables with windows NT shell @rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
if %ERRORLEVEL% equ 0 goto mainEnd "%COMSPEC%" /c exit %ERRORLEVEL%
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
-2
View File
@@ -1,2 +0,0 @@
rootProject.name='Test Project'
include ':app'
+28
View File
@@ -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
View File
@@ -2,7 +2,7 @@
"compilerOptions": { "compilerOptions": {
/* Basic Options */ /* Basic Options */
// "incremental": true, /* Enable incremental compilation */ // "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'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
// "allowJs": true, /* Allow javascript files to be compiled. */ // "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */ // "checkJs": true, /* Report errors in .js files. */
@@ -45,7 +45,8 @@
// "typeRoots": [], /* List of folders to include type definitions from. */ // "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */ // "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. */ // "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. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */