2021-08-24 12:57:17 -06:00
|
|
|
import * as core from '@actions/core'
|
2021-09-06 11:16:08 -06:00
|
|
|
import * as cache from '@actions/cache'
|
2021-09-05 17:10:47 -06:00
|
|
|
import * as github from '@actions/github'
|
2021-09-05 21:35:17 -06:00
|
|
|
import * as crypto from 'crypto'
|
2021-08-24 12:57:17 -06:00
|
|
|
|
|
|
|
export function isCacheReadEnabled(cacheName: string): boolean {
|
|
|
|
const configValue = getCacheEnabledValue(cacheName)
|
|
|
|
return configValue === 'true' || configValue === 'read-only'
|
|
|
|
}
|
|
|
|
|
|
|
|
export function isCacheSaveEnabled(cacheName: string): boolean {
|
|
|
|
const configValue = getCacheEnabledValue(cacheName)
|
|
|
|
return configValue === 'true'
|
|
|
|
}
|
|
|
|
|
|
|
|
function getCacheEnabledValue(cacheName: string): string {
|
|
|
|
const configValue = core
|
|
|
|
.getInput(`${cacheName}-cache-enabled`)
|
|
|
|
.toLowerCase()
|
|
|
|
|
|
|
|
if (['true', 'false', 'read-only'].includes(configValue)) {
|
|
|
|
return configValue
|
|
|
|
}
|
|
|
|
throw new Error(
|
|
|
|
`Invalid cache-enabled parameter '${configValue}'. Valid values are ['true', 'false', 'read-only']`
|
|
|
|
)
|
|
|
|
}
|
2021-09-05 17:10:47 -06:00
|
|
|
|
2021-09-06 11:16:08 -06:00
|
|
|
function generateCacheKey(cacheName: string): CacheKey {
|
2021-09-05 21:35:17 -06:00
|
|
|
// Prefix can be used to force change all cache keys
|
|
|
|
const cacheKeyPrefix = process.env['CACHE_KEY_PREFIX'] || ''
|
2021-09-05 17:10:47 -06:00
|
|
|
|
2021-09-05 21:35:17 -06:00
|
|
|
// At the most general level, share caches for all executions on the same OS
|
|
|
|
const runnerOs = process.env['RUNNER_OS'] || ''
|
|
|
|
const cacheKeyForOs = `${cacheKeyPrefix}${cacheName}|${runnerOs}`
|
2021-09-05 17:10:47 -06:00
|
|
|
|
2021-09-05 21:35:17 -06:00
|
|
|
// Prefer caches that run this job
|
|
|
|
const cacheKeyForJob = `${cacheKeyForOs}|${github.context.job}`
|
|
|
|
|
|
|
|
// Prefer (even more) jobs that run this job with the same context (matrix)
|
|
|
|
const cacheKeyForJobContext = `${cacheKeyForJob}[${determineJobContext()}]`
|
|
|
|
|
|
|
|
// Exact match on Git SHA
|
|
|
|
const cacheKey = `${cacheKeyForJobContext}-${github.context.sha}`
|
|
|
|
|
|
|
|
return new CacheKey(cacheKey, [
|
|
|
|
cacheKeyForJobContext,
|
|
|
|
cacheKeyForJob,
|
|
|
|
cacheKeyForOs
|
|
|
|
])
|
|
|
|
}
|
|
|
|
|
|
|
|
function determineJobContext(): string {
|
|
|
|
// Ideally we'd serialize the entire matrix values here, but matrix is not available within the action invocation.
|
|
|
|
// Use the JAVA_HOME value as a proxy for the java version
|
|
|
|
const javaHome = process.env['JAVA_HOME'] || ''
|
|
|
|
|
|
|
|
// Approximate overall context based on the first gradle invocation in the Job
|
|
|
|
const args = core.getInput('arguments')
|
|
|
|
const buildRootDirectory = core.getInput('build-root-directory')
|
|
|
|
const gradleVersion = core.getInput('gradle-version')
|
|
|
|
|
|
|
|
return hashStrings([javaHome, args, buildRootDirectory, gradleVersion])
|
2021-09-05 17:10:47 -06:00
|
|
|
}
|
|
|
|
|
2021-09-05 21:35:17 -06:00
|
|
|
export function hashStrings(values: string[]): string {
|
|
|
|
const hash = crypto.createHash('md5')
|
|
|
|
for (const value of values) {
|
|
|
|
hash.update(value)
|
|
|
|
}
|
|
|
|
return hash.digest('hex')
|
2021-09-05 17:10:47 -06:00
|
|
|
}
|
|
|
|
|
2021-09-06 11:16:08 -06:00
|
|
|
class CacheKey {
|
2021-09-05 17:10:47 -06:00
|
|
|
key: string
|
|
|
|
restoreKeys: string[]
|
|
|
|
|
|
|
|
constructor(key: string, restoreKeys: string[]) {
|
|
|
|
this.key = key
|
|
|
|
this.restoreKeys = restoreKeys
|
|
|
|
}
|
|
|
|
}
|
2021-09-06 11:16:08 -06:00
|
|
|
|
|
|
|
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[]
|
|
|
|
}
|