Compare commits

...

46 Commits

Author SHA1 Message Date
Daz DeBoer
bebb162342 Usability improvements:
- Include bundle name in cache key
- Emit a few more messages at 'info'
2021-09-15 17:50:53 -06:00
Daz DeBoer
6084a4eb65 Cache artifacts with single entry per type
When caching is too fine-grained, an excessive number of cache
requests can result in HTTP 429 errors due to rate limiting.
By caching all artifacts of a particular type in a single entry
we hope to mitigate this, at the expense of some reduction in
cache space optimization.

This change also adds caching for all dependency jars, as well as
instrumented jars in the 'caches/jars-X' directory.
2021-09-15 17:45:47 -06:00
Daz DeBoer
dbb485d80d Only perform post-restore and pre-save cache actions when required 2021-09-15 17:45:47 -06:00
Daz DeBoer
9bfa003014 Build outputs 2021-09-14 18:11:31 -06:00
Daz DeBoer
fe64d05f86 Do not cache dependency jars individually
Unfortunately, doing this overloads the GitHub actions cache infrastructure
leading to failures and unpredictable results.
A later solution may re-implement artifact sharing for dependency jars
as well as jars within the `caches/jars-9` directory. But for now these
will be duplicated across each Gradle User Home cache entry.
2021-09-14 18:07:28 -06:00
Daz DeBoer
decca791c5 Consolidate error handling for cache restore/save 2021-09-14 13:39:37 -06:00
Daz DeBoer
bd08e7b7cd Do not fail build on error restoring artifact for marker
Instead, catch and report these errors before continuing.
2021-09-14 13:38:48 -06:00
Daz DeBoer
cca55d0890 Fail action execution on unhandled errors
Without this, the error logs contain an "UnhandledPromiseRejectionError"
but the action is reported as succeeding.
2021-09-14 07:48:06 -06:00
Daz DeBoer
a802a3c0ce Generate source-map files when compiling
This will make reported errors easier to link back to Typescript sources.
2021-09-14 05:46:11 -06:00
Daz DeBoer
d06e19f862 Build outputs 2021-09-13 11:50:07 -06:00
Daz DeBoer
bbe1574290 Add some TODOs to the code for future reference 2021-09-13 11:50:07 -06:00
Daz DeBoer
4264cda558 Group log messages generated when provisioning Gradle 2021-09-13 11:50:07 -06:00
Daz DeBoer
3390540145 Simplify setting caches to disabled or read-only 2021-09-13 11:04:42 -06:00
Daz DeBoer
1c72a31463 Allow cache debug logging to be selectively enabled
- Move more messages out of main 'info' log
- Only log cache entry size when debugging
- Process cache entries sequentially when debugging
2021-09-13 10:52:09 -06:00
Daz DeBoer
7dfbe33bba Improve formatting for cache entry size reporting 2021-09-13 10:51:24 -06:00
Daz DeBoer
e63ddf9c00 Avoid warning for cache reservation errors
We have no way of knowing if another build has already cached these
artifacts, so CacheReservationError is expected.
2021-09-13 10:50:47 -06:00
Daz DeBoer
d5cd9d86a1 Split the 'prod' workflow up into different isolated workflows
This is required to ensure that cache entries are namespaced correctly
for the integration tests.
2021-09-12 12:17:05 -06:00
Daz DeBoer
cae99bf6d9 Tidy up cache entry de-duplication
- Rename feature to "common artifact caching"
- Cleanup logging
- Refactor/rename for clarity
2021-09-12 10:31:52 -06:00
Daz DeBoer
5a90152b1f Restore/cache deduplicated files in parallel 2021-09-12 10:31:52 -06:00
Daz DeBoer
4b92b8d013 Cache downloaded dependency jars separately
This will further remove common files out the the Gradle User Home
cache entries, reducing cache usage by removing redundancy.
2021-09-12 10:31:51 -06:00
Daz DeBoer
693293c29a Consolidate logic for separate artifact file caching
Making this functionality more general will make it easier to add other
artifacts to the set that we cache separately.
2021-09-12 10:31:51 -06:00
Daz DeBoer
ac5d8920dd Cache generated-gradle-jars separately from Gradle User Home
Similar to wrapper distributions, these large files are common
to many Gradle User Home cache entries. Storing them separately removes
this redundancy from the Gradle User Home cache.
2021-09-12 10:31:51 -06:00
Daz DeBoer
e833360307 Cache downloaded wrapper dists separately
In the current model, each cached Gradle User Home could contain
a copy of one or more downloaded wrapper distributions. This results
in large cache entries which could easily lead to premature eviction.

With this change, wrapper dists are cached separately from the rest
of the Gradle User Home directory. The artifact file is replaced by
a marker file which allows the action to restore the artifact from
cache when the Gradle user Home cache is restored.
2021-09-12 10:31:51 -06:00
Daz DeBoer
b5a08466b4 Add kotlin-dsl sample to prod workflow tests 2021-09-12 10:30:49 -06:00
Daz DeBoer
4032438d2b Build outputs 2021-09-07 16:26:37 -06:00
Daz DeBoer
9b3abaad52 Use current matrix values for workflow job context 2021-09-07 16:26:28 -06:00
Daz DeBoer
d20d631365 Cleanup code to resolve Gradle wrapper script 2021-09-07 15:17:39 -06:00
Daz DeBoer
378bd0b6f8 Save and restore caches in parallel 2021-09-07 15:17:39 -06:00
Daz DeBoer
6d1455a33e Tidy-up caching code
- Extracted common code for Gradle User Home and Project .gradle caches
  into abstract supertype.
- Improve error handling by checking error types
2021-09-07 15:17:39 -06:00
Daz DeBoer
c44ebadf6f Add build scan link as a notice annotation
- Use 'notice' method introduced in @actions/core v1.5.0
2021-09-05 21:38:34 -06:00
Daz DeBoer
4d37378696 Consolidate cache-enabled options 2021-09-05 21:38:33 -06:00
Daz DeBoer
777a6fc967 Generate cache key based on Job invocation
Attempt to capture as much context as possible about the job run
to generate a unique cache key. Unfortunately much of the matrix context
is not available to the action implementation.
2021-09-05 21:35:17 -06:00
Daz DeBoer
d7ed6d7e8d Extract cache key generation into common function 2021-09-05 17:11:50 -06:00
Daz DeBoer
0ecbac99f3 Group log messsages for restore/save cache 2021-09-03 11:34:22 -06:00
Daz DeBoer
436390bd4e Test coverage: Verify configuration-cache is restored with project-dot-gradle 2021-08-27 12:53:02 -06:00
Daz DeBoer
a587e93714 Test coverage: Verify local build cache is restored with gradle-user-home 2021-08-27 12:53:02 -06:00
Daz DeBoer
75e00ee3d1 Test coverage: Verify dependency caching by running offline build 2021-08-27 12:53:02 -06:00
Daz DeBoer
c01af7a6f6 Test coverage: verify correct Gradle version is being used 2021-08-27 12:53:02 -06:00
Daz DeBoer
c79d4172e0 Build outputs 2021-08-27 12:53:01 -06:00
Daz DeBoer
b85ac67c9a Seed the cache key with workflow id in CI
This will eliminate cache entries from previous workflow runs, allowing
us to test cache functionality in isolation. If the `CACHE_KEY_SEED` environment
variable is not set, this will have no impact.
2021-08-27 12:52:01 -06:00
Daz DeBoer
fa0c026e07 Always execute Gradle with --no-daemon
The Gradle daemon is not useful for ephemeral builds, and the process
can hold file locks which interfere with cache entry generation.

In the case where multiple Gradle invocations occur in the same job,
we could provide a way for users to override this behaviour, taking care
of stopping any daemon process at the end of the job.
2021-08-27 12:21:46 -06:00
Daz DeBoer
986024f0b7 Adapt README and workflow for new caching 2021-08-26 17:25:50 -06:00
Daz DeBoer
6fca6b3929 Add support for read-only caching in v2 2021-08-26 17:25:49 -06:00
Daz DeBoer
d9cc0aeccf Remove old caching code 2021-08-24 12:54:21 -06:00
Daz DeBoer
5340f6e816 Add cache for project .gradle dir
- For now, this is limited to configuration-cache directory
2021-08-24 12:52:51 -06:00
Daz DeBoer
c211be411e Use monolithic cache for Gradle User Home
- Do not restore cache when GUH exists
- Include RUNNER_OS in the cache key
- Do not save cache on exact hit
- Only save cache in the final post action
- Log before saving cache
2021-08-24 12:52:51 -06:00
41 changed files with 9080 additions and 703 deletions

38
.github/workflows/failure-cases.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
# Run builds under certain failure conditions to allow the output to be manually inspected.
# These build invocations are informational only, and are expected to fail
name: failure-cases
on:
pull_request:
push:
workflow_dispatch:
env:
CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-
jobs:
wrapper-missing:
continue-on-error: true
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Test wrapper missing
uses: ./
with:
build-root-directory: __tests__/samples/no-wrapper
arguments: help
bad-configuration:
continue-on-error: true
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Test bad config value
uses: ./
with:
build-root-directory: __tests__/samples/no-wrapper
arguments: help
cache-disabled: yes

View File

@@ -0,0 +1,37 @@
# Make sure the action works on a clean machine without building
name: integration-testing-kotlin-dsl
on:
pull_request:
push:
workflow_dispatch:
env:
CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-
CACHE_DEBUG_ENABLED: true
jobs:
# Use kotlin-dsl project to verify caching of generated jars and compiled scripts
seed-build:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Build kotlin-dsl project
uses: ./
with:
build-root-directory: __tests__/samples/kotlin-dsl
arguments: test
# Check that the build can run --offline
verify-build:
needs: seed-build
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Build kotlin-dsl project
uses: ./
with:
build-root-directory: __tests__/samples/kotlin-dsl
arguments: test --offline

View File

@@ -1,13 +1,18 @@
# make sure the action works on a clean machine without building
name: prod
# Verify the functionality works as expected
name: integration-testing
on:
pull_request:
push:
workflow_dispatch:
env:
CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-
jobs:
basic-build:
# Run initial Gradle builds to push initial cache entries
# These builds should start fresh without cache hits, due to the seed injected into the cache key above.
seed-build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
@@ -20,9 +25,16 @@ jobs:
with:
build-root-directory: __tests__/samples/basic
arguments: test
- name: Build with configuration-cache enabled
uses: ./
with:
build-root-directory: __tests__/samples/basic
arguments: test --configuration-cache
# Tests for executing with different Gradle versions.
# Each build verifies that it is executed with the expected Gradle version.
gradle-execution:
needs: basic-build
needs: seed-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
@@ -38,87 +50,72 @@ jobs:
with:
gradle-version: 6.9
build-root-directory: __tests__/samples/no-wrapper
arguments: help
arguments: help -DgradleVersionCheck=6.9
cache-read-only: true
- name: Test use Gradle version alias
uses: ./
with:
gradle-version: release-candidate
build-root-directory: __tests__/samples/no-wrapper
arguments: help
arguments: help -DgradleVersionCheck=7.2
cache-read-only: true
- name: Test use defined Gradle executable
uses: ./
with:
gradle-executable: __tests__/samples/basic/gradlew${{ matrix.script-suffix }}
build-root-directory: __tests__/samples/no-wrapper
arguments: help
- name: Test custom wrapper location (deprecated)
uses: ./
with:
build-root-directory: __tests__/samples/no-wrapper
wrapper-directory: __tests__/samples/basic
arguments: help
dependencies-cache:
needs: basic-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Test dependencies-cache-enabled
uses: ./
with:
build-root-directory: __tests__/samples/basic
arguments: test --no-daemon
dependencies-cache-enabled: true
configuration-cache:
needs: basic-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Test configuration-cache-enabled
uses: ./
with:
build-root-directory: __tests__/samples/basic
arguments: test --configuration-cache --no-daemon
configuration-cache-enabled: true
dependencies-cache-enabled: true
# Configuration cache requires dependencies cache, since it assumes dependencies are already downloaded.
cache-read-only:
needs: basic-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Test cache-read-only
uses: ./
with:
build-root-directory: __tests__/samples/basic
arguments: test --no-daemon
dependencies-cache-enabled: true
configuration-cache-enabled: true
arguments: help -DgradleVersionCheck=7.1.1
cache-read-only: true
failures: # These build invocations are informational only, and are expected to fail
needs: basic-build
runs-on: ubuntu-latest
# Test that the gradle-user-home cache will cache dependencies, by running build with --offline
dependencies-cache:
needs: seed-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Test wrapper missing
- name: Execute Gradle build with --offline
uses: ./
continue-on-error: true
with:
build-root-directory: __tests__/samples/no-wrapper
arguments: help
build-root-directory: __tests__/samples/basic
arguments: test --offline
cache-read-only: true
# Test that the gradle-user-home cache will cache and restore local build-cache
build-cache:
needs: seed-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Execute Gradle build and verify tasks from cache
uses: ./
with:
build-root-directory: __tests__/samples/basic
arguments: test -DverifyCachedBuild=true
cache-read-only: true
# Test that the project-dot-gradle cache will cache and restore configuration-cache
configuration-cache:
needs: seed-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Execute Gradle build and verify cached configuration
uses: ./
env:
VERIFY_CACHED_CONFIGURATION: true
with:
build-root-directory: __tests__/samples/basic
arguments: test --configuration-cache
cache-read-only: true

View File

@@ -74,8 +74,6 @@ If you need to pass environment variables, simply use the GitHub Actions workflo
gradle-executable: path/to/gradlew
```
NOTE: The `wrapper-directory` input has been deprecated. Use `gradle-executable` instead.
## Setup and use a declared Gradle version
```yaml
@@ -122,81 +120,39 @@ jobs:
This action provides 3 levels of caching to help speed up your GitHub Actions:
- `distributions` caches any downloaded Gradle zips, including any downloaded [wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html) versions, saving time downloading Gradle distributions ;
- `dependencies` caches the [dependencies](https://docs.gradle.org/current/userguide/dependency_resolution.html#sub:cache_copy), saving time downloading dependencies ;
- `configuration` caches the [build configuration](https://docs.gradle.org/nightly/userguide/configuration_cache.html), saving time configuring the build.
- `distributions` caches any distributions downloaded to satisfy a `gradle-version` parameter ;
- `gradle-user-home` caches downloaded dependencies, wrapper distributions, and other stuff from the Gradle User home directory ;
- `project-dot-gradle` caches stored [configuration-cache](https://docs.gradle.org/nightly/userguide/configuration_cache.html) state, saving time configuring the build.
Only the first one, caching downloaded distributions, is enabled by default.
Future versions of this action will enable all caching by default.
You can control which level is enabled as follows:
Each of these are enabled by default. To save caching space, you can disable any of them as follows:
```yaml
distributions-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
gradle-user-home-cache-enabled: true
project-dot-gradle-cache-enabled: true
```
NOTE: The `wrapper-cache-enabled` flag has been deprecated, replaced by `distributions-cache-enabled` which enables caching for all downloaded distributions, including Gradle wrapper downloads.
The distributions cache uses a cache key that is unique to the downloaded distribution. This will not change over time.
The distributions cache is simple and can't be configured further.
The `gradle-user-home` and `project-dot-gradle` caches compute a cache key based on the current commit and the Gradle invocation.
As such, these are likely to change on each subsequent run of GitHub actions, allowing the most recent state to always be available in the GitHub actions cache.
The dependencies and configuration cache will compute a cache key in a best effort manner.
Keep reading to learn how to better control how they work.
By default, this action aims to cache any and all reusable state that may be speed up a subsequent build invocation.
Note that enabling configuration cache without the dependencies cache is not permitted, since a hit in the configuration cache assumes that dependencies are already present in the local dependencies cache.
### Configuring the dependencies and configuration caches
Both the dependencies and configuration caches use the same default configuration:
They use the following inputs to calculate the cache key:
```text
**/*.gradle
**/*.gradle.kts
**/gradle.properties
gradle/**
```
This is a good enough approximation.
They restore cached state even if there isn't an exact match.
If the defaults don't suit your needs you can override them with the following inputs:
```yaml
dependencies-cache-key: |
**/gradle.properties
gradle/dependency-locks/**
dependencies-cache-exact: true
configuration-cache-key: |
**/gradle.properties
gradle/dependency-locks/**
configuration-cache-exact: true
```
Coming up with a good cache key isn't trivial and depends on your build.
The above example isn't realistic.
Stick to the defaults unless you know what you are doing.
If you happen to use Gradle [dependency locking](https://docs.gradle.org/current/userguide/dependency_locking.html) you can make the dependencies cache more precise with the following configuration:
```yaml
dependencies-cache-enabled: true
dependencies-cache-key: gradle/dependency-locks/**
dependencies-cache-exact: true
```
At this time it is not possible to fine-tune this caching. If you have a legitimate use case for fine-grained caching or restricting which files are cached, please raise an issue.
### Using the caches read-only
Cache storage space is limited for GitHub actions, and writing new cache entries can trigger the deletion of exising entries.
In some circumstances, it makes sense for a Gradle invocation to use any existing cache entries but not to write and changes back.
In some circumstances, it makes sense for a Gradle invocation to read any existing cache entries but not to write changes back.
For example, you may want to write cache entries for builds on your `main` branch, but not for any PR build invocations.
Use the following configuration to avoid writing cache entries for the action invocation:
You can enable read-only caching for any of the caches asfollows:
```yaml
cache-read-only: true
distributions-cache-enabled: read-only
gradle-user-home-cache-enabled: read-only
project-dot-gradle-cache-enabled: read-only
```
## Build scans

View File

@@ -3,63 +3,13 @@ import * as path from 'path'
describe('cacheUtils-utils', () => {
describe('can hash', () => {
it('a directory', async () => {
const hash = await cacheUtils.hashFiles(
path.resolve('__tests__/data/crypto-utils-test/gradle')
)
expect(hash).toBe(
process.platform === 'win32'
? '3364336e94e746ce65a31748a6371b7efd7d499e18ad605c74c91cde0edc0a44'
: '63b9f14f65d014e585099c9c274b9dcbddf5cfd1a8978e5a24efb89ff9304348'
)
it('a string', async () => {
const hash = cacheUtils.hashStrings(['foo'])
expect(hash).toBe('acbd18db4cc2f85cedef654fccc4a4d8')
})
it('a directory with a glob', async () => {
const hash = await cacheUtils.hashFiles(
path.resolve('__tests__/data/crypto-utils-test/'),
['gradle/**']
)
expect(hash).toBe(
process.platform === 'win32'
? '3364336e94e746ce65a31748a6371b7efd7d499e18ad605c74c91cde0edc0a44'
: '63b9f14f65d014e585099c9c274b9dcbddf5cfd1a8978e5a24efb89ff9304348'
)
})
it('a directory with globs', async () => {
const hash = await cacheUtils.hashFiles(
path.resolve('__tests__/data/crypto-utils-test/'),
['**/*.gradle', 'gradle/**']
)
expect(hash).toBe(
process.platform === 'win32'
? 'd9b66fded38f79f601ce745d64ed726a8df8c0b242b02bcd2c1d331f54742ad6'
: 'f42cd10636f09799f4e01cc84e7ae906cc1d9140f1446f8dcd054d19cbc44c2b'
)
})
})
describe('can truncate args', () => {
test('handles zero-length string', () => {
expect(cacheUtils.truncateArgs('')).toBe('')
})
test('leaves short string untouched', () => {
expect(
cacheUtils.truncateArgs('short string that-should-be-untouched')
).toBe('short string that-should-be-untouched')
})
test('truncates long string', () => {
const longString = 'a'.repeat(500)
expect(cacheUtils.truncateArgs(longString)).toBe('a'.repeat(400))
})
test('trims leading and trailing whitespace', () => {
expect(cacheUtils.truncateArgs(' this is an arg ')).toBe(
'this is an arg'
)
})
test('removes repeated whitespace', () => {
expect(
cacheUtils.truncateArgs(
' this one has long \t\n\t\r spaces '
)
).toBe('this one has long spaces')
it('multiple strings', async () => {
const hash = cacheUtils.hashStrings(['foo', 'bar', 'baz'])
expect(hash).toBe('6df23dc03f9b54cc38a0fc1483df6e21')
})
})
})

View File

@@ -1,33 +0,0 @@
import * as cacheWrapper from '../src/cache-wrapper'
import * as path from 'path'
describe('cache', () => {
describe('can extract gradle wrapper slug', () => {
it('from wrapper properties file', async () => {
const version = cacheWrapper.extractGradleWrapperSlugFrom(
path.resolve(
'__tests__/data/cache-wrapper-test/gradle-wrapper.properties'
)
)
expect(version).toBe('6.6.1-bin')
})
it('for -bin dist', async () => {
const version = cacheWrapper.extractGradleWrapperSlugFromDistUri(
'distributionUrl=https\\://services.gradle.org/distributions/gradle-6.6.1-bin.zip'
)
expect(version).toBe('6.6.1-bin')
})
it('for -all dist', async () => {
const version = cacheWrapper.extractGradleWrapperSlugFromDistUri(
'distributionUrl=https\\://services.gradle.org/distributions/gradle-6.6.1-all.zip'
)
expect(version).toBe('6.6.1-all')
})
it('for milestone', async () => {
const version = cacheWrapper.extractGradleWrapperSlugFromDistUri(
'distributionUrl=https\\://services.gradle.org/distributions/gradle-6.6-milestone-1-all.zip'
)
expect(version).toBe('6.6-milestone-1-all')
})
})
})

View File

@@ -9,3 +9,15 @@ repositories {
dependencies {
testImplementation('junit:junit:4.12')
}
tasks.named("test").configure {
// Use an environment variable to bypass config-cache checks
if (System.getenv('VERIFY_CACHED_CONFIGURATION') != null) {
throw new RuntimeException("Configuration was not cached: unexpected configuration of test task")
}
doLast {
if (System.properties.verifyCachedBuild) {
throw new RuntimeException("Build was not cached: unexpected execution of test task")
}
}
}

View File

@@ -0,0 +1 @@
org.gradle.caching=true

View File

@@ -0,0 +1,6 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf

View File

@@ -0,0 +1,5 @@
# Ignore Gradle project-specific cache directory
.gradle
# Ignore Gradle build output directory
build

View File

@@ -0,0 +1,18 @@
plugins {
`java-library`
}
repositories {
mavenCentral()
}
dependencies {
api("org.apache.commons:commons-math3:3.6.1")
implementation("com.google.guava:guava:30.1.1-jre")
testImplementation("org.junit.jupiter:junit-jupiter:5.7.2")
}
tasks.test {
useJUnitPlatform()
}

View File

@@ -0,0 +1 @@
org.gradle.caching=true

Binary file not shown.

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

234
__tests__/samples/kotlin-dsl/gradlew vendored Executable file
View File

@@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,15 @@
plugins {
id("com.gradle.enterprise") version("3.6.4")
}
gradleEnterprise {
buildScan {
termsOfServiceUrl = "https://gradle.com/terms-of-service"
termsOfServiceAgree = "yes"
publishAlways()
isUploadInBackground = false
}
}
rootProject.name = "kotlin-dsl"

View File

@@ -0,0 +1,10 @@
/*
* This Java source file was generated by the Gradle 'init' task.
*/
package com.example;
public class Library {
public boolean someLibraryMethod() {
return true;
}
}

View File

@@ -0,0 +1,14 @@
/*
* This Java source file was generated by the Gradle 'init' task.
*/
package com.example;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class LibraryTest {
@Test void someLibraryMethodReturnsTrue() {
Library classUnderTest = new Library();
assertTrue(classUnderTest.someLibraryMethod(), "someLibraryMethod should return 'true'");
}
}

View File

@@ -1,3 +1,8 @@
rootProject.name = 'no-wrapper'
println "Using Gradle version: ${gradle.gradleVersion}"
println "Using Gradle version: ${gradle.gradleVersion}"
def gradleVersionCheck = System.properties.gradleVersionCheck
if (gradleVersionCheck && gradle.gradleVersion != gradleVersionCheck) {
throw new RuntimeException("Got the wrong version: expected ${gradleVersionCheck} but was ${gradle.gradleVersion}")
}

View File

@@ -4,10 +4,6 @@ description: 'Executes a Gradle build, caching useful state in the GitHub action
# https://help.github.com/en/articles/metadata-syntax-for-github-actions
inputs:
wrapper-directory:
description: Path to the Gradle Wrapper directory
required: false
deprecationMessage: Use 'gradle-executable' to point to a gradlew[.bat] file in a non-default location
gradle-executable:
description: Path to the Gradle executable
required: false
@@ -20,41 +16,20 @@ inputs:
arguments:
description: Gradle command line arguments, see gradle --help
required: false
distributions-cache-enabled:
description: Whether caching downloaded Gradle distributions is enabled or not, default to 'true'
required: false
default: true
wrapper-cache-enabled:
description: Whether caching wrapper installation is enabled or not, default to 'true'
required: false
default: true
deprecationMessage: Replaced by 'distributions-cache-enabled' which enables caching for all downloaded Gradle distributions
dependencies-cache-enabled:
description: Whether caching dependencies is enabled or not, default to 'false'
required: false
default: false
dependencies-cache-key:
description: Globs of files to hash in the build root directory, separated by new lines, use best-effort if unset
required: false
dependencies-cache-exact:
description: Whether to restore only if exact match, default to 'false'
required: false
default: false
configuration-cache-enabled:
description: Whether caching build configuration is enabled or not, default to 'false'
required: false
default: false
configuration-cache-key:
description: Globs of files to hash in the build root directory, separated by new lines, use best-effort if unset
required: false
configuration-cache-exact:
description: Whether to restore only if exact match, default to 'false'
cache-disabled:
description: When 'true', all caching is disabled. No entries will be written to or read from the cache.
required: false
default: false
cache-read-only:
description: When 'true', existing entries will be read from the cache but no entries will be written
required: false
default: false
# TODO: It might be useful to default to read-only for PRs, or non-main branch.
default: false
workflow-job-context:
description: Used to uniquely identify the current job invocation. Defaults to the matrix values for this job; this should not be overridden by users.
required: false
default: ${{ toJSON(matrix) }}
outputs:
build-scan-url:

3
dist/main/index.js vendored

File diff suppressed because one or more lines are too long

1
dist/main/index.js.map vendored Normal file

File diff suppressed because one or more lines are too long

3912
dist/main/sourcemap-register.js vendored Normal file

File diff suppressed because it is too large Load Diff

3
dist/post/index.js vendored

File diff suppressed because one or more lines are too long

1
dist/post/index.js.map vendored Normal file

File diff suppressed because one or more lines are too long

3912
dist/post/sourcemap-register.js vendored Normal file

File diff suppressed because it is too large Load Diff

145
package-lock.json generated
View File

@@ -32,9 +32,9 @@
}
},
"@actions/core": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.4.0.tgz",
"integrity": "sha512-CGx2ilGq5i7zSLgiiGUtBCxhRRxibJYU6Fim0Q1Wg2aQL2LTnF27zbqZOrxfvFQ55eSBW0L8uVStgtKMpa0Qlg=="
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.5.0.tgz",
"integrity": "sha512-eDOLH1Nq9zh+PJlYLqEMkS/jLQxhksPNmUGNBHfa4G+tQmnIhzpctxmchETtVGyBOvXgOVVpYuE40+eS4cUnwQ=="
},
"@actions/exec": {
"version": "1.1.0",
@@ -44,6 +44,17 @@
"@actions/io": "^1.0.1"
}
},
"@actions/github": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.0.tgz",
"integrity": "sha512-QvE9eAAfEsS+yOOk0cylLBIO/d6WyWIOvsxxzdrPFaud39G6BOkUwScXZn1iBzQzHyu9SBkkLSWlohDWdsasAQ==",
"requires": {
"@actions/http-client": "^1.0.11",
"@octokit/core": "^3.4.0",
"@octokit/plugin-paginate-rest": "^2.13.3",
"@octokit/plugin-rest-endpoint-methods": "^5.1.1"
}
},
"@actions/glob": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.2.0.tgz",
@@ -1094,6 +1105,115 @@
"fastq": "^1.6.0"
}
},
"@octokit/auth-token": {
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.5.tgz",
"integrity": "sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==",
"requires": {
"@octokit/types": "^6.0.3"
}
},
"@octokit/core": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz",
"integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==",
"requires": {
"@octokit/auth-token": "^2.4.4",
"@octokit/graphql": "^4.5.8",
"@octokit/request": "^5.6.0",
"@octokit/request-error": "^2.0.5",
"@octokit/types": "^6.0.3",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/endpoint": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
"requires": {
"@octokit/types": "^6.0.3",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
},
"dependencies": {
"is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
}
}
},
"@octokit/graphql": {
"version": "4.6.4",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.6.4.tgz",
"integrity": "sha512-SWTdXsVheRmlotWNjKzPOb6Js6tjSqA2a8z9+glDJng0Aqjzti8MEWOtuT8ZSu6wHnci7LZNuarE87+WJBG4vg==",
"requires": {
"@octokit/request": "^5.6.0",
"@octokit/types": "^6.0.3",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/openapi-types": {
"version": "9.7.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-9.7.0.tgz",
"integrity": "sha512-TUJ16DJU8mekne6+KVcMV5g6g/rJlrnIKn7aALG9QrNpnEipFc1xjoarh0PKaAWf2Hf+HwthRKYt+9mCm5RsRg=="
},
"@octokit/plugin-paginate-rest": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.15.1.tgz",
"integrity": "sha512-47r52KkhQDkmvUKZqXzA1lKvcyJEfYh3TKAIe5+EzMeyDM3d+/s5v11i2gTk8/n6No6DPi3k5Ind6wtDbo/AEg==",
"requires": {
"@octokit/types": "^6.24.0"
}
},
"@octokit/plugin-rest-endpoint-methods": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.8.0.tgz",
"integrity": "sha512-qeLZZLotNkoq+it6F+xahydkkbnvSK0iDjlXFo3jNTB+Ss0qIbYQb9V/soKLMkgGw8Q2sHjY5YEXiA47IVPp4A==",
"requires": {
"@octokit/types": "^6.25.0",
"deprecation": "^2.3.1"
}
},
"@octokit/request": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.1.tgz",
"integrity": "sha512-Ls2cfs1OfXaOKzkcxnqw5MR6drMA/zWX/LIS/p8Yjdz7QKTPQLMsB3R+OvoxE6XnXeXEE2X7xe4G4l4X0gRiKQ==",
"requires": {
"@octokit/endpoint": "^6.0.1",
"@octokit/request-error": "^2.1.0",
"@octokit/types": "^6.16.1",
"is-plain-object": "^5.0.0",
"node-fetch": "^2.6.1",
"universal-user-agent": "^6.0.0"
},
"dependencies": {
"is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
}
}
},
"@octokit/request-error": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
"requires": {
"@octokit/types": "^6.0.3",
"deprecation": "^2.0.0",
"once": "^1.4.0"
}
},
"@octokit/types": {
"version": "6.25.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.25.0.tgz",
"integrity": "sha512-bNvyQKfngvAd/08COlYIN54nRgxskmejgywodizQNyiKoXmWRAjKup2/LYwm+T9V0gsKH6tuld1gM0PzmOiB4Q==",
"requires": {
"@octokit/openapi-types": "^9.5.0"
}
},
"@opencensus/web-types": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@opencensus/web-types/-/web-types-0.0.7.tgz",
@@ -1807,6 +1927,11 @@
"tweetnacl": "^0.14.3"
}
},
"before-after-hook": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz",
"integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ=="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -2202,6 +2327,11 @@
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"deprecation": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
},
"detect-newline": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@@ -5128,7 +5258,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
@@ -6554,6 +6683,11 @@
"set-value": "^2.0.1"
}
},
"universal-user-agent": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
},
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
@@ -6778,8 +6912,7 @@
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"write-file-atomic": {
"version": "3.0.3",

View File

@@ -7,7 +7,7 @@
"format": "prettier --write **/*.ts",
"format-check": "prettier --check **/*.ts",
"lint": "eslint src/**/*.ts",
"build": "ncc build src/main.ts --out dist/main --minify && ncc build src/post.ts --out dist/post --minify",
"build": "ncc build src/main.ts --out dist/main --source-map --minify && ncc build src/post.ts --out dist/post --source-map --minify",
"test": "jest",
"all": "npm run format && npm run lint && npm run build && npm test"
},
@@ -25,8 +25,9 @@
"license": "MIT",
"dependencies": {
"@actions/cache": "1.0.7",
"@actions/core": "1.4.0",
"@actions/core": "1.5.0",
"@actions/exec": "1.1.0",
"@actions/github": "5.0.0",
"@actions/glob": "0.2.0",
"@actions/http-client": "1.0.11",
"@actions/tool-cache": "1.7.1",

View File

@@ -1,113 +0,0 @@
import * as path from 'path'
import * as fs from 'fs'
import * as core from '@actions/core'
import * as cache from '@actions/cache'
import * as cacheUtils from './cache-utils'
import {
inputCacheKeyGlobs,
tryDeleteFiles,
isDependenciesCacheDisabled
} from './cache-dependencies'
const CONFIGURATION_CACHE_PATH = 'CONFIGURATION_CACHE_PATH'
const CONFIGURATION_CACHE_KEY = 'CONFIGURATION_CACHE_KEY'
const CONFIGURATION_CACHE_RESULT = 'CONFIGURATION_CACHE_RESULT'
export async function restoreCachedConfiguration(
rootDir: string
): Promise<void> {
if (isConfigurationCacheDisabled()) return
if (isDependenciesCacheDisabled()) {
throw new Error(
`Must enable dependencies-cache when configuration-cache is enabled`
)
}
const cachePath = path.resolve(rootDir, '.gradle/configuration-cache')
if (fs.existsSync(cachePath)) return
core.saveState(CONFIGURATION_CACHE_PATH, cachePath)
const inputCacheExact = core.getBooleanInput('configuration-cache-exact')
const cacheKeyPrefix = 'configuration|'
const args = core.getInput('arguments')
const argsKey = cacheUtils.truncateArgs(args)
const cacheKeyWithArgs = `${cacheKeyPrefix}${argsKey}|`
const cacheKeyGlobs = inputCacheKeyGlobs('configuration-cache-key')
const hash = await cacheUtils.hashFiles(rootDir, cacheKeyGlobs)
const cacheKey = `${cacheKeyWithArgs}${hash}`
core.saveState(CONFIGURATION_CACHE_KEY, cacheKey)
const cacheResult = await cache.restoreCache(
[cachePath],
cacheKey,
inputCacheExact ? [] : [cacheKeyWithArgs, cacheKeyPrefix]
)
if (!cacheResult) {
core.info(
'Configuration cache not found, expect task graph calculation.'
)
return
}
core.saveState(CONFIGURATION_CACHE_RESULT, cacheResult)
core.info(`Configuration restored from cache key: ${cacheResult}`)
return
}
export async function cacheConfiguration(): Promise<void> {
if (isConfigurationCacheDisabled()) return
const cachePath = core.getState(CONFIGURATION_CACHE_PATH)
const cacheKey = core.getState(CONFIGURATION_CACHE_KEY)
const cacheResult = core.getState(CONFIGURATION_CACHE_RESULT)
if (!cachePath || !fs.existsSync(cachePath)) {
core.debug('No configuration to cache.')
return
}
if (cacheResult && cacheKey === cacheResult) {
core.info(
`Configuration cache hit occurred on the cache key ${cacheKey}, not saving cache.`
)
return
}
const locksDeleted = tryDeleteFiles([
path.resolve(cachePath, 'configuration-cache.lock')
])
if (!locksDeleted) {
core.warning(
'Unable to delete configuration lock files, try using --no-daemon or stopping the daemon last if you have several Gradle steps, not saving cache.'
)
return
}
core.info(`Will cache configuration with key ${cacheKey}`)
try {
await cache.saveCache([cachePath], cacheKey)
} catch (error) {
if (error.name === cache.ValidationError.name) {
throw error
} else if (error.name === cache.ReserveCacheError.name) {
core.info(error.message)
} else {
core.info(`[warning] ${error.message}`)
}
}
return
}
function isConfigurationCacheDisabled(): boolean {
return !core.getBooleanInput('configuration-cache-enabled')
}

View File

@@ -1,126 +0,0 @@
import * as path from 'path'
import * as fs from 'fs'
import * as os from 'os'
import * as core from '@actions/core'
import * as cache from '@actions/cache'
import * as cacheUtils from './cache-utils'
const DEPENDENCIES_CACHE_PATH = 'DEPENDENCIES_CACHE_PATH'
const DEPENDENCIES_CACHE_KEY = 'DEPENDENCIES_CACHE_KEY'
const DEPENDENCIES_CACHE_RESULT = 'DEPENDENCIES_CACHE_RESULT'
export async function restoreCachedDependencies(
rootDir: string
): Promise<void> {
if (isDependenciesCacheDisabled()) return
const cachePath = path.resolve(os.homedir(), '.gradle/caches/modules-2')
if (fs.existsSync(cachePath)) return
core.saveState(DEPENDENCIES_CACHE_PATH, cachePath)
const inputCacheExact = core.getBooleanInput('dependencies-cache-exact')
const cacheKeyPrefix = 'dependencies|'
const args = core.getInput('arguments')
const argsKey = cacheUtils.truncateArgs(args)
const cacheKeyWithArgs = `${cacheKeyPrefix}${argsKey}|`
const cacheKeyGlobs = inputCacheKeyGlobs('dependencies-cache-key')
const hash = await cacheUtils.hashFiles(rootDir, cacheKeyGlobs)
const cacheKey = `${cacheKeyWithArgs}${hash}`
core.saveState(DEPENDENCIES_CACHE_KEY, cacheKey)
const cacheResult = await cache.restoreCache(
[cachePath],
cacheKey,
inputCacheExact ? [] : [cacheKeyWithArgs, cacheKeyPrefix]
)
if (!cacheResult) {
core.info('Dependencies cache not found, expect dependencies download.')
return
}
core.saveState(DEPENDENCIES_CACHE_RESULT, cacheResult)
core.info(`Dependencies restored from cache key: ${cacheResult}`)
return
}
export async function cacheDependencies(): Promise<void> {
if (isDependenciesCacheDisabled()) return
const cachePath = core.getState(DEPENDENCIES_CACHE_PATH)
const cacheKey = core.getState(DEPENDENCIES_CACHE_KEY)
const cacheResult = core.getState(DEPENDENCIES_CACHE_RESULT)
if (!cachePath || !fs.existsSync(cachePath)) {
core.debug('No dependencies to cache.')
return
}
if (cacheResult && cacheKey === cacheResult) {
core.info(
`Dependencies cache hit occurred on the cache key ${cacheKey}, not saving cache.`
)
return
}
const locksDeleted = tryDeleteFiles([
path.resolve(cachePath, 'modules-2.lock')
])
if (!locksDeleted) {
core.warning(
'Unable to delete dependencies lock files, try using --no-daemon or stopping the daemon last if you have several Gradle steps, not saving cache.'
)
return
}
core.info(`Will cache dependencies with key ${cacheKey}`)
try {
await cache.saveCache([cachePath], cacheKey)
} catch (error) {
if (error.name === cache.ValidationError.name) {
throw error
} else if (error.name === cache.ReserveCacheError.name) {
core.info(error.message)
} else {
core.info(`[warning] ${error.message}`)
}
}
return
}
export function tryDeleteFiles(filePaths: string[]): boolean {
let failure = false
for (const filePath of filePaths) {
if (fs.existsSync(filePath)) {
try {
fs.unlinkSync(filePath)
} catch (error) {
failure = true
}
}
}
return !failure
}
export function isDependenciesCacheDisabled(): boolean {
return !core.getBooleanInput('dependencies-cache-enabled')
}
export function inputCacheKeyGlobs(input: string): string[] {
const inputValue = core.getMultilineInput(input)
return inputValue.length > 0
? inputValue
: [
'**/*.gradle',
'**/*.gradle.kts',
'**/gradle.properties',
'gradle/**'
]
}

View File

@@ -0,0 +1,192 @@
import path from 'path'
import fs from 'fs'
import os from 'os'
import * as core from '@actions/core'
import * as glob from '@actions/glob'
import * as exec from '@actions/exec'
import {AbstractCache, hashStrings} from './cache-utils'
// Which paths under Gradle User Home should be cached
// TODO: This should adapt for the `GRADLE_USER_HOME` environment variable
// TODO: Allow the user to override / tweak this set
const CACHE_PATH = ['~/.gradle/caches', '~/.gradle/notifications']
const COMMON_ARTIFACT_CACHES = new Map([
['generated-gradle-jars', '~/.gradle/caches/*/generated-gradle-jars/*.jar'],
['wrapper-zips', '~/.gradle/wrapper/dists/*/*/*.zip'],
['dependency-jars', '~/.gradle/caches/modules-*/files-*/**/*.jar'],
['instrumented-jars', '~/.gradle/caches/jars-*/*/*.jar']
])
export class GradleUserHomeCache extends AbstractCache {
constructor() {
super('gradle', 'Gradle User Home')
}
async afterRestore(): Promise<void> {
await this.reportCacheEntrySize('as restored from cache')
await this.restoreCommonArtifacts()
await this.reportCacheEntrySize('after restoring common artifacts')
}
private async restoreCommonArtifacts(): Promise<void> {
const processes: Promise<void>[] = []
for (const [bundle, pattern] of COMMON_ARTIFACT_CACHES) {
const p = this.restoreCommonArtifactBundle(bundle, pattern)
// Run sequentially when debugging enabled
if (this.cacheDebuggingEnabled) {
await p
}
processes.push(p)
}
await Promise.all(processes)
}
private async restoreCommonArtifactBundle(
bundle: string,
pattern: string
): Promise<void> {
const cacheMetaFile = this.getCacheMetaFile(bundle)
if (fs.existsSync(cacheMetaFile)) {
const cacheKey = fs.readFileSync(cacheMetaFile, 'utf-8').trim()
const restoreKey = await this.restoreCache([pattern], cacheKey)
if (restoreKey) {
core.info(
`Restored ${bundle} with key ${cacheKey} to ${pattern}`
)
} else {
this.debug(
`Failed to restore ${bundle} with key ${cacheKey} to ${pattern}`
)
}
} else {
this.debug(
`No metafile found to restore ${bundle}: ${cacheMetaFile}`
)
}
}
private getCacheMetaFile(name: string): string {
return path.resolve(
this.getGradleUserHome(),
'caches',
`.gradle-build-action.${name}.cache`
)
}
private async reportCacheEntrySize(label: string): Promise<void> {
if (!this.cacheDebuggingEnabled) {
return
}
const gradleUserHome = path.resolve(os.homedir(), '.gradle')
if (!fs.existsSync(gradleUserHome)) {
return
}
const result = await exec.getExecOutput(
'du',
['-h', '-c', '-t', '5M'],
{
cwd: gradleUserHome,
silent: true,
ignoreReturnCode: true
}
)
core.info(`Gradle User Home cache entry (directories >5M): ${label}`)
core.info(
result.stdout
.trimEnd()
.replace(/\t/g, ' ')
.split('\n')
.map(it => {
return ` ${it}`
})
.join('\n')
)
core.info('-----------------------')
}
async beforeSave(): Promise<void> {
await this.saveCommonArtifacts()
}
private async saveCommonArtifacts(): Promise<void> {
const processes: Promise<void>[] = []
for (const [bundle, pattern] of COMMON_ARTIFACT_CACHES) {
const p = this.saveCommonArtifactBundle(bundle, pattern)
// Run sequentially when debugging enabled
if (this.cacheDebuggingEnabled) {
await p
}
processes.push(p)
}
await Promise.all(processes)
}
private async saveCommonArtifactBundle(
bundle: string,
pattern: string
): Promise<void> {
const cacheMetaFile = this.getCacheMetaFile(bundle)
const globber = await glob.create(pattern)
const commonArtifactFiles = await globber.glob()
// Handle no matching files
if (commonArtifactFiles.length === 0) {
this.debug(`No files found to cache for ${bundle}`)
if (fs.existsSync(cacheMetaFile)) {
fs.unlinkSync(cacheMetaFile)
}
return
}
const previouslyRestoredKey = fs.existsSync(cacheMetaFile)
? fs.readFileSync(cacheMetaFile, 'utf-8').trim()
: ''
const cacheKey = this.createCacheKey(
bundle,
hashStrings(commonArtifactFiles)
)
if (previouslyRestoredKey === cacheKey) {
this.debug(
`No change to previously restored ${bundle}. Not caching.`
)
} else {
core.info(`Caching ${bundle} with cache key: ${cacheKey}`)
await this.saveCache([pattern], cacheKey)
this.debug(`Writing cache metafile: ${cacheMetaFile}`)
fs.writeFileSync(cacheMetaFile, cacheKey)
}
for (const file of commonArtifactFiles) {
fs.unlinkSync(file)
}
}
protected createCacheKey(bundle: string, key: string): string {
const cacheKeyPrefix = process.env['CACHE_KEY_PREFIX'] || ''
return `${cacheKeyPrefix}${bundle}-${key}`
}
protected getGradleUserHome(): string {
return path.resolve(os.homedir(), '.gradle')
}
protected cacheOutputExists(): boolean {
// Need to check for 'caches' directory to avoid incorrect detection on MacOS agents
const dir = path.resolve(this.getGradleUserHome(), 'caches')
return fs.existsSync(dir)
}
protected getCachePath(): string[] {
return CACHE_PATH
}
}

View File

@@ -0,0 +1,30 @@
import path from 'path'
import fs from 'fs'
import {AbstractCache} from './cache-utils'
// TODO: Maybe allow the user to override / tweak this set
const PATHS_TO_CACHE = [
'configuration-cache' // Only configuration-cache is stored at present
]
export class ProjectDotGradleCache extends AbstractCache {
private rootDir: string
constructor(rootDir: string) {
super('project', 'Project .gradle directory')
this.rootDir = rootDir
}
protected cacheOutputExists(): boolean {
const dir = this.getProjectDotGradleDir()
return fs.existsSync(dir)
}
protected getCachePath(): string[] {
const dir = this.getProjectDotGradleDir()
return PATHS_TO_CACHE.map(x => path.resolve(dir, x))
}
private getProjectDotGradleDir(): string {
return path.resolve(this.rootDir, '.gradle')
}
}

View File

@@ -1,17 +1,208 @@
import * as path from 'path'
import * as glob from '@actions/glob'
import * as core from '@actions/core'
import * as cache from '@actions/cache'
import * as github from '@actions/github'
import * as crypto from 'crypto'
export async function hashFiles(
baseDir: string,
patterns: string[] = ['**'],
followSymbolicLinks = false
): Promise<string | null> {
const combinedPatterns = patterns
.map(pattern => `${baseDir}${path.sep}${pattern}`)
.join('\n')
return glob.hashFiles(combinedPatterns, {followSymbolicLinks})
export function isCacheDisabled(): boolean {
return core.getBooleanInput('cache-disabled')
}
export function truncateArgs(args: string): string {
return args.trim().replace(/\s+/g, ' ').substr(0, 400)
export function isCacheReadOnly(): boolean {
return core.getBooleanInput('cache-read-only')
}
export function isCacheDebuggingEnabled(): boolean {
return process.env['CACHE_DEBUG_ENABLED'] ? true : false
}
function generateCacheKey(cacheName: string): CacheKey {
// Prefix can be used to force change all cache keys
const cacheKeyPrefix = process.env['CACHE_KEY_PREFIX'] || ''
// At the most general level, share caches for all executions on the same OS
const runnerOs = process.env['RUNNER_OS'] || ''
const cacheKeyForOs = `${cacheKeyPrefix}${cacheName}|${runnerOs}`
// Prefer caches that run this job
const cacheKeyForJob = `${cacheKeyForOs}|${github.context.job}`
// Prefer (even more) jobs that run this job with the same context (matrix)
const cacheKeyForJobContext = `${cacheKeyForJob}[${determineJobContext()}]`
// Exact match on Git SHA
const cacheKey = `${cacheKeyForJobContext}-${github.context.sha}`
return new CacheKey(cacheKey, [
cacheKeyForJobContext,
cacheKeyForJob,
cacheKeyForOs
])
}
function determineJobContext(): string {
// By default, we hash the full `matrix` data for the run, to uniquely identify this job invocation
const workflowJobContext = core.getInput('workflow-job-context')
return hashStrings([workflowJobContext])
}
export function hashStrings(values: string[]): string {
const hash = crypto.createHash('md5')
for (const value of values) {
hash.update(value)
}
return hash.digest('hex')
}
class CacheKey {
key: string
restoreKeys: string[]
constructor(key: string, restoreKeys: string[]) {
this.key = key
this.restoreKeys = restoreKeys
}
}
export abstract class AbstractCache {
private cacheName: string
private cacheDescription: string
private cacheKeyStateKey: string
private cacheResultStateKey: string
protected readonly cacheDebuggingEnabled: boolean
constructor(cacheName: string, cacheDescription: string) {
this.cacheName = cacheName
this.cacheDescription = cacheDescription
this.cacheKeyStateKey = `CACHE_KEY_${cacheName}`
this.cacheResultStateKey = `CACHE_RESULT_${cacheName}`
this.cacheDebuggingEnabled = isCacheDebuggingEnabled()
}
async restore(): Promise<void> {
if (this.cacheOutputExists()) {
core.info(
`${this.cacheDescription} already exists. Not restoring from cache.`
)
return
}
const cacheKey = generateCacheKey(this.cacheName)
core.saveState(this.cacheKeyStateKey, cacheKey.key)
const cacheResult = await this.restoreCache(
this.getCachePath(),
cacheKey.key,
cacheKey.restoreKeys
)
if (!cacheResult) {
core.info(
`${this.cacheDescription} cache not found. Will start with empty.`
)
return
}
core.saveState(this.cacheResultStateKey, cacheResult)
core.info(
`Restored ${this.cacheDescription} from cache key: ${cacheResult}`
)
await this.afterRestore()
return
}
protected async restoreCache(
cachePath: string[],
cacheKey: string,
cacheRestoreKeys: string[] = []
): Promise<string | undefined> {
try {
return await cache.restoreCache(
cachePath,
cacheKey,
cacheRestoreKeys
)
} catch (error) {
if (error instanceof cache.ValidationError) {
// Validation errors should fail the build action
throw error
}
// Warn about any other error and continue
core.warning(`Failed to restore ${cacheKey}: ${error}`)
return undefined
}
}
protected async afterRestore(): Promise<void> {}
async save(): Promise<void> {
if (!this.cacheOutputExists()) {
this.debug(`No ${this.cacheDescription} to cache.`)
return
}
const cacheKey = core.getState(this.cacheKeyStateKey)
const cacheResult = core.getState(this.cacheResultStateKey)
if (!cacheKey) {
this.debug(
`${this.cacheDescription} existed prior to cache restore. Not saving.`
)
return
}
if (cacheResult && cacheKey === cacheResult) {
core.info(
`Cache hit occurred on the cache key ${cacheKey}, not saving cache.`
)
return
}
await this.beforeSave()
core.info(
`Caching ${this.cacheDescription} with cache key: ${cacheKey}`
)
const cachePath = this.getCachePath()
await this.saveCache(cachePath, cacheKey)
return
}
protected async beforeSave(): Promise<void> {}
protected async saveCache(
cachePath: string[],
cacheKey: string
): Promise<void> {
try {
await cache.saveCache(cachePath, cacheKey)
} catch (error) {
if (error instanceof cache.ValidationError) {
// Validation errors should fail the build action
throw error
} else if (error instanceof cache.ReserveCacheError) {
// Reserve cache errors are expected if the artifact has been previously cached
this.debug(error.message)
} else {
// Warn about any other error and continue
core.warning(String(error))
}
}
}
protected debug(message: string): void {
if (this.cacheDebuggingEnabled) {
core.info(message)
} else {
core.debug(message)
}
}
protected abstract cacheOutputExists(): boolean
protected abstract getCachePath(): string[]
}

View File

@@ -1,132 +0,0 @@
import * as path from 'path'
import * as fs from 'fs'
import * as os from 'os'
import * as core from '@actions/core'
import * as cache from '@actions/cache'
const WRAPPER_SLUG = 'WRAPPER_SLUG'
export async function restoreCachedWrapperDist(
gradlewDirectory: string | null
): Promise<void> {
if (isWrapperCacheDisabled()) return
if (gradlewDirectory == null) return
const wrapperProperties = path.join(
path.resolve(gradlewDirectory),
'gradle/wrapper/gradle-wrapper.properties'
)
const wrapperSlug = extractGradleWrapperSlugFrom(wrapperProperties)
if (!wrapperSlug) {
core.warning(
`Could not calculate wrapper version from ${wrapperProperties}`
)
return
}
const wrapperDir = getWrapperDir(wrapperSlug)
const cacheKey = getCacheKey(wrapperSlug)
const cachePath = getCachePath(wrapperSlug)
// Check if the wrapper has already been downloaded to Gradle User Home
if (fs.existsSync(wrapperDir)) return
try {
const restoredKey = await cache.restoreCache([cachePath], cacheKey)
if (restoredKey) {
core.info(
`Wrapper installation restored from cache key: ${restoredKey}`
)
} else {
core.info(
`Wrapper installation cache not found. Will download and cache with key: ${cacheKey}.`
)
// Save the slug to trigger caching of the downloaded wrapper
core.saveState(WRAPPER_SLUG, wrapperSlug)
}
} catch (error) {
core.info(
`Wrapper installation cache restore failed for key: ${cacheKey}.\n ${error}`
)
}
}
export async function cacheWrapperDist(): Promise<void> {
if (isWrapperCacheDisabled()) return
const wrapperSlug = core.getState(WRAPPER_SLUG)
if (!wrapperSlug) return
const wrapperDir = getWrapperDir(wrapperSlug)
const cacheKey = getCacheKey(wrapperSlug)
const cachePath = getCachePath(wrapperSlug)
if (!fs.existsSync(wrapperDir)) {
core.warning(`No wrapper installation to cache at ${wrapperDir}`)
return
}
core.info(`Will cache wrapper zip ${cachePath} with key ${cacheKey}`)
try {
await cache.saveCache([cachePath], cacheKey)
} catch (error) {
if (error.name === cache.ValidationError.name) {
throw error
} else if (error.name === cache.ReserveCacheError.name) {
core.info(error.message)
} else {
core.info(`[warning] ${error.message}`)
}
}
return
}
export function extractGradleWrapperSlugFrom(
wrapperProperties: string
): string | null {
const props = fs.readFileSync(wrapperProperties, {encoding: 'utf8'})
const distUrlLine = props
.split('\n')
.find(line => line.startsWith('distributionUrl'))
if (!distUrlLine) return null
return extractGradleWrapperSlugFromDistUri(distUrlLine.substr(16).trim())
}
export function extractGradleWrapperSlugFromDistUri(
distUri: string
): string | null {
const regex = /.*gradle-(.*-(bin|all))\.zip/
const match = distUri.match(regex)
return match ? match[1] : null
}
function isWrapperCacheDisabled(): boolean {
// Check if either 'distributions' or 'wrapper' cache has been disabled
const wrapperCacheEnabled = core.getBooleanInput('wrapper-cache-enabled')
const distributionsCacheEnabled = core.getBooleanInput(
'distributions-cache-enabled'
)
return !wrapperCacheEnabled || !distributionsCacheEnabled
}
function getCacheKey(wrapperSlug: string): string {
return `wrapper-v1-${wrapperSlug}`
}
function getWrapperDir(wrapperSlug: string): string {
return path.resolve(
os.homedir(),
`.gradle/wrapper/dists/gradle-${wrapperSlug}`
)
}
function getCachePath(wrapperSlug: string): string {
return path.resolve(
os.homedir(),
`.gradle/wrapper/dists/gradle-${wrapperSlug}/*/gradle-${wrapperSlug}.zip`
)
}

36
src/caches.ts Normal file
View File

@@ -0,0 +1,36 @@
import {GradleUserHomeCache} from './cache-gradle-user-home'
import {ProjectDotGradleCache} from './cache-project-dot-gradle'
import * as core from '@actions/core'
import {isCacheDisabled, isCacheReadOnly} from './cache-utils'
const BUILD_ROOT_DIR = 'BUILD_ROOT_DIR'
export async function restore(buildRootDirectory: string): Promise<void> {
if (isCacheDisabled()) {
core.debug('Cache read disabled')
return
}
await core.group('Restore Gradle state from cache', async () => {
core.saveState(BUILD_ROOT_DIR, buildRootDirectory)
return Promise.all([
new GradleUserHomeCache().restore(),
new ProjectDotGradleCache(buildRootDirectory).restore()
])
})
}
export async function save(): Promise<void> {
if (isCacheReadOnly()) {
core.debug('Cache is read-only: not saving cache entry')
return
}
await core.group('Caching Gradle state', async () => {
const buildRootDirectory = core.getState(BUILD_ROOT_DIR)
return Promise.all([
new GradleUserHomeCache().save(),
new ProjectDotGradleCache(buildRootDirectory).save()
])
})
}

View File

@@ -1,15 +1,10 @@
import * as exec from '@actions/exec'
import * as cacheDependencies from './cache-dependencies'
import * as cacheConfiguration from './cache-configuration'
export async function execute(
executable: string,
root: string,
argv: string[]
): Promise<BuildResult> {
await cacheDependencies.restoreCachedDependencies(root)
await cacheConfiguration.restoreCachedConfiguration(root)
let publishing = false
let buildScanUrl: string | undefined

View File

@@ -3,7 +3,7 @@ import fs from 'fs'
const IS_WINDOWS = process.platform === 'win32'
export function wrapperFilename(): string {
export function wrapperScriptFilename(): string {
return IS_WINDOWS ? 'gradlew.bat' : 'gradlew'
}
@@ -11,9 +11,14 @@ export function installScriptFilename(): string {
return IS_WINDOWS ? 'gradle.bat' : 'gradle'
}
export function validateGradleWrapper(gradlewDirectory: string): void {
export function locateGradleWrapperScript(buildRootDirectory: string): string {
validateGradleWrapper(buildRootDirectory)
return path.resolve(buildRootDirectory, wrapperScriptFilename())
}
function validateGradleWrapper(buildRootDirectory: string): void {
const wrapperProperties = path.resolve(
gradlewDirectory,
buildRootDirectory,
'gradle/wrapper/gradle-wrapper.properties'
)
if (!fs.existsSync(wrapperProperties)) {

View File

@@ -2,7 +2,7 @@ import * as core from '@actions/core'
import * as path from 'path'
import {parseArgsStringToArgv} from 'string-argv'
import * as cacheWrapper from './cache-wrapper'
import * as caches from './caches'
import * as execution from './execution'
import * as gradlew from './gradlew'
import * as provision from './provision'
@@ -13,24 +13,36 @@ export async function run(): Promise<void> {
const workspaceDirectory = process.env[`GITHUB_WORKSPACE`] || ''
const buildRootDirectory = resolveBuildRootDirectory(workspaceDirectory)
await caches.restore(buildRootDirectory)
const args: string[] = parseCommandLineArguments()
// TODO: instead of running with no-daemon, run `--stop` in post action.
args.push('--no-daemon')
const result = await execution.execute(
await resolveGradleExecutable(
workspaceDirectory,
buildRootDirectory
),
buildRootDirectory,
parseCommandLineArguments()
args
)
if (result.buildScanUrl) {
core.setOutput('build-scan-url', result.buildScanUrl)
// TODO Include context about the invocation (eg step name) in this message
// Unfortunately it doesn't seem possible to access the current step name here
core.notice(`Gradle build scan: ${result.buildScanUrl}`)
}
if (result.status !== 0) {
core.setFailed(`Gradle process exited with status ${result.status}`)
}
} catch (error) {
core.setFailed(error.message)
core.setFailed(String(error))
if (error instanceof Error && error.stack) {
core.info(error.stack)
}
}
}
@@ -47,24 +59,10 @@ async function resolveGradleExecutable(
const gradleExecutable = core.getInput('gradle-executable')
if (gradleExecutable !== '') {
if (gradleExecutable.endsWith(gradlew.wrapperFilename())) {
await cacheWrapper.restoreCachedWrapperDist(
path.resolve(gradleExecutable, '..')
)
}
return path.resolve(workspaceDirectory, gradleExecutable)
}
const wrapperDirectory = core.getInput('wrapper-directory')
const gradlewDirectory =
wrapperDirectory !== ''
? path.resolve(workspaceDirectory, wrapperDirectory)
: buildRootDirectory
gradlew.validateGradleWrapper(gradlewDirectory)
await cacheWrapper.restoreCachedWrapperDist(gradlewDirectory)
return path.resolve(gradlewDirectory, gradlew.wrapperFilename())
return gradlew.locateGradleWrapperScript(buildRootDirectory)
}
function resolveBuildRootDirectory(baseDirectory: string): string {

View File

@@ -1,20 +1,16 @@
import * as core from '@actions/core'
import * as cacheWrapper from './cache-wrapper'
import * as cacheDependencies from './cache-dependencies'
import * as cacheConfiguration from './cache-configuration'
import * as caches from './caches'
// Invoked by GitHub Actions
export async function run(): Promise<void> {
if (isCacheReadOnly()) return
await cacheWrapper.cacheWrapperDist()
await cacheDependencies.cacheDependencies()
await cacheConfiguration.cacheConfiguration()
}
function isCacheReadOnly(): boolean {
return core.getBooleanInput('cache-read-only')
try {
await caches.save()
} catch (error) {
core.setFailed(String(error))
if (error instanceof Error && error.stack) {
core.info(error.stack)
}
}
}
run()

View File

@@ -7,6 +7,7 @@ import * as cache from '@actions/cache'
import * as toolCache from '@actions/tool-cache'
import * as gradlew from './gradlew'
import {isCacheDisabled, isCacheReadOnly} from './cache-utils'
const gradleVersionsBaseUrl = 'https://services.gradle.org/versions'
@@ -92,6 +93,14 @@ async function findGradleVersionDeclaration(
async function provisionGradle(
versionInfo: GradleVersionInfo
): Promise<string> {
return core.group(`Provision Gradle ${versionInfo.version}`, async () => {
return locateGradleAndDownloadIfRequired(versionInfo)
})
}
async function locateGradleAndDownloadIfRequired(
versionInfo: GradleVersionInfo
): Promise<string> {
const installsDir = path.join(os.homedir(), 'gradle-installations/installs')
const installDir = path.join(installsDir, `gradle-${versionInfo.version}`)
@@ -119,7 +128,7 @@ async function downloadAndCacheGradleDistribution(
`gradle-installations/downloads/gradle-${versionInfo.version}-bin.zip`
)
if (isDistributionsCacheDisabled()) {
if (isCacheDisabled()) {
await downloadGradleDistribution(versionInfo, downloadPath)
return downloadPath
}
@@ -130,22 +139,25 @@ async function downloadAndCacheGradleDistribution(
core.info(
`Restored Gradle distribution ${cacheKey} from cache to ${downloadPath}`
)
} else {
core.info(
`Gradle distribution ${versionInfo.version} not found in cache. Will download.`
)
await downloadGradleDistribution(versionInfo, downloadPath)
return downloadPath
}
core.info(
`Gradle distribution ${versionInfo.version} not found in cache. Will download.`
)
await downloadGradleDistribution(versionInfo, downloadPath)
if (!isCacheReadOnly()) {
try {
await cache.saveCache([downloadPath], cacheKey)
} catch (error) {
if (error.name === cache.ValidationError.name) {
// Fail on validation errors or non-errors (the latter to keep Typescript happy)
if (
error instanceof cache.ValidationError ||
!(error instanceof Error)
) {
throw error
} else if (error.name === cache.ReserveCacheError.name) {
core.info(error.message)
} else {
core.info(`[warning] ${error.message}`)
}
core.warning(error.message)
}
}
return downloadPath
@@ -183,10 +195,6 @@ async function httpGetString(url: string): Promise<string> {
return response.readBody()
}
function isDistributionsCacheDisabled(): boolean {
return !core.getBooleanInput('distributions-cache-enabled')
}
interface GradleVersionInfo {
version: string
downloadUrl: string