mirror of
https://github.com/gradle/gradle-build-action.git
synced 2024-11-22 17:12:51 +00:00
Track 'fully-restored' by tracking each cache restore
Instead of tracking a single 'fully-restored' flag, track the restore status of each cache entry restore. If any of these are requested but not restored, then the overall Gradle User Home cache is not fully restored. Added special handling for the case when zero artifact bundles are set: this is used in tests to simulate a not-fully-restored state.
This commit is contained in:
parent
9edc2a11bd
commit
8ba5a0033b
3 changed files with 97 additions and 31 deletions
|
@ -5,7 +5,14 @@ import * as core from '@actions/core'
|
||||||
import * as glob from '@actions/glob'
|
import * as glob from '@actions/glob'
|
||||||
import * as exec from '@actions/exec'
|
import * as exec from '@actions/exec'
|
||||||
|
|
||||||
import {AbstractCache, CachingReport, getCacheKeyPrefix, hashFileNames, tryDelete} from './cache-utils'
|
import {
|
||||||
|
AbstractCache,
|
||||||
|
CacheEntryReport,
|
||||||
|
CachingReport,
|
||||||
|
getCacheKeyPrefix,
|
||||||
|
hashFileNames,
|
||||||
|
tryDelete
|
||||||
|
} from './cache-utils'
|
||||||
|
|
||||||
const META_FILE_DIR = '.gradle-build-action'
|
const META_FILE_DIR = '.gradle-build-action'
|
||||||
|
|
||||||
|
@ -23,17 +30,33 @@ export class GradleUserHomeCache extends AbstractCache {
|
||||||
|
|
||||||
async afterRestore(report: CachingReport): Promise<void> {
|
async afterRestore(report: CachingReport): Promise<void> {
|
||||||
await this.reportGradleUserHomeSize('as restored from cache')
|
await this.reportGradleUserHomeSize('as restored from cache')
|
||||||
const result = await this.restoreArtifactBundles()
|
await this.restoreArtifactBundles(report)
|
||||||
await this.reportGradleUserHomeSize('after restoring common artifacts')
|
await this.reportGradleUserHomeSize('after restoring common artifacts')
|
||||||
if (!result) {
|
|
||||||
report.fullyRestored = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async restoreArtifactBundles(): Promise<boolean> {
|
private async restoreArtifactBundles(report: CachingReport): Promise<void> {
|
||||||
const processes: Promise<boolean>[] = []
|
const processes: Promise<void>[] = []
|
||||||
|
|
||||||
|
// This is special logic that allows the tests to simulate a "not restored" state by configuring an empty set of bundles
|
||||||
|
// This is similar to how the primary implementation should work:
|
||||||
|
// - Iterate over bundle meta-files as the basis for restoring content.
|
||||||
|
// - Leave bundle meta-files for successful restore.
|
||||||
|
// - Remove bundle meta-files that are not restored.
|
||||||
|
if (this.getArtifactBundles().size === 0) {
|
||||||
|
const bundleMetaFiles = await this.getBundleMetaFiles()
|
||||||
|
|
||||||
|
for (const bundleMetaFile of bundleMetaFiles) {
|
||||||
|
const bundle = path.basename(bundleMetaFile, '.cache')
|
||||||
|
|
||||||
|
core.info(`Found bundle metafile for ${bundle} but no such bundle configured`)
|
||||||
|
report.addEntryReport(bundleMetaFile).markRequested('BUNDLE_NOT_CONFIGURED')
|
||||||
|
tryDelete(bundleMetaFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const [bundle, pattern] of this.getArtifactBundles()) {
|
for (const [bundle, pattern] of this.getArtifactBundles()) {
|
||||||
const p = this.restoreArtifactBundle(bundle, pattern)
|
const bundleEntryReport = report.addEntryReport(bundle)
|
||||||
|
const p = this.restoreArtifactBundle(bundle, pattern, bundleEntryReport)
|
||||||
// Run sequentially when debugging enabled
|
// Run sequentially when debugging enabled
|
||||||
if (this.cacheDebuggingEnabled) {
|
if (this.cacheDebuggingEnabled) {
|
||||||
await p
|
await p
|
||||||
|
@ -41,32 +64,39 @@ export class GradleUserHomeCache extends AbstractCache {
|
||||||
processes.push(p)
|
processes.push(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = await Promise.all(processes)
|
await Promise.all(processes)
|
||||||
// Assume that no-bundles means not-fully-restored
|
|
||||||
return results.length > 0 && results.every(Boolean)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async restoreArtifactBundle(bundle: string, artifactPath: string): Promise<boolean> {
|
private async restoreArtifactBundle(bundle: string, artifactPath: string, report: CacheEntryReport): Promise<void> {
|
||||||
const bundleMetaFile = this.getBundleMetaFile(bundle)
|
const bundleMetaFile = this.getBundleMetaFile(bundle)
|
||||||
if (fs.existsSync(bundleMetaFile)) {
|
if (fs.existsSync(bundleMetaFile)) {
|
||||||
const cacheKey = fs.readFileSync(bundleMetaFile, 'utf-8').trim()
|
const cacheKey = fs.readFileSync(bundleMetaFile, 'utf-8').trim()
|
||||||
const restoreKey = await this.restoreCache([artifactPath], cacheKey)
|
report.markRequested(cacheKey)
|
||||||
if (restoreKey) {
|
|
||||||
|
const restoredKey = await this.restoreCache([artifactPath], cacheKey)
|
||||||
|
if (restoredKey) {
|
||||||
core.info(`Restored ${bundle} with key ${cacheKey} to ${artifactPath}`)
|
core.info(`Restored ${bundle} with key ${cacheKey} to ${artifactPath}`)
|
||||||
|
report.markRestored(restoredKey)
|
||||||
} else {
|
} else {
|
||||||
this.debug(`Did not restore ${bundle} with key ${cacheKey} to ${artifactPath}`)
|
core.info(`Did not restore ${bundle} with key ${cacheKey} to ${artifactPath}`)
|
||||||
return false
|
// TODO Remove the .cache file here?
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.debug(`No metafile found to restore ${bundle}: ${bundleMetaFile}`)
|
this.debug(`No metafile found to restore ${bundle}: ${bundleMetaFile}`)
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getBundleMetaFile(name: string): string {
|
private getBundleMetaFile(name: string): string {
|
||||||
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()
|
||||||
|
|
|
@ -106,10 +106,44 @@ class CacheKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CachingReport {
|
export class CachingReport {
|
||||||
fullyRestored: boolean
|
cacheEntryReports: CacheEntryReport[] = []
|
||||||
|
|
||||||
constructor(fullyRestored: boolean) {
|
get fullyRestored(): boolean {
|
||||||
this.fullyRestored = fullyRestored
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,13 +163,15 @@ export abstract class AbstractCache {
|
||||||
this.cacheDebuggingEnabled = isCacheDebuggingEnabled()
|
this.cacheDebuggingEnabled = isCacheDebuggingEnabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
async restore(): Promise<CachingReport> {
|
async restore(report: CachingReport): Promise<void> {
|
||||||
if (this.cacheOutputExists()) {
|
if (this.cacheOutputExists()) {
|
||||||
core.info(`${this.cacheDescription} already exists. Not restoring from cache.`)
|
core.info(`${this.cacheDescription} already exists. Not restoring from cache.`)
|
||||||
return new CachingReport(false)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const cacheKey = this.prepareCacheKey()
|
const cacheKey = this.prepareCacheKey()
|
||||||
|
const entryReport = report.addEntryReport(this.cacheName)
|
||||||
|
entryReport.markRequested(cacheKey.key, cacheKey.restoreKeys)
|
||||||
|
|
||||||
this.debug(
|
this.debug(
|
||||||
`Requesting ${this.cacheDescription} with
|
`Requesting ${this.cacheDescription} with
|
||||||
|
@ -147,21 +183,18 @@ export abstract class AbstractCache {
|
||||||
|
|
||||||
if (!cacheResult) {
|
if (!cacheResult) {
|
||||||
core.info(`${this.cacheDescription} cache not found. Will start with empty.`)
|
core.info(`${this.cacheDescription} cache not found. Will start with empty.`)
|
||||||
return new CachingReport(false)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
core.saveState(this.cacheResultStateKey, cacheResult)
|
core.saveState(this.cacheResultStateKey, cacheResult)
|
||||||
|
entryReport.markRestored(cacheResult)
|
||||||
core.info(`Restored ${this.cacheDescription} from cache key: ${cacheResult}`)
|
core.info(`Restored ${this.cacheDescription} from cache key: ${cacheResult}`)
|
||||||
|
|
||||||
const report = new CachingReport(true)
|
|
||||||
try {
|
try {
|
||||||
await this.afterRestore(report)
|
await this.afterRestore(report)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.warning(`Restore ${this.cacheDescription} failed in 'afterRestore': ${error}`)
|
core.warning(`Restore ${this.cacheDescription} failed in 'afterRestore': ${error}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return report
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareCacheKey(): CacheKey {
|
prepareCacheKey(): CacheKey {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
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'
|
||||||
|
|
||||||
|
@ -13,12 +13,15 @@ export async function restore(buildRootDirectory: string): Promise<void> {
|
||||||
|
|
||||||
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)
|
||||||
const gradleHomeRestore = await new GradleUserHomeCache(buildRootDirectory).restore()
|
|
||||||
|
const cachingReport = new CachingReport()
|
||||||
|
|
||||||
|
await new GradleUserHomeCache(buildRootDirectory).restore(cachingReport)
|
||||||
|
|
||||||
const projectDotGradleCache = new ProjectDotGradleCache(buildRootDirectory)
|
const projectDotGradleCache = new ProjectDotGradleCache(buildRootDirectory)
|
||||||
if (gradleHomeRestore.fullyRestored) {
|
if (cachingReport.fullyRestored) {
|
||||||
// Only restore the configuration-cache if the Gradle Home is fully restored
|
// Only restore the configuration-cache if the Gradle Home is fully restored
|
||||||
await projectDotGradleCache.restore()
|
await projectDotGradleCache.restore(cachingReport)
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, prepare the cache key for later save()
|
// Otherwise, prepare the cache key for later save()
|
||||||
projectDotGradleCache.prepareCacheKey()
|
projectDotGradleCache.prepareCacheKey()
|
||||||
|
|
Loading…
Reference in a new issue