Tidy-up caching code

- Extracted common code for Gradle User Home and Project .gradle caches
  into abstract supertype.
- Improve error handling by checking error types
This commit is contained in:
Daz DeBoer 2021-09-06 11:16:08 -06:00
parent c44ebadf6f
commit 6d1455a33e
No known key found for this signature in database
GPG Key ID: DD6B9F0B06683D5D
6 changed files with 138 additions and 166 deletions

View File

@ -2,86 +2,26 @@ import path from 'path'
import fs from 'fs' import fs from 'fs'
import os from 'os' import os from 'os'
import * as core from '@actions/core' import {AbstractCache} from './cache-utils'
import * as cache from '@actions/cache'
import {generateCacheKey} from './cache-utils'
const CACHE_PATH = [ const CACHE_PATH = [
'~/.gradle/caches/*', // All directories in 'caches' '~/.gradle/caches/*', // All directories in 'caches'
'~/.gradle/notifications/*', // Prevent the re-rendering of first-use message for version '~/.gradle/notifications/*', // Prevent the re-rendering of first-use message for version
'~/.gradle/wrapper/dists/*/*/*.zip' // Only wrapper zips are required : Gradle will expand these on demand '~/.gradle/wrapper/dists/*/*/*.zip' // Only wrapper zips are required : Gradle will expand these on demand
] ]
const CACHE_KEY = 'GUH_CACHE_KEY'
const CACHE_RESULT = 'GUH_CACHE_RESULT'
export async function restore(): Promise<void> { export class GradleUserHomeCache extends AbstractCache {
if (gradleUserHomeExists()) { constructor() {
core.info('Gradle User Home already exists. Not restoring from cache.') super('gradle', 'Gradle User Home')
return
} }
const cacheKey = generateCacheKey('gradle') protected cacheOutputExists(): boolean {
// Need to check for 'caches' directory to avoid incorrect detection on MacOS agents
core.saveState(CACHE_KEY, cacheKey.key) const dir = path.resolve(os.homedir(), '.gradle/caches')
return fs.existsSync(dir)
const cacheResult = await cache.restoreCache(
CACHE_PATH,
cacheKey.key,
cacheKey.restoreKeys
)
if (!cacheResult) {
core.info(
'Gradle User Home cache not found. Will start with empty home.'
)
return
} }
core.info(`Gradle User Home restored from cache key: ${cacheResult}`) protected getCachePath(): string[] {
return return CACHE_PATH
} }
export async function save(): Promise<void> {
if (!gradleUserHomeExists()) {
core.debug('No Gradle User Home to cache.')
return
}
const cacheKey = core.getState(CACHE_KEY)
const cacheResult = core.getState(CACHE_RESULT)
if (!cacheKey) {
core.info(
'Gradle User Home existed prior to cache restore. Not saving.'
)
return
}
if (cacheResult && cacheKey === cacheResult) {
core.info(
`Cache hit occurred on the cache key ${cacheKey}, not saving cache.`
)
return
}
core.info(`Caching Gradle User Home with cache key: ${cacheKey}`)
try {
await cache.saveCache(CACHE_PATH, cacheKey)
} catch (error) {
if (error.name === cache.ValidationError.name) {
throw error
} else if (error.name === cache.ReserveCacheError.name) {
core.info(error.message)
} else {
core.info(`[warning] ${error.message}`)
}
}
return
}
function gradleUserHomeExists(): boolean {
// Need to check for 'caches' directory to avoid incorrect detection on MacOS agents
const dir = path.resolve(os.homedir(), '.gradle/caches')
return fs.existsSync(dir)
} }

View File

@ -1,94 +1,29 @@
import path from 'path' import path from 'path'
import fs from 'fs' import fs from 'fs'
import {AbstractCache} from './cache-utils'
import * as core from '@actions/core'
import * as cache from '@actions/cache'
import {generateCacheKey} from './cache-utils'
const PATHS_TO_CACHE = [ const PATHS_TO_CACHE = [
'configuration-cache' // Only configuration-cache is stored at present 'configuration-cache' // Only configuration-cache is stored at present
] ]
const CACHE_KEY = 'PROJECT_CACHE_KEY'
const CACHE_RESULT = 'PROJECT_CACHE_RESULT'
export async function restore(rootDir: string): Promise<void> { export class ProjectDotGradleCache extends AbstractCache {
if (projectDotGradleDirExists(rootDir)) { private rootDir: string
core.info( constructor(rootDir: string) {
'Project .gradle directory already exists. Not restoring from cache.' super('project', 'Project .gradle directory')
) this.rootDir = rootDir
return
} }
const cacheKey = generateCacheKey('project') protected cacheOutputExists(): boolean {
const dir = this.getProjectDotGradleDir()
core.saveState(CACHE_KEY, cacheKey.key) return fs.existsSync(dir)
const cacheResult = await cache.restoreCache(
getCachePath(rootDir),
cacheKey.key,
cacheKey.restoreKeys
)
if (!cacheResult) {
core.info('Project .gradle cache not found. Will start with empty.')
return
} }
core.info(`Project .gradle dir restored from cache key: ${cacheResult}`) protected getCachePath(): string[] {
return const dir = this.getProjectDotGradleDir()
} return PATHS_TO_CACHE.map(x => path.resolve(dir, x))
}
export async function save(rootDir: string): Promise<void> {
if (!projectDotGradleDirExists(rootDir)) { private getProjectDotGradleDir(): string {
core.debug('No project .gradle dir to cache.') return path.resolve(this.rootDir, '.gradle')
return }
}
const cacheKey = core.getState(CACHE_KEY)
const cacheResult = core.getState(CACHE_RESULT)
if (!cacheKey) {
core.info(
'Project .gradle dir existed prior to cache restore. Not saving.'
)
return
}
if (cacheResult && cacheKey === cacheResult) {
core.info(
`Cache hit occurred on the cache key ${cacheKey}, not saving cache.`
)
return
}
core.info(`Caching project .gradle dir with cache key: ${cacheKey}`)
try {
await cache.saveCache(getCachePath(rootDir), cacheKey)
} catch (error) {
if (error.name === cache.ValidationError.name) {
throw error
} else if (error.name === cache.ReserveCacheError.name) {
core.info(error.message)
} else {
core.info(`[warning] ${error.message}`)
}
}
return
}
function getCachePath(rootDir: string): string[] {
const dir = getProjectDotGradleDir(rootDir)
return PATHS_TO_CACHE.map(x => path.resolve(dir, x))
}
function getProjectDotGradleDir(rootDir: string): string {
core.debug(`Resolving .gradle dir in ${rootDir}`)
return path.resolve(rootDir, '.gradle')
}
function projectDotGradleDirExists(rootDir: string): boolean {
const dir = getProjectDotGradleDir(rootDir)
core.debug(`Checking for existence of project .gradle dir: ${dir}`)
return fs.existsSync(dir)
} }

View File

@ -1,4 +1,5 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as cache from '@actions/cache'
import * as github from '@actions/github' import * as github from '@actions/github'
import * as crypto from 'crypto' import * as crypto from 'crypto'
@ -25,7 +26,7 @@ function getCacheEnabledValue(cacheName: string): string {
) )
} }
export function generateCacheKey(cacheName: string): CacheKey { function generateCacheKey(cacheName: string): CacheKey {
// Prefix can be used to force change all cache keys // Prefix can be used to force change all cache keys
const cacheKeyPrefix = process.env['CACHE_KEY_PREFIX'] || '' const cacheKeyPrefix = process.env['CACHE_KEY_PREFIX'] || ''
@ -70,7 +71,7 @@ export function hashStrings(values: string[]): string {
return hash.digest('hex') return hash.digest('hex')
} }
export class CacheKey { class CacheKey {
key: string key: string
restoreKeys: string[] restoreKeys: string[]
@ -79,3 +80,95 @@ export class CacheKey {
this.restoreKeys = restoreKeys this.restoreKeys = restoreKeys
} }
} }
export abstract class AbstractCache {
private cacheName: string
private cacheDescription: string
private cacheKeyStateKey: string
private cacheResultStateKey: string
constructor(cacheName: string, cacheDescription: string) {
this.cacheName = cacheName
this.cacheDescription = cacheDescription
this.cacheKeyStateKey = `CACHE_KEY_${cacheName}`
this.cacheResultStateKey = `CACHE_RESULT_${cacheName}`
}
async restore(): Promise<void> {
if (this.cacheOutputExists()) {
core.info(
`${this.cacheDescription} already exists. Not restoring from cache.`
)
return
}
const cacheKey = generateCacheKey(this.cacheName)
core.saveState(this.cacheKeyStateKey, cacheKey.key)
const cacheResult = await cache.restoreCache(
this.getCachePath(),
cacheKey.key,
cacheKey.restoreKeys
)
if (!cacheResult) {
core.info(
`${this.cacheDescription} cache not found. Will start with empty.`
)
return
}
core.saveState(this.cacheResultStateKey, cacheResult)
core.info(
`${this.cacheDescription} restored from cache key: ${cacheResult}`
)
return
}
async save(): Promise<void> {
if (!this.cacheOutputExists()) {
core.debug(`No ${this.cacheDescription} to cache.`)
return
}
const cacheKey = core.getState(this.cacheKeyStateKey)
const cacheResult = core.getState(this.cacheResultStateKey)
if (!cacheKey) {
core.info(
`${this.cacheDescription} existed prior to cache restore. Not saving.`
)
return
}
if (cacheResult && cacheKey === cacheResult) {
core.info(
`Cache hit occurred on the cache key ${cacheKey}, not saving cache.`
)
return
}
core.info(
`Caching ${this.cacheDescription} with cache key: ${cacheKey}`
)
try {
await cache.saveCache(this.getCachePath(), cacheKey)
} catch (error) {
// Fail on validation errors or non-errors (the latter to keep Typescript happy)
if (
error instanceof cache.ValidationError ||
!(error instanceof Error)
) {
throw error
}
core.warning(error.message)
}
return
}
protected abstract cacheOutputExists(): boolean
protected abstract getCachePath(): string[]
}

View File

@ -1,5 +1,5 @@
import * as cacheGradleUserHome from './cache-gradle-user-home' import {GradleUserHomeCache} from './cache-gradle-user-home'
import * as cacheProjectDotGradle 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 {isCacheReadEnabled, isCacheSaveEnabled} from './cache-utils' import {isCacheReadEnabled, isCacheSaveEnabled} from './cache-utils'
@ -12,9 +12,9 @@ export async function restore(buildRootDirectory: string): Promise<void> {
} }
core.startGroup('Restore Gradle state from cache') core.startGroup('Restore Gradle state from cache')
await cacheGradleUserHome.restore()
core.saveState(BUILD_ROOT_DIR, buildRootDirectory) core.saveState(BUILD_ROOT_DIR, buildRootDirectory)
await cacheProjectDotGradle.restore(buildRootDirectory) new GradleUserHomeCache().restore()
new ProjectDotGradleCache(buildRootDirectory).restore()
core.endGroup() core.endGroup()
} }
@ -25,8 +25,8 @@ export async function save(): Promise<void> {
} }
core.startGroup('Caching Gradle state') core.startGroup('Caching Gradle state')
await cacheGradleUserHome.save()
const buildRootDirectory = core.getState(BUILD_ROOT_DIR) const buildRootDirectory = core.getState(BUILD_ROOT_DIR)
await cacheProjectDotGradle.save(buildRootDirectory) new GradleUserHomeCache().save()
new ProjectDotGradleCache(buildRootDirectory).save()
core.endGroup() core.endGroup()
} }

View File

@ -36,6 +36,9 @@ export async function run(): Promise<void> {
core.setFailed(`Gradle process exited with status ${result.status}`) core.setFailed(`Gradle process exited with status ${result.status}`)
} }
} catch (error) { } catch (error) {
if (!(error instanceof Error)) {
throw error
}
core.setFailed(error.message) core.setFailed(error.message)
} }
} }

View File

@ -142,13 +142,14 @@ async function downloadAndCacheGradleDistribution(
try { try {
await cache.saveCache([downloadPath], cacheKey) await cache.saveCache([downloadPath], cacheKey)
} catch (error) { } catch (error) {
if (error.name === cache.ValidationError.name) { // Fail on validation errors or non-errors (the latter to keep Typescript happy)
if (
error instanceof cache.ValidationError ||
!(error instanceof Error)
) {
throw error throw error
} else if (error.name === cache.ReserveCacheError.name) {
core.info(error.message)
} else {
core.info(`[warning] ${error.message}`)
} }
core.warning(error.message)
} }
} }
return downloadPath return downloadPath