Ensure save/restore only on first action step

Instead of relying on the separate cache implementations to check for the
existence of cached products, we now explicitly track whether or not the execution
is the first time the action has been invoked for a job.
This commit is contained in:
Daz DeBoer 2021-12-07 12:56:36 -07:00
parent 253d6427fd
commit 1041604f29
No known key found for this signature in database
GPG key ID: DD6B9F0B06683D5D
4 changed files with 45 additions and 38 deletions

View file

@ -83,13 +83,8 @@ export abstract class AbstractCache {
/**
* Restores the cache entry, finding the closest match to the currently running job.
* If the target output already exists, caching will be skipped.
*/
async restore(listener: CacheListener): Promise<void> {
if (this.cacheOutputExists()) {
core.info(`${this.cacheDescription} already exists. Not restoring from cache.`)
return
}
const entryListener = listener.entry(this.cacheDescription)
const cacheKey = this.prepareCacheKey()
@ -142,28 +137,17 @@ export abstract class AbstractCache {
protected async afterRestore(_listener: CacheListener): Promise<void> {}
/**
* Saves the cache entry based on the current cache key, unless:
* - If the cache output existed before restore, then it is not saved.
* - If the cache was restored with the exact key, we cannot overwrite it.
* Saves the cache entry based on the current cache key unless the cache was restored with the exact key,
* in which case we cannot overwrite it.
*
* If the cache entry was restored with a partial match on a restore key, then
* it is saved with the exact key.
*/
async save(listener: CacheListener): Promise<void> {
if (!this.cacheOutputExists()) {
core.info(`No ${this.cacheDescription} to cache.`)
return
}
// Retrieve the state set in the previous 'restore' step.
const cacheKeyFromRestore = core.getState(this.cacheKeyStateKey)
const cacheResultFromRestore = core.getState(this.cacheResultStateKey)
if (!cacheKeyFromRestore) {
core.info(`${this.cacheDescription} existed prior to cache restore. Not saving.`)
return
}
if (cacheResultFromRestore && cacheKeyFromRestore === cacheResultFromRestore) {
core.info(`Cache hit occurred on the cache key ${cacheKeyFromRestore}, not saving cache.`)
return
@ -206,6 +190,5 @@ export abstract class AbstractCache {
}
}
protected abstract cacheOutputExists(): boolean
protected abstract getCachePath(): string[]
}

View file

@ -294,12 +294,6 @@ export class GradleUserHomeCache extends AbstractCache {
return path.resolve(os.homedir(), '.gradle')
}
protected cacheOutputExists(): boolean {
// Need to check for 'caches' directory to avoid incorrect detection on MacOS agents
const dir = path.resolve(this.gradleUserHome, 'caches')
return fs.existsSync(dir)
}
/**
* Determines the paths within Gradle User Home to cache.
* By default, this is the 'caches' and 'notifications' directories,

View file

@ -1,5 +1,4 @@
import path from 'path'
import fs from 'fs'
import {AbstractCache} from './cache-base'
// TODO: Maybe allow the user to override / tweak this set
@ -17,11 +16,6 @@ export class ProjectDotGradleCache extends AbstractCache {
this.rootDir = rootDir
}
protected cacheOutputExists(): boolean {
const dir = this.getProjectDotGradleDir()
return fs.existsSync(dir)
}
protected getCachePath(): string[] {
const dir = this.getProjectDotGradleDir()
return PATHS_TO_CACHE.map(x => path.resolve(dir, x))

View file

@ -4,20 +4,20 @@ import {ProjectDotGradleCache} from './cache-project-dot-gradle'
import {isCacheDisabled, isCacheReadOnly} from './cache-utils'
import {logCachingReport, CacheListener} from './cache-reporting'
const CACHE_RESTORED_VAR = 'GRADLE_BUILD_ACTION_CACHE_RESTORED'
const BUILD_ROOT_DIR = 'BUILD_ROOT_DIR'
const CACHE_LISTENER = 'CACHE_LISTENER'
export async function restore(buildRootDirectory: string): Promise<void> {
if (!shouldRestoreCaches()) {
return
}
const gradleUserHomeCache = new GradleUserHomeCache(buildRootDirectory)
const projectDotGradleCache = new ProjectDotGradleCache(buildRootDirectory)
gradleUserHomeCache.init()
if (isCacheDisabled()) {
core.info('Cache is disabled: will not restore state from previous builds.')
return
}
await core.group('Restore Gradle state from cache', async () => {
core.saveState(BUILD_ROOT_DIR, buildRootDirectory)
@ -38,6 +38,10 @@ export async function restore(buildRootDirectory: string): Promise<void> {
}
export async function save(): Promise<void> {
if (!shouldSaveCaches()) {
return
}
const cacheListener: CacheListener = CacheListener.rehydrate(core.getState(CACHE_LISTENER))
if (isCacheReadOnly()) {
@ -56,3 +60,35 @@ export async function save(): Promise<void> {
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
}
// 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)
return true
}
function shouldSaveCaches(): boolean {
if (isCacheDisabled()) {
core.info('Cache is disabled: will not save state for later builds.')
return false
}
if (!core.getState(CACHE_RESTORED_VAR)) {
core.info('Cache will only be saved in final post-action step.')
return false
}
return true
}