2021-10-30 07:15:20 -06:00
|
|
|
import * as core from '@actions/core'
|
2021-12-29 16:07:33 -07:00
|
|
|
import * as exec from '@actions/exec'
|
2023-12-20 10:05:25 -07:00
|
|
|
import * as glob from '@actions/glob'
|
|
|
|
|
2021-12-07 16:52:53 -07:00
|
|
|
import path from 'path'
|
|
|
|
import fs from 'fs'
|
2023-06-03 15:58:54 -06:00
|
|
|
import * as params from './input-params'
|
2021-12-07 12:29:37 -07:00
|
|
|
import {CacheListener} from './cache-reporting'
|
2022-05-28 11:23:31 -06:00
|
|
|
import {saveCache, restoreCache, cacheDebug, isCacheDebuggingEnabled, tryDelete, generateCacheKey} from './cache-utils'
|
2023-04-14 16:23:09 -06:00
|
|
|
import {GradleHomeEntryExtractor} from './cache-extract-entries'
|
2021-10-30 07:15:20 -06:00
|
|
|
|
2022-01-20 09:36:57 -07:00
|
|
|
const RESTORED_CACHE_KEY_KEY = 'restored-cache-key'
|
2021-12-07 16:52:53 -07:00
|
|
|
|
|
|
|
export const META_FILE_DIR = '.gradle-build-action'
|
2021-10-30 07:15:20 -06:00
|
|
|
|
2021-12-29 16:07:33 -07:00
|
|
|
export class GradleStateCache {
|
2021-10-30 07:15:20 -06:00
|
|
|
private cacheName: string
|
|
|
|
private cacheDescription: string
|
|
|
|
|
2021-12-07 16:52:53 -07:00
|
|
|
protected readonly gradleUserHome: string
|
2021-10-30 07:15:20 -06:00
|
|
|
|
2021-12-29 16:07:33 -07:00
|
|
|
constructor(gradleUserHome: string) {
|
2021-12-07 16:52:53 -07:00
|
|
|
this.gradleUserHome = gradleUserHome
|
2021-12-29 16:07:33 -07:00
|
|
|
this.cacheName = 'gradle'
|
|
|
|
this.cacheDescription = 'Gradle User Home'
|
2021-10-30 07:15:20 -06:00
|
|
|
}
|
|
|
|
|
2021-12-07 16:52:53 -07:00
|
|
|
init(): void {
|
|
|
|
const actionCacheDir = path.resolve(this.gradleUserHome, '.gradle-build-action')
|
|
|
|
fs.mkdirSync(actionCacheDir, {recursive: true})
|
|
|
|
|
|
|
|
const initScriptsDir = path.resolve(this.gradleUserHome, 'init.d')
|
|
|
|
fs.mkdirSync(initScriptsDir, {recursive: true})
|
|
|
|
|
|
|
|
this.initializeGradleUserHome(this.gradleUserHome, initScriptsDir)
|
|
|
|
}
|
|
|
|
|
2022-01-17 12:38:14 -07:00
|
|
|
cacheOutputExists(): boolean {
|
2022-02-03 09:46:37 -07:00
|
|
|
const cachesDir = path.resolve(this.gradleUserHome, 'caches')
|
|
|
|
if (fs.existsSync(cachesDir)) {
|
|
|
|
cacheDebug(`Cache output exists at ${cachesDir}`)
|
|
|
|
return true
|
2022-01-17 14:06:56 -07:00
|
|
|
}
|
|
|
|
return false
|
2022-01-17 12:38:14 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 10:19:56 -07:00
|
|
|
/**
|
|
|
|
* Restores the cache entry, finding the closest match to the currently running job.
|
|
|
|
*/
|
2021-10-30 07:21:27 -06:00
|
|
|
async restore(listener: CacheListener): Promise<void> {
|
2021-11-28 10:19:56 -07:00
|
|
|
const entryListener = listener.entry(this.cacheDescription)
|
2021-10-30 07:15:20 -06:00
|
|
|
|
2021-12-29 16:07:33 -07:00
|
|
|
const cacheKey = generateCacheKey(this.cacheName)
|
2021-10-30 07:15:20 -06:00
|
|
|
|
2021-12-29 16:07:33 -07:00
|
|
|
cacheDebug(
|
2021-10-30 07:15:20 -06:00
|
|
|
`Requesting ${this.cacheDescription} with
|
2021-12-07 12:56:36 -07:00
|
|
|
key:${cacheKey.key}
|
|
|
|
restoreKeys:[${cacheKey.restoreKeys}]`
|
2021-10-30 07:15:20 -06:00
|
|
|
)
|
|
|
|
|
2022-01-19 12:11:51 -07:00
|
|
|
const cacheResult = await restoreCache(this.getCachePath(), cacheKey.key, cacheKey.restoreKeys, entryListener)
|
2021-10-30 07:15:20 -06:00
|
|
|
if (!cacheResult) {
|
2021-11-27 16:07:07 -07:00
|
|
|
core.info(`${this.cacheDescription} cache not found. Will initialize empty.`)
|
2021-10-30 07:15:20 -06:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-20 09:36:57 -07:00
|
|
|
core.saveState(RESTORED_CACHE_KEY_KEY, cacheResult.key)
|
2021-11-28 10:19:56 -07:00
|
|
|
|
2021-10-31 20:35:28 -06:00
|
|
|
core.info(`Restored ${this.cacheDescription} from cache key: ${cacheResult.key}`)
|
2021-10-30 07:15:20 -06:00
|
|
|
|
|
|
|
try {
|
2021-10-30 07:21:27 -06:00
|
|
|
await this.afterRestore(listener)
|
2021-10-30 07:15:20 -06:00
|
|
|
} catch (error) {
|
|
|
|
core.warning(`Restore ${this.cacheDescription} failed in 'afterRestore': ${error}`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-29 16:07:33 -07:00
|
|
|
/**
|
|
|
|
* Restore any extracted cache entries after the main Gradle User Home entry is restored.
|
|
|
|
*/
|
|
|
|
async afterRestore(listener: CacheListener): Promise<void> {
|
|
|
|
await this.debugReportGradleUserHomeSize('as restored from cache')
|
|
|
|
await new GradleHomeEntryExtractor(this.gradleUserHome).restore(listener)
|
2023-04-14 16:23:09 -06:00
|
|
|
// await new ConfigurationCacheEntryExtractor(this.gradleUserHome).restore(listener)
|
2021-12-29 16:07:33 -07:00
|
|
|
await this.debugReportGradleUserHomeSize('after restoring common artifacts')
|
2021-10-30 07:15:20 -06:00
|
|
|
}
|
|
|
|
|
2021-11-28 10:19:56 -07:00
|
|
|
/**
|
2021-12-07 12:56:36 -07:00
|
|
|
* 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.
|
2021-11-28 10:19:56 -07:00
|
|
|
*
|
|
|
|
* If the cache entry was restored with a partial match on a restore key, then
|
|
|
|
* it is saved with the exact key.
|
|
|
|
*/
|
2021-10-30 07:21:27 -06:00
|
|
|
async save(listener: CacheListener): Promise<void> {
|
2022-01-20 09:36:57 -07:00
|
|
|
const cacheKey = generateCacheKey(this.cacheName).key
|
|
|
|
const restoredCacheKey = core.getState(RESTORED_CACHE_KEY_KEY)
|
2022-06-06 14:57:03 -06:00
|
|
|
const gradleHomeEntryListener = listener.entry(this.cacheDescription)
|
2021-10-30 07:15:20 -06:00
|
|
|
|
2022-01-20 09:36:57 -07:00
|
|
|
if (restoredCacheKey && cacheKey === restoredCacheKey) {
|
|
|
|
core.info(`Cache hit occurred on the cache key ${cacheKey}, not saving cache.`)
|
2022-06-06 14:57:03 -06:00
|
|
|
|
|
|
|
for (const entryListener of listener.cacheEntries) {
|
|
|
|
if (entryListener === gradleHomeEntryListener) {
|
2022-08-16 15:29:23 -06:00
|
|
|
entryListener.markNotSaved('cache key not changed')
|
2022-06-06 14:57:03 -06:00
|
|
|
} else {
|
2022-08-16 15:29:23 -06:00
|
|
|
entryListener.markNotSaved(`referencing '${this.cacheDescription}' cache entry not saved`)
|
2022-06-06 14:57:03 -06:00
|
|
|
}
|
|
|
|
}
|
2021-10-30 07:15:20 -06:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2021-10-30 07:21:27 -06:00
|
|
|
await this.beforeSave(listener)
|
2021-10-30 07:15:20 -06:00
|
|
|
} catch (error) {
|
|
|
|
core.warning(`Save ${this.cacheDescription} failed in 'beforeSave': ${error}`)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-20 09:36:57 -07:00
|
|
|
core.info(`Caching ${this.cacheDescription} with cache key: ${cacheKey}`)
|
2021-10-30 07:15:20 -06:00
|
|
|
const cachePath = this.getCachePath()
|
2022-06-06 14:57:03 -06:00
|
|
|
await saveCache(cachePath, cacheKey, gradleHomeEntryListener)
|
2021-10-30 07:15:20 -06:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-12-29 16:07:33 -07:00
|
|
|
/**
|
|
|
|
* Extract and save any defined extracted cache entries prior to the main Gradle User Home entry being saved.
|
|
|
|
*/
|
|
|
|
async beforeSave(listener: CacheListener): Promise<void> {
|
|
|
|
await this.debugReportGradleUserHomeSize('before saving common artifacts')
|
2023-12-20 10:05:25 -07:00
|
|
|
await this.deleteExcludedPaths()
|
2021-12-29 16:07:33 -07:00
|
|
|
await Promise.all([
|
2023-04-14 16:23:09 -06:00
|
|
|
new GradleHomeEntryExtractor(this.gradleUserHome).extract(listener)
|
|
|
|
// new ConfigurationCacheEntryExtractor(this.gradleUserHome).extract(listener)
|
2021-12-29 16:07:33 -07:00
|
|
|
])
|
|
|
|
await this.debugReportGradleUserHomeSize(
|
|
|
|
"after extracting common artifacts (only 'caches' and 'notifications' will be stored)"
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete any file paths that are excluded by the `gradle-home-cache-excludes` parameter.
|
|
|
|
*/
|
2023-12-20 10:05:25 -07:00
|
|
|
private async deleteExcludedPaths(): Promise<void> {
|
2023-06-03 15:58:54 -06:00
|
|
|
const rawPaths: string[] = params.getCacheExcludes()
|
2023-12-20 10:07:03 -07:00
|
|
|
rawPaths.push('caches/*/cc-keystore')
|
2021-12-29 16:07:33 -07:00
|
|
|
const resolvedPaths = rawPaths.map(x => path.resolve(this.gradleUserHome, x))
|
2021-10-30 07:15:20 -06:00
|
|
|
|
2021-12-29 16:07:33 -07:00
|
|
|
for (const p of resolvedPaths) {
|
2023-12-20 10:05:25 -07:00
|
|
|
cacheDebug(`Removing excluded path: ${p}`)
|
|
|
|
const globber = await glob.create(p, {
|
|
|
|
implicitDescendants: false
|
|
|
|
})
|
|
|
|
|
|
|
|
for (const toDelete of await globber.glob()) {
|
|
|
|
cacheDebug(`Removing excluded file: ${toDelete}`)
|
|
|
|
await tryDelete(toDelete)
|
|
|
|
}
|
2021-12-29 16:07:33 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines the paths within Gradle User Home to cache.
|
|
|
|
* By default, this is the 'caches' and 'notifications' directories,
|
|
|
|
* but this can be overridden by the `gradle-home-cache-includes` parameter.
|
|
|
|
*/
|
|
|
|
protected getCachePath(): string[] {
|
2023-06-03 15:58:54 -06:00
|
|
|
const rawPaths: string[] = params.getCacheIncludes()
|
2021-12-29 16:07:33 -07:00
|
|
|
rawPaths.push(META_FILE_DIR)
|
|
|
|
const resolvedPaths = rawPaths.map(x => this.resolveCachePath(x))
|
|
|
|
cacheDebug(`Using cache paths: ${resolvedPaths}`)
|
|
|
|
return resolvedPaths
|
|
|
|
}
|
|
|
|
|
|
|
|
private resolveCachePath(rawPath: string): string {
|
|
|
|
if (rawPath.startsWith('!')) {
|
|
|
|
const resolved = this.resolveCachePath(rawPath.substring(1))
|
|
|
|
return `!${resolved}`
|
|
|
|
}
|
|
|
|
return path.resolve(this.gradleUserHome, rawPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
private initializeGradleUserHome(gradleUserHome: string, initScriptsDir: string): void {
|
2023-08-19 18:11:04 -06:00
|
|
|
// Ensure that pre-installed java versions are detected. Only add property if it isn't already defined.
|
|
|
|
const gradleProperties = path.resolve(gradleUserHome, 'gradle.properties')
|
|
|
|
const existingGradleProperties = fs.existsSync(gradleProperties)
|
|
|
|
? fs.readFileSync(gradleProperties, 'utf8')
|
|
|
|
: ''
|
|
|
|
if (!existingGradleProperties.includes('org.gradle.java.installations.fromEnv=')) {
|
|
|
|
fs.appendFileSync(
|
|
|
|
gradleProperties,
|
|
|
|
`
|
|
|
|
# Auto-detect pre-installed JDKs
|
|
|
|
org.gradle.java.installations.fromEnv=JAVA_HOME_8_X64,JAVA_HOME_11_X64,JAVA_HOME_17_X64
|
|
|
|
`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy init scripts from src/resources
|
2023-07-01 19:00:02 -06:00
|
|
|
const initScriptFilenames = [
|
2023-08-20 14:52:11 -06:00
|
|
|
'gradle-build-action.build-result-capture.init.gradle',
|
|
|
|
'gradle-build-action.build-result-capture-service.plugin.groovy',
|
|
|
|
'gradle-build-action.github-dependency-graph.init.gradle',
|
|
|
|
'gradle-build-action.github-dependency-graph-gradle-plugin-apply.groovy',
|
|
|
|
'gradle-build-action.inject-gradle-enterprise.init.gradle'
|
2023-07-01 19:00:02 -06:00
|
|
|
]
|
2022-06-02 11:53:33 -06:00
|
|
|
for (const initScriptFilename of initScriptFilenames) {
|
2022-06-11 09:18:48 -06:00
|
|
|
const initScriptContent = this.readInitScriptAsString(initScriptFilename)
|
2022-06-02 11:53:33 -06:00
|
|
|
const initScriptPath = path.resolve(initScriptsDir, initScriptFilename)
|
|
|
|
fs.writeFileSync(initScriptPath, initScriptContent)
|
2021-12-29 16:07:33 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-11 09:18:48 -06:00
|
|
|
private readInitScriptAsString(resource: string): string {
|
2022-06-03 07:50:08 -06:00
|
|
|
// Resolving relative to __dirname will allow node to find the resource at runtime
|
2022-06-11 09:18:48 -06:00
|
|
|
const absolutePath = path.resolve(__dirname, '..', '..', 'src', 'resources', 'init-scripts', resource)
|
2022-06-02 11:53:33 -06:00
|
|
|
return fs.readFileSync(absolutePath, 'utf8')
|
2021-12-29 16:07:33 -07:00
|
|
|
}
|
2021-10-30 07:15:20 -06:00
|
|
|
|
2021-12-29 16:07:33 -07:00
|
|
|
/**
|
|
|
|
* When cache debugging is enabled, this method will give a detailed report
|
|
|
|
* of the Gradle User Home contents.
|
|
|
|
*/
|
|
|
|
private async debugReportGradleUserHomeSize(label: string): Promise<void> {
|
|
|
|
if (!isCacheDebuggingEnabled()) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (!fs.existsSync(this.gradleUserHome)) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const result = await exec.getExecOutput('du', ['-h', '-c', '-t', '5M'], {
|
|
|
|
cwd: this.gradleUserHome,
|
|
|
|
silent: true,
|
|
|
|
ignoreReturnCode: true
|
|
|
|
})
|
|
|
|
|
|
|
|
core.info(`Gradle User Home (directories >5M): ${label}`)
|
|
|
|
|
|
|
|
core.info(
|
|
|
|
result.stdout
|
|
|
|
.trimEnd()
|
|
|
|
.replace(/\t/g, ' ')
|
|
|
|
.split('\n')
|
|
|
|
.map(it => {
|
|
|
|
return ` ${it}`
|
|
|
|
})
|
|
|
|
.join('\n')
|
|
|
|
)
|
|
|
|
|
|
|
|
core.info('-----------------------')
|
|
|
|
}
|
2021-10-30 07:15:20 -06:00
|
|
|
}
|