Compare commits

...

22 Commits

Author SHA1 Message Date
Daz DeBoer
b9c806c75d Fix example in action.yml 2022-01-24 15:08:55 -07:00
Daz DeBoer
d2799e6f98 Merge pull request #142 from gradle/strict-cache-match
Improves logging for caching, and adds 2 experimental cache options:

- `gradle-home-cache-strict-match`: Avoid re-using a Gradle Home cache entry written by a different job. Only cache entries written by the current Job will be used.
- `cache-write-only`: Do not restore existing cache entries, but still save cache entries in post-action.  Fixes #141.
2022-01-24 12:56:25 -07:00
Daz DeBoer
2734714920 Build outputs 2022-01-21 08:09:33 -07:00
Daz DeBoer
f75a3e8436 Make build-scan notice a WARNING for failed builds 2022-01-21 08:09:32 -07:00
Daz DeBoer
c349fa5b1f Do not hide ReserveCacheError failures
When the target entry already exists, we are unable to save. Ensuring this situation
is logged helps make the logs more understandable.
2022-01-21 08:08:32 -07:00
Daz DeBoer
08d5b40ca5 Add experimental support for 'cache-write-only'
There may be cases where it a "fresh" cache entry would be beneficial,
for example if the Gradle User Home cache entry grows over time.

This setting would run the build as if no prior cache entry exists.
2022-01-20 15:04:11 -07:00
Daz DeBoer
0a5ede19a9 Improve cache-reporting when entry already exists 2022-01-20 15:04:11 -07:00
Daz DeBoer
a23ac1d61c Add experimental support for 'strict-match' caching
By default, the action will attempt to restore a Gradle User Home
cache entry from a different set of matrix inputs (or a different Job entirely)
if an existing entry is not found for the current Job (including matrix inputs).

By specifying the experimental `gradle-home-cache-strict-match` parameter, a user
can avoid this fuzzy matching and ensure that a job execution starts with an
empty Gradle User Home if no entry from a prior execution is found.
2022-01-20 15:03:54 -07:00
Daz DeBoer
a8da4e5ca3 Merge pull request #140 from gradle/cache-disable
Fix Build Scan capture when cache is disabled

With the change to use init-scripts to inject Build Scan capture, this functionality broke when caching was disabled and Gradle User Home wasn't initialized.

This PR fixes a couple of issues and ensures that the Gradle User Home is initialized even when it is not restored from the GitHub Actions cache.

Fixes #138 
Fixes #139
2022-01-19 11:31:29 -07:00
Daz DeBoer
4f616f683a Include github.run_attempt in test cache keys
This change to the integration tests should properly isolate multiple runs of the same job.
Previously, some jobs would fail if you attempted to re-run them via the UI, because the cache
entries would be re-used from the previous run.
2022-01-18 10:52:22 -07:00
Daz DeBoer
222b714890 Build outputs 2022-01-17 14:44:32 -07:00
Daz DeBoer
7dee0f45c2 Fix check for pre-existing Gradle User Home 2022-01-17 14:44:32 -07:00
Daz DeBoer
50ca2bca83 Improve messages for cache-disabled scenario 2022-01-17 14:44:32 -07:00
Daz DeBoer
3aa7bfe163 Initialize pre-existing Gradle User Home 2022-01-17 14:44:32 -07:00
Daz DeBoer
755aebc7d1 Test build-scan capture with pre-existing GUH 2022-01-17 14:44:31 -07:00
Daz DeBoer
06d64212d3 Initialize the Gradle User Home even when caching is disabled 2022-01-17 14:29:11 -07:00
Daz DeBoer
97a4d7a5fd Test build-scan capture works with cache-disabled 2022-01-17 14:29:11 -07:00
Daz DeBoer
a7260b277e Specify mandatory 'distribution' arg for setup-java 2022-01-17 14:28:53 -07:00
Daz DeBoer
f4e053dda1 Upgrade to reference actions/setup-java@v2
- Update all references in README.md
- Update version used in `pr-build-scan-comment`
- Discourage the use of `cache: gradle` with `setup-java@v2`
2022-01-16 16:18:09 -07:00
Benedikt Ritter
a7c463b44a Update to latest version of actions/github-script
See https://github.com/marketplace/actions/github-script#breaking-changes-in-v5

* Update docs to latest github-script action version
* Update CI build to latest GitHub API
2022-01-16 15:45:15 -07:00
Daz DeBoer
42faf281e6 Include names of requested tasks in build scan notice
This should make it easier to differentiate between build scan notices for different
Gradle invocations in the same Job.

Fixes: #124
2021-12-31 10:34:53 -07:00
Daz DeBoer
13d93c1ca1 Check executable bit on Gradle script
Fixes #76
2021-12-30 21:40:27 -07:00
25 changed files with 249 additions and 108 deletions

View File

@@ -4,7 +4,7 @@ on:
workflow_dispatch: workflow_dispatch:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}- GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-
jobs: jobs:

View File

@@ -7,7 +7,7 @@ on:
workflow_dispatch: workflow_dispatch:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}- GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-
jobs: jobs:
action-inputs: action-inputs:

View File

@@ -7,7 +7,7 @@ on:
workflow_dispatch: workflow_dispatch:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}- GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-
GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED: true GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED: true
jobs: jobs:
@@ -46,7 +46,7 @@ jobs:
- name: Setup Gradle - name: Setup Gradle
uses: ./ uses: ./
with: with:
# Use the same configuration when restoring state from cache # Use the same configuration as used in the seed build
gradle-home-cache-includes: | gradle-home-cache-includes: |
caches caches
enterprise enterprise
@@ -57,4 +57,86 @@ jobs:
working-directory: __tests__/samples/groovy-dsl working-directory: __tests__/samples/groovy-dsl
run: ./gradlew test --offline run: ./gradlew test --offline
# Test that build scans are captured when caching is explicitly disabled
cache-disabled:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Setup Gradle
uses: ./
with:
cache-disabled: true
- name: Run Gradle build
id: gradle
working-directory: __tests__/samples/no-wrapper${{ matrix.build-root-suffix }}
run: gradle help "-DgradleVersionCheck=${{matrix.gradle}}"
- name: Check build scan url is captured
if: ${{ !steps.gradle.outputs.build-scan-url }}
uses: actions/github-script@v3
with:
script: |
core.setFailed('No build scan detected')
# Test that build scans are captured when caching is disabled because Gradle User Home already exists
cache-disabled-pre-existing-gradle-home:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Create dummy Gradle User Home
run: mkdir -p ~/.gradle/caches
- name: Setup Gradle
uses: ./
- name: Run Gradle build
id: gradle
working-directory: __tests__/samples/no-wrapper${{ matrix.build-root-suffix }}
run: gradle help "-DgradleVersionCheck=${{matrix.gradle}}"
- name: Check build scan url is captured
if: ${{ !steps.gradle.outputs.build-scan-url }}
uses: actions/github-script@v3
with:
script: |
core.setFailed('No build scan detected')
# Test seed the cache with cache-write-only and verify with cache-read-only
seed-build-write-only:
env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-write-only-
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Setup Gradle
uses: ./
with:
cache-write-only: true
- name: Build using Gradle wrapper
working-directory: __tests__/samples/groovy-dsl
run: ./gradlew test
verify-write-only-build:
env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-write-only-
needs: seed-build-write-only
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Setup Gradle
uses: ./
with:
cache-read-only: true
- name: Execute Gradle build with --offline
working-directory: __tests__/samples/groovy-dsl
run: ./gradlew test --offline

View File

@@ -14,7 +14,7 @@ jobs:
# These builds should start fresh without cache hits, due to the seed injected into the cache key above. # These builds should start fresh without cache hits, due to the seed injected into the cache key above.
seed-build-groovy: seed-build-groovy:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-groovy- GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-groovy-
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, windows-latest] os: [ubuntu-latest, windows-latest]
@@ -30,7 +30,7 @@ jobs:
configuration-cache-groovy: configuration-cache-groovy:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-groovy- GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-groovy-
needs: seed-build-groovy needs: seed-build-groovy
strategy: strategy:
matrix: matrix:
@@ -52,7 +52,7 @@ jobs:
# Check that the build can run when no extracted cache entries are restored # Check that the build can run when no extracted cache entries are restored
no-extracted-cache-entries-restored: no-extracted-cache-entries-restored:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-groovy- GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-groovy-
needs: seed-build-groovy needs: seed-build-groovy
strategy: strategy:
matrix: matrix:
@@ -73,7 +73,7 @@ jobs:
seed-build-kotlin: seed-build-kotlin:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-kotlin- GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-kotlin-
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, windows-latest] os: [ubuntu-latest, windows-latest]
@@ -83,13 +83,13 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Setup Gradle - name: Setup Gradle
uses: ./ uses: ./
- name: Kotlin build with configuration-cache enabled - name: Execute 'help' with configuration-cache enabled
working-directory: __tests__/samples/kotlin-dsl working-directory: __tests__/samples/kotlin-dsl
run: ./gradlew help --configuration-cache run: ./gradlew help --configuration-cache
modify-build-kotlin: modify-build-kotlin:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-kotlin- GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-kotlin-
needs: seed-build-kotlin needs: seed-build-kotlin
strategy: strategy:
matrix: matrix:
@@ -100,14 +100,14 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Setup Gradle - name: Setup Gradle
uses: ./ uses: ./
- name: Kotlin build with configuration-cache enabled - name: Execute 'test' with configuration-cache enabled
working-directory: __tests__/samples/kotlin-dsl working-directory: __tests__/samples/kotlin-dsl
run: ./gradlew test --configuration-cache run: ./gradlew test --configuration-cache
# Test restore configuration-cache from the third build invocation # Test restore configuration-cache from the third build invocation
configuration-cache-kotlin: configuration-cache-kotlin:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}-kotlin- GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-kotlin-
needs: modify-build-kotlin needs: modify-build-kotlin
strategy: strategy:
matrix: matrix:
@@ -120,7 +120,7 @@ jobs:
uses: ./ uses: ./
with: with:
cache-read-only: true cache-read-only: true
- name: Execute Gradle build and verify cached configuration - name: Execute 'test' again and verify cached configuration
env: env:
VERIFY_CACHED_CONFIGURATION: true VERIFY_CACHED_CONFIGURATION: true
working-directory: __tests__/samples/kotlin-dsl working-directory: __tests__/samples/kotlin-dsl

View File

@@ -7,7 +7,7 @@ on:
workflow_dispatch: workflow_dispatch:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}- GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-
jobs: jobs:
seed-build: seed-build:

View File

@@ -7,7 +7,7 @@ on:
workflow_dispatch: workflow_dispatch:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}- GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-
jobs: jobs:
seed-build: seed-build:

View File

@@ -7,7 +7,7 @@ on:
workflow_dispatch: workflow_dispatch:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}- GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-
jobs: jobs:
seed-build: seed-build:

View File

@@ -7,7 +7,7 @@ on:
workflow_dispatch: workflow_dispatch:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}- GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-
jobs: jobs:
# Tests for executing with different Gradle versions. # Tests for executing with different Gradle versions.

View File

@@ -7,7 +7,7 @@ on:
workflow_dispatch: workflow_dispatch:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}- GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-
GRADLE_USER_HOME: ${{github.workspace}}/custom/gradle/home GRADLE_USER_HOME: ${{github.workspace}}/custom/gradle/home
jobs: jobs:

View File

@@ -7,7 +7,7 @@ on:
workflow_dispatch: workflow_dispatch:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}- GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-
jobs: jobs:
# Tests for executing with different Gradle versions. # Tests for executing with different Gradle versions.

View File

@@ -7,7 +7,7 @@ on:
workflow_dispatch: workflow_dispatch:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}- GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-
GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED: true GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED: true
jobs: jobs:

View File

@@ -7,7 +7,7 @@ on:
workflow_dispatch: workflow_dispatch:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}- GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-
GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED: true GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED: true
jobs: jobs:

View File

@@ -7,19 +7,20 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-java@v1 - uses: actions/setup-java@v2
with: with:
distribution: temurin
java-version: 11 java-version: 11
- uses: ./ - uses: ./
- id: gradle - id: gradle
working-directory: __tests__/samples/kotlin-dsl working-directory: __tests__/samples/kotlin-dsl
run: ./gradlew help run: ./gradlew help
- name: "Comment build scan url" - name: "Comment build scan url"
uses: actions/github-script@v3 uses: actions/github-script@v5
with: with:
github-token: ${{secrets.GITHUB_TOKEN}} github-token: ${{secrets.GITHUB_TOKEN}}
script: | script: |
github.issues.createComment({ github.rest.issues.createComment({
issue_number: context.issue.number, issue_number: context.issue.number,
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,

View File

@@ -20,8 +20,9 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-java@v1 - uses: actions/setup-java@v2
with: with:
distribution: temurin
java-version: 11 java-version: 11
- name: Setup Gradle - name: Setup Gradle
@@ -33,7 +34,7 @@ jobs:
## Why use the `gradle-build-action`? ## Why use the `gradle-build-action`?
It is possible to directly invoke Gradle in your workflow, and the `setup-java` action provides a simple way to cache Gradle dependencies. It is possible to directly invoke Gradle in your workflow, and the `actions/setup-java@v2` action provides a simple way to cache Gradle dependencies.
However, the `gradle-build-action` offers a number of advantages over this approach: However, the `gradle-build-action` offers a number of advantages over this approach:
@@ -45,6 +46,8 @@ However, the `gradle-build-action` offers a number of advantages over this appro
The `gradle-build-action` is designed to provide these benefits with minimal configuration. The `gradle-build-action` is designed to provide these benefits with minimal configuration.
These features work both when Gradle is executed via the `gradle-build-action` and for any Gradle execution in subsequent steps. These features work both when Gradle is executed via the `gradle-build-action` and for any Gradle execution in subsequent steps.
When using `gradle-build-action` we recommend that you _not_ use `actions/cache` or `actions/setup-java@v2` to explicitly cache the Gradle User Home. Doing so may interfere with the caching provided by this action.
## Use a specific Gradle version ## Use a specific Gradle version
The `gradle-build-action` can download and install a specified Gradle version, adding this installed version to the PATH. The `gradle-build-action` can download and install a specified Gradle version, adding this installed version to the PATH.
@@ -80,7 +83,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-java@v1 - uses: actions/setup-java@v2
with: with:
java-version: 11 java-version: 11
- uses: gradle/gradle-build-action@v2 - uses: gradle/gradle-build-action@v2
@@ -106,7 +109,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-java@v1 - uses: actions/setup-java@v2
with: with:
java-version: 11 java-version: 11
@@ -302,19 +305,20 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-java@v1 - uses: actions/setup-java@v2
with: with:
distribution: temurin
java-version: 11 java-version: 11
- uses: gradle/gradle-build-action@v2 - uses: gradle/gradle-build-action@v2
- id: gradle - id: gradle
run: ./gradlew build run: ./gradlew build
- name: "Comment build scan url" - name: "Comment build scan url"
uses: actions/github-script@v3 uses: actions/github-script@v5
if: github.event_name == 'pull_request' && failure() if: github.event_name == 'pull_request' && failure()
with: with:
github-token: ${{secrets.GITHUB_TOKEN}} github-token: ${{secrets.GITHUB_TOKEN}}
script: | script: |
github.issues.createComment({ github.rest.issues.createComment({
issue_number: context.issue.number, issue_number: context.issue.number,
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,

View File

@@ -32,7 +32,7 @@ inputs:
required: false required: false
# e.g. Use the following setting to prevent the local build cache from being saved/restored # e.g. Use the following setting to prevent the local build cache from being saved/restored
# gradle-home-cache-excludes: | # gradle-home-cache-excludes: |
# ["caches/build-cache-1"] # caches/build-cache-1
arguments: arguments:
description: Gradle command line arguments (supports multi-line input) description: Gradle command line arguments (supports multi-line input)
@@ -48,8 +48,16 @@ inputs:
# EXPERIMENTAL & INTERNAL ACTION INPUTS # EXPERIMENTAL & INTERNAL ACTION INPUTS
# The following action properties allow fine-grained tweaking of the action caching behaviour. # 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`. # These properties are experimental and not (yet) designed for production use, and may change without notice in a subsequent release of `gradle-build-action`.
# Use at your own risk! # Use at your own risk!
cache-write-only:
description: When 'true', entries will not be restored from the cache but will be saved at the end of the Job. This allows a 'clean' cache entry to be written.
required: false
default: false
gradle-home-cache-strict-match:
description: When 'true', the action will not attempt to restore the Gradle User Home entries from other Jobs.
required: false
default: false
workflow-job-context: 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 (INTERNAL). 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 required: false

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

View File

@@ -15,12 +15,14 @@ import {
} from './cache-utils' } from './cache-utils'
import {ConfigurationCacheEntryExtractor, GradleHomeEntryExtractor} from './cache-extract-entries' import {ConfigurationCacheEntryExtractor, GradleHomeEntryExtractor} from './cache-extract-entries'
const CACHE_PROTOCOL_VERSION = 'v5-' const CACHE_PROTOCOL_VERSION = 'v6-'
const RESTORED_CACHE_KEY_KEY = 'restored-cache-key'
export const META_FILE_DIR = '.gradle-build-action' export const META_FILE_DIR = '.gradle-build-action'
export const PROJECT_ROOTS_FILE = 'project-roots.txt' export const PROJECT_ROOTS_FILE = 'project-roots.txt'
const INCLUDE_PATHS_PARAMETER = 'gradle-home-cache-includes' const INCLUDE_PATHS_PARAMETER = 'gradle-home-cache-includes'
const EXCLUDE_PATHS_PARAMETER = 'gradle-home-cache-excludes' const EXCLUDE_PATHS_PARAMETER = 'gradle-home-cache-excludes'
const STRICT_CACHE_MATCH_PARAMETER = 'gradle-home-cache-strict-match'
/** /**
* Represents a key used to restore a cache entry. * Represents a key used to restore a cache entry.
@@ -70,14 +72,16 @@ function generateCacheKey(cacheName: string): CacheKey {
// Exact match on Git SHA // Exact match on Git SHA
const cacheKey = `${cacheKeyForJobContext}-${github.context.sha}` const cacheKey = `${cacheKeyForJobContext}-${github.context.sha}`
if (core.getBooleanInput(STRICT_CACHE_MATCH_PARAMETER)) {
return new CacheKey(cacheKey, [cacheKeyForJobContext])
}
return new CacheKey(cacheKey, [cacheKeyForJobContext, cacheKeyForJob, cacheKeyForOs]) return new CacheKey(cacheKey, [cacheKeyForJobContext, cacheKeyForJob, cacheKeyForOs])
} }
export class GradleStateCache { export class GradleStateCache {
private cacheName: string private cacheName: string
private cacheDescription: string private cacheDescription: string
private cacheKeyStateKey: string
private cacheResultStateKey: string
protected readonly gradleUserHome: string protected readonly gradleUserHome: string
@@ -85,8 +89,6 @@ export class GradleStateCache {
this.gradleUserHome = gradleUserHome this.gradleUserHome = gradleUserHome
this.cacheName = 'gradle' this.cacheName = 'gradle'
this.cacheDescription = 'Gradle User Home' this.cacheDescription = 'Gradle User Home'
this.cacheKeyStateKey = `CACHE_KEY_gradle`
this.cacheResultStateKey = `CACHE_RESULT_gradle`
} }
init(): void { init(): void {
@@ -99,6 +101,17 @@ export class GradleStateCache {
this.initializeGradleUserHome(this.gradleUserHome, initScriptsDir) this.initializeGradleUserHome(this.gradleUserHome, initScriptsDir)
} }
cacheOutputExists(): boolean {
const paths = this.getCachePath()
for (const p of paths) {
if (fs.existsSync(p)) {
cacheDebug(`Cache output exists at ${p}`)
return true
}
}
return false
}
/** /**
* Restores the cache entry, finding the closest match to the currently running job. * Restores the cache entry, finding the closest match to the currently running job.
*/ */
@@ -106,7 +119,6 @@ export class GradleStateCache {
const entryListener = listener.entry(this.cacheDescription) const entryListener = listener.entry(this.cacheDescription)
const cacheKey = generateCacheKey(this.cacheName) const cacheKey = generateCacheKey(this.cacheName)
core.saveState(this.cacheKeyStateKey, cacheKey.key)
cacheDebug( cacheDebug(
`Requesting ${this.cacheDescription} with `Requesting ${this.cacheDescription} with
@@ -114,16 +126,13 @@ export class GradleStateCache {
restoreKeys:[${cacheKey.restoreKeys}]` restoreKeys:[${cacheKey.restoreKeys}]`
) )
const cacheResult = await restoreCache(this.getCachePath(), cacheKey.key, cacheKey.restoreKeys) const cacheResult = await restoreCache(this.getCachePath(), cacheKey.key, cacheKey.restoreKeys, entryListener)
entryListener.markRequested(cacheKey.key, cacheKey.restoreKeys)
if (!cacheResult) { if (!cacheResult) {
core.info(`${this.cacheDescription} cache not found. Will initialize empty.`) core.info(`${this.cacheDescription} cache not found. Will initialize empty.`)
return return
} }
core.saveState(this.cacheResultStateKey, cacheResult.key) core.saveState(RESTORED_CACHE_KEY_KEY, cacheResult.key)
entryListener.markRestored(cacheResult.key, cacheResult.size)
core.info(`Restored ${this.cacheDescription} from cache key: ${cacheResult.key}`) core.info(`Restored ${this.cacheDescription} from cache key: ${cacheResult.key}`)
@@ -152,12 +161,11 @@ export class GradleStateCache {
* it is saved with the exact key. * it is saved with the exact key.
*/ */
async save(listener: CacheListener): Promise<void> { async save(listener: CacheListener): Promise<void> {
// Retrieve the state set in the previous 'restore' step. const cacheKey = generateCacheKey(this.cacheName).key
const cacheKeyFromRestore = core.getState(this.cacheKeyStateKey) const restoredCacheKey = core.getState(RESTORED_CACHE_KEY_KEY)
const cacheResultFromRestore = core.getState(this.cacheResultStateKey)
if (cacheResultFromRestore && cacheKeyFromRestore === cacheResultFromRestore) { if (restoredCacheKey && cacheKey === restoredCacheKey) {
core.info(`Cache hit occurred on the cache key ${cacheKeyFromRestore}, not saving cache.`) core.info(`Cache hit occurred on the cache key ${cacheKey}, not saving cache.`)
return return
} }
@@ -168,13 +176,10 @@ export class GradleStateCache {
return return
} }
core.info(`Caching ${this.cacheDescription} with cache key: ${cacheKeyFromRestore}`) core.info(`Caching ${this.cacheDescription} with cache key: ${cacheKey}`)
const cachePath = this.getCachePath() const cachePath = this.getCachePath()
const savedEntry = await saveCache(cachePath, cacheKeyFromRestore) const entryListener = listener.entry(this.cacheDescription)
await saveCache(cachePath, cacheKey, entryListener)
if (savedEntry) {
listener.entry(this.cacheDescription).markSaved(savedEntry.key, savedEntry.size)
}
return return
} }
@@ -237,7 +242,7 @@ export class GradleStateCache {
buildScanCapture, buildScanCapture,
`import org.gradle.util.GradleVersion `import org.gradle.util.GradleVersion
// Only run again root build. Do not run against included builds. // Only run against root build. Do not run against included builds.
def isTopLevelBuild = gradle.getParent() == null def isTopLevelBuild = gradle.getParent() == null
if (isTopLevelBuild) { if (isTopLevelBuild) {
def version = GradleVersion.current().baseVersion def version = GradleVersion.current().baseVersion
@@ -261,19 +266,23 @@ if (isTopLevelBuild) {
def registerCallbacks(buildScanExtension, rootProjectName) { def registerCallbacks(buildScanExtension, rootProjectName) {
buildScanExtension.with { buildScanExtension.with {
def buildOutcome = ""
def scanFile = new File("gradle-build-scan.txt") def scanFile = new File("gradle-build-scan.txt")
def buildFailed = false
buildFinished { result -> buildFinished { result ->
buildOutcome = result.failure == null ? " succeeded" : " failed" buildFailed = (result.failure != null)
} }
buildScanPublished { buildScan -> buildScanPublished { buildScan ->
scanFile.text = buildScan.buildScanUri scanFile.text = buildScan.buildScanUri
// Send commands directly to GitHub Actions via STDOUT. // Send commands directly to GitHub Actions via STDOUT.
def message = "Build '\${rootProjectName}'\${buildOutcome} - \${buildScan.buildScanUri}" def gradleCommand = rootProjectName + " " + gradle.startParameter.taskNames.join(" ")
println("::notice ::\${message}") if (buildFailed) {
println("::warning ::Gradle build '\${gradleCommand}' FAILED - \${buildScan.buildScanUri}")
} else {
println("::notice ::Gradle build '\${gradleCommand}' - \${buildScan.buildScanUri}")
}
println("::set-output name=build-scan-url::\${buildScan.buildScanUri}") println("::set-output name=build-scan-url::\${buildScan.buildScanUri}")
} }
} }
@@ -284,7 +293,7 @@ def registerCallbacks(buildScanExtension, rootProjectName) {
fs.writeFileSync( fs.writeFileSync(
projectRootCapture, projectRootCapture,
` `
// Only run again root build. Do not run against included builds. // Only run against root build. Do not run against included builds.
def isTopLevelBuild = gradle.getParent() == null def isTopLevelBuild = gradle.getParent() == null
if (isTopLevelBuild) { if (isTopLevelBuild) {
settingsEvaluated { settings -> settingsEvaluated { settings ->

View File

@@ -118,12 +118,9 @@ abstract class AbstractEntryExtractor {
pattern: string, pattern: string,
listener: CacheEntryListener listener: CacheEntryListener
): Promise<ExtractedCacheEntry> { ): Promise<ExtractedCacheEntry> {
listener.markRequested(cacheKey) const restoredEntry = await restoreCache([pattern], cacheKey, [], listener)
const restoredEntry = await restoreCache([pattern], cacheKey)
if (restoredEntry) { if (restoredEntry) {
core.info(`Restored ${artifactType} with key ${cacheKey} to ${pattern}`) core.info(`Restored ${artifactType} with key ${cacheKey} to ${pattern}`)
listener.markRestored(restoredEntry.key, restoredEntry.size)
return new ExtractedCacheEntry(artifactType, pattern, cacheKey) return new ExtractedCacheEntry(artifactType, pattern, cacheKey)
} else { } else {
core.info(`Did not restore ${artifactType} with key ${cacheKey} to ${pattern}`) core.info(`Did not restore ${artifactType} with key ${cacheKey} to ${pattern}`)
@@ -217,10 +214,7 @@ abstract class AbstractEntryExtractor {
cacheDebug(`No change to previously restored ${artifactType}. Not saving.`) cacheDebug(`No change to previously restored ${artifactType}. Not saving.`)
} else { } else {
core.info(`Caching ${artifactType} with path '${pattern}' and cache key: ${cacheKey}`) core.info(`Caching ${artifactType} with path '${pattern}' and cache key: ${cacheKey}`)
const savedEntry = await saveCache([pattern], cacheKey) await saveCache([pattern], cacheKey, entryListener)
if (savedEntry !== undefined) {
entryListener.markSaved(savedEntry.key, savedEntry.size)
}
} }
for (const file of matchingFiles) { for (const file of matchingFiles) {

View File

@@ -28,6 +28,9 @@ export class CacheListener {
} }
static rehydrate(stringRep: string): CacheListener { static rehydrate(stringRep: string): CacheListener {
if (stringRep === '') {
return new CacheListener()
}
const rehydrated: CacheListener = Object.assign(new CacheListener(), JSON.parse(stringRep)) const rehydrated: CacheListener = Object.assign(new CacheListener(), JSON.parse(stringRep))
const entries = rehydrated.cacheEntries const entries = rehydrated.cacheEntries
for (let index = 0; index < entries.length; index++) { for (let index = 0; index < entries.length; index++) {
@@ -76,6 +79,12 @@ export class CacheEntryListener {
this.savedSize = size this.savedSize = size
return this return this
} }
markAlreadyExists(key: string): CacheEntryListener {
this.savedKey = key
this.savedSize = 0
return this
}
} }
export function logCachingReport(listener: CacheListener): void { export function logCachingReport(listener: CacheListener): void {
@@ -112,12 +121,18 @@ function getSum(
cacheEntries: CacheEntryListener[], cacheEntries: CacheEntryListener[],
predicate: (value: CacheEntryListener) => number | undefined predicate: (value: CacheEntryListener) => number | undefined
): string { ): string {
if (cacheEntries.length === 0) {
return '0'
}
return formatSize(cacheEntries.map(e => predicate(e) ?? 0).reduce((p, v) => p + v, 0)) return formatSize(cacheEntries.map(e => predicate(e) ?? 0).reduce((p, v) => p + v, 0))
} }
function formatSize(bytes: number | undefined): string { function formatSize(bytes: number | undefined): string {
if (bytes === undefined || bytes === 0) { if (bytes === undefined) {
return '' return ''
} }
if (bytes === 0) {
return '0 (Entry already exists)'
}
return `${Math.round(bytes / (1024 * 1024))} MB (${bytes} B)` return `${Math.round(bytes / (1024 * 1024))} MB (${bytes} B)`
} }

View File

@@ -4,9 +4,12 @@ import * as crypto from 'crypto'
import * as path from 'path' import * as path from 'path'
import * as fs from 'fs' import * as fs from 'fs'
import {CacheEntryListener} from './cache-reporting'
const JOB_CONTEXT_PARAMETER = 'workflow-job-context' const JOB_CONTEXT_PARAMETER = 'workflow-job-context'
const CACHE_DISABLED_PARAMETER = 'cache-disabled' const CACHE_DISABLED_PARAMETER = 'cache-disabled'
const CACHE_READONLY_PARAMETER = 'cache-read-only' const CACHE_READONLY_PARAMETER = 'cache-read-only'
const CACHE_WRITEONLY_PARAMETER = 'cache-write-only'
const CACHE_DEBUG_VAR = 'GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED' const CACHE_DEBUG_VAR = 'GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED'
const CACHE_PREFIX_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX' const CACHE_PREFIX_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX'
@@ -18,6 +21,10 @@ export function isCacheReadOnly(): boolean {
return core.getBooleanInput(CACHE_READONLY_PARAMETER) return core.getBooleanInput(CACHE_READONLY_PARAMETER)
} }
export function isCacheWriteOnly(): boolean {
return core.getBooleanInput(CACHE_WRITEONLY_PARAMETER)
}
export function isCacheDebuggingEnabled(): boolean { export function isCacheDebuggingEnabled(): boolean {
return process.env[CACHE_DEBUG_VAR] ? true : false return process.env[CACHE_DEBUG_VAR] ? true : false
} }
@@ -49,23 +56,32 @@ export function hashStrings(values: string[]): string {
export async function restoreCache( export async function restoreCache(
cachePath: string[], cachePath: string[],
cacheKey: string, cacheKey: string,
cacheRestoreKeys: string[] = [] cacheRestoreKeys: string[],
listener: CacheEntryListener
): Promise<cache.CacheEntry | undefined> { ): Promise<cache.CacheEntry | undefined> {
listener.markRequested(cacheKey, cacheRestoreKeys)
try { try {
return await cache.restoreCache(cachePath, cacheKey, cacheRestoreKeys) const restoredEntry = await cache.restoreCache(cachePath, cacheKey, cacheRestoreKeys)
if (restoredEntry !== undefined) {
listener.markRestored(restoredEntry.key, restoredEntry.size)
}
return restoredEntry
} catch (error) { } catch (error) {
handleCacheFailure(error, `Failed to restore ${cacheKey}`) handleCacheFailure(error, `Failed to restore ${cacheKey}`)
return undefined return undefined
} }
} }
export async function saveCache(cachePath: string[], cacheKey: string): Promise<cache.CacheEntry | undefined> { export async function saveCache(cachePath: string[], cacheKey: string, listener: CacheEntryListener): Promise<void> {
try { try {
return await cache.saveCache(cachePath, cacheKey) const savedEntry = await cache.saveCache(cachePath, cacheKey)
listener.markSaved(savedEntry.key, savedEntry.size)
} catch (error) { } catch (error) {
if (error instanceof cache.ReserveCacheError) {
listener.markAlreadyExists(cacheKey)
}
handleCacheFailure(error, `Failed to save cache entry ${cacheKey}`) handleCacheFailure(error, `Failed to save cache entry ${cacheKey}`)
} }
return undefined
} }
export function cacheDebug(message: string): void { export function cacheDebug(message: string): void {
@@ -83,11 +99,7 @@ export function handleCacheFailure(error: unknown, message: string): void {
} }
if (error instanceof cache.ReserveCacheError) { if (error instanceof cache.ReserveCacheError) {
// Reserve cache errors are expected if the artifact has been previously cached // Reserve cache errors are expected if the artifact has been previously cached
if (isCacheDebuggingEnabled()) { core.info(`${message}: ${error}`)
core.info(`${message}: ${error}`)
} else {
core.debug(`${message}: ${error}`)
}
} else { } else {
// Warn on all other errors // Warn on all other errors
core.warning(`${message}: ${error}`) core.warning(`${message}: ${error}`)

View File

@@ -1,5 +1,5 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {isCacheDisabled, isCacheReadOnly} from './cache-utils' import {isCacheDisabled, isCacheReadOnly, isCacheWriteOnly} from './cache-utils'
import {logCachingReport, CacheListener} from './cache-reporting' import {logCachingReport, CacheListener} from './cache-reporting'
import {GradleStateCache} from './cache-base' import {GradleStateCache} from './cache-base'
@@ -8,26 +8,46 @@ const GRADLE_USER_HOME = 'GRADLE_USER_HOME'
const CACHE_LISTENER = 'CACHE_LISTENER' const CACHE_LISTENER = 'CACHE_LISTENER'
export async function restore(gradleUserHome: string): Promise<void> { export async function restore(gradleUserHome: string): Promise<void> {
if (!shouldRestoreCaches()) { // Bypass restore cache on all but first action step in workflow.
if (process.env[CACHE_RESTORED_VAR]) {
core.info('Cache only restored on first action step.')
return
}
core.exportVariable(CACHE_RESTORED_VAR, true)
const gradleStateCache = new GradleStateCache(gradleUserHome)
if (isCacheDisabled()) {
core.info('Cache is disabled: will not restore state from previous builds.')
// Initialize the Gradle User Home even when caching is disabled.
gradleStateCache.init()
return
}
if (gradleStateCache.cacheOutputExists()) {
core.info('Gradle User Home already exists: will not restore from cache.')
// Initialize pre-existing Gradle User Home.
gradleStateCache.init()
return return
} }
const gradleStateCache = new GradleStateCache(gradleUserHome)
gradleStateCache.init() gradleStateCache.init()
// Mark the state as restored so that post-action will perform save.
core.saveState(CACHE_RESTORED_VAR, true)
// Save the Gradle User Home for the post-action step.
core.saveState(GRADLE_USER_HOME, gradleUserHome)
if (isCacheWriteOnly()) {
core.info('Cache is write-only: will not restore from cache.')
return
}
await core.group('Restore Gradle state from cache', async () => { await core.group('Restore Gradle state from cache', async () => {
core.saveState(GRADLE_USER_HOME, gradleUserHome)
const cacheListener = new CacheListener() const cacheListener = new CacheListener()
await gradleStateCache.restore(cacheListener) await gradleStateCache.restore(cacheListener)
core.saveState(CACHE_LISTENER, cacheListener.stringify()) core.saveState(CACHE_LISTENER, cacheListener.stringify())
}) })
// Export var that is detected in all later restore steps
core.exportVariable(CACHE_RESTORED_VAR, true)
// Export state that is detected in corresponding post-action step
core.saveState(CACHE_RESTORED_VAR, true)
} }
export async function save(): Promise<void> { export async function save(): Promise<void> {
@@ -51,19 +71,6 @@ export async function save(): Promise<void> {
logCachingReport(cacheListener) logCachingReport(cacheListener)
} }
function shouldRestoreCaches(): boolean {
if (isCacheDisabled()) {
core.info('Cache is disabled: will not restore state from previous builds.')
return false
}
if (process.env[CACHE_RESTORED_VAR]) {
core.info('Cache only restored on first action step.')
return false
}
return true
}
function shouldSaveCaches(): boolean { function shouldSaveCaches(): boolean {
if (isCacheDisabled()) { if (isCacheDisabled()) {
core.info('Cache is disabled: will not save state for later builds.') core.info('Cache is disabled: will not save state for later builds.')
@@ -71,7 +78,7 @@ function shouldSaveCaches(): boolean {
} }
if (!core.getState(CACHE_RESTORED_VAR)) { if (!core.getState(CACHE_RESTORED_VAR)) {
core.info('Cache will only be saved in final post-action step.') core.info('Cache will not be saved: not restored in main action step.')
return false return false
} }

View File

@@ -14,6 +14,7 @@ export async function executeGradleBuild(executable: string | undefined, root: s
// Use the provided executable, or look for a Gradle wrapper script to run // Use the provided executable, or look for a Gradle wrapper script to run
const toExecute = executable ?? gradlew.locateGradleWrapperScript(root) const toExecute = executable ?? gradlew.locateGradleWrapperScript(root)
verifyIsExecutableScript(toExecute)
const status: number = await exec.exec(toExecute, args, { const status: number = await exec.exec(toExecute, args, {
cwd: root, cwd: root,
ignoreReturnCode: true ignoreReturnCode: true
@@ -31,3 +32,11 @@ export async function executeGradleBuild(executable: string | undefined, root: s
} }
} }
} }
function verifyIsExecutableScript(toExecute: string): void {
try {
fs.accessSync(toExecute, fs.constants.X_OK)
} catch (err) {
throw new Error(`Gradle script '${toExecute}' is not executable.`)
}
}