From 08d5b40ca5e3112080e203cd34a664488f8ca234 Mon Sep 17 00:00:00 2001 From: Daz DeBoer Date: Thu, 20 Jan 2022 09:36:57 -0700 Subject: [PATCH] Add experimental support for 'cache-write-only' There may be cases where it a "fresh" cache entry would be beneficial, for example if the Gradle User Home cache entry grows over time. This setting would run the build as if no prior cache entry exists. --- .../workflows/integTest-caching-config.yml | 39 +++++++++++++++++++ action.yml | 4 ++ src/cache-base.ts | 21 ++++------ src/cache-reporting.ts | 3 ++ src/cache-utils.ts | 5 +++ src/caches.ts | 16 +++++--- 6 files changed, 69 insertions(+), 19 deletions(-) diff --git a/.github/workflows/integTest-caching-config.yml b/.github/workflows/integTest-caching-config.yml index 2557081..dfe8165 100644 --- a/.github/workflows/integTest-caching-config.yml +++ b/.github/workflows/integTest-caching-config.yml @@ -101,3 +101,42 @@ jobs: with: script: | core.setFailed('No build scan detected') + + # Test seed the cache with cache-write-only and verify with cache-read-only + seed-build-write-only: + env: + GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-write-only- + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout sources + uses: actions/checkout@v2 + - name: Setup Gradle + uses: ./ + with: + cache-write-only: true + - name: Build using Gradle wrapper + working-directory: __tests__/samples/groovy-dsl + run: ./gradlew test + + verify-write-only-build: + env: + GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: ${{github.workflow}}#${{github.run_number}}:${{github.run_attempt}}-write-only- + needs: seed-build-write-only + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout sources + uses: actions/checkout@v2 + - name: Setup Gradle + uses: ./ + with: + cache-read-only: true + - name: Execute Gradle build with --offline + working-directory: __tests__/samples/groovy-dsl + run: ./gradlew test --offline + diff --git a/action.yml b/action.yml index 7101643..a87098e 100644 --- a/action.yml +++ b/action.yml @@ -50,6 +50,10 @@ inputs: # The following action properties allow fine-grained tweaking of the action caching behaviour. # These properties are experimental and not (yet) designed for production use, and may change without notice in a subsequent release of `gradle-build-action`. # Use at your own risk! + cache-write-only: + description: When 'true', entries will not be restored from the cache but will be saved at the end of the Job. This allows a 'clean' cache entry to be written. + required: false + default: false gradle-home-cache-strict-match: description: When 'true', the action will not attempt to restore the Gradle User Home entries from other Jobs. required: false diff --git a/src/cache-base.ts b/src/cache-base.ts index 8bcd3ae..7b19b88 100644 --- a/src/cache-base.ts +++ b/src/cache-base.ts @@ -16,6 +16,7 @@ import { 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' @@ -81,8 +82,6 @@ function generateCacheKey(cacheName: string): CacheKey { export class GradleStateCache { private cacheName: string private cacheDescription: string - private cacheKeyStateKey: string - private cacheResultStateKey: string protected readonly gradleUserHome: string @@ -90,8 +89,6 @@ export class GradleStateCache { this.gradleUserHome = gradleUserHome this.cacheName = 'gradle' this.cacheDescription = 'Gradle User Home' - this.cacheKeyStateKey = `CACHE_KEY_gradle` - this.cacheResultStateKey = `CACHE_RESULT_gradle` } init(): void { @@ -122,7 +119,6 @@ export class GradleStateCache { const entryListener = listener.entry(this.cacheDescription) const cacheKey = generateCacheKey(this.cacheName) - core.saveState(this.cacheKeyStateKey, cacheKey.key) cacheDebug( `Requesting ${this.cacheDescription} with @@ -136,7 +132,7 @@ export class GradleStateCache { return } - core.saveState(this.cacheResultStateKey, cacheResult.key) + core.saveState(RESTORED_CACHE_KEY_KEY, cacheResult.key) core.info(`Restored ${this.cacheDescription} from cache key: ${cacheResult.key}`) @@ -165,12 +161,11 @@ export class GradleStateCache { * it is saved with the exact key. */ async save(listener: CacheListener): Promise { - // Retrieve the state set in the previous 'restore' step. - const cacheKeyFromRestore = core.getState(this.cacheKeyStateKey) - const cacheResultFromRestore = core.getState(this.cacheResultStateKey) + const cacheKey = generateCacheKey(this.cacheName).key + const restoredCacheKey = core.getState(RESTORED_CACHE_KEY_KEY) - if (cacheResultFromRestore && cacheKeyFromRestore === cacheResultFromRestore) { - core.info(`Cache hit occurred on the cache key ${cacheKeyFromRestore}, not saving cache.`) + if (restoredCacheKey && cacheKey === restoredCacheKey) { + core.info(`Cache hit occurred on the cache key ${cacheKey}, not saving cache.`) return } @@ -181,10 +176,10 @@ export class GradleStateCache { return } - core.info(`Caching ${this.cacheDescription} with cache key: ${cacheKeyFromRestore}`) + core.info(`Caching ${this.cacheDescription} with cache key: ${cacheKey}`) const cachePath = this.getCachePath() const entryListener = listener.entry(this.cacheDescription) - await saveCache(cachePath, cacheKeyFromRestore, entryListener) + await saveCache(cachePath, cacheKey, entryListener) return } diff --git a/src/cache-reporting.ts b/src/cache-reporting.ts index c7880f9..10c7d83 100644 --- a/src/cache-reporting.ts +++ b/src/cache-reporting.ts @@ -28,6 +28,9 @@ export class CacheListener { } static rehydrate(stringRep: string): CacheListener { + if (stringRep === '') { + return new CacheListener() + } const rehydrated: CacheListener = Object.assign(new CacheListener(), JSON.parse(stringRep)) const entries = rehydrated.cacheEntries for (let index = 0; index < entries.length; index++) { diff --git a/src/cache-utils.ts b/src/cache-utils.ts index dfc7fd4..13402ab 100644 --- a/src/cache-utils.ts +++ b/src/cache-utils.ts @@ -9,6 +9,7 @@ import {CacheEntryListener} from './cache-reporting' 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 CACHE_DEBUG_VAR = 'GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED' const CACHE_PREFIX_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX' @@ -20,6 +21,10 @@ export function isCacheReadOnly(): boolean { return core.getBooleanInput(CACHE_READONLY_PARAMETER) } +export function isCacheWriteOnly(): boolean { + return core.getBooleanInput(CACHE_WRITEONLY_PARAMETER) +} + export function isCacheDebuggingEnabled(): boolean { return process.env[CACHE_DEBUG_VAR] ? true : false } diff --git a/src/caches.ts b/src/caches.ts index 9b3653e..04f8d22 100644 --- a/src/caches.ts +++ b/src/caches.ts @@ -1,5 +1,5 @@ import * as core from '@actions/core' -import {isCacheDisabled, isCacheReadOnly} from './cache-utils' +import {isCacheDisabled, isCacheReadOnly, isCacheWriteOnly} from './cache-utils' import {logCachingReport, CacheListener} from './cache-reporting' import {GradleStateCache} from './cache-base' @@ -32,18 +32,22 @@ export async function restore(gradleUserHome: string): Promise { } gradleStateCache.init() + // Mark the state as restored so that post-action will perform save. + core.saveState(CACHE_RESTORED_VAR, true) + // Save the Gradle User Home for the post-action step. + core.saveState(GRADLE_USER_HOME, gradleUserHome) + + if (isCacheWriteOnly()) { + core.info('Cache is write-only: will not restore from cache.') + return + } await core.group('Restore Gradle state from cache', async () => { - core.saveState(GRADLE_USER_HOME, gradleUserHome) - const cacheListener = new CacheListener() await gradleStateCache.restore(cacheListener) core.saveState(CACHE_LISTENER, cacheListener.stringify()) }) - - // Export state that is detected in corresponding post-action step - core.saveState(CACHE_RESTORED_VAR, true) } export async function save(): Promise {