Compare commits

..

No commits in common. "main" and "release/v1" have entirely different histories.

26 changed files with 7588 additions and 42389 deletions

View file

@ -1,55 +1,59 @@
{
"plugins": ["jest", "@typescript-eslint"],
"extends": ["plugin:github/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 9,
"sourceType": "module",
"project": "./tsconfig.json"
},
"rules": {
"eslint-comments/no-use": "off",
"import/no-namespace": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
"@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/array-type": "error",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/ban-ts-comment": "error",
"camelcase": "off",
"@typescript-eslint/consistent-type-assertions": "error",
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
"@typescript-eslint/func-call-spacing": ["error", "never"],
"@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-extraneous-class": "error",
"@typescript-eslint/no-for-in-array": "error",
"@typescript-eslint/no-inferrable-types": "error",
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-useless-constructor": "error",
"@typescript-eslint/no-var-requires": "error",
"@typescript-eslint/prefer-for-of": "warn",
"@typescript-eslint/prefer-function-type": "warn",
"@typescript-eslint/prefer-includes": "error",
"@typescript-eslint/prefer-string-starts-ends-with": "error",
"@typescript-eslint/promise-function-async": "error",
"@typescript-eslint/require-array-sort-compare": "error",
"@typescript-eslint/restrict-plus-operands": "error",
"semi": "off",
"@typescript-eslint/semi": ["error", "never"],
"@typescript-eslint/type-annotation-spacing": "error",
"i18n-text/no-en": "off",
"@typescript-eslint/unbound-method": "error"
},
"env": {
"node": true,
"es6": true,
"jest/globals": true
}
}
{
"plugins": ["jest", "@typescript-eslint"],
"extends": ["plugin:github/es6"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 9,
"sourceType": "module",
"project": "./tsconfig.json"
},
"rules": {
"eslint-comments/no-use": "off",
"import/no-namespace": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
"@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/array-type": "error",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/ban-ts-ignore": "error",
"camelcase": "off",
"@typescript-eslint/camelcase": "error",
"@typescript-eslint/class-name-casing": "error",
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
"@typescript-eslint/func-call-spacing": ["error", "never"],
"@typescript-eslint/generic-type-naming": ["error", "^[A-Z][A-Za-z]*$"],
"@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-extraneous-class": "error",
"@typescript-eslint/no-for-in-array": "error",
"@typescript-eslint/no-inferrable-types": "error",
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/no-object-literal-type-assertion": "error",
"@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-useless-constructor": "error",
"@typescript-eslint/no-var-requires": "error",
"@typescript-eslint/prefer-for-of": "warn",
"@typescript-eslint/prefer-function-type": "warn",
"@typescript-eslint/prefer-includes": "error",
"@typescript-eslint/prefer-interface": "error",
"@typescript-eslint/prefer-string-starts-ends-with": "error",
"@typescript-eslint/promise-function-async": "error",
"@typescript-eslint/require-array-sort-compare": "error",
"@typescript-eslint/restrict-plus-operands": "error",
"semi": "off",
"@typescript-eslint/semi": ["error", "never"],
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unbound-method": "error",
"no-console": "off"
},
"env": {
"node": true,
"es6": true,
"jest/globals": true
}
}

19
.github/android-lint-file-matcher.json vendored Normal file
View file

@ -0,0 +1,19 @@
{
// Example:
// /path/to/file/file.class: Warning: checkClientTrusted is empty, which could cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers [TrustAllX509TrustManager]
"problemMatcher": [
{
"owner": "android-lint-file",
"pattern": [
{
"regexp": "^(.+):\\s+(Warning|Error):\\s+(.+)\\s+\\[(.+)\\]$",
"file": 1,
"severity": 2,
"message": 3,
"code": 4,
"kind": "file"
}
]
}
]
}

19
.github/android-lint-line-matcher.json vendored Normal file
View file

@ -0,0 +1,19 @@
{
// Example:
// /path/to/file/build.gradle:55: Warning: A newer version of androidx.core:core-ktx than 1.2.0-beta01 is available: 1.2.0-rc01 [GradleDependency]
"problemMatcher": [
{
"owner": "android-lint-line",
"pattern": [
{
"regexp": "^(.+):(\\d+):\\s+(Warning|Error):\\s+(.+)\\s+\\[(.+)\\]$",
"file": 1,
"line": 2,
"severity": 3,
"message": 4,
"code": 5
}
]
}
]
}

23
.github/gradle-matcher.json vendored Normal file
View file

@ -0,0 +1,23 @@
{
// Example:
// warning unused-exclude-by-conf the exclude dependency is not in your dependency graph, so has no effect
// app/build.gradle:45
"problemMatcher": [
{
"owner": "gradle",
"pattern": [
{
"regexp": "^(error|quiet|warning|lifecycle|info|debug)\\s+(\\S+)\\s+(.+)$",
"severity": 1,
"code": 2,
"message": 3
},
{
"regexp": "^([^\\s]+):([\\d]+)$",
"file": 1,
"line": 2
}
]
}
]
}

19
.github/kotlin-error-matcher.json vendored Normal file
View file

@ -0,0 +1,19 @@
{
// Example:
// e: /path/to/file/KotlinFile.kt: (14, 5): Val cannot be reassigned
"problemMatcher": [
{
"owner": "kotlin-error",
"pattern": [
{
"regexp": "^e:\\s+(\\S+):\\s+\\((\\d+),\\s+(\\d+)\\):\\s+(.+)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4
}
],
"severity": "error"
}
]
}

19
.github/kotlin-warning-matcher.json vendored Normal file
View file

@ -0,0 +1,19 @@
{
// Example:
// w: /path/to/file/KotlinFile.kt: (14, 5): Parameter 'foo' is never used
"problemMatcher": [
{
"owner": "kotlin-warning",
"pattern": [
{
"regexp": "^w:\\s+(\\S+):\\s+\\((\\d+),\\s+(\\d+)\\):\\s+(.+)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4
}
],
"severity": "warning"
}
]
}

View file

@ -1,7 +1,13 @@
name: Build & Test
on:
pull_request:
paths-ignore:
- '**.md'
push:
branches:
- main
- releases/*
paths-ignore:
- '**.md'
@ -9,109 +15,17 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Setup node 20
uses: actions/setup-node@v4
- name: Setup node 12
uses: actions/setup-node@v2.1.1
with:
node-version: 20
node-version: 12.x
- run: npm ci
- run: npm run build
- name: Check if dist/index.js needs to be rebuilt
run: diff <(git status dist/index.js --short) <(echo -n "")
format-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup node 20
uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run format-check
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup node 20
uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test
runSdkManager:
runs-on: ${{ matrix.os }}
name: ${{ matrix.os }} - ${{ matrix.cmdline-tools-version }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-24.04
- ubuntu-22.04
- ubuntu-20.04
- macos-14
- macos-13
- macos-12
- windows-2022
- windows-2019
cmdline-tools-version:
- 12266719
- 11479570
- 11076708
- 10406996
- 9862592
- 9477386
- 9123335
- 8512546
steps:
- uses: actions/checkout@v4
- name: Setup node 20
uses: actions/setup-node@v4
with:
node-version: 20
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'temurin'
- run: npm ci
- run: npm run build
- name: Run setup-android
uses: ./
with:
cmdline-tools-version: ${{ matrix.cmdline-tools-version }}
log-accepted-android-sdk-licenses: 'false'
- run: sdkmanager --list_installed
- run: sdkmanager --list
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup node 20
uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run lint

View file

@ -1,43 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '44 12 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: 'javascript'
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3

View file

@ -1,47 +1,47 @@
name: Run Android build
on:
workflow_dispatch:
push:
paths:
- 'dist/**.js'
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- ubuntu-latest
- windows-latest
- macos-latest
# Disabled, because daveol/SampleApplication is missing
if: ${{ false }}
steps:
- uses: actions/checkout@v4
with:
repository: daveol/SampleApplication
- uses: actions/checkout@v4
with:
path: ./build/
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'temurin'
- name: Setup Android SDK
id: 'setup-android'
uses: ./build/
- name: Build SampleApplication
if: runner.os != 'windows'
run: ./gradlew --no-daemon build
- name: Build SampleApplication (Windows)
if: runner.os == 'windows'
run: .\gradlew.bat --no-daemon build
name: Run Android build
on:
pull_request:
paths-ignore:
- '**.md'
push:
branches:
- main
- releases/*
paths-ignore:
- '**.md'
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- ubuntu-latest
- windows-latest
- macos-latest
steps:
- uses: actions/checkout@v2
with:
repository: daveol/SampleApplication
- uses: actions/checkout@v2
with:
path: ./build/
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Setup Android SDK
id: 'setup-android'
uses: ./build/
- name: Build SampleApplication
if: runner.os != 'windows'
run: ./gradlew --no-daemon build
- name: Build SampleApplication (Windows)
if: runner.os == 'windows'
run: .\gradlew.bat --no-daemon build

6
.idea/vcs.xml generated
View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

21
LICENSE
View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020 Dave Olsthoorn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,100 +1,7 @@
# setup-android
For setting up the android sdk in a github-action
![Build & Test](https://github.com/android-actions/setup-android/workflows/Build%20&%20Test/badge.svg)
This action sets up the Android SDK tools by:
- Downloading the SDK commandline tools, if the current version (16.0) is not found in either `$ANDROID_SDK_ROOT` or `$HOME/.android/sdk`.
- Accepting the SDK licenses.
- Installing `tools` and `platform-tools`.
- Adding `platform-tools` (contains adb) and `cmdline-tools/16.0/bin` (contains sdkmanager) to `$PATH`.
- Setting up problem [matchers](/matchers.json).
On Windows 2016 runners, this action also checks if `$ANDROID_SDK_ROOT` path contains spaces.
If it does - it moves SDK to a path without spaces. This is needed because spaces are highly problematic:
```
C:\windows\system32\cmd.exe /D /S /C ""C:\Program Files (x86)\Android\android-sdk\cmdline-tools\3.0\bin\sdkmanager.bat" --licenses"
Error: Could not find or load main class Files
```
# Usage
See [action.yml](action.yml)
## Basic
```yaml
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Build SampleApplication
run: ./gradlew --no-daemon build
```
## Additional packages
Input parameter `packages` controls which packages this action will install from Android SDK.
Default value is `tools platform-tools`, supply an empty string to skip installing additional packages.
Additional packages can be installed at a later time by calling sdkmanager manually.
```yaml
- name: Setup Android SDK
uses: android-actions/setup-android@v3
with:
packages: ''
# ...
- run: sdkmanager tools platform-tools
```
## SDK Version selection
Command line tools are versioned using two variables - short and long.
Long one is the build number, used in the zip URL, short one is the human friendly version name.
By default, setup-android installs version 12266719 (short version 16.0).
To install a different version, call setup-android with desired long version as the input parameter `cmdline-tools-version`:
```yaml
- name: Setup Android SDK
uses: android-actions/setup-android@v3
with:
cmdline-tools-version: 8512546
```
#### Version table
| Short version | Long version |
|---------------| --- |
| 16.0 | 12266719 |
| 13.0 | 11479570 |
| 12.0 | 11076708 |
| 11.0 | 10406996 |
| 10.0 | 9862592 |
| 9.0 | 9477386 |
| 8.0 | 9123335 |
| 7.0 | 8512546 |
Current cmdline tools version can be found at https://developer.android.com/studio#command-line-tools-only
# Android SDK Licenses
Android SDK is not public domain software, it comes with a license.
Input parameter `accept-android-sdk-licenses` decides if Android SDK licenses should be agreed to on behalf of the user of this action.
Default option is 'yes', because otherwise SDK is unusable until said licenses are agreed to.
licenses are quite long, to prevent a wall of text in the action output, licenses can be agreed to silently.
Input parameter `log-accepted-android-sdk-licenses` controls whether license texts should be printed or omitted from the text output. Defaults to 'true'.
<!-- TODO: write readme -->
# Thanks
Based on the project [android-problem-matchers-action](https://github.com/jonasb/android-problem-matchers-action) from [@jonasb](https://github.com/jonasb)

View file

@ -1,5 +1,5 @@
describe('setup-android', () => {
it('has sanity', async () => {
expect(true)
})
})
describe('setup-android', () => {
it('has sanity', async () => {
expect(true)
})
})

View file

@ -2,30 +2,11 @@ name: 'Setup Android SDK Tools'
author: 'Android-Actions'
description: 'Setup the Android SDK Tools and add them to the path'
inputs:
cmdline-tools-version:
description: 'cmdline-tools-version. See https://developer.android.com/studio#command-line-tools-only'
required: false
default: '12266719'
accept-android-sdk-licenses:
description: 'Android SDK is usable only after the license agreement. Should setup-android agree to the licences, provided by "sdkmanager --licenses"'
required: false
default: 'true'
log-accepted-android-sdk-licenses:
description: 'Should accepted licenses be logged. If not, accepted licences will be accepted silently'
required: false
default: 'true'
packages:
description: 'Additional packages to install'
required: false
default: 'tools platform-tools'
runs:
using: node20
main: 'dist/index.js'
using: 'node12'
main: 'dist/main/index.js'
post: 'dist/post/index.js'
post-if: success()
branding:
icon: 'list'

30360
dist/index.js vendored

File diff suppressed because one or more lines are too long

1
dist/main/index.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/post/index.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,72 +0,0 @@
{
"problemMatcher": [
{
"owner": "android-lint-file",
"pattern": [
{
"regexp": "^(.+):\\s+(Warning|Error):\\s+(.+)\\s+\\[(.+)\\]$",
"file": 1,
"severity": 2,
"message": 3,
"code": 4,
"kind": "file"
}
]
},
{
"owner": "android-lint-line",
"pattern": [
{
"regexp": "^(.+):(\\d+):\\s+(Warning|Error):\\s+(.+)\\s+\\[(.+)\\]$",
"file": 1,
"line": 2,
"severity": 3,
"message": 4,
"code": 5
}
]
},
{
"owner": "gradle",
"pattern": [
{
"regexp": "^(error|quiet|warning|lifecycle|info|debug)\\s+(\\S+)\\s+(.+)$",
"severity": 1,
"code": 2,
"message": 3
},
{
"regexp": "^([^\\s]+):([\\d]+)$",
"file": 1,
"line": 2
}
]
},
{
"owner": "kotlin-error",
"pattern": [
{
"regexp": "^e:\\s+(\\S+):\\s+\\((\\d+),\\s+(\\d+)\\):\\s+(.+)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4
}
],
"severity": "error"
},
{
"owner": "kotlin-warning",
"pattern": [
{
"regexp": "^w:\\s+(\\S+):\\s+\\((\\d+),\\s+(\\d+)\\):\\s+(.+)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4
}
],
"severity": "warning"
}
]
}

18352
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,10 +5,10 @@
"description": "setup android action",
"main": "lib/main.js",
"scripts": {
"build": "ncc build src/main.ts --out dist/",
"build": "ncc build src/main.ts --out dist/main --minify && ncc build src/post.ts --out dist/post --minify",
"format": "prettier --write **/*.ts",
"format-check": "prettier --check **/*.ts",
"lint": "eslint src/*.ts",
"lint": "eslint src/**/*.ts",
"test": "jest",
"all": "npm run format && npm run lint && npm run build && npm test"
},
@ -25,24 +25,25 @@
"author": "daveol",
"license": "ISC",
"dependencies": {
"@actions/cache": "^3.2.4",
"@actions/core": "^1.10.1",
"@actions/tool-cache": "^2.0.1"
"@actions/cache": "^1.0.2",
"@actions/core": "^1.2.0",
"@actions/tool-cache": "^1.6.0",
"axios": "^0.19.2",
"xml-js": "^1.6.11"
},
"devDependencies": {
"@types/jest": "^29.5.5",
"@types/node": "^20.6.5",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@vercel/ncc": "^0.38.0",
"eslint": "^8.57.0",
"eslint-plugin-github": "^5.0.1",
"eslint-plugin-jest": "^28.6.0",
"jest": "^29.7.0",
"jest-circus": "^29.7.0",
"js-yaml": "^4.1.0",
"prettier": "^3.3.2",
"ts-jest": "^29.1.5",
"typescript": "^5.5.4"
"@types/jest": "^26.0.9",
"@types/node": "^12.7.12",
"@typescript-eslint/parser": "^2.8.0",
"@zeit/ncc": "^0.20.5",
"eslint": "^5.16.0",
"eslint-plugin-github": "^4.1.1",
"eslint-plugin-jest": "^22.21.0",
"jest": "^24.9.0",
"jest-circus": "^26.3.0",
"js-yaml": "^3.13.1",
"prettier": "^1.19.1",
"ts-jest": "^24.2.0",
"typescript": "^3.6.4"
}
}

189
src/cache.ts Normal file
View file

@ -0,0 +1,189 @@
import * as glob from '@actions/glob'
import * as cache from '@actions/cache'
import * as core from '@actions/core'
import * as crypto from 'crypto'
import * as fs from 'fs'
import {platform} from 'os'
import {
GRADLE_WRAPPER_GLOB,
GRADLE_CACHE_GLOB,
GRADLE_WRAPPER_DIR,
GRADLE_CACHE_DIR,
GRADLE_WRAPPER_KEY,
GRADLE_CACHE_KEY,
ANDROID_GLOB,
ANDROID_KEY,
COMMANDLINE_TOOLS_VERSION,
ANDROID_SDK_ROOT,
ANDROID_REPOSITORIES_CACHE,
ANDROID_REPOSITORIES_CFG,
VERSION
} from './constants'
async function hashFiles(globs: string[]): Promise<string | undefined> {
const globber = await glob.create(globs.join('\n'), {
followSymbolicLinks: false
})
const hashes: Buffer[] = []
for await (const file of globber.globGenerator()) {
// skip directories
if (fs.statSync(file).isDirectory()) continue
core.debug(`hashFiles: found ${file}`)
const hash = crypto.createHash('sha256')
fs.createReadStream(file).pipe(hash)
hashes.push(hash.digest())
}
// No files hashed
if (hashes.length === 0) {
core.debug('hashFiles: no hashes in array')
return
}
// Loop trough files
const completeHash = crypto.createHash('sha256')
for (const hash of hashes) {
completeHash.update(hash)
}
completeHash.end()
return completeHash.digest('hex')
}
export async function preGradleWrapper(): Promise<void> {
const wrapperHash = await hashFiles(GRADLE_WRAPPER_GLOB)
const wrapperKey = `gradle-wrapper-${platform}-${wrapperHash}`
const wrapperRestoreKeys = [`gradle-wrapper-${platform}-`, `gradle-wrapper-`]
// if no wrapper is present skip trying to retrieve it
if (!wrapperHash) {
core.info('A hash for the gradle wrapper could not be generated')
return
}
const wrapperCache = await cache.restoreCache(
[GRADLE_WRAPPER_DIR],
wrapperKey,
wrapperRestoreKeys
)
if (!wrapperCache) {
core.info(
'Gradle wrapper cache not found, expect a download from gradle wrapper.'
)
}
if (wrapperCache !== wrapperKey) {
core.saveState(GRADLE_WRAPPER_KEY, wrapperKey)
}
return
}
export async function postGradleWrapper(): Promise<void> {
const wrapperKey = core.getState(GRADLE_WRAPPER_KEY)
if (wrapperKey === '') {
core.info(
'A key for gradle wrapper was not defined, and thus there will not be a cache'
)
return
}
await cache.saveCache([GRADLE_WRAPPER_DIR], wrapperKey)
return
}
export async function preGradleCache(): Promise<void> {
const cacheHash = await hashFiles(GRADLE_CACHE_GLOB)
const cacheKey = `gradle-cache-${platform}-${cacheHash}`
const cacheRestoreKeys = [`gradle-cache-${platform}-`, `gradle-cache-`]
if (!cacheHash) {
core.info('A hash for the gradle dependencies could not be generated')
return
}
const cacheCache = await cache.restoreCache(
[GRADLE_CACHE_DIR],
cacheKey,
cacheRestoreKeys
)
if (!cacheCache) {
core.info('Gradle cache not found, expect dependency downloads from gradle')
}
if (cacheCache !== cacheKey) {
core.saveState(GRADLE_CACHE_KEY, cacheKey)
}
return
}
export async function postGradleCache(): Promise<void> {
const cacheKey = core.getState(GRADLE_CACHE_KEY)
if (cacheKey === '') {
core.info(
'A key for gradle cache was not defined, and thus there will not be a cache'
)
return
}
await cache.saveCache([GRADLE_CACHE_DIR], cacheKey)
return
}
export async function preAndroidCache(): Promise<void> {
const androidHash = await hashFiles(ANDROID_GLOB)
const androidKey = `android-${VERSION}-${platform}-${COMMANDLINE_TOOLS_VERSION}-${androidHash}`
const androidRestoreKeys = [
`android-${VERSION}-${platform}-${COMMANDLINE_TOOLS_VERSION}-`,
`android-${VERSION}-${platform}-`
]
if (!androidHash) {
core.info('A hash for the android sdk could not be generated')
return
}
const androidCache = await cache.restoreCache(
[ANDROID_SDK_ROOT, ANDROID_REPOSITORIES_CACHE, ANDROID_REPOSITORIES_CFG],
androidKey,
androidRestoreKeys
)
if (!androidCache) {
core.info(
'Android cache not found, expect dependency downloads from gradle'
)
}
if (androidCache !== androidKey) {
core.saveState(ANDROID_KEY, androidKey)
}
return
}
export async function postAndroidCache(): Promise<void> {
const androidKey = core.getState(ANDROID_KEY)
if (androidKey === '') {
core.info(
'A key for the android sdk was not defined, and thus there will not be a cache'
)
return
}
await cache.saveCache(
[ANDROID_SDK_ROOT, ANDROID_REPOSITORIES_CACHE, ANDROID_REPOSITORIES_CFG],
androidKey
)
return
}

49
src/constants.ts Normal file
View file

@ -0,0 +1,49 @@
import * as os from 'os'
import * as path from 'path'
export const ANNOTATION_MATCHERS = [
'android-lint-file-matcher.json',
'android-lint-line-matcher.json',
'gradle-matcher.json',
'kotlin-error-matcher.json',
'kotlin-warning-matcher.json'
]
export const HOME = os.homedir()
export const VERSION = 'v1'
// Gradle constants
// For caching the gradle cache in ~/.gradle/cache
export const GRADLE_CACHE_GLOB = [
'**/*.gradle',
'**.gradle',
'**/gradle.properties'
]
export const GRADLE_CACHE_DIR = path.join(HOME, '.gradle', 'cache')
export const GRADLE_CACHE_KEY = 'GRADLE_CACHE_KEY'
// For caching the gradle wrapper in ~/.gradle/wrapper
export const GRADLE_WRAPPER_GLOB = ['**/gradle/wrapper/**', '**/gradlew*']
export const GRADLE_WRAPPER_DIR = path.join(HOME, '.gradle', 'wrapper')
export const GRADLE_WRAPPER_KEY = 'GRADLE_WRAPPER_KEY'
// Android constants
export const ANDROID_SDK_ROOT = path.join(HOME, 'android')
export const ANDROID_GLOB = GRADLE_CACHE_GLOB
export const ANDROID_KEY = 'ANDROID_KEY'
export const ANDROID_REPOSITORIES_DIR = path.join(HOME, '.android')
export const ANDROID_REPOSITORIES_CFG = path.join(
ANDROID_REPOSITORIES_DIR,
'repositories.cfg'
)
export const ANDROID_REPOSITORIES_CACHE = path.join(
ANDROID_REPOSITORIES_DIR,
'cache'
)
export const COMMANDLINE_TOOLS_VERSION = '6609375'
export const COMMANDLINE_TOOLS_WIN_URL = `https://dl.google.com/android/repository/commandlinetools-win-${COMMANDLINE_TOOLS_VERSION}_latest.zip`
export const COMMANDLINE_TOOLS_MAC_URL = `https://dl.google.com/android/repository/commandlinetools-mac-${COMMANDLINE_TOOLS_VERSION}_latest.zip`
export const COMMANDLINE_TOOLS_LIN_URL = `https://dl.google.com/android/repository/commandlinetools-linux-${COMMANDLINE_TOOLS_VERSION}_latest.zip`

63
src/install.ts Normal file
View file

@ -0,0 +1,63 @@
import * as fs from 'fs'
import * as path from 'path'
import * as core from '@actions/core'
import * as exec from '@actions/exec'
import * as tc from '@actions/tool-cache'
import {
ANDROID_SDK_ROOT,
COMMANDLINE_TOOLS_LIN_URL,
COMMANDLINE_TOOLS_MAC_URL,
COMMANDLINE_TOOLS_WIN_URL,
ANDROID_REPOSITORIES_CFG,
ANDROID_REPOSITORIES_DIR
} from './constants'
export async function install(): Promise<void> {
const licenseDir = path.join(ANDROID_SDK_ROOT, 'licenses')
// If the licences exist, the rest does too
if (fs.existsSync(licenseDir) && fs.existsSync(ANDROID_REPOSITORIES_CFG)) {
core.debug(`Skipping install, licenseDir found: ${licenseDir}`)
return
}
// create ~/.android/repositories.cfg
fs.mkdirSync(ANDROID_REPOSITORIES_DIR, {recursive: true})
fs.closeSync(fs.openSync(ANDROID_REPOSITORIES_CFG, 'w'))
const acceptBuffer = Buffer.from(
Array(10)
.fill('y')
.join('\n'),
'utf8'
)
let sdkManager = ''
if (process.platform === 'linux') {
const cmdlineToolsZip = await tc.downloadTool(COMMANDLINE_TOOLS_LIN_URL)
const cmdlineTools = await tc.extractZip(cmdlineToolsZip)
sdkManager = path.join(cmdlineTools, 'tools', 'bin', 'sdkmanager')
} else if (process.platform === 'darwin') {
const cmdlineToolsZip = await tc.downloadTool(COMMANDLINE_TOOLS_MAC_URL)
const cmdlineTools = await tc.extractZip(cmdlineToolsZip)
sdkManager = path.join(cmdlineTools, 'tools', 'bin', 'sdkmanager')
} else if (process.platform === 'win32') {
const cmdlineToolsZip = await tc.downloadTool(COMMANDLINE_TOOLS_WIN_URL)
const cmdlineTools = await tc.extractZip(cmdlineToolsZip)
sdkManager = path.join(cmdlineTools, 'tools', 'bin', 'sdkmanager.bat')
} else {
core.error(`Unsupported platform: ${process.platform}`)
}
await exec.exec(
sdkManager,
['--licenses', `--sdk_root=${ANDROID_SDK_ROOT}`],
{input: acceptBuffer}
)
exec.exec(
sdkManager,
['--include_obsolete', `--sdk_root=${ANDROID_SDK_ROOT}`, 'tools'],
{input: acceptBuffer}
)
}

View file

@ -1,192 +1,26 @@
import * as core from '@actions/core'
import * as tc from '@actions/tool-cache'
import * as exec from '@actions/exec'
import * as path from 'path'
import * as fs from 'fs'
import * as os from 'os'
function getVersionShort(versionLong: string): string {
switch (versionLong) {
case '12266719':
return '16.0'
case '11479570':
return '13.0'
case '11076708':
return '12.0'
case '10406996':
return '11.0'
case '9862592':
return '10.0'
case '9477386':
return '9.0'
case '9123335':
return '8.0'
case '8512546':
return '7.0'
default:
return versionLong
}
}
const VERSION_LONG = core.getInput('cmdline-tools-version', {
trimWhitespace: true
})
if (VERSION_LONG.includes('/') || VERSION_LONG.includes('\\')) {
core.setFailed('Malformed cmdline-tools-version!')
throw new Error('Malformed cmdline-tools-version!')
}
const VERSION_SHORT = getVersionShort(VERSION_LONG)
const COMMANDLINE_TOOLS_WIN_URL = `https://dl.google.com/android/repository/commandlinetools-win-${VERSION_LONG}_latest.zip`
const COMMANDLINE_TOOLS_MAC_URL = `https://dl.google.com/android/repository/commandlinetools-mac-${VERSION_LONG}_latest.zip`
const COMMANDLINE_TOOLS_LIN_URL = `https://dl.google.com/android/repository/commandlinetools-linux-${VERSION_LONG}_latest.zip`
const ANDROID_HOME_SDK_DIR = path.join(os.homedir(), '.android', 'sdk')
let ANDROID_SDK_ROOT = process.env['ANDROID_SDK_ROOT'] || ANDROID_HOME_SDK_DIR
async function callSdkManager(
sdkManager: string,
arg: string,
printOutput: Boolean = true
): Promise<void> {
const acceptBuffer = Buffer.from(Array(10).fill('y').join('\n'), 'utf8')
await exec.exec(sdkManager, [arg], {
input: acceptBuffer,
silent: !printOutput
})
}
async function installSdkManager(): Promise<string> {
const cmdlineTools = path.join(
ANDROID_SDK_ROOT,
'cmdline-tools',
VERSION_SHORT
)
let sdkManagerExe = path.join(cmdlineTools, 'bin', 'sdkmanager')
if (!fs.existsSync(sdkManagerExe)) {
const latestCmdlineTools = path.join(
ANDROID_SDK_ROOT,
'cmdline-tools',
'latest'
)
const sourcePropertiesFile = path.join(
latestCmdlineTools,
'source.properties'
)
const latestSdkManagerExe = path.join(
latestCmdlineTools,
'bin',
'sdkmanager'
)
if (
fs.existsSync(latestCmdlineTools) &&
fs.existsSync(sourcePropertiesFile) &&
fs.existsSync(latestSdkManagerExe)
) {
const sourceProperties = fs.readFileSync(sourcePropertiesFile)
core.info(
`Found preinstalled sdkmanager in ${latestCmdlineTools} with following source.properties:`
)
core.info(sourceProperties.toString())
if (sourceProperties.includes(`Pkg.Revision=${VERSION_SHORT}`)) {
core.info(`Preinstalled sdkmanager has the correct version`)
sdkManagerExe = latestSdkManagerExe
} else {
core.info(`Wrong version in preinstalled sdkmanager`)
}
}
}
if (!fs.existsSync(sdkManagerExe)) {
let cmdlineToolsURL
if (process.platform === 'linux') {
cmdlineToolsURL = COMMANDLINE_TOOLS_LIN_URL
} else if (process.platform === 'darwin') {
cmdlineToolsURL = COMMANDLINE_TOOLS_MAC_URL
} else if (process.platform === 'win32') {
cmdlineToolsURL = COMMANDLINE_TOOLS_WIN_URL
} else {
core.error(`Unsupported platform: ${process.platform}`)
return ''
}
core.info(`Downloading commandline tools from ${cmdlineToolsURL}`)
const cmdlineToolsZip = await tc.downloadTool(cmdlineToolsURL)
const extractTo = path.join(ANDROID_SDK_ROOT, 'cmdline-tools')
await tc.extractZip(cmdlineToolsZip, extractTo)
// Make sure we don't have leftover target directory (happens sometimes...)
if (fs.existsSync(cmdlineTools)) {
core.info(`Removing leftovers from ${cmdlineTools}`)
fs.rmSync(cmdlineTools, {recursive: true})
}
fs.renameSync(path.join(extractTo, 'cmdline-tools'), cmdlineTools)
}
// touch $ANDROID_SDK_ROOT/repositories.cfg
fs.closeSync(
fs.openSync(path.join(ANDROID_SDK_ROOT, 'repositories.cfg'), 'w')
)
core.debug(`sdkmanager available at: ${sdkManagerExe}`)
return sdkManagerExe
}
async function run(): Promise<void> {
if ('win16' === process.env['ImageOS']) {
if (-1 !== ANDROID_SDK_ROOT.indexOf(' ')) {
// On Windows2016, Android SDK is installed to Program Files,
// and it doesn't really work..
// C:\windows\system32\cmd.exe /D /S /C ""C:\Program Files (x86)\Android\android-sdk\cmdline-tools\3.0\bin\sdkmanager.bat" --licenses"
// Error: Could not find or load main class Files
const newSDKLocation = ANDROID_SDK_ROOT.replace(/\s/gi, '-')
core.debug(`moving ${ANDROID_SDK_ROOT} to ${newSDKLocation}`)
fs.mkdirSync(path.dirname(newSDKLocation), {recursive: true})
// intentionally using fs.renameSync,
// because it doesn't move across drives
fs.renameSync(ANDROID_SDK_ROOT, newSDKLocation)
ANDROID_SDK_ROOT = newSDKLocation
}
}
const sdkManagerExe = await installSdkManager()
if (core.getBooleanInput('accept-android-sdk-licenses')) {
core.info('Accepting Android SDK licenses')
await callSdkManager(
sdkManagerExe,
'--licenses',
core.getBooleanInput('log-accepted-android-sdk-licenses')
)
}
const packages = core
.getInput('packages', {required: false})
.split(' ')
.map(function (str) {
return str.trim()
})
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
.filter(function (element, index, array) {
return element
})
for (const pkg of packages) {
await callSdkManager(sdkManagerExe, pkg)
}
core.setOutput('ANDROID_COMMANDLINE_TOOLS_VERSION', VERSION_LONG)
core.exportVariable('ANDROID_HOME', ANDROID_SDK_ROOT)
core.exportVariable('ANDROID_SDK_ROOT', ANDROID_SDK_ROOT)
core.addPath(path.dirname(sdkManagerExe))
core.addPath(path.join(ANDROID_SDK_ROOT, 'platform-tools'))
core.debug('add matchers')
// eslint-disable-next-line no-console
console.log(`##[add-matcher]${path.join(__dirname, '..', 'matchers.json')}`)
}
run()
import * as core from '@actions/core'
import * as path from 'path'
import {ANDROID_SDK_ROOT, ANNOTATION_MATCHERS} from './constants'
import {preGradleCache, preAndroidCache, preGradleWrapper} from './cache'
import {install} from './install'
async function run(): Promise<void> {
// process all caching but wait for them to all complete
await Promise.all([preGradleWrapper(), preGradleCache(), preAndroidCache()])
await install()
core.exportVariable('ANDROID_HOME', ANDROID_SDK_ROOT)
core.exportVariable('ANDROID_SDK_ROOT', ANDROID_SDK_ROOT)
core.addPath(path.join(ANDROID_SDK_ROOT, 'tools', 'bin'))
core.addPath(path.join(ANDROID_SDK_ROOT, 'platform-tools'))
core.debug('add matchers')
const matchersPath = path.join(__dirname, '..', '..', '.github')
for (const matcher of ANNOTATION_MATCHERS) {
console.log(`##[add-matcher]${path.join(matchersPath, matcher)}`)
}
}
run()

13
src/post.ts Normal file
View file

@ -0,0 +1,13 @@
import {postAndroidCache, postGradleCache, postGradleWrapper} from './cache'
async function run(): Promise<void> {
await Promise.all([
postGradleCache(),
postGradleWrapper(),
postAndroidCache()
])
return
}
run()

View file

@ -6,8 +6,7 @@
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
"moduleResolution": "node"
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
},
"exclude": ["node_modules", "**/*.test.ts"]
}