Merge pull request #109 from gradle/dd/rc21

Fix issues for 2.0-rc.2
- Support multi-line strings for cache-tuning parameters #106 
- Include all downloaded files in `dependencies` bundle #100 
- Only restore configuration-cache if Gradle User Home is fully restored #107
This commit is contained in:
Daz DeBoer 2021-10-30 14:39:28 +02:00 committed by GitHub
commit 613f4ec588
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 263 additions and 260 deletions

View file

@ -11,7 +11,7 @@
"eslint-comments/no-use": "off", "eslint-comments/no-use": "off",
"import/no-namespace": "off", "import/no-namespace": "off",
"no-unused-vars": "off", "no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error", "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}], "@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
"@typescript-eslint/no-require-imports": "error", "@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/array-type": "error", "@typescript-eslint/array-type": "error",

View file

@ -28,13 +28,15 @@ jobs:
# Add "wrapper" to main cache entry and remove 'wrapper-zips' bundle # Add "wrapper" to main cache entry and remove 'wrapper-zips' bundle
# Exclude build-cache from main cache entry # Exclude build-cache from main cache entry
gradle-home-cache-includes: | gradle-home-cache-includes: |
["caches", "notifications", "wrapper"] caches
notifications
wrapper
gradle-home-cache-excludes: | gradle-home-cache-excludes: |
["caches/build-cache-1"] caches/build-cache-1
gradle-home-cache-artifact-bundles: | gradle-home-cache-artifact-bundles: |
[ [
["generated-gradle-jars", "caches/*/generated-gradle-jars/*.jar"], ["generated-gradle-jars", "caches/*/generated-gradle-jars/*.jar"],
["dependency-jars", "caches/modules-*/files-*/**/*.jar"], ["dependencies", "caches/modules-*/files-*/*/*/*/*/"],
["instrumented-jars", "caches/jars-*/*/"], ["instrumented-jars", "caches/jars-*/*/"],
["kotlin-dsl", "caches/*/kotlin-dsl/*/*/"] ["kotlin-dsl", "caches/*/kotlin-dsl/*/*/"]
] ]
@ -57,13 +59,15 @@ jobs:
cache-read-only: true cache-read-only: true
# Need the same configuration when restoring state from cache # Need the same configuration when restoring state from cache
gradle-home-cache-includes: | gradle-home-cache-includes: |
["caches", "notifications", "wrapper"] caches
notifications
wrapper
gradle-home-cache-excludes: | gradle-home-cache-excludes: |
["caches/build-cache-1"] caches/build-cache-1
gradle-home-cache-artifact-bundles: | gradle-home-cache-artifact-bundles: |
[ [
["generated-gradle-jars", "caches/*/generated-gradle-jars/*.jar"], ["generated-gradle-jars", "caches/*/generated-gradle-jars/*.jar"],
["dependency-jars", "caches/modules-*/files-*/**/*.jar"], ["dependencies", "caches/modules-*/files-*/*/*/*/*/"],
["instrumented-jars", "caches/jars-*/*/"], ["instrumented-jars", "caches/jars-*/*/"],
["kotlin-dsl", "caches/*/kotlin-dsl/*/*/"] ["kotlin-dsl", "caches/*/kotlin-dsl/*/*/"]
] ]

View file

@ -0,0 +1,65 @@
name: Test save/restore configuration-cache state
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 with configuration-cache enabled
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test --configuration-cache
# Test that the project-dot-gradle cache will cache and restore configuration-cache
configuration-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 cached configuration
uses: ./
env:
VERIFY_CACHED_CONFIGURATION: true
with:
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 --configuration-cache
cache-read-only: true
gradle-home-cache-artifact-bundles: '[]'

View file

@ -1,4 +1,4 @@
name: Test caching name: Test save/restore Gradle Home directory
on: on:
pull_request: pull_request:
@ -24,11 +24,6 @@ jobs:
with: with:
build-root-directory: __tests__/samples/groovy-dsl build-root-directory: __tests__/samples/groovy-dsl
arguments: test arguments: test
- name: Build with configuration-cache enabled
uses: ./
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test --configuration-cache
# Test that the gradle-user-home cache will cache dependencies, by running build with --offline # Test that the gradle-user-home cache will cache dependencies, by running build with --offline
dependencies-cache: dependencies-cache:
@ -64,25 +59,6 @@ jobs:
arguments: test -DverifyCachedBuild=true arguments: test -DverifyCachedBuild=true
cache-read-only: true cache-read-only: true
# Test that the project-dot-gradle cache will cache and restore configuration-cache
configuration-cache:
needs: seed-build
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Execute Gradle build and verify cached configuration
uses: ./
env:
VERIFY_CACHED_CONFIGURATION: true
with:
build-root-directory: __tests__/samples/groovy-dsl
arguments: test --configuration-cache
cache-read-only: true
# Check that the build can run when no bundles are restored # Check that the build can run when no bundles are restored
no-bundles-restored: no-bundles-restored:
needs: seed-build needs: seed-build

View file

@ -1,5 +1,5 @@
{ {
"printWidth": 80, "printWidth": 120,
"tabWidth": 4, "tabWidth": 4,
"useTabs": false, "useTabs": false,
"semi": false, "semi": false,

View file

@ -191,10 +191,12 @@ The contents to be cached can be fine tuned by including and excluding certain p
```yaml ```yaml
# Cache downloaded JDKs in addition to the default directories. # Cache downloaded JDKs in addition to the default directories.
gradle-home-cache-includes: | gradle-home-cache-includes: |
["caches", "notifications", "jdks"] caches
notifications
jdks
# Exclude the local build-cache from the directories cached. # Exclude the local build-cache from the directories cached.
gradle-home-cache-excludes: | gradle-home-cache-excludes: |
["caches/build-cache-1"] caches/build-cache-1
``` ```
You can specify any number of fixed paths or patterns to include or exclude. You can specify any number of fixed paths or patterns to include or exclude.

View file

@ -36,13 +36,13 @@ inputs:
description: Paths within Gradle User Home to cache. description: Paths within Gradle User Home to cache.
required: false required: false
default: | default: |
["caches", "notifications"] caches
notifications
gradle-home-cache-excludes: gradle-home-cache-excludes:
description: Paths within Gradle User Home to exclude from cache. description: Paths within Gradle User Home to exclude from cache.
required: false required: false
default: |
[]
# 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"]
@ -62,7 +62,7 @@ inputs:
[ [
["generated-gradle-jars", "caches/*/generated-gradle-jars/*.jar"], ["generated-gradle-jars", "caches/*/generated-gradle-jars/*.jar"],
["wrapper-zips", "wrapper/dists/*/*/*.zip"], ["wrapper-zips", "wrapper/dists/*/*/*.zip"],
["dependency-jars", "caches/modules-*/files-*/**/*.jar"], ["dependencies", "caches/modules-*/files-*/*/*/*/*/"],
["instrumented-jars", "caches/jars-*/*/"], ["instrumented-jars", "caches/jars-*/*/"],
["kotlin-dsl", "caches/*/kotlin-dsl/*/*/"] ["kotlin-dsl", "caches/*/kotlin-dsl/*/*/"]
] ]

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

@ -7,6 +7,8 @@ import * as exec from '@actions/exec'
import { import {
AbstractCache, AbstractCache,
CacheEntryReport,
CachingReport,
getCacheKeyPrefix, getCacheKeyPrefix,
hashFileNames, hashFileNames,
tryDelete tryDelete
@ -26,21 +28,38 @@ export class GradleUserHomeCache extends AbstractCache {
this.gradleUserHome = this.determineGradleUserHome(rootDir) this.gradleUserHome = this.determineGradleUserHome(rootDir)
} }
async afterRestore(): Promise<void> { async afterRestore(report: CachingReport): Promise<void> {
await this.reportGradleUserHomeSize('as restored from cache') await this.reportGradleUserHomeSize('as restored from cache')
await this.restoreArtifactBundles() await this.restoreArtifactBundles(report)
await this.reportGradleUserHomeSize('after restoring common artifacts') await this.reportGradleUserHomeSize('after restoring common artifacts')
} }
private async restoreArtifactBundles(): Promise<void> { private async restoreArtifactBundles(report: CachingReport): Promise<void> {
const processes: Promise<void>[] = [] const processes: Promise<void>[] = []
for (const [bundle, pattern] of this.getArtifactBundles()) {
const p = this.restoreArtifactBundle(bundle, pattern) const bundleMetaFiles = await this.getBundleMetaFiles()
// Run sequentially when debugging enabled const bundlePatterns = this.getArtifactBundles()
if (this.cacheDebuggingEnabled) {
await p // Iterate over all bundle meta files and try to restore
for (const bundleMetaFile of bundleMetaFiles) {
const bundle = path.basename(bundleMetaFile, '.cache')
const bundleEntryReport = report.addEntryReport(bundle)
const bundlePattern = bundlePatterns.get(bundle)
// Handle case where the 'artifactBundlePatterns' have been changed
if (bundlePattern === undefined) {
core.info(`Found bundle metafile for ${bundle} but no such bundle defined`)
bundleEntryReport.markRequested('BUNDLE_NOT_CONFIGURED')
tryDelete(bundleMetaFile)
return
} else {
const p = this.restoreArtifactBundle(bundle, bundlePattern, bundleMetaFile, bundleEntryReport)
// Run sequentially when debugging enabled
if (this.cacheDebuggingEnabled) {
await p
}
processes.push(p)
} }
processes.push(p)
} }
await Promise.all(processes) await Promise.all(processes)
@ -48,25 +67,20 @@ export class GradleUserHomeCache extends AbstractCache {
private async restoreArtifactBundle( private async restoreArtifactBundle(
bundle: string, bundle: string,
artifactPath: string bundlePattern: string,
bundleMetaFile: string,
report: CacheEntryReport
): Promise<void> { ): Promise<void> {
const bundleMetaFile = this.getBundleMetaFile(bundle) const cacheKey = fs.readFileSync(bundleMetaFile, 'utf-8').trim()
if (fs.existsSync(bundleMetaFile)) { report.markRequested(cacheKey)
const cacheKey = fs.readFileSync(bundleMetaFile, 'utf-8').trim()
const restoreKey = await this.restoreCache([artifactPath], cacheKey) const restoredKey = await this.restoreCache([bundlePattern], cacheKey)
if (restoreKey) { if (restoredKey) {
core.info( core.info(`Restored ${bundle} with key ${cacheKey} to ${bundlePattern}`)
`Restored ${bundle} with key ${cacheKey} to ${artifactPath}` report.markRestored(restoredKey)
)
} else {
this.debug(
`Did not restore ${bundle} with key ${cacheKey} to ${artifactPath}`
)
}
} else { } else {
this.debug( core.info(`Did not restore ${bundle} with key ${cacheKey} to ${bundlePattern}`)
`No metafile found to restore ${bundle}: ${bundleMetaFile}` tryDelete(bundleMetaFile)
)
} }
} }
@ -74,6 +88,13 @@ export class GradleUserHomeCache extends AbstractCache {
return path.resolve(this.gradleUserHome, META_FILE_DIR, `${name}.cache`) return path.resolve(this.gradleUserHome, META_FILE_DIR, `${name}.cache`)
} }
private async getBundleMetaFiles(): Promise<string[]> {
const metaFiles = path.resolve(this.gradleUserHome, META_FILE_DIR, '*.cache')
const globber = await glob.create(metaFiles)
const bundleFiles = await globber.glob()
return bundleFiles
}
async beforeSave(): Promise<void> { async beforeSave(): Promise<void> {
await this.reportGradleUserHomeSize('before saving common artifacts') await this.reportGradleUserHomeSize('before saving common artifacts')
this.removeExcludedPaths() this.removeExcludedPaths()
@ -84,12 +105,8 @@ export class GradleUserHomeCache extends AbstractCache {
} }
private removeExcludedPaths(): void { private removeExcludedPaths(): void {
const rawPaths: string[] = JSON.parse( const rawPaths: string[] = core.getMultilineInput(EXCLUDE_PATHS_PARAMETER)
core.getInput(EXCLUDE_PATHS_PARAMETER) const resolvedPaths = rawPaths.map(x => path.resolve(this.gradleUserHome, x))
)
const resolvedPaths = rawPaths.map(x =>
path.resolve(this.gradleUserHome, x)
)
for (const p of resolvedPaths) { for (const p of resolvedPaths) {
this.debug(`Deleting excluded path: ${p}`) this.debug(`Deleting excluded path: ${p}`)
@ -111,10 +128,7 @@ export class GradleUserHomeCache extends AbstractCache {
await Promise.all(processes) await Promise.all(processes)
} }
private async saveArtifactBundle( private async saveArtifactBundle(bundle: string, artifactPath: string): Promise<void> {
bundle: string,
artifactPath: string
): Promise<void> {
const bundleMetaFile = this.getBundleMetaFile(bundle) const bundleMetaFile = this.getBundleMetaFile(bundle)
const globber = await glob.create(artifactPath, { const globber = await glob.create(artifactPath, {
@ -138,9 +152,7 @@ export class GradleUserHomeCache extends AbstractCache {
const cacheKey = this.createCacheKey(bundle, bundleFiles) const cacheKey = this.createCacheKey(bundle, bundleFiles)
if (previouslyRestoredKey === cacheKey) { if (previouslyRestoredKey === cacheKey) {
this.debug( this.debug(`No change to previously restored ${bundle}. Not caching.`)
`No change to previously restored ${bundle}. Not caching.`
)
} else { } else {
core.info(`Caching ${bundle} with cache key: ${cacheKey}`) core.info(`Caching ${bundle} with cache key: ${cacheKey}`)
await this.saveCache([artifactPath], cacheKey) await this.saveCache([artifactPath], cacheKey)
@ -154,14 +166,10 @@ export class GradleUserHomeCache extends AbstractCache {
protected createCacheKey(bundle: string, files: string[]): string { protected createCacheKey(bundle: string, files: string[]): string {
const cacheKeyPrefix = getCacheKeyPrefix() const cacheKeyPrefix = getCacheKeyPrefix()
const relativeFiles = files.map(x => const relativeFiles = files.map(x => path.relative(this.gradleUserHome, x))
path.relative(this.gradleUserHome, x)
)
const key = hashFileNames(relativeFiles) const key = hashFileNames(relativeFiles)
this.debug( this.debug(`Generating cache key for ${bundle} from files: ${relativeFiles}`)
`Generating cache key for ${bundle} from files: ${relativeFiles}`
)
return `${cacheKeyPrefix}${bundle}-${key}` return `${cacheKeyPrefix}${bundle}-${key}`
} }
@ -193,9 +201,7 @@ export class GradleUserHomeCache extends AbstractCache {
} }
protected getCachePath(): string[] { protected getCachePath(): string[] {
const rawPaths: string[] = JSON.parse( const rawPaths: string[] = core.getMultilineInput(INCLUDE_PATHS_PARAMETER)
core.getInput(INCLUDE_PATHS_PARAMETER)
)
rawPaths.push(META_FILE_DIR) rawPaths.push(META_FILE_DIR)
const resolvedPaths = rawPaths.map(x => this.resolveCachePath(x)) const resolvedPaths = rawPaths.map(x => this.resolveCachePath(x))
this.debug(`Using cache paths: ${resolvedPaths}`) this.debug(`Using cache paths: ${resolvedPaths}`)
@ -211,19 +217,10 @@ export class GradleUserHomeCache extends AbstractCache {
} }
private getArtifactBundles(): Map<string, string> { private getArtifactBundles(): Map<string, string> {
const artifactBundleDefinition = core.getInput( const artifactBundleDefinition = core.getInput(ARTIFACT_BUNDLES_PARAMETER)
ARTIFACT_BUNDLES_PARAMETER this.debug(`Using artifact bundle definition: ${artifactBundleDefinition}`)
)
this.debug(
`Using artifact bundle definition: ${artifactBundleDefinition}`
)
const artifactBundles = JSON.parse(artifactBundleDefinition) const artifactBundles = JSON.parse(artifactBundleDefinition)
return new Map( return new Map(Array.from(artifactBundles, ([key, value]) => [key, path.resolve(this.gradleUserHome, value)]))
Array.from(artifactBundles, ([key, value]) => [
key,
path.resolve(this.gradleUserHome, value)
])
)
} }
private async reportGradleUserHomeSize(label: string): Promise<void> { private async reportGradleUserHomeSize(label: string): Promise<void> {
@ -233,15 +230,11 @@ export class GradleUserHomeCache extends AbstractCache {
if (!fs.existsSync(this.gradleUserHome)) { if (!fs.existsSync(this.gradleUserHome)) {
return return
} }
const result = await exec.getExecOutput( const result = await exec.getExecOutput('du', ['-h', '-c', '-t', '5M'], {
'du', cwd: this.gradleUserHome,
['-h', '-c', '-t', '5M'], silent: true,
{ ignoreReturnCode: true
cwd: this.gradleUserHome, })
silent: true,
ignoreReturnCode: true
}
)
core.info(`Gradle User Home (directories >5M): ${label}`) core.info(`Gradle User Home (directories >5M): ${label}`)

View file

@ -5,6 +5,8 @@ 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'
const CACHE_PROTOCOL_VERSION = 'v4-'
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 JOB_CONTEXT_PARAMETER = 'workflow-job-context' const JOB_CONTEXT_PARAMETER = 'workflow-job-context'
@ -25,7 +27,7 @@ export function isCacheDebuggingEnabled(): boolean {
export function getCacheKeyPrefix(): string { export function getCacheKeyPrefix(): string {
// Prefix can be used to force change all cache keys (defaults to cache protocol version) // Prefix can be used to force change all cache keys (defaults to cache protocol version)
return process.env[CACHE_PREFIX_VAR] || 'v3-' return process.env[CACHE_PREFIX_VAR] || CACHE_PROTOCOL_VERSION
} }
function generateCacheKey(cacheName: string): CacheKey { function generateCacheKey(cacheName: string): CacheKey {
@ -44,11 +46,7 @@ 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}`
return new CacheKey(cacheKey, [ return new CacheKey(cacheKey, [cacheKeyForJobContext, cacheKeyForJob, cacheKeyForOs])
cacheKeyForJobContext,
cacheKeyForJob,
cacheKeyForOs
])
} }
function determineJobContext(): string { function determineJobContext(): string {
@ -66,9 +64,7 @@ export function hashStrings(values: string[]): string {
} }
export function hashFileNames(fileNames: string[]): string { export function hashFileNames(fileNames: string[]): string {
return hashStrings( return hashStrings(fileNames.map(x => x.replace(new RegExp(`\\${path.sep}`, 'g'), '/')))
fileNames.map(x => x.replace(new RegExp(`\\${path.sep}`, 'g'), '/'))
)
} }
/** /**
@ -109,6 +105,48 @@ class CacheKey {
} }
} }
export class CachingReport {
cacheEntryReports: CacheEntryReport[] = []
get fullyRestored(): boolean {
return this.cacheEntryReports.every(x => !x.wasRequestedButNotRestored())
}
addEntryReport(name: string): CacheEntryReport {
const report = new CacheEntryReport(name)
this.cacheEntryReports.push(report)
return report
}
}
export class CacheEntryReport {
entryName: string
requestedKey: string | undefined
requestedRestoreKeys: string[] | undefined
restoredKey: string | undefined
restoredSize: number | undefined
savedKey: string | undefined
savedSize: number | undefined
constructor(entryName: string) {
this.entryName = entryName
}
wasRequestedButNotRestored(): boolean {
return this.requestedKey !== undefined && this.restoredKey === undefined
}
markRequested(key: string, restoreKeys: string[] = []): void {
this.requestedKey = key
this.requestedRestoreKeys = restoreKeys
}
markRestored(key: string): void {
this.restoredKey = key
}
}
export abstract class AbstractCache { export abstract class AbstractCache {
private cacheName: string private cacheName: string
private cacheDescription: string private cacheDescription: string
@ -125,17 +163,15 @@ export abstract class AbstractCache {
this.cacheDebuggingEnabled = isCacheDebuggingEnabled() this.cacheDebuggingEnabled = isCacheDebuggingEnabled()
} }
async restore(): Promise<void> { async restore(report: CachingReport): Promise<void> {
if (this.cacheOutputExists()) { if (this.cacheOutputExists()) {
core.info( core.info(`${this.cacheDescription} already exists. Not restoring from cache.`)
`${this.cacheDescription} already exists. Not restoring from cache.`
)
return return
} }
const cacheKey = generateCacheKey(this.cacheName) const cacheKey = this.prepareCacheKey()
const entryReport = report.addEntryReport(this.cacheName)
core.saveState(this.cacheKeyStateKey, cacheKey.key) entryReport.markRequested(cacheKey.key, cacheKey.restoreKeys)
this.debug( this.debug(
`Requesting ${this.cacheDescription} with `Requesting ${this.cacheDescription} with
@ -143,34 +179,29 @@ export abstract class AbstractCache {
restoreKeys:[${cacheKey.restoreKeys}]` restoreKeys:[${cacheKey.restoreKeys}]`
) )
const cacheResult = await this.restoreCache( const cacheResult = await this.restoreCache(this.getCachePath(), cacheKey.key, cacheKey.restoreKeys)
this.getCachePath(),
cacheKey.key,
cacheKey.restoreKeys
)
if (!cacheResult) { if (!cacheResult) {
core.info( core.info(`${this.cacheDescription} cache not found. Will start with empty.`)
`${this.cacheDescription} cache not found. Will start with empty.`
)
return return
} }
core.saveState(this.cacheResultStateKey, cacheResult) core.saveState(this.cacheResultStateKey, cacheResult)
entryReport.markRestored(cacheResult)
core.info( core.info(`Restored ${this.cacheDescription} from cache key: ${cacheResult}`)
`Restored ${this.cacheDescription} from cache key: ${cacheResult}`
)
try { try {
await this.afterRestore() await this.afterRestore(report)
} catch (error) { } catch (error) {
core.warning( core.warning(`Restore ${this.cacheDescription} failed in 'afterRestore': ${error}`)
`Restore ${this.cacheDescription} failed in 'afterRestore': ${error}`
)
} }
}
return prepareCacheKey(): CacheKey {
const cacheKey = generateCacheKey(this.cacheName)
core.saveState(this.cacheKeyStateKey, cacheKey.key)
return cacheKey
} }
protected async restoreCache( protected async restoreCache(
@ -179,11 +210,7 @@ export abstract class AbstractCache {
cacheRestoreKeys: string[] = [] cacheRestoreKeys: string[] = []
): Promise<string | undefined> { ): Promise<string | undefined> {
try { try {
return await cache.restoreCache( return await cache.restoreCache(cachePath, cacheKey, cacheRestoreKeys)
cachePath,
cacheKey,
cacheRestoreKeys
)
} catch (error) { } catch (error) {
if (error instanceof cache.ValidationError) { if (error instanceof cache.ValidationError) {
// Validation errors should fail the build action // Validation errors should fail the build action
@ -195,7 +222,7 @@ export abstract class AbstractCache {
} }
} }
protected async afterRestore(): Promise<void> {} protected async afterRestore(_report: CachingReport): Promise<void> {}
async save(): Promise<void> { async save(): Promise<void> {
if (!this.cacheOutputExists()) { if (!this.cacheOutputExists()) {
@ -207,31 +234,23 @@ export abstract class AbstractCache {
const cacheResult = core.getState(this.cacheResultStateKey) const cacheResult = core.getState(this.cacheResultStateKey)
if (!cacheKey) { if (!cacheKey) {
this.debug( this.debug(`${this.cacheDescription} existed prior to cache restore. Not saving.`)
`${this.cacheDescription} existed prior to cache restore. Not saving.`
)
return return
} }
if (cacheResult && cacheKey === cacheResult) { if (cacheResult && cacheKey === cacheResult) {
core.info( core.info(`Cache hit occurred on the cache key ${cacheKey}, not saving cache.`)
`Cache hit occurred on the cache key ${cacheKey}, not saving cache.`
)
return return
} }
try { try {
await this.beforeSave() await this.beforeSave()
} catch (error) { } catch (error) {
core.warning( core.warning(`Save ${this.cacheDescription} failed in 'beforeSave': ${error}`)
`Save ${this.cacheDescription} failed in 'beforeSave': ${error}`
)
return return
} }
core.info( core.info(`Caching ${this.cacheDescription} with cache key: ${cacheKey}`)
`Caching ${this.cacheDescription} with cache key: ${cacheKey}`
)
const cachePath = this.getCachePath() const cachePath = this.getCachePath()
await this.saveCache(cachePath, cacheKey) await this.saveCache(cachePath, cacheKey)
@ -240,10 +259,7 @@ export abstract class AbstractCache {
protected async beforeSave(): Promise<void> {} protected async beforeSave(): Promise<void> {}
protected async saveCache( protected async saveCache(cachePath: string[], cacheKey: string): Promise<void> {
cachePath: string[],
cacheKey: string
): Promise<void> {
try { try {
await cache.saveCache(cachePath, cacheKey) await cache.saveCache(cachePath, cacheKey)
} catch (error) { } catch (error) {

View file

@ -1,32 +1,37 @@
import {GradleUserHomeCache} from './cache-gradle-user-home' import {GradleUserHomeCache} from './cache-gradle-user-home'
import {ProjectDotGradleCache} from './cache-project-dot-gradle' import {ProjectDotGradleCache} from './cache-project-dot-gradle'
import * as core from '@actions/core' import * as core from '@actions/core'
import {isCacheDisabled, isCacheReadOnly} from './cache-utils' import {CachingReport, isCacheDisabled, isCacheReadOnly} from './cache-utils'
const BUILD_ROOT_DIR = 'BUILD_ROOT_DIR' const BUILD_ROOT_DIR = 'BUILD_ROOT_DIR'
export async function restore(buildRootDirectory: string): Promise<void> { export async function restore(buildRootDirectory: string): Promise<void> {
if (isCacheDisabled()) { if (isCacheDisabled()) {
core.info( core.info('Cache is disabled: will not restore state from previous builds.')
'Cache is disabled: will not restore state from previous builds.'
)
return return
} }
await core.group('Restore Gradle state from cache', async () => { await core.group('Restore Gradle state from cache', async () => {
core.saveState(BUILD_ROOT_DIR, buildRootDirectory) core.saveState(BUILD_ROOT_DIR, buildRootDirectory)
return Promise.all([
new GradleUserHomeCache(buildRootDirectory).restore(), const cachingReport = new CachingReport()
new ProjectDotGradleCache(buildRootDirectory).restore()
]) await new GradleUserHomeCache(buildRootDirectory).restore(cachingReport)
const projectDotGradleCache = new ProjectDotGradleCache(buildRootDirectory)
if (cachingReport.fullyRestored) {
// Only restore the configuration-cache if the Gradle Home is fully restored
await projectDotGradleCache.restore(cachingReport)
} else {
// Otherwise, prepare the cache key for later save()
projectDotGradleCache.prepareCacheKey()
}
}) })
} }
export async function save(): Promise<void> { export async function save(): Promise<void> {
if (isCacheReadOnly()) { if (isCacheReadOnly()) {
core.info( core.info('Cache is read-only: will not save state for use in subsequent builds.')
'Cache is read-only: will not save state for use in subsequent builds.'
)
return return
} }

View file

@ -3,11 +3,7 @@ import fs from 'fs'
import path from 'path' import path from 'path'
import {writeInitScript} from './build-scan-capture' import {writeInitScript} from './build-scan-capture'
export async function execute( export async function execute(executable: string, root: string, args: string[]): Promise<BuildResult> {
executable: string,
root: string,
args: string[]
): Promise<BuildResult> {
let buildScanUrl: string | undefined let buildScanUrl: string | undefined
// TODO: instead of running with no-daemon, run `--stop` in post action. // TODO: instead of running with no-daemon, run `--stop` in post action.

View file

@ -17,10 +17,7 @@ export function locateGradleWrapperScript(buildRootDirectory: string): string {
} }
function validateGradleWrapper(buildRootDirectory: string): void { function validateGradleWrapper(buildRootDirectory: string): void {
const wrapperProperties = path.resolve( const wrapperProperties = path.resolve(buildRootDirectory, 'gradle/wrapper/gradle-wrapper.properties')
buildRootDirectory,
'gradle/wrapper/gradle-wrapper.properties'
)
if (!fs.existsSync(wrapperProperties)) { if (!fs.existsSync(wrapperProperties)) {
throw new Error( throw new Error(
`Cannot locate a Gradle wrapper properties file at '${wrapperProperties}'. Specify 'gradle-version' or 'gradle-executable' for projects without Gradle wrapper configured.` `Cannot locate a Gradle wrapper properties file at '${wrapperProperties}'. Specify 'gradle-version' or 'gradle-executable' for projects without Gradle wrapper configured.`

View file

@ -18,10 +18,7 @@ export async function run(): Promise<void> {
const args: string[] = parseCommandLineArguments() const args: string[] = parseCommandLineArguments()
const result = await execution.execute( const result = await execution.execute(
await resolveGradleExecutable( await resolveGradleExecutable(workspaceDirectory, buildRootDirectory),
workspaceDirectory,
buildRootDirectory
),
buildRootDirectory, buildRootDirectory,
args args
) )
@ -34,9 +31,7 @@ export async function run(): Promise<void> {
if (result.buildScanUrl) { if (result.buildScanUrl) {
core.setFailed(`Gradle build failed: ${result.buildScanUrl}`) core.setFailed(`Gradle build failed: ${result.buildScanUrl}`)
} else { } else {
core.setFailed( core.setFailed(`Gradle build failed: process exited with status ${result.status}`)
`Gradle build failed: process exited with status ${result.status}`
)
} }
} else { } else {
if (result.buildScanUrl) { if (result.buildScanUrl) {
@ -53,10 +48,7 @@ export async function run(): Promise<void> {
run() run()
async function resolveGradleExecutable( async function resolveGradleExecutable(workspaceDirectory: string, buildRootDirectory: string): Promise<string> {
workspaceDirectory: string,
buildRootDirectory: string
): Promise<string> {
const gradleVersion = core.getInput('gradle-version') const gradleVersion = core.getInput('gradle-version')
if (gradleVersion !== '' && gradleVersion !== 'wrapper') { if (gradleVersion !== '' && gradleVersion !== 'wrapper') {
return path.resolve(await provision.gradleVersion(gradleVersion)) return path.resolve(await provision.gradleVersion(gradleVersion))
@ -73,9 +65,7 @@ async function resolveGradleExecutable(
function resolveBuildRootDirectory(baseDirectory: string): string { function resolveBuildRootDirectory(baseDirectory: string): string {
const buildRootDirectory = core.getInput('build-root-directory') const buildRootDirectory = core.getInput('build-root-directory')
const resolvedBuildRootDirectory = const resolvedBuildRootDirectory =
buildRootDirectory === '' buildRootDirectory === '' ? path.resolve(baseDirectory) : path.resolve(baseDirectory, buildRootDirectory)
? path.resolve(baseDirectory)
: path.resolve(baseDirectory, buildRootDirectory)
return resolvedBuildRootDirectory return resolvedBuildRootDirectory
} }

View file

@ -19,9 +19,7 @@ export async function gradleVersion(version: string): Promise<string> {
case 'current': case 'current':
return gradleCurrent() return gradleCurrent()
case 'rc': case 'rc':
core.warning( core.warning(`Specifying gradle-version 'rc' has been deprecated. Use 'release-candidate' instead.`)
`Specifying gradle-version 'rc' has been deprecated. Use 'release-candidate' instead.`
)
return gradleReleaseCandidate() return gradleReleaseCandidate()
case 'release-candidate': case 'release-candidate':
return gradleReleaseCandidate() return gradleReleaseCandidate()
@ -35,16 +33,12 @@ export async function gradleVersion(version: string): Promise<string> {
} }
async function gradleCurrent(): Promise<string> { async function gradleCurrent(): Promise<string> {
const versionInfo = await gradleVersionDeclaration( const versionInfo = await gradleVersionDeclaration(`${gradleVersionsBaseUrl}/current`)
`${gradleVersionsBaseUrl}/current`
)
return provisionGradle(versionInfo) return provisionGradle(versionInfo)
} }
async function gradleReleaseCandidate(): Promise<string> { async function gradleReleaseCandidate(): Promise<string> {
const versionInfo = await gradleVersionDeclaration( const versionInfo = await gradleVersionDeclaration(`${gradleVersionsBaseUrl}/release-candidate`)
`${gradleVersionsBaseUrl}/release-candidate`
)
if (versionInfo && versionInfo.version && versionInfo.downloadUrl) { if (versionInfo && versionInfo.version && versionInfo.downloadUrl) {
return provisionGradle(versionInfo) return provisionGradle(versionInfo)
} }
@ -53,16 +47,12 @@ async function gradleReleaseCandidate(): Promise<string> {
} }
async function gradleNightly(): Promise<string> { async function gradleNightly(): Promise<string> {
const versionInfo = await gradleVersionDeclaration( const versionInfo = await gradleVersionDeclaration(`${gradleVersionsBaseUrl}/nightly`)
`${gradleVersionsBaseUrl}/nightly`
)
return provisionGradle(versionInfo) return provisionGradle(versionInfo)
} }
async function gradleReleaseNightly(): Promise<string> { async function gradleReleaseNightly(): Promise<string> {
const versionInfo = await gradleVersionDeclaration( const versionInfo = await gradleVersionDeclaration(`${gradleVersionsBaseUrl}/release-nightly`)
`${gradleVersionsBaseUrl}/release-nightly`
)
return provisionGradle(versionInfo) return provisionGradle(versionInfo)
} }
@ -74,34 +64,24 @@ async function gradle(version: string): Promise<string> {
return provisionGradle(versionInfo) return provisionGradle(versionInfo)
} }
async function gradleVersionDeclaration( async function gradleVersionDeclaration(url: string): Promise<GradleVersionInfo> {
url: string
): Promise<GradleVersionInfo> {
return await httpGetGradleVersion(url) return await httpGetGradleVersion(url)
} }
async function findGradleVersionDeclaration( async function findGradleVersionDeclaration(version: string): Promise<GradleVersionInfo | undefined> {
version: string const gradleVersions = await httpGetGradleVersions(`${gradleVersionsBaseUrl}/all`)
): Promise<GradleVersionInfo | undefined> {
const gradleVersions = await httpGetGradleVersions(
`${gradleVersionsBaseUrl}/all`
)
return gradleVersions.find((entry: GradleVersionInfo) => { return gradleVersions.find((entry: GradleVersionInfo) => {
return entry.version === version return entry.version === version
}) })
} }
async function provisionGradle( async function provisionGradle(versionInfo: GradleVersionInfo): Promise<string> {
versionInfo: GradleVersionInfo
): Promise<string> {
return core.group(`Provision Gradle ${versionInfo.version}`, async () => { return core.group(`Provision Gradle ${versionInfo.version}`, async () => {
return locateGradleAndDownloadIfRequired(versionInfo) return locateGradleAndDownloadIfRequired(versionInfo)
}) })
} }
async function locateGradleAndDownloadIfRequired( async function locateGradleAndDownloadIfRequired(versionInfo: GradleVersionInfo): Promise<string> {
versionInfo: GradleVersionInfo
): Promise<string> {
const installsDir = path.join(os.homedir(), 'gradle-installations/installs') const installsDir = path.join(os.homedir(), 'gradle-installations/installs')
const installDir = path.join(installsDir, `gradle-${versionInfo.version}`) const installDir = path.join(installsDir, `gradle-${versionInfo.version}`)
if (fs.existsSync(installDir)) { if (fs.existsSync(installDir)) {
@ -120,13 +100,8 @@ async function locateGradleAndDownloadIfRequired(
return executable return executable
} }
async function downloadAndCacheGradleDistribution( async function downloadAndCacheGradleDistribution(versionInfo: GradleVersionInfo): Promise<string> {
versionInfo: GradleVersionInfo const downloadPath = path.join(os.homedir(), `gradle-installations/downloads/gradle-${versionInfo.version}-bin.zip`)
): Promise<string> {
const downloadPath = path.join(
os.homedir(),
`gradle-installations/downloads/gradle-${versionInfo.version}-bin.zip`
)
if (isCacheDisabled()) { if (isCacheDisabled()) {
await downloadGradleDistribution(versionInfo, downloadPath) await downloadGradleDistribution(versionInfo, downloadPath)
@ -136,14 +111,10 @@ async function downloadAndCacheGradleDistribution(
const cacheKey = `gradle-${versionInfo.version}` const cacheKey = `gradle-${versionInfo.version}`
const restoreKey = await cache.restoreCache([downloadPath], cacheKey) const restoreKey = await cache.restoreCache([downloadPath], cacheKey)
if (restoreKey) { if (restoreKey) {
core.info( core.info(`Restored Gradle distribution ${cacheKey} from cache to ${downloadPath}`)
`Restored Gradle distribution ${cacheKey} from cache to ${downloadPath}`
)
return downloadPath return downloadPath
} }
core.info( core.info(`Gradle distribution ${versionInfo.version} not found in cache. Will download.`)
`Gradle distribution ${versionInfo.version} not found in cache. Will download.`
)
await downloadGradleDistribution(versionInfo, downloadPath) await downloadGradleDistribution(versionInfo, downloadPath)
if (!isCacheReadOnly()) { if (!isCacheReadOnly()) {
@ -151,10 +122,7 @@ async function downloadAndCacheGradleDistribution(
await cache.saveCache([downloadPath], cacheKey) await cache.saveCache([downloadPath], cacheKey)
} catch (error) { } catch (error) {
// Fail on validation errors or non-errors (the latter to keep Typescript happy) // Fail on validation errors or non-errors (the latter to keep Typescript happy)
if ( if (error instanceof cache.ValidationError || !(error instanceof Error)) {
error instanceof cache.ValidationError ||
!(error instanceof Error)
) {
throw error throw error
} }
core.warning(error.message) core.warning(error.message)
@ -163,16 +131,9 @@ async function downloadAndCacheGradleDistribution(
return downloadPath return downloadPath
} }
async function downloadGradleDistribution( async function downloadGradleDistribution(versionInfo: GradleVersionInfo, downloadPath: string): Promise<void> {
versionInfo: GradleVersionInfo,
downloadPath: string
): Promise<void> {
await toolCache.downloadTool(versionInfo.downloadUrl, downloadPath) await toolCache.downloadTool(versionInfo.downloadUrl, downloadPath)
core.info( core.info(`Downloaded ${versionInfo.downloadUrl} to ${downloadPath} (size ${fs.statSync(downloadPath).size})`)
`Downloaded ${versionInfo.downloadUrl} to ${downloadPath} (size ${
fs.statSync(downloadPath).size
})`
)
} }
function executableFrom(installDir: string): string { function executableFrom(installDir: string): string {
@ -183,9 +144,7 @@ async function httpGetGradleVersion(url: string): Promise<GradleVersionInfo> {
return JSON.parse(await httpGetString(url)) return JSON.parse(await httpGetString(url))
} }
async function httpGetGradleVersions( async function httpGetGradleVersions(url: string): Promise<GradleVersionInfo[]> {
url: string
): Promise<GradleVersionInfo[]> {
return JSON.parse(await httpGetString(url)) return JSON.parse(await httpGetString(url))
} }