From 6d1455a33e4b9b7097b4ce1423ec5cc3fb20fffe Mon Sep 17 00:00:00 2001 From: Daz DeBoer Date: Mon, 6 Sep 2021 11:16:08 -0600 Subject: [PATCH] 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 --- src/cache-gradle-user-home.ts | 82 ++++----------------------- src/cache-project-dot-gradle.ts | 99 ++++++--------------------------- src/cache-utils.ts | 97 +++++++++++++++++++++++++++++++- src/caches.ts | 12 ++-- src/main.ts | 3 + src/provision.ts | 11 ++-- 6 files changed, 138 insertions(+), 166 deletions(-) diff --git a/src/cache-gradle-user-home.ts b/src/cache-gradle-user-home.ts index c4bf854..f2b844e 100644 --- a/src/cache-gradle-user-home.ts +++ b/src/cache-gradle-user-home.ts @@ -2,86 +2,26 @@ import path from 'path' import fs from 'fs' import os from 'os' -import * as core from '@actions/core' -import * as cache from '@actions/cache' -import {generateCacheKey} from './cache-utils' +import {AbstractCache} from './cache-utils' const CACHE_PATH = [ '~/.gradle/caches/*', // All directories in 'caches' '~/.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 ] -const CACHE_KEY = 'GUH_CACHE_KEY' -const CACHE_RESULT = 'GUH_CACHE_RESULT' -export async function restore(): Promise { - if (gradleUserHomeExists()) { - core.info('Gradle User Home already exists. Not restoring from cache.') - return +export class GradleUserHomeCache extends AbstractCache { + constructor() { + super('gradle', 'Gradle User Home') } - const cacheKey = generateCacheKey('gradle') - - core.saveState(CACHE_KEY, cacheKey.key) - - 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 + protected cacheOutputExists(): 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) } - core.info(`Gradle User Home restored from cache key: ${cacheResult}`) - return -} - -export async function save(): Promise { - 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) + protected getCachePath(): string[] { + return CACHE_PATH + } } diff --git a/src/cache-project-dot-gradle.ts b/src/cache-project-dot-gradle.ts index 2efcda1..10d741d 100644 --- a/src/cache-project-dot-gradle.ts +++ b/src/cache-project-dot-gradle.ts @@ -1,94 +1,29 @@ import path from 'path' import fs from 'fs' - -import * as core from '@actions/core' -import * as cache from '@actions/cache' -import {generateCacheKey} from './cache-utils' +import {AbstractCache} from './cache-utils' const PATHS_TO_CACHE = [ '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 { - if (projectDotGradleDirExists(rootDir)) { - core.info( - 'Project .gradle directory already exists. Not restoring from cache.' - ) - return +export class ProjectDotGradleCache extends AbstractCache { + private rootDir: string + constructor(rootDir: string) { + super('project', 'Project .gradle directory') + this.rootDir = rootDir } - const cacheKey = generateCacheKey('project') - - core.saveState(CACHE_KEY, cacheKey.key) - - 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 + protected cacheOutputExists(): boolean { + const dir = this.getProjectDotGradleDir() + return fs.existsSync(dir) } - core.info(`Project .gradle dir restored from cache key: ${cacheResult}`) - return -} - -export async function save(rootDir: string): Promise { - if (!projectDotGradleDirExists(rootDir)) { - core.debug('No project .gradle dir to cache.') - 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) + protected getCachePath(): string[] { + const dir = this.getProjectDotGradleDir() + return PATHS_TO_CACHE.map(x => path.resolve(dir, x)) + } + + private getProjectDotGradleDir(): string { + return path.resolve(this.rootDir, '.gradle') + } } diff --git a/src/cache-utils.ts b/src/cache-utils.ts index afd5d81..8d7c33c 100644 --- a/src/cache-utils.ts +++ b/src/cache-utils.ts @@ -1,4 +1,5 @@ import * as core from '@actions/core' +import * as cache from '@actions/cache' import * as github from '@actions/github' 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 const cacheKeyPrefix = process.env['CACHE_KEY_PREFIX'] || '' @@ -70,7 +71,7 @@ export function hashStrings(values: string[]): string { return hash.digest('hex') } -export class CacheKey { +class CacheKey { key: string restoreKeys: string[] @@ -79,3 +80,95 @@ export class CacheKey { 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 { + 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 { + 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[] +} diff --git a/src/caches.ts b/src/caches.ts index bc8dc64..714ef2c 100644 --- a/src/caches.ts +++ b/src/caches.ts @@ -1,5 +1,5 @@ -import * as cacheGradleUserHome from './cache-gradle-user-home' -import * as cacheProjectDotGradle from './cache-project-dot-gradle' +import {GradleUserHomeCache} from './cache-gradle-user-home' +import {ProjectDotGradleCache} from './cache-project-dot-gradle' import * as core from '@actions/core' import {isCacheReadEnabled, isCacheSaveEnabled} from './cache-utils' @@ -12,9 +12,9 @@ export async function restore(buildRootDirectory: string): Promise { } core.startGroup('Restore Gradle state from cache') - await cacheGradleUserHome.restore() core.saveState(BUILD_ROOT_DIR, buildRootDirectory) - await cacheProjectDotGradle.restore(buildRootDirectory) + new GradleUserHomeCache().restore() + new ProjectDotGradleCache(buildRootDirectory).restore() core.endGroup() } @@ -25,8 +25,8 @@ export async function save(): Promise { } core.startGroup('Caching Gradle state') - await cacheGradleUserHome.save() const buildRootDirectory = core.getState(BUILD_ROOT_DIR) - await cacheProjectDotGradle.save(buildRootDirectory) + new GradleUserHomeCache().save() + new ProjectDotGradleCache(buildRootDirectory).save() core.endGroup() } diff --git a/src/main.ts b/src/main.ts index 0f06865..bfa159f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -36,6 +36,9 @@ export async function run(): Promise { core.setFailed(`Gradle process exited with status ${result.status}`) } } catch (error) { + if (!(error instanceof Error)) { + throw error + } core.setFailed(error.message) } } diff --git a/src/provision.ts b/src/provision.ts index a4932ae..d487acf 100644 --- a/src/provision.ts +++ b/src/provision.ts @@ -142,13 +142,14 @@ async function downloadAndCacheGradleDistribution( try { await cache.saveCache([downloadPath], cacheKey) } 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 - } else if (error.name === cache.ReserveCacheError.name) { - core.info(error.message) - } else { - core.info(`[warning] ${error.message}`) } + core.warning(error.message) } } return downloadPath