diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml index b1f31f9..450eaa5 100644 --- a/.github/workflows/prod.yml +++ b/.github/workflows/prod.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: env: - CACHE_KEY_SEED: ${{github.workflow}}#${{github.run_number}}- + CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}- jobs: # Run initial Gradle builds to push initial cache entries diff --git a/__tests__/cache-utils.test.ts b/__tests__/cache-utils.test.ts index 89a7715..ded9021 100644 --- a/__tests__/cache-utils.test.ts +++ b/__tests__/cache-utils.test.ts @@ -2,30 +2,14 @@ import * as cacheUtils from '../src/cache-utils' import * as path from 'path' describe('cacheUtils-utils', () => { - describe('can truncate args', () => { - test('handles zero-length string', () => { - expect(cacheUtils.truncateArgs('')).toBe('') + describe('can hash', () => { + it('a string', async () => { + const hash = cacheUtils.hashStrings(['foo']) + expect(hash).toBe('acbd18db4cc2f85cedef654fccc4a4d8') }) - test('leaves short string untouched', () => { - expect( - cacheUtils.truncateArgs('short string that-should-be-untouched') - ).toBe('short string that-should-be-untouched') - }) - test('truncates long string', () => { - const longString = 'a'.repeat(500) - expect(cacheUtils.truncateArgs(longString)).toBe('a'.repeat(400)) - }) - test('trims leading and trailing whitespace', () => { - expect(cacheUtils.truncateArgs(' this is an arg ')).toBe( - 'this is an arg' - ) - }) - test('removes repeated whitespace', () => { - expect( - cacheUtils.truncateArgs( - ' this one has long \t\n\t\r spaces ' - ) - ).toBe('this one has long spaces') + it('multiple strings', async () => { + const hash = cacheUtils.hashStrings(['foo', 'bar', 'baz']) + expect(hash).toBe('6df23dc03f9b54cc38a0fc1483df6e21') }) }) }) diff --git a/src/cache-utils.ts b/src/cache-utils.ts index b5dcfe6..afd5d81 100644 --- a/src/cache-utils.ts +++ b/src/cache-utils.ts @@ -1,5 +1,6 @@ import * as core from '@actions/core' import * as github from '@actions/github' +import * as crypto from 'crypto' export function isCacheReadEnabled(cacheName: string): boolean { const configValue = getCacheEnabledValue(cacheName) @@ -25,19 +26,48 @@ function getCacheEnabledValue(cacheName: string): string { } export function generateCacheKey(cacheName: string): CacheKey { - const cacheKeySeed = process.env[`CACHE_KEY_SEED`] || '' - const runnerOs = process.env[`RUNNER_OS`] || '' - const cacheKeyPrefix = `${cacheKeySeed}${runnerOs}|${cacheName}|` + // Prefix can be used to force change all cache keys + const cacheKeyPrefix = process.env['CACHE_KEY_PREFIX'] || '' - const args = truncateArgs(core.getInput('arguments')) - const cacheKeyWithArgs = `${cacheKeyPrefix}${args}|` + // 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}` - const cacheKey = `${cacheKeyWithArgs}${github.context.sha}` - return new CacheKey(cacheKey, [cacheKeyWithArgs, cacheKeyPrefix]) + // 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 truncateArgs(args: string): string { - return args.trim().replace(/\s+/g, ' ').substr(0, 400) +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]) +} + +export function hashStrings(values: string[]): string { + const hash = crypto.createHash('md5') + for (const value of values) { + hash.update(value) + } + return hash.digest('hex') } export class CacheKey {