From 28b774ebdb0de4d8f49ae380fb0291685d7cc2bb Mon Sep 17 00:00:00 2001 From: Daz DeBoer Date: Sat, 28 May 2022 11:23:31 -0600 Subject: [PATCH] Allow override of sections of cache key These internal env vars are designed primarily for testing, but may also prove useful for folks to experiment with more optimal caching setups. --- src/cache-base.ts | 68 +--------------------------------- src/cache-utils.ts | 92 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 88 insertions(+), 72 deletions(-) diff --git a/src/cache-base.ts b/src/cache-base.ts index b053843..8963c74 100644 --- a/src/cache-base.ts +++ b/src/cache-base.ts @@ -1,83 +1,17 @@ import * as core from '@actions/core' import * as exec from '@actions/exec' -import * as github from '@actions/github' import path from 'path' import fs from 'fs' import {CacheListener} from './cache-reporting' -import { - getCacheKeyPrefix, - determineJobContext, - saveCache, - restoreCache, - cacheDebug, - isCacheDebuggingEnabled, - tryDelete -} from './cache-utils' +import {saveCache, restoreCache, cacheDebug, isCacheDebuggingEnabled, tryDelete, generateCacheKey} from './cache-utils' import {ConfigurationCacheEntryExtractor, GradleHomeEntryExtractor} from './cache-extract-entries' -const CACHE_PROTOCOL_VERSION = 'v6-' const RESTORED_CACHE_KEY_KEY = 'restored-cache-key' export const META_FILE_DIR = '.gradle-build-action' export const PROJECT_ROOTS_FILE = 'project-roots.txt' const INCLUDE_PATHS_PARAMETER = 'gradle-home-cache-includes' const EXCLUDE_PATHS_PARAMETER = 'gradle-home-cache-excludes' -const STRICT_CACHE_MATCH_PARAMETER = 'gradle-home-cache-strict-match' - -/** - * Represents a key used to restore a cache entry. - * The Github Actions cache will first try for an exact match on the key. - * If that fails, it will try for a prefix match on any of the restoreKeys. - */ -class CacheKey { - key: string - restoreKeys: string[] - - constructor(key: string, restoreKeys: string[]) { - this.key = key - this.restoreKeys = restoreKeys - } -} - -/** - * Generates a cache key specific to the current job execution. - * The key is constructed from the following inputs: - * - A user-defined prefix (optional) - * - The cache protocol version - * - The name of the cache - * - The runner operating system - * - The name of the Job being executed - * - The matrix values for the Job being executed (job context) - * - The SHA of the commit being executed - * - * Caches are restored by trying to match the these key prefixes in order: - * - The full key with SHA - * - A previous key for this Job + matrix - * - Any previous key for this Job (any matrix) - * - Any previous key for this cache on the current OS - */ -function generateCacheKey(cacheName: string): CacheKey { - const cacheKeyBase = `${getCacheKeyPrefix()}${CACHE_PROTOCOL_VERSION}${cacheName}` - - // At the most general level, share caches for all executions on the same OS - const runnerOs = process.env['RUNNER_OS'] || '' - const cacheKeyForOs = `${cacheKeyBase}|${runnerOs}` - - // 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}` - - if (core.getBooleanInput(STRICT_CACHE_MATCH_PARAMETER)) { - return new CacheKey(cacheKey, [cacheKeyForJobContext]) - } - - return new CacheKey(cacheKey, [cacheKeyForJobContext, cacheKeyForJob, cacheKeyForOs]) -} export class GradleStateCache { private cacheName: string diff --git a/src/cache-utils.ts b/src/cache-utils.ts index e18b34b..5b605ef 100644 --- a/src/cache-utils.ts +++ b/src/cache-utils.ts @@ -1,17 +1,26 @@ import * as core from '@actions/core' import * as cache from '@actions/cache' +import * as github from '@actions/github' import * as crypto from 'crypto' import * as path from 'path' import * as fs from 'fs' import {CacheEntryListener} from './cache-reporting' +const CACHE_PROTOCOL_VERSION = 'v6-' + const JOB_CONTEXT_PARAMETER = 'workflow-job-context' const CACHE_DISABLED_PARAMETER = 'cache-disabled' const CACHE_READONLY_PARAMETER = 'cache-read-only' const CACHE_WRITEONLY_PARAMETER = 'cache-write-only' +const STRICT_CACHE_MATCH_PARAMETER = 'gradle-home-cache-strict-match' const CACHE_DEBUG_VAR = 'GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED' -const CACHE_PREFIX_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX' + +const CACHE_KEY_PREFIX_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX' +const CACHE_KEY_OS_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_ENVIRONMENT' +const CACHE_KEY_JOB_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_JOB' +const CACHE_KEY_JOB_INSTANCE_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_JOB_INSTANCE' +const CACHE_KEY_JOB_EXECUTION_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_JOB_EXECUTION' export function isCacheDisabled(): boolean { return core.getBooleanInput(CACHE_DISABLED_PARAMETER) @@ -29,18 +38,91 @@ export function isCacheDebuggingEnabled(): boolean { return process.env[CACHE_DEBUG_VAR] ? true : false } -export function getCacheKeyPrefix(): string { - // Prefix can be used to force change all cache keys (defaults to cache protocol version) - return process.env[CACHE_PREFIX_VAR] || '' +/** + * Represents a key used to restore a cache entry. + * The Github Actions cache will first try for an exact match on the key. + * If that fails, it will try for a prefix match on any of the restoreKeys. + */ +export class CacheKey { + key: string + restoreKeys: string[] + + constructor(key: string, restoreKeys: string[]) { + this.key = key + this.restoreKeys = restoreKeys + } } -export function determineJobContext(): string { +/** + * Generates a cache key specific to the current job execution. + * The key is constructed from the following inputs (with some user overrides): + * - The cache protocol version + * - The name of the cache + * - The runner operating system + * - The name of the Job being executed + * - The matrix values for the Job being executed (job context) + * - The SHA of the commit being executed + * + * Caches are restored by trying to match the these key prefixes in order: + * - The full key with SHA + * - A previous key for this Job + matrix + * - Any previous key for this Job (any matrix) + * - Any previous key for this cache on the current OS + */ +export function generateCacheKey(cacheName: string): CacheKey { + const cacheKeyBase = `${getCacheKeyPrefix()}${CACHE_PROTOCOL_VERSION}${cacheName}` + + // At the most general level, share caches for all executions on the same OS + const cacheKeyForEnvironment = `${cacheKeyBase}|${getCacheKeyEnvironment()}` + + // Prefer caches that run this job + const cacheKeyForJob = `${cacheKeyForEnvironment}|${getCacheKeyJob()}` + + // Prefer (even more) jobs that run this job with the same context (matrix) + const cacheKeyForJobContext = `${cacheKeyForJob}[${getCacheKeyJobInstance()}]` + + // Exact match on Git SHA + const cacheKey = `${cacheKeyForJobContext}-${getCacheKeyJobExecution()}` + + if (core.getBooleanInput(STRICT_CACHE_MATCH_PARAMETER)) { + return new CacheKey(cacheKey, [cacheKeyForJobContext]) + } + + return new CacheKey(cacheKey, [cacheKeyForJobContext, cacheKeyForJob, cacheKeyForEnvironment]) +} + +export function getCacheKeyPrefix(): string { + // Prefix can be used to force change all cache keys (defaults to cache protocol version) + return process.env[CACHE_KEY_PREFIX_VAR] || '' +} + +function getCacheKeyEnvironment(): string { + const runnerOs = process.env['RUNNER_OS'] || '' + return process.env[CACHE_KEY_OS_VAR] || runnerOs +} + +function getCacheKeyJob(): string { + // Prefix can be used to force change all cache keys (defaults to cache protocol version) + return process.env[CACHE_KEY_JOB_VAR] || github.context.job +} + +function getCacheKeyJobInstance(): string { + const override = process.env[CACHE_KEY_JOB_INSTANCE_VAR] + if (override) { + return override + } + // By default, we hash the full `matrix` data for the run, to uniquely identify this job invocation // The only way we can obtain the `matrix` data is via the `workflow-job-context` parameter in action.yml. const workflowJobContext = core.getInput(JOB_CONTEXT_PARAMETER) return hashStrings([workflowJobContext]) } +function getCacheKeyJobExecution(): string { + // Used to associate a cache key with a particular execution (default is bound to the git commit sha) + return process.env[CACHE_KEY_JOB_EXECUTION_VAR] || github.context.sha +} + export function hashFileNames(fileNames: string[]): string { return hashStrings(fileNames.map(x => x.replace(new RegExp(`\\${path.sep}`, 'g'), '/'))) }