Compare commits

...

61 Commits

Author SHA1 Message Date
Daz DeBoer
db2b34260f Merge pull request #101 from gradle/dd/rename-parameters
Rename and document parameters for clarity 

- cache-paths -> gradle-home-cache-includes
- cache-exclude-paths -> gradle-home-cache-excludes
- CACHE_DEBUG_ENABLED -> GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED
2021-10-28 00:18:36 +02:00
Daz DeBoer
c031dc946b Build outputs 2021-10-27 16:09:53 -06:00
Daz DeBoer
0eb881f067 Adapt workflows to renamed parameters 2021-10-27 16:07:24 -06:00
Daz DeBoer
27f2dc276c Rename and document parameters for clarity
- cache-paths -> gradle-home-cache-includes
- cache-exclude-paths -> gradle-home-cache-excludes
- CACHE_DEBUG_ENABLED -> GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED
2021-10-27 16:05:07 -06:00
Daz DeBoer
cba1833dde Run test builds on Ubuntu and Windows
- Remove MacOS since the agents are current flaky
- Add Windows to a few more test pipelines
2021-10-21 12:08:17 -06:00
Daz DeBoer
39db90e99b Include build scan link in failure notice 2021-10-21 12:04:25 -06:00
Daz DeBoer
947a893558 Merge pull request #97 from gradle/dd/configurable-cache
Add some internal options for fine-tuning the gradle-build-action cache

- Can specify cache-paths to override the default directories cached from Gradle User Home
- Can specify cache-exclude-paths to exclude files from the Gradle User Home cache
- Files under caches/<gradle-version>/kotlin-dsl are now cached as a bundle
2021-10-21 19:26:19 +02:00
Daz DeBoer
b99e9f0bc3 Build outputs 2021-10-21 11:13:10 -06:00
Daz DeBoer
4cf255df10 Move bundle metadata files out of caches directory
Instead, use a separate '.gradle-build-action' directory for bundle
metadata files. This directory is always part of the cache-path.
2021-10-21 11:13:09 -06:00
Daz DeBoer
614d8770a4 Add test for cache configuration 2021-10-21 11:13:09 -06:00
Daz DeBoer
69453dbfc5 Include 'kotlin-dsl' as a cache-artifact-bundle 2021-10-21 11:13:09 -06:00
Daz DeBoer
1113cb87cb Allow cache-exclude-paths to be configured for action
All excluded paths are deleted prior to caching the Gradle User Home.
2021-10-21 11:13:09 -06:00
Daz DeBoer
9c95294209 Allow cache-paths to be set via action config 2021-10-21 11:13:08 -06:00
Daz DeBoer
f901ec9c20 Bump cache-protocol version 2021-10-20 15:04:14 -06:00
Daz DeBoer
a94b9252d5 Improve cache logging 2021-10-16 10:15:40 -06:00
Daz DeBoer
25672bf196 Build outputs 2021-10-16 09:50:40 -06:00
Daz DeBoer
cb6a0acca4 Use precise matching for artifact bundles
This should fix the warnings issued when saving artifact bundles.
2021-10-16 09:49:15 -06:00
Daz DeBoer
aa2ed2e033 Use cache protocol version for bundle keys too 2021-10-16 09:49:14 -06:00
Daz DeBoer
263f84178a Prefix cache key with protocol version
This will ensure that incompatiblee cache entries generated by previous action releases
will not be used.
2021-10-16 08:33:42 -06:00
Daz DeBoer
0eb5996567 Merge pull request #93 from gradle/dd/instrumented-jars-fix
Ensure all-or-nothing restore of cached instrumented-jars

Leaving the .lock and .receipt files lying around was causing issues when the actual jar files were not restored. Now the entire directory will either be missing, or completely restored.

Fixes #91
2021-10-15 23:04:47 +02:00
Daz DeBoer
fe55bf4667 Build outputs 2021-10-15 14:56:44 -06:00
Daz DeBoer
709ded51a5 Treat directory for instrumented jar as single artifact
Leaving the `.lock` and `.receipt` files lying around was causing
issues when the actual jar files were not restored. Now the entire
directory will either be missing, or completely restored.
2021-10-15 14:54:29 -06:00
Daz DeBoer
8b1f1a3817 Add test for execution when no bundles are restored 2021-10-15 13:29:09 -06:00
Daz DeBoer
7abf13ee48 Build outputs 2021-10-15 13:21:26 -06:00
Daz DeBoer
da64595ccc Make artifact bundle definitions an input parameter 2021-10-15 13:21:13 -06:00
Daz DeBoer
29b14c7fca Refactor: rename methods for 'bundle' concept 2021-10-15 12:34:38 -06:00
Daz DeBoer
d1ab42cddf Document support for multi-line arguments
Fixes #88
2021-10-15 12:24:41 -06:00
Daz DeBoer
422726cec5 Add test for multi-line input arguments 2021-10-15 12:20:01 -06:00
Daz DeBoer
4bc52c85c3 Merge pull request #92 from gradle/dd/cache-debug-logging
Improve cache logging and behaviour
2021-10-15 20:17:35 +02:00
Daz DeBoer
e7b5fd0b28 Build outputs 2021-10-15 11:46:51 -06:00
Daz DeBoer
53ccc3e0d7 Add more cache debug logging 2021-10-15 11:45:15 -06:00
Daz DeBoer
8ab7c9d8dd Do not fail action on cache errors
Ensure that we catch and log errors in `beforeSave` and `afterRestore`,
and do not fail the entire workflow in these cases.
2021-10-14 12:19:24 -06:00
Daz DeBoer
0cf00ed767 Fix test for release-candidate
Since this is an ever-changing version, this fix removes the `gradleVersionCheck` from the invocation.
2021-10-14 10:48:44 -06:00
Daz DeBoer
aedc5fc8f9 Mention that CACHE_DEBUG_ENABLED will disable parallel save/restore 2021-10-14 10:41:45 -06:00
Daz DeBoer
78e25cd233 Document the CACHE_DEBUG_ENABLED flag 2021-10-14 10:39:12 -06:00
Daz DeBoer
29894757f3 Merge pull request #90 from gradle/dd/windows-locks
Allow time for processes to delete file locks on windows
2021-10-05 01:30:13 +02:00
Daz DeBoer
5328161026 Build outputs 2021-10-04 23:59:42 +02:00
Daz DeBoer
4968d2280b Allow time for processes to release file locks on windows 2021-10-04 23:59:08 +02:00
Daz DeBoer
c000a0b58f Merge pull request #87 from gradle/dd/gradle-versions
Fix build-scan link detection with older build-scan plugins

Fixes #86
2021-09-29 15:15:24 -06:00
Daz DeBoer
6ff498182a Add checks for build scan links 2021-09-29 15:10:39 -06:00
Daz DeBoer
60b1ffac6b Fix build-scan-init script to work with build-in build-scan plugins 2021-09-29 14:39:48 -06:00
Daz DeBoer
9b7c81f8f6 Test execution with older Gradle versions 2021-09-29 13:34:05 -06:00
Daz DeBoer
b650771559 Cleanup samples 2021-09-29 13:23:25 -06:00
Daz DeBoer
17f624cb5b Rename 'basic' sample to 'groovy-dsl' 2021-09-28 20:31:11 -06:00
Daz DeBoer
21dee71590 Update README for v2 2021-09-28 11:30:55 -06:00
Daz DeBoer
83e2129213 Merge pull request #84 from gradle/dependabot/npm_and_yarn/ansi-regex-5.0.1
Bump ansi-regex from 5.0.0 to 5.0.1
2021-09-28 08:45:48 -06:00
Daz DeBoer
b16787d530 Merge pull request #80 from gradle/dependabot/npm_and_yarn/tmpl-1.0.5
Bump tmpl from 1.0.4 to 1.0.5
2021-09-28 08:45:34 -06:00
Daz DeBoer
204870af89 Merge pull request #85 from gradle/dd/init-script
Use an init-script to capture buildScanPublished event
2021-09-28 00:08:42 -06:00
Daz DeBoer
0918f5f2a4 Rename source file for clarity 2021-09-28 00:04:50 -06:00
Daz DeBoer
1b1a3c48ad Publish scans from no-wrapper sample build
This allows more testing of build scan capture functionality
2021-09-28 00:04:50 -06:00
Daz DeBoer
a7174b82a2 Use init script to capture build scan URL
Instead of parsing the log output, we instead register a
buildScanPublished listener and record the build scan URL
to a file. This file is subsequently read to report the
build scan URL.

Fixes #30
2021-09-28 00:04:41 -06:00
dependabot[bot]
3de71f2c52 Bump ansi-regex from 5.0.0 to 5.0.1
Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/chalk/ansi-regex/releases)
- [Commits](https://github.com/chalk/ansi-regex/compare/v5.0.0...v5.0.1)

---
updated-dependencies:
- dependency-name: ansi-regex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-28 04:09:27 +00:00
Daz DeBoer
5576baa56b Merge pull request #83 from gradle/dd/v2
Adapt paths to differing Gradle User Home
2021-09-27 22:08:40 -06:00
Daz DeBoer
1026c62889 Only run failure-cases workflow on manual trigger 2021-09-27 22:05:23 -06:00
Daz DeBoer
1fb8644035 Build outputs 2021-09-27 21:53:19 -06:00
Daz DeBoer
5a5a5b4387 Normalize paths to Gradle User Home when calculating cache keys
Fixes #77
2021-09-27 21:52:03 -06:00
Daz DeBoer
cc5cdb7fe0 Adapt caching for GRADLE_USER_HOME env var
Fixes #74
2021-09-27 21:50:14 -06:00
Daz DeBoer
e0d37eb073 Workflow to test with custom GRADLE_USER_HOME 2021-09-27 19:48:26 -06:00
Daz DeBoer
bdd89aa34f Refactored workflows
- Improve workflow names
- Split execution testing from cache testing
2021-09-27 19:33:22 -06:00
dependabot[bot]
18cdc8bf28 Bump tmpl from 1.0.4 to 1.0.5
Bumps [tmpl](https://github.com/daaku/nodejs-tmpl) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/daaku/nodejs-tmpl/releases)
- [Commits](https://github.com/daaku/nodejs-tmpl/commits/v1.0.5)

---
updated-dependencies:
- dependency-name: tmpl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-21 13:17:04 +00:00
Daz DeBoer
656ad4b5f2 Move continue-on-error into the step definition
Hopefully this will allow GitHub to ignore the failure.
2021-09-15 17:57:43 -06:00
42 changed files with 887 additions and 315 deletions

View File

@@ -1,5 +1,4 @@
# make sure the build works and doesn't produce spurious changes
name: dev
name: Verify generated outputs
on:
pull_request:

View File

@@ -1,37 +1,33 @@
# 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
name: Execute failure cases
on:
pull_request:
push:
workflow_dispatch:
env:
CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-
GRADLE_BUILD_ACTION_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: ./
continue-on-error: true
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: ./
continue-on-error: true
with:
build-root-directory: __tests__/samples/no-wrapper
arguments: help

View File

@@ -0,0 +1,30 @@
name: Test different action inputs
on:
pull_request:
push:
workflow_dispatch:
env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-
jobs:
action-inputs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Invoke with multi-line arguments
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: |
--configuration-cache
--build-cache
-DsystemProperty=FOO
-PgradleProperty=BAR
test
jar

View File

@@ -0,0 +1,71 @@
name: Test caching configuration
on:
pull_request:
push:
workflow_dispatch:
env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-
GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED: true
jobs:
# 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, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Build using Gradle wrapper
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test
# Add "wrapper" to main cache entry and remove 'wrapper-zips' bundle
# Exclude build-cache from main cache entry
gradle-home-cache-includes: |
["caches", "notifications", "wrapper"]
gradle-home-cache-excludes: |
["caches/build-cache-1"]
gradle-home-cache-artifact-bundles: |
[
["generated-gradle-jars", "caches/*/generated-gradle-jars/*.jar"],
["dependency-jars", "caches/modules-*/files-*/**/*.jar"],
["instrumented-jars", "caches/jars-*/*/"],
["kotlin-dsl", "caches/*/kotlin-dsl/*/*/"]
]
# Test that the gradle-user-home cache will cache dependencies, by running build with --offline
verify-build:
needs: seed-build
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Execute Gradle build with --offline
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test --offline
cache-read-only: true
# Need the same configuration when restoring state from cache
gradle-home-cache-includes: |
["caches", "notifications", "wrapper"]
gradle-home-cache-excludes: |
["caches/build-cache-1"]
gradle-home-cache-artifact-bundles: |
[
["generated-gradle-jars", "caches/*/generated-gradle-jars/*.jar"],
["dependency-jars", "caches/modules-*/files-*/**/*.jar"],
["instrumented-jars", "caches/jars-*/*/"],
["kotlin-dsl", "caches/*/kotlin-dsl/*/*/"]
]

View File

@@ -1,5 +1,4 @@
# Verify the functionality works as expected
name: integration-testing
name: Test caching
on:
pull_request:
@@ -7,7 +6,7 @@ on:
workflow_dispatch:
env:
CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-
jobs:
# Run initial Gradle builds to push initial cache entries
@@ -15,7 +14,7 @@ jobs:
seed-build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
@@ -23,56 +22,20 @@ jobs:
- name: Build using Gradle wrapper
uses: ./
with:
build-root-directory: __tests__/samples/basic
build-root-directory: __tests__/samples/groovy-dsl
arguments: test
- name: Build with configuration-cache enabled
uses: ./
with:
build-root-directory: __tests__/samples/basic
build-root-directory: __tests__/samples/groovy-dsl
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: seed-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
include:
- os: windows-latest
script-suffix: '.bat'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Test use defined Gradle version
uses: ./
with:
gradle-version: 6.9
build-root-directory: __tests__/samples/no-wrapper
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 -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 -DgradleVersionCheck=7.1.1
cache-read-only: true
# 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]
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
@@ -80,7 +43,7 @@ jobs:
- name: Execute Gradle build with --offline
uses: ./
with:
build-root-directory: __tests__/samples/basic
build-root-directory: __tests__/samples/groovy-dsl
arguments: test --offline
cache-read-only: true
@@ -89,7 +52,7 @@ jobs:
needs: seed-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
@@ -97,7 +60,7 @@ jobs:
- name: Execute Gradle build and verify tasks from cache
uses: ./
with:
build-root-directory: __tests__/samples/basic
build-root-directory: __tests__/samples/groovy-dsl
arguments: test -DverifyCachedBuild=true
cache-read-only: true
@@ -106,7 +69,7 @@ jobs:
needs: seed-build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
@@ -116,6 +79,25 @@ jobs:
env:
VERIFY_CACHED_CONFIGURATION: true
with:
build-root-directory: __tests__/samples/basic
build-root-directory: __tests__/samples/groovy-dsl
arguments: test --configuration-cache
cache-read-only: true
# Check that the build can run when no bundles are restored
no-bundles-restored:
needs: seed-build
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Execute Gradle build with no cache artifact bundles restored
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test
cache-read-only: true
gradle-home-cache-artifact-bundles: '[]'

View File

@@ -0,0 +1,107 @@
name: Test Gradle execution
on:
pull_request:
push:
workflow_dispatch:
env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-
jobs:
# Tests for executing with different Gradle versions.
# Each build verifies that it is executed with the expected Gradle version.
gradle-execution:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
include:
- os: windows-latest
script-suffix: '.bat'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Test use defined Gradle version
uses: ./
with:
gradle-version: 6.9
build-root-directory: __tests__/samples/no-wrapper
arguments: help -DgradleVersionCheck=6.9
- name: Test use Gradle version alias
uses: ./
with:
gradle-version: release-candidate
build-root-directory: __tests__/samples/no-wrapper
arguments: help
- name: Test use defined Gradle executable
uses: ./
with:
gradle-executable: __tests__/samples/groovy-dsl/gradlew${{ matrix.script-suffix }}
build-root-directory: __tests__/samples/no-wrapper
arguments: help -DgradleVersionCheck=7.1.1
gradle-versions:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
include:
- os: windows-latest
script-suffix: '.bat'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Test Gradle 7
uses: ./
id: gradle7
with:
gradle-version: 7.2
build-root-directory: __tests__/samples/no-wrapper
arguments: help -DgradleVersionCheck=7.2
- name: Check Gradle 7 scan
if: ${{ !steps.gradle7.outputs.build-scan-url }}
uses: actions/github-script@v3
with:
script: |
core.setFailed('No build scan detected')
- name: Test Gradle 6
uses: ./
id: gradle6
with:
gradle-version: 6.9
build-root-directory: __tests__/samples/no-wrapper
arguments: help -DgradleVersionCheck=6.9
- name: Check Gradle 6 scan
if: ${{ !steps.gradle6.outputs.build-scan-url }}
uses: actions/github-script@v3
with:
script: |
core.setFailed('No build scan detected')
- name: Test Gradle 5
uses: ./
id: gradle5
with:
gradle-version: 5.6.4
build-root-directory: __tests__/samples/no-wrapper-gradle-5
arguments: help -DgradleVersionCheck=5.6.4
- name: Check Gradle 5 scan
if: ${{ !steps.gradle5.outputs.build-scan-url }}
uses: actions/github-script@v3
with:
script: |
core.setFailed('No build scan detected')
- name: Test Gradle 4
uses: ./
id: gradle4
with:
gradle-version: 4.10.3
build-root-directory: __tests__/samples/no-wrapper-gradle-4
arguments: help -DgradleVersionCheck=4.10.3
- name: Check Gradle 4 scan
if: ${{ !steps.gradle4.outputs.build-scan-url }}
uses: actions/github-script@v3
with:
script: |
core.setFailed('No build scan detected')

View File

@@ -0,0 +1,61 @@
name: Test caching with a custom GRADLE_USER_HOME
on:
pull_request:
push:
workflow_dispatch:
env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-
GRADLE_USER_HOME: custom/gradle/home
jobs:
# 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, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Build using Gradle wrapper
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test
# 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, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Execute Gradle build with --offline
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
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, 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/groovy-dsl
arguments: test -DverifyCachedBuild=true
cache-read-only: true

View File

@@ -1,5 +1,4 @@
# Make sure the action works on a clean machine without building
name: integration-testing-kotlin-dsl
name: Test caching with Kotlin DSL
on:
pull_request:
@@ -7,13 +6,16 @@ on:
workflow_dispatch:
env:
CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-
CACHE_DEBUG_ENABLED: true
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-
GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED: true
jobs:
# Use kotlin-dsl project to verify caching of generated jars and compiled scripts
seed-build:
runs-on: ubuntu-latest
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
@@ -26,7 +28,10 @@ jobs:
# Check that the build can run --offline
verify-build:
needs: seed-build
runs-on: ubuntu-latest
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2

170
README.md
View File

@@ -2,9 +2,13 @@
This GitHub Action can be used to execute a Gradle build on any platform supported by GitHub Actions.
**Note:** The following documentation is for `gradle/gradle-build-action@v2`, currently in Beta release.
You can view the documentation for the latest stable release (v1.5.1) [on the GitHub Marketplace](https://github.com/marketplace/actions/gradle-build-action?version=v1.5.1).
## Usage
The following workflow will run `./gradlew build` using the wrapper from the repository on ubuntu, macos and windows. The only prerequisite is to have Java installed: you define the version of Java you need to run the build using the `actions/setup-java` action.
The following workflow will run `./gradlew build` on ubuntu, macos and windows.
The only prerequisite is to have Java installed: you define the version of Java you need to run the build using the `actions/setup-java` action.
```yaml
# .github/workflows/gradle-build-pr.yml
@@ -21,14 +25,29 @@ jobs:
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: gradle/gradle-build-action@v1
- uses: gradle/gradle-build-action@v2
with:
arguments: build
```
## Gradle arguments
It is possible to configure multiple Gradle executions to run sequentially in the same job.
Each invocation will start its run with the filesystem state remaining from the previous execution.
```yaml
- uses: gradle/gradle-build-action@v2
with:
arguments: assemble
- uses: gradle/gradle-build-action@v2
with:
arguments: check
```
## Gradle Execution
### Command-line arguments
The `arguments` input can used to pass arbitrary arguments to the `gradle` command line.
Arguments can be supplied in a single line, or as a multi-line input.
Here are some valid examples:
```yaml
@@ -36,48 +55,56 @@ arguments: build
arguments: check --scan
arguments: some arbitrary tasks
arguments: build -PgradleProperty=foo
arguments: build -DsystemProperty=bar
....
arguments: |
build
--scan
-PgradleProperty=foo
-DsystemProperty=bar
```
See `gradle --help` for more information.
If you need to pass environment variables, simply use the GitHub Actions workflow syntax:
If you need to pass environment variables, use the GitHub Actions workflow syntax:
```yaml
- uses: gradle/gradle-build-action@v1
- uses: gradle/gradle-build-action@v2
env:
CI: true
with:
arguments: build
```
## Run a build from a different directory
### Gradle build located in a subdirectory
By default, the action will execute Gradle in the root directory of your project.
Use the `build-root-directory` input to target a Gradle build in a subdirectory.
```yaml
- uses: gradle/gradle-build-action@v1
- uses: gradle/gradle-build-action@v2
with:
build-root-directory: some/subdirectory
```
## Use a specific `gradle` executable
### Using a specific Gradle executable
The action will first look for a Gradle wrapper script in the root directory of your project.
If not found, `gradle` will be executed from the PATH.
Use the `gradle-executable` input to execute using a specific Gradle installation.
```yaml
- uses: gradle/gradle-build-action@v1
- uses: gradle/gradle-build-action@v2
with:
gradle-executable: path/to/gradle
gradle-executable: /path/to/installed/gradle
```
## Use a Gradle wrapper from a different directory
```yaml
- uses: gradle/gradle-build-action@v1
with:
gradle-executable: path/to/gradlew
```
This mechanism can also be used to target a Gradle wrapper script that is located in a non-default location.
## Setup and use a declared Gradle version
### Download, install and use a specific Gradle version
The `gradle-build-action` is able to download and install a specific Gradle version to execute.
```yaml
- uses: gradle/gradle-build-action@v1
- uses: gradle/gradle-build-action@v2
with:
gradle-version: 6.5
```
@@ -94,7 +121,7 @@ Moreover, you can use the following aliases:
| `nightly` | The latest [nightly](https://gradle.org/nightly/), fails if none. |
| `release-nightly` | The latest [release nightly](https://gradle.org/release-nightly/), fails if none. |
This can be handy to, for example, automatically test your build with the next Gradle version once a release candidate is out:
This can be handy to automatically verify your build works with the latest release candidate of Gradle:
```yaml
# .github/workflows/test-gradle-rc.yml
@@ -110,7 +137,7 @@ jobs:
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: gradle/gradle-build-action@v1
- uses: gradle/gradle-build-action@v2
with:
gradle-version: release-candidate
arguments: build --dry-run # just test build configuration
@@ -118,28 +145,31 @@ jobs:
## Caching
This action provides 3 levels of caching to help speed up your GitHub Actions:
- `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.
Each of these are enabled by default. To save caching space, you can disable any of them as follows:
```yaml
distributions-cache-enabled: true
gradle-user-home-cache-enabled: true
project-dot-gradle-cache-enabled: true
```
The distributions cache uses a cache key that is unique to the downloaded distribution. This will not change over time.
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.
By default, this action aims to cache any and all reusable state that may be speed up a subsequent build invocation.
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.
The state that is cached includes:
- Any distributions downloaded to satisfy a `gradle-version` parameter ;
- A subset of the Gradle User Home directory, including downloaded dependencies, wrapper distributions, and the local build cache ;
- Any [configuration-cache](https://docs.gradle.org/nightly/userguide/configuration_cache.html) data stored in the project `.gradle` directory.
To reduce the space required for caching, this action makes a best effort to reduce duplication in cache entries.
Caching is enabled by default. You can disable caching for the action as follows:
```yaml
cache-disabled: true
```
### Cache keys
For cached distributions outside of Gradle User Home, the cache key is unique to the downloaded distribution. This will not change over time.
The state of the Gradle User Home and configuration-cache are highly dependent on the Gradle execution, so the cache key is composed of the current commit hash and the GitHub actions job id.
As such, the cache key is likely to change on each subsequent run of GitHub actions.
This allows the most recent state to always be available in the GitHub actions cache.
To reduce duplication between cache entries, certain artifacts are cached independently based on their identity.
Artifacts that are cached independently include downloaded dependencies, downloaded wrapper distributions and generated Gradle API jars.
For example, this means that all jobs executing a particular version of the Gradle wrapper will share common entries for wrapper distributions and for generated Gradle API jars.
### Using the caches read-only
@@ -147,21 +177,50 @@ Cache storage space is limited for GitHub actions, and writing new cache entries
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.
You can enable read-only caching for any of the caches asfollows:
You can enable read-only caching for any of the caches as follows:
```yaml
distributions-cache-enabled: read-only
gradle-user-home-cache-enabled: read-only
project-dot-gradle-cache-enabled: read-only
# Only write to the cache for builds on the 'main' branch.
# Builds on other branches will only read existing entries from the cache.
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
```
### Gradle User Home cache tuning
As well as any wrapper distributions, the action will attempt to save and restore the `caches` and `notifications` directories from Gradle User Home.
The contents to be cached can be fine tuned by including and excluding certain paths with Gradle User Home.
```yaml
# Cache downloaded JDKs in addition to the default directories.
gradle-home-cache-includes: |
["caches", "notifications", "jdks"]
# Exclude the local build-cache from the directories cached.
gradle-home-cache-excludes: |
["caches/build-cache-1"]
```
You can specify any number of fixed paths or patterns to include or exclude.
File pattern support is documented at https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#patterns-to-match-file-paths.
### Cache debugging
It is possible to enable additional debug logging for cache operations. You do via the `GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED` environment variable:
```yaml
env:
GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED: true
```
Note that this setting will also prevent certain cache operations from running in parallel, further assisting with debugging.
## Build scans
If your build publishes a [build scan](https://gradle.com/build-scans/) the `gradle-build-action` action will emit the link to the published build scan as an output named `build-scan-url`.
If your build publishes a [build scan](https://gradle.com/build-scans/) the `gradle-build-action` action will:
- Add a notice with the link to the GitHub Actions user interface
- Emit the link to the published build scan as an output named `build-scan-url`.
You can then use that link in subsequent actions of your workflow.
For example:
You can then use that link in subsequent actions of your workflow. For example:
```yaml
# .github/workflows/gradle-build-pr.yml
@@ -169,19 +228,16 @@ name: Run Gradle on PRs
on: pull_request
jobs:
gradle:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: gradle/gradle-build-action@v1
- uses: gradle/gradle-build-action@v2
id: gradle
with:
arguments: build
id: gradle
- name: "Comment build scan url"
uses: actions/github-script@v3
if: github.event_name == 'pull_request' && failure()

View File

@@ -11,5 +11,11 @@ describe('cacheUtils-utils', () => {
const hash = cacheUtils.hashStrings(['foo', 'bar', 'baz'])
expect(hash).toBe('6df23dc03f9b54cc38a0fc1483df6e21')
})
it('normalized filenames', async () => {
const fileNames = ['/foo/bar/baz.zip', '../boo.html']
const posixHash = cacheUtils.hashFileNames(fileNames)
const windowsHash = cacheUtils.hashFileNames(fileNames)
expect(posixHash).toBe(windowsHash)
})
})
})

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
plugins {
id("com.gradle.enterprise") version("3.6.4")
id("com.gradle.enterprise") version("3.7")
}
gradleEnterprise {

View File

@@ -0,0 +1,10 @@
plugins {
id "com.gradle.build-scan" version "1.16"
}
buildScan {
termsOfServiceUrl = "https://gradle.com/terms-of-service"
termsOfServiceAgree = "yes"
publishAlways()
}

View File

@@ -0,0 +1,8 @@
rootProject.name = 'no-wrapper'
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

@@ -0,0 +1,12 @@
plugins {
id("com.gradle.build-scan") version("3.7")
}
gradleEnterprise {
buildScan {
termsOfServiceUrl = "https://gradle.com/terms-of-service"
termsOfServiceAgree = "yes"
publishAlways()
uploadInBackground = false
}
}

View File

@@ -0,0 +1,8 @@
rootProject.name = 'no-wrapper'
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

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

View File

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

View File

@@ -1,3 +1,16 @@
plugins {
id("com.gradle.enterprise") version("3.7")
}
gradleEnterprise {
buildScan {
termsOfServiceUrl = "https://gradle.com/terms-of-service"
termsOfServiceAgree = "yes"
publishAlways()
uploadInBackground = false
}
}
rootProject.name = 'no-wrapper'
println "Using Gradle version: ${gradle.gradleVersion}"

View File

@@ -7,29 +7,65 @@ inputs:
gradle-executable:
description: Path to the Gradle executable
required: false
gradle-version:
description: Gradle version to use
required: false
build-root-directory:
description: Path to the root directory of the build
required: false
arguments:
description: Gradle command line arguments, see gradle --help
description: Gradle command line arguments (supports multi-line input)
required: 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
description: When 'true', existing entries will be read from the cache but no entries will be written.
required: false
# TODO: It might be useful to default to read-only for PRs, or non-main branch.
default: false
# e.g. Use the following setting to only write cache entries from your 'main' branch
# cache-read-only: ${{ github.ref != 'refs/heads/main' }}
gradle-home-cache-includes:
description: Paths within Gradle User Home to cache.
required: false
default: |
["caches", "notifications"]
gradle-home-cache-excludes:
description: Paths within Gradle User Home to exclude from cache.
required: false
default: |
[]
# e.g. Use the following setting to prevent the local build cache from being saved/restored
# gradle-home-cache-excludes: |
# ["caches/build-cache-1"]
# EXPERIMENTAL & INTERNAL CONFIGURATION PROPERTIES
# The following action properties allow fine-grained tweaking of the action caching behaviour.
# These properties are not designed for production use, and may change without notice in a subsequent release of `gradle-build-action`.
# Use at your own risk!
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.
description: Used to uniquely identify the current job invocation. Defaults to the matrix values for this job; this should not be overridden by users (INTERNAL).
required: false
default: ${{ toJSON(matrix) }}
gradle-home-cache-artifact-bundles:
description: Names and patterns of artifact bundles to cache separately. (EXPERIMENTAL - may be changed/removed without notice)
required: false
default: |
[
["generated-gradle-jars", "caches/*/generated-gradle-jars/*.jar"],
["wrapper-zips", "wrapper/dists/*/*/*.zip"],
["dependency-jars", "caches/modules-*/files-*/**/*.jar"],
["instrumented-jars", "caches/jars-*/*/"],
["kotlin-dsl", "caches/*/kotlin-dsl/*/*/"]
]
outputs:
build-scan-url:

2
dist/main/index.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/post/index.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

12
package-lock.json generated
View File

@@ -1642,9 +1642,9 @@
}
},
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true
},
"ansi-styles": {
@@ -6456,9 +6456,9 @@
"dev": true
},
"tmpl": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz",
"integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=",
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
"dev": true
},
"to-fast-properties": {

50
src/build-scan-capture.ts Normal file
View File

@@ -0,0 +1,50 @@
import fs from 'fs'
import path from 'path'
import * as core from '@actions/core'
export function writeInitScript(): string {
const tmpDir = process.env['RUNNER_TEMP'] || ''
const initScript = path.resolve(tmpDir, 'build-scan-capture.init.gradle')
core.info(`Writing init script: ${initScript}`)
if (fs.existsSync(initScript)) {
return initScript
}
fs.writeFileSync(
initScript,
`
import org.gradle.util.GradleVersion
// Don't run against the included builds (if the main build has any).
def isTopLevelBuild = gradle.getParent() == null
if (isTopLevelBuild) {
def version = GradleVersion.current().baseVersion
def atLeastGradle4 = version >= GradleVersion.version("4.0")
def atLeastGradle6 = version >= GradleVersion.version("6.0")
if (atLeastGradle6) {
settingsEvaluated { settings ->
if (settings.pluginManager.hasPlugin("com.gradle.enterprise")) {
registerCallbacks(settings.extensions["gradleEnterprise"].buildScan, settings.rootProject.name)
}
}
} else if (atLeastGradle4) {
projectsEvaluated { gradle ->
if (gradle.rootProject.pluginManager.hasPlugin("com.gradle.build-scan")) {
registerCallbacks(gradle.rootProject.extensions["buildScan"], gradle.rootProject.name)
}
}
}
}
def registerCallbacks(buildScanExtension, rootProjectName) {
buildScanExtension.with {
def scanFile = new File("gradle-build-scan.txt")
buildScanPublished { buildScan ->
scanFile.text = buildScan.buildScanUri
}
}
}
`
)
return initScript
}

View File

@@ -5,35 +5,37 @@ import * as core from '@actions/core'
import * as glob from '@actions/glob'
import * as exec from '@actions/exec'
import {AbstractCache, hashStrings} from './cache-utils'
import {
AbstractCache,
getCacheKeyPrefix,
hashFileNames,
tryDelete
} 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 META_FILE_DIR = '.gradle-build-action'
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']
])
const INCLUDE_PATHS_PARAMETER = 'gradle-home-cache-includes'
const EXCLUDE_PATHS_PARAMETER = 'gradle-home-cache-excludes'
const ARTIFACT_BUNDLES_PARAMETER = 'gradle-home-cache-artifact-bundles'
export class GradleUserHomeCache extends AbstractCache {
constructor() {
private gradleUserHome: string
constructor(rootDir: string) {
super('gradle', 'Gradle User Home')
this.gradleUserHome = this.determineGradleUserHome(rootDir)
}
async afterRestore(): Promise<void> {
await this.reportCacheEntrySize('as restored from cache')
await this.restoreCommonArtifacts()
await this.reportCacheEntrySize('after restoring common artifacts')
await this.reportGradleUserHomeSize('as restored from cache')
await this.restoreArtifactBundles()
await this.reportGradleUserHomeSize('after restoring common artifacts')
}
private async restoreCommonArtifacts(): Promise<void> {
private async restoreArtifactBundles(): Promise<void> {
const processes: Promise<void>[] = []
for (const [bundle, pattern] of COMMON_ARTIFACT_CACHES) {
const p = this.restoreCommonArtifactBundle(bundle, pattern)
for (const [bundle, pattern] of this.getArtifactBundles()) {
const p = this.restoreArtifactBundle(bundle, pattern)
// Run sequentially when debugging enabled
if (this.cacheDebuggingEnabled) {
await p
@@ -44,57 +46,204 @@ export class GradleUserHomeCache extends AbstractCache {
await Promise.all(processes)
}
private async restoreCommonArtifactBundle(
private async restoreArtifactBundle(
bundle: string,
pattern: string
artifactPath: 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)
const bundleMetaFile = this.getBundleMetaFile(bundle)
if (fs.existsSync(bundleMetaFile)) {
const cacheKey = fs.readFileSync(bundleMetaFile, 'utf-8').trim()
const restoreKey = await this.restoreCache([artifactPath], cacheKey)
if (restoreKey) {
core.info(
`Restored ${bundle} with key ${cacheKey} to ${pattern}`
`Restored ${bundle} with key ${cacheKey} to ${artifactPath}`
)
} else {
this.debug(
`Failed to restore ${bundle} with key ${cacheKey} to ${pattern}`
`Did not restore ${bundle} with key ${cacheKey} to ${artifactPath}`
)
}
} else {
this.debug(
`No metafile found to restore ${bundle}: ${cacheMetaFile}`
`No metafile found to restore ${bundle}: ${bundleMetaFile}`
)
}
}
private getCacheMetaFile(name: string): string {
return path.resolve(
this.getGradleUserHome(),
'caches',
`.gradle-build-action.${name}.cache`
private getBundleMetaFile(name: string): string {
return path.resolve(this.gradleUserHome, META_FILE_DIR, `${name}.cache`)
}
async beforeSave(): Promise<void> {
await this.reportGradleUserHomeSize('before saving common artifacts')
this.removeExcludedPaths()
await this.saveArtifactBundles()
await this.reportGradleUserHomeSize(
"after saving common artifacts (only 'caches' and 'notifications' will be stored)"
)
}
private async reportCacheEntrySize(label: string): Promise<void> {
private removeExcludedPaths(): void {
const rawPaths: string[] = JSON.parse(
core.getInput(EXCLUDE_PATHS_PARAMETER)
)
const resolvedPaths = rawPaths.map(x =>
path.resolve(this.gradleUserHome, x)
)
for (const p of resolvedPaths) {
this.debug(`Deleting excluded path: ${p}`)
tryDelete(p)
}
}
private async saveArtifactBundles(): Promise<void> {
const processes: Promise<void>[] = []
for (const [bundle, pattern] of this.getArtifactBundles()) {
const p = this.saveArtifactBundle(bundle, pattern)
// Run sequentially when debugging enabled
if (this.cacheDebuggingEnabled) {
await p
}
processes.push(p)
}
await Promise.all(processes)
}
private async saveArtifactBundle(
bundle: string,
artifactPath: string
): Promise<void> {
const bundleMetaFile = this.getBundleMetaFile(bundle)
const globber = await glob.create(artifactPath, {
implicitDescendants: false,
followSymbolicLinks: false
})
const bundleFiles = await globber.glob()
// Handle no matching files
if (bundleFiles.length === 0) {
this.debug(`No files found to cache for ${bundle}`)
if (fs.existsSync(bundleMetaFile)) {
tryDelete(bundleMetaFile)
}
return
}
const previouslyRestoredKey = fs.existsSync(bundleMetaFile)
? fs.readFileSync(bundleMetaFile, 'utf-8').trim()
: ''
const cacheKey = this.createCacheKey(bundle, bundleFiles)
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([artifactPath], cacheKey)
this.writeBundleMetaFile(bundleMetaFile, cacheKey)
}
for (const file of bundleFiles) {
tryDelete(file)
}
}
protected createCacheKey(bundle: string, files: string[]): string {
const cacheKeyPrefix = getCacheKeyPrefix()
const relativeFiles = files.map(x =>
path.relative(this.gradleUserHome, x)
)
const key = hashFileNames(relativeFiles)
this.debug(
`Generating cache key for ${bundle} from files: ${relativeFiles}`
)
return `${cacheKeyPrefix}${bundle}-${key}`
}
private writeBundleMetaFile(metaFile: string, cacheKey: string): void {
this.debug(`Writing bundle metafile: ${metaFile}`)
const dirName = path.dirname(metaFile)
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName)
}
fs.writeFileSync(metaFile, cacheKey)
}
protected determineGradleUserHome(rootDir: string): string {
const customGradleUserHome = process.env['GRADLE_USER_HOME']
if (customGradleUserHome) {
return path.resolve(rootDir, customGradleUserHome)
}
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.gradleUserHome, 'caches')
return fs.existsSync(dir)
}
protected getCachePath(): string[] {
const rawPaths: string[] = JSON.parse(
core.getInput(INCLUDE_PATHS_PARAMETER)
)
rawPaths.push(META_FILE_DIR)
const resolvedPaths = rawPaths.map(x => this.resolveCachePath(x))
this.debug(`Using cache paths: ${resolvedPaths}`)
return resolvedPaths
}
private resolveCachePath(rawPath: string): string {
if (rawPath.startsWith('!')) {
const resolved = this.resolveCachePath(rawPath.substring(1))
return `!${resolved}`
}
return path.resolve(this.gradleUserHome, rawPath)
}
private getArtifactBundles(): Map<string, string> {
const artifactBundleDefinition = core.getInput(
ARTIFACT_BUNDLES_PARAMETER
)
this.debug(
`Using artifact bundle definition: ${artifactBundleDefinition}`
)
const artifactBundles = JSON.parse(artifactBundleDefinition)
return new Map(
Array.from(artifactBundles, ([key, value]) => [
key,
path.resolve(this.gradleUserHome, value)
])
)
}
private async reportGradleUserHomeSize(label: string): Promise<void> {
if (!this.cacheDebuggingEnabled) {
return
}
const gradleUserHome = path.resolve(os.homedir(), '.gradle')
if (!fs.existsSync(gradleUserHome)) {
if (!fs.existsSync(this.gradleUserHome)) {
return
}
const result = await exec.getExecOutput(
'du',
['-h', '-c', '-t', '5M'],
{
cwd: gradleUserHome,
cwd: this.gradleUserHome,
silent: true,
ignoreReturnCode: true
}
)
core.info(`Gradle User Home cache entry (directories >5M): ${label}`)
core.info(`Gradle User Home (directories >5M): ${label}`)
core.info(
result.stdout
@@ -109,84 +258,4 @@ export class GradleUserHomeCache extends AbstractCache {
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

@@ -2,22 +2,34 @@ import * as core from '@actions/core'
import * as cache from '@actions/cache'
import * as github from '@actions/github'
import * as crypto from 'crypto'
import * as path from 'path'
import * as fs from 'fs'
const CACHE_DISABLED_PARAMETER = 'cache-disabled'
const CACHE_READONLY_PARAMETER = 'cache-read-only'
const JOB_CONTEXT_PARAMETER = 'workflow-job-context'
const CACHE_DEBUG_VAR = 'GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED'
const CACHE_PREFIX_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX'
export function isCacheDisabled(): boolean {
return core.getBooleanInput('cache-disabled')
return core.getBooleanInput(CACHE_DISABLED_PARAMETER)
}
export function isCacheReadOnly(): boolean {
return core.getBooleanInput('cache-read-only')
return core.getBooleanInput(CACHE_READONLY_PARAMETER)
}
export function isCacheDebuggingEnabled(): boolean {
return process.env['CACHE_DEBUG_ENABLED'] ? true : false
return process.env[CACHE_DEBUG_VAR] ? true : false
}
export function getCacheKeyPrefix(): string {
// Prefix can be used to force change all cache keys (defaults to cache protocol version)
return process.env[CACHE_PREFIX_VAR] || 'v3-'
}
function generateCacheKey(cacheName: string): CacheKey {
// Prefix can be used to force change all cache keys
const cacheKeyPrefix = process.env['CACHE_KEY_PREFIX'] || ''
const cacheKeyPrefix = getCacheKeyPrefix()
// At the most general level, share caches for all executions on the same OS
const runnerOs = process.env['RUNNER_OS'] || ''
@@ -41,7 +53,7 @@ function generateCacheKey(cacheName: string): CacheKey {
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')
const workflowJobContext = core.getInput(JOB_CONTEXT_PARAMETER)
return hashStrings([workflowJobContext])
}
@@ -53,6 +65,40 @@ export function hashStrings(values: string[]): string {
return hash.digest('hex')
}
export function hashFileNames(fileNames: string[]): string {
return hashStrings(
fileNames.map(x => x.replace(new RegExp(`\\${path.sep}`, 'g'), '/'))
)
}
/**
* Attempt to delete a file or directory, waiting to allow locks to be released
*/
export async function tryDelete(file: string): Promise<void> {
const stat = fs.lstatSync(file)
for (let count = 0; count < 3; count++) {
try {
if (stat.isDirectory()) {
fs.rmdirSync(file, {recursive: true})
} else {
fs.unlinkSync(file)
}
return
} catch (error) {
if (count === 2) {
throw error
} else {
core.warning(String(error))
await delay(1000)
}
}
}
}
async function delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
class CacheKey {
key: string
restoreKeys: string[]
@@ -91,6 +137,12 @@ export abstract class AbstractCache {
core.saveState(this.cacheKeyStateKey, cacheKey.key)
this.debug(
`Requesting ${this.cacheDescription} with
key:${cacheKey.key}
restoreKeys:[${cacheKey.restoreKeys}]`
)
const cacheResult = await this.restoreCache(
this.getCachePath(),
cacheKey.key,
@@ -110,7 +162,13 @@ export abstract class AbstractCache {
`Restored ${this.cacheDescription} from cache key: ${cacheResult}`
)
await this.afterRestore()
try {
await this.afterRestore()
} catch (error) {
core.warning(
`Restore ${this.cacheDescription} failed in 'afterRestore': ${error}`
)
}
return
}
@@ -162,7 +220,14 @@ export abstract class AbstractCache {
return
}
await this.beforeSave()
try {
await this.beforeSave()
} catch (error) {
core.warning(
`Save ${this.cacheDescription} failed in 'beforeSave': ${error}`
)
return
}
core.info(
`Caching ${this.cacheDescription} with cache key: ${cacheKey}`

View File

@@ -7,14 +7,16 @@ const BUILD_ROOT_DIR = 'BUILD_ROOT_DIR'
export async function restore(buildRootDirectory: string): Promise<void> {
if (isCacheDisabled()) {
core.debug('Cache read disabled')
core.info(
'Cache is disabled: will not restore state from previous builds.'
)
return
}
await core.group('Restore Gradle state from cache', async () => {
core.saveState(BUILD_ROOT_DIR, buildRootDirectory)
return Promise.all([
new GradleUserHomeCache().restore(),
new GradleUserHomeCache(buildRootDirectory).restore(),
new ProjectDotGradleCache(buildRootDirectory).restore()
])
})
@@ -22,14 +24,16 @@ export async function restore(buildRootDirectory: string): Promise<void> {
export async function save(): Promise<void> {
if (isCacheReadOnly()) {
core.debug('Cache is read-only: not saving cache entry')
core.info(
'Cache is read-only: will not save state for use in subsequent builds.'
)
return
}
await core.group('Caching Gradle state', async () => {
const buildRootDirectory = core.getState(BUILD_ROOT_DIR)
return Promise.all([
new GradleUserHomeCache().save(),
new GradleUserHomeCache(buildRootDirectory).save(),
new ProjectDotGradleCache(buildRootDirectory).save()
])
})

View File

@@ -1,29 +1,36 @@
import * as exec from '@actions/exec'
import fs from 'fs'
import path from 'path'
import {writeInitScript} from './build-scan-capture'
export async function execute(
executable: string,
root: string,
argv: string[]
args: string[]
): Promise<BuildResult> {
let publishing = false
let buildScanUrl: string | undefined
const status: number = await exec.exec(executable, argv, {
// TODO: instead of running with no-daemon, run `--stop` in post action.
args.push('--no-daemon')
const initScript = writeInitScript()
args.push('--init-script')
args.push(initScript)
const buildScanFile = path.resolve(root, 'gradle-build-scan.txt')
if (fs.existsSync(buildScanFile)) {
fs.unlinkSync(buildScanFile)
}
const status: number = await exec.exec(executable, args, {
cwd: root,
ignoreReturnCode: true,
listeners: {
stdline: (line: string) => {
if (line.includes('Publishing build scan...')) {
publishing = true
}
if (publishing && line.startsWith('http')) {
buildScanUrl = line.trim()
publishing = false
}
}
}
ignoreReturnCode: true
})
if (fs.existsSync(buildScanFile)) {
buildScanUrl = fs.readFileSync(buildScanFile, 'utf-8')
}
return new BuildResultImpl(status, buildScanUrl)
}

View File

@@ -16,8 +16,6 @@ export async function run(): Promise<void> {
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(
@@ -30,13 +28,20 @@ export async function run(): Promise<void> {
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}`)
if (result.buildScanUrl) {
core.setFailed(`Gradle build failed: ${result.buildScanUrl}`)
} else {
core.setFailed(
`Gradle build failed: process exited with status ${result.status}`
)
}
} else {
if (result.buildScanUrl) {
core.notice(`Gradle build succeeded: ${result.buildScanUrl}`)
}
}
} catch (error) {
core.setFailed(String(error))