Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make cache-keys more consistent #131

Merged
merged 4 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2,996 changes: 1,501 additions & 1,495 deletions dist/dependency-submission/main/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/dependency-submission/main/index.js.map

Large diffs are not rendered by default.

1,671 changes: 831 additions & 840 deletions dist/dependency-submission/post/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/dependency-submission/post/index.js.map

Large diffs are not rendered by default.

2,900 changes: 1,452 additions & 1,448 deletions dist/setup-gradle/main/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/setup-gradle/main/index.js.map

Large diffs are not rendered by default.

1,706 changes: 848 additions & 858 deletions dist/setup-gradle/post/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/setup-gradle/post/index.js.map

Large diffs are not rendered by default.

File renamed without changes.
99 changes: 99 additions & 0 deletions sources/src/caching/cache-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import * as github from '@actions/github'

import {CacheConfig, getJobMatrix} from '../input-params'
import {hashStrings} from './cache-utils'

const CACHE_PROTOCOL_VERSION = 'v1'

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'

/**
* 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
}
}

/**
* Generates a cache key specific to the current job execution.
* The key is constructed from the following inputs (with some user overrides):
* - The cache key prefix: defaults to 'gradle-' but can be overridden by the user
* - The cache protocol version
* - The runner operating system
* - The name of the workflow and 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, config: CacheConfig): CacheKey {
const prefix = process.env[CACHE_KEY_PREFIX_VAR] || ''

const cacheKeyBase = `${prefix}${getCacheKeyBase(cacheName, CACHE_PROTOCOL_VERSION)}`

// At the most general level, share caches for all executions on the same OS
const cacheKeyForEnvironment = `${cacheKeyBase}|${getCacheKeyEnvironment()}`

// Then prefer caches that run job with the same ID
const cacheKeyForJob = `${cacheKeyForEnvironment}|${getCacheKeyJob()}`

// Prefer (even more) jobs that run this job in the same workflow with the same context (matrix)
const cacheKeyForJobContext = `${cacheKeyForJob}[${getCacheKeyJobInstance()}]`

// Exact match on Git SHA
const cacheKey = `${cacheKeyForJobContext}-${getCacheKeyJobExecution()}`

if (config.isCacheStrictMatch()) {
return new CacheKey(cacheKey, [cacheKeyForJobContext])
}

return new CacheKey(cacheKey, [cacheKeyForJobContext, cacheKeyForJob, cacheKeyForEnvironment])
}

export function getCacheKeyBase(cacheName: string, cacheProtocolVersion: string): string {
// Prefix can be used to force change all cache keys (defaults to cache protocol version)
return `gradle-${cacheName}-${cacheProtocolVersion}`
}

function getCacheKeyEnvironment(): string {
const runnerOs = process.env['RUNNER_OS'] || ''
return process.env[CACHE_KEY_OS_VAR] || runnerOs
}

function getCacheKeyJob(): string {
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 workflow name and 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 workflowName = github.context.workflow
const workflowJobContext = getJobMatrix()
return hashStrings([workflowName, 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
}
File renamed without changes.
95 changes: 0 additions & 95 deletions sources/src/cache-utils.ts → sources/src/caching/cache-utils.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
import * as core from '@actions/core'
import * as cache from '@actions/cache'
import * as github from '@actions/github'
import * as exec from '@actions/exec'

import * as crypto from 'crypto'
import * as path from 'path'
import * as fs from 'fs'

import {CacheEntryListener} from './cache-reporting'
import {CacheConfig, getJobMatrix} from './input-params'

const CACHE_PROTOCOL_VERSION = 'v9-'

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'

const SEGMENT_DOWNLOAD_TIMEOUT_VAR = 'SEGMENT_DOWNLOAD_TIMEOUT_MINS'
const SEGMENT_DOWNLOAD_TIMEOUT_DEFAULT = 10 * 60 * 1000 // 10 minutes
Expand All @@ -28,91 +18,6 @@ export function isCacheDebuggingEnabled(): boolean {
return process.env['GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED'] ? true : false
}

/**
* 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
}
}

/**
* 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 workflow and 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, config: CacheConfig): 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()}`

// Then prefer caches that run job with the same ID
const cacheKeyForJob = `${cacheKeyForEnvironment}|${getCacheKeyJob()}`

// Prefer (even more) jobs that run this job in the same workflow with the same context (matrix)
const cacheKeyForJobContext = `${cacheKeyForJob}[${getCacheKeyJobInstance()}]`

// Exact match on Git SHA
const cacheKey = `${cacheKeyForJobContext}-${getCacheKeyJobExecution()}`

if (config.isCacheStrictMatch()) {
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 {
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 workflow name and 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 workflowName = github.context.workflow
const workflowJobContext = getJobMatrix()
return hashStrings([workflowName, 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'), '/')))
}
Expand Down
10 changes: 5 additions & 5 deletions sources/src/caches.ts → sources/src/caching/caches.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as core from '@actions/core'
import {CacheListener} from './cache-reporting'
import {DaemonController} from './daemon-controller'
import {GradleStateCache} from './cache-base'
import {GradleUserHomeCache} from './gradle-user-home-cache'
import {CacheCleaner} from './cache-cleaner'
import {CacheConfig} from './input-params'
import {DaemonController} from '../daemon-controller'
import {CacheConfig} from '../input-params'

const CACHE_RESTORED_VAR = 'GRADLE_BUILD_ACTION_CACHE_RESTORED'

Expand All @@ -20,7 +20,7 @@ export async function restore(
}
core.exportVariable(CACHE_RESTORED_VAR, true)

const gradleStateCache = new GradleStateCache(userHome, gradleUserHome, cacheConfig)
const gradleStateCache = new GradleUserHomeCache(userHome, gradleUserHome, cacheConfig)

if (cacheConfig.isCacheDisabled()) {
core.info('Cache is disabled: will not restore state from previous builds.')
Expand Down Expand Up @@ -99,6 +99,6 @@ export async function save(
}

await core.group('Caching Gradle state', async () => {
return new GradleStateCache(userHome, gradleUserHome, cacheConfig).save(cacheListener)
return new GradleUserHomeCache(userHome, gradleUserHome, cacheConfig).save(cacheListener)
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,16 @@ import * as core from '@actions/core'
import * as glob from '@actions/glob'
import * as semver from 'semver'

import {META_FILE_DIR} from './cache-base'
import {META_FILE_DIR} from './gradle-user-home-cache'
import {CacheEntryListener, CacheListener} from './cache-reporting'
import {
cacheDebug,
getCacheKeyPrefix,
hashFileNames,
isCacheDebuggingEnabled,
restoreCache,
saveCache,
tryDelete
} from './cache-utils'
import {BuildResult, loadBuildResults} from './build-results'
import {CacheConfig} from './input-params'
import {cacheDebug, hashFileNames, isCacheDebuggingEnabled, restoreCache, saveCache, tryDelete} from './cache-utils'

import {BuildResult, loadBuildResults} from '../build-results'
import {CacheConfig} from '../input-params'
import {getCacheKeyBase} from './cache-key'

const SKIP_RESTORE_VAR = 'GRADLE_BUILD_ACTION_SKIP_RESTORE'
const CACHE_PROTOCOL_VERSION = 'v1'

/**
* Represents the result of attempting to load or store an extracted cache entry.
Expand Down Expand Up @@ -250,22 +245,20 @@ abstract class AbstractEntryExtractor {
}

protected createCacheKeyFromFileNames(artifactType: string, files: string[]): string {
const cacheKeyPrefix = getCacheKeyPrefix()
const relativeFiles = files.map(x => path.relative(this.gradleUserHome, x))
const key = hashFileNames(relativeFiles)

cacheDebug(`Generating cache key for ${artifactType} from file names: ${relativeFiles}`)

return `${cacheKeyPrefix}${artifactType}-${key}`
return `${getCacheKeyBase(artifactType, CACHE_PROTOCOL_VERSION)}-${key}`
}

protected async createCacheKeyFromFileContents(artifactType: string, pattern: string): Promise<string> {
const cacheKeyPrefix = getCacheKeyPrefix()
const key = await glob.hashFiles(pattern)

cacheDebug(`Generating cache key for ${artifactType} from files matching: ${pattern}`)

return `${cacheKeyPrefix}${artifactType}-${key}`
return `${getCacheKeyBase(artifactType, CACHE_PROTOCOL_VERSION)}-${key}`
}

// Run actions sequentially if debugging is enabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,28 @@ import * as glob from '@actions/glob'

import path from 'path'
import fs from 'fs'
import {CacheConfig} from './input-params'
import {generateCacheKey} from './cache-key'
import {CacheListener} from './cache-reporting'
import {saveCache, restoreCache, cacheDebug, isCacheDebuggingEnabled, tryDelete, generateCacheKey} from './cache-utils'
import {GradleHomeEntryExtractor, ConfigurationCacheEntryExtractor} from './cache-extract-entries'
import {saveCache, restoreCache, cacheDebug, isCacheDebuggingEnabled, tryDelete} from './cache-utils'
import {GradleHomeEntryExtractor, ConfigurationCacheEntryExtractor} from './gradle-home-extry-extractor'
import {CacheConfig} from '../input-params'

const RESTORED_CACHE_KEY_KEY = 'restored-cache-key'

export const META_FILE_DIR = '.setup-gradle'

export class GradleStateCache {
private cacheConfig: CacheConfig
private cacheName: string
private cacheDescription: string
export class GradleUserHomeCache {
private readonly cacheName = 'home'
private readonly cacheDescription = 'Gradle User Home'

protected readonly userHome: string
protected readonly gradleUserHome: string
private readonly userHome: string
private readonly gradleUserHome: string
private readonly cacheConfig: CacheConfig

constructor(userHome: string, gradleUserHome: string, cacheConfig: CacheConfig) {
this.userHome = userHome
this.gradleUserHome = gradleUserHome
this.cacheConfig = cacheConfig
this.cacheName = 'gradle'
this.cacheDescription = 'Gradle User Home'
}

init(): void {
Expand Down
15 changes: 7 additions & 8 deletions sources/src/dependency-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import type {PullRequestEvent} from '@octokit/webhooks-types'
import * as path from 'path'
import fs from 'fs'

import * as layout from './repository-layout'
import {PostActionJobFailure} from './errors'
import {DependencyGraphConfig, DependencyGraphOption, getGithubToken} from './input-params'
import {DependencyGraphConfig, DependencyGraphOption, getGithubToken, getWorkspaceDirectory} from './input-params'

const DEPENDENCY_GRAPH_PREFIX = 'dependency-graph_'

Expand All @@ -34,10 +33,10 @@ export async function setup(config: DependencyGraphConfig): Promise<void> {
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_ID', github.context.runId)
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_REF', github.context.ref)
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_SHA', getShaFromContext())
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_WORKSPACE', layout.workspaceDirectory())
maybeExportVariable('GITHUB_DEPENDENCY_GRAPH_WORKSPACE', getWorkspaceDirectory())
maybeExportVariable(
'DEPENDENCY_GRAPH_REPORT_DIR',
path.resolve(layout.workspaceDirectory(), 'dependency-graph-reports')
path.resolve(getWorkspaceDirectory(), 'dependency-graph-reports')
)

// To clear the dependency graph, we generate an empty graph by excluding all projects and configurations
Expand Down Expand Up @@ -74,7 +73,7 @@ export async function complete(config: DependencyGraphConfig): Promise<void> {
}

async function findGeneratedDependencyGraphFiles(): Promise<string[]> {
const workspaceDirectory = layout.workspaceDirectory()
const workspaceDirectory = getWorkspaceDirectory()
return await findDependencyGraphFiles(workspaceDirectory)
}

Expand All @@ -85,7 +84,7 @@ async function uploadDependencyGraphs(dependencyGraphFiles: string[], config: De
return
}

const workspaceDirectory = layout.workspaceDirectory()
const workspaceDirectory = getWorkspaceDirectory()

const artifactClient = new DefaultArtifactClient()
for (const dependencyGraphFile of dependencyGraphFiles) {
Expand Down Expand Up @@ -157,7 +156,7 @@ async function submitDependencyGraphFile(jsonFile: string): Promise<void> {
}

async function downloadDependencyGraphs(): Promise<string[]> {
const workspaceDirectory = layout.workspaceDirectory()
const workspaceDirectory = getWorkspaceDirectory()

const findBy = github.context.payload.workflow_run
? {
Expand Down Expand Up @@ -220,7 +219,7 @@ function getOctokit(): InstanceType<typeof GitHub> {
}

function getRelativePathFromWorkspace(file: string): string {
const workspaceDirectory = layout.workspaceDirectory()
const workspaceDirectory = getWorkspaceDirectory()
return path.relative(workspaceDirectory, file)
}

Expand Down