mirror of
https://github.com/gradle/actions.git
synced 2026-08-18 20:02:52 +00:00
Follow-up to #1027, which added Windows coverage for the caching smoke tests. `basic-cache-verify-build` failed on Windows for a reason unrelated to #1013. ## Root cause The Windows seed job **never uploaded a cache entry, but reported that it did**. `gh cache list` showed no `setup-java-Windows-*` entry at all, despite the job logging `Basic caching saved entry with key: setup-java-Windows-x64-gradle-594edf…`. The keys were never the problem — seed and verify requested the identical key. The seed build leaves a Gradle daemon running, holding the `*.lock` files in the Gradle User Home. On Windows those locks are mandatory, so `tar` cannot read them: ``` /usr/bin/tar: ../../.gradle/caches/modules-2/modules-2.lock: Read error at byte 0, while reading 38 bytes: Device or resource busy /usr/bin/tar: Exiting with failure status due to previous errors ``` 38 bytes is exactly Gradle's lock-file header — the region the daemon holds via `FileChannel.lock()`. On Linux the lock is advisory and tar reads straight through, which is why this only ever failed on Windows. `cache.saveCache()` catches the tar failure, logs it, and returns `-1` rather than throwing. `BasicCacheService.save()` ignored the return value, so the seed job went green and the failure surfaced only later — as a plugin resolution error in the verify job, pointing nowhere near caching. ## Changes **1. Warn when the save fails.** Check the returned `cacheId` and, when it is `-1`, emit a warning and report `(Entry not saved: save failed)` in the job summary. Caching failures still do not fail the build. **2. Run the seed build with `--no-daemon`.** Daemon management for enhanced caching lives in the `gradle-actions-caching` library; basic caching leaves it to the workflow, so the smoke test now ensures no daemon is holding locks when the post-action save runs. ## Not related to #1013 The enhanced provider fails differently on Windows — every entry dies at path validation, before tar runs (`Path Validation Error: Path(s) specified in the action for caching do(es) not exist`). Same symptom, different mechanism; that one is unchanged here and is still expected to be red. ## Verification `npm run check` and `npm test` pass locally (373 tests). The real check is this PR's Windows run: `basic-cache-seed-build` and `basic-cache-verify-build` should both be green on `windows-latest`, while the `restore-gradle-home-*` Windows jobs stay red pending the #1013 fix. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
169 lines
6.4 KiB
TypeScript
169 lines
6.4 KiB
TypeScript
import * as cache from '@actions/cache'
|
|
import * as core from '@actions/core'
|
|
import * as glob from '@actions/glob'
|
|
import * as path from 'path'
|
|
|
|
import {BuildResult} from './build-results'
|
|
import {CacheEntryReport, CacheOptions, CacheReport, CacheService} from './cache-service'
|
|
|
|
const ENTRY_NAME = 'Gradle User Home'
|
|
|
|
const PRIMARY_KEY_STATE = 'BASIC_CACHE_PRIMARY_KEY'
|
|
const RESTORED_KEY_STATE = 'BASIC_CACHE_RESTORED_KEY'
|
|
const CACHE_KEY_PREFIX = 'setup-java'
|
|
|
|
const GRADLE_BUILD_FILE_PATTERNS = [
|
|
'**/*.gradle*',
|
|
'**/gradle-wrapper.properties',
|
|
'buildSrc/**/Versions.kt',
|
|
'buildSrc/**/Dependencies.kt',
|
|
'gradle/*.versions.toml',
|
|
'**/versions.properties'
|
|
]
|
|
|
|
export class BasicCacheService implements CacheService {
|
|
async restore(gradleUserHome: string, _cacheOptions: CacheOptions): Promise<void> {
|
|
const cachePaths = getCachePaths(gradleUserHome)
|
|
const primaryKey = await computeCacheKey()
|
|
core.saveState(PRIMARY_KEY_STATE, primaryKey)
|
|
|
|
// No "restoreKeys" is set, to start with a clear cache after dependency update
|
|
// See https://github.com/actions/setup-java/issues/269
|
|
try {
|
|
const restoredKey = await cache.restoreCache(cachePaths, primaryKey)
|
|
if (restoredKey) {
|
|
core.saveState(RESTORED_KEY_STATE, restoredKey)
|
|
core.info(`Basic caching restored from cache key: ${restoredKey}`)
|
|
} else {
|
|
core.info('Basic caching did not find an entry to restore. Will start with empty state.')
|
|
}
|
|
} catch (error) {
|
|
core.warning(`Basic caching failed to restore from cache: ${error}`)
|
|
}
|
|
}
|
|
|
|
async save(gradleUserHome: string, _buildResults: BuildResult[], cacheOptions: CacheOptions): Promise<CacheReport> {
|
|
const primaryKey = core.getState(PRIMARY_KEY_STATE)
|
|
const restoredKey = core.getState(RESTORED_KEY_STATE)
|
|
|
|
if (cacheOptions.readOnly) {
|
|
return {
|
|
status: 'read-only',
|
|
entries: [
|
|
entryReport({
|
|
primaryKey,
|
|
restoredKey,
|
|
restoredOutcome: restoredKey
|
|
? '(Entry restored: exact match found)'
|
|
: '(Entry not restored: no match found)',
|
|
savedOutcome: '(Entry not saved: cache is read-only)'
|
|
})
|
|
]
|
|
}
|
|
}
|
|
|
|
if (restoredKey === primaryKey) {
|
|
core.info(`Basic caching restored entry with key \`${primaryKey}\`. Save was skipped.`)
|
|
return {
|
|
status: 'enabled',
|
|
entries: [
|
|
entryReport({
|
|
primaryKey,
|
|
restoredKey,
|
|
restoredOutcome: '(Entry restored: exact match found)',
|
|
savedOutcome: '(Entry not saved: entry with key already exists)'
|
|
})
|
|
]
|
|
}
|
|
}
|
|
|
|
const cachePaths = getCachePaths(gradleUserHome)
|
|
|
|
try {
|
|
// A cacheId of -1 means the save failed: `saveCache` reports the underlying cause and returns
|
|
// normally, rather than throwing. Warn and continue: caching failures should not fail the build.
|
|
const cacheId = await cache.saveCache(cachePaths, primaryKey)
|
|
if (cacheId === -1) {
|
|
core.warning(
|
|
`Basic caching failed to save entry with key \`${primaryKey}\`. See preceding log output for the cause.`
|
|
)
|
|
return {
|
|
status: 'enabled',
|
|
entries: [
|
|
entryReport({
|
|
primaryKey,
|
|
restoredKey,
|
|
restoredOutcome: restoredKey
|
|
? '(Entry restored: exact match found)'
|
|
: '(Entry not restored: no match found)',
|
|
savedOutcome: '(Entry not saved: save failed)'
|
|
})
|
|
]
|
|
}
|
|
}
|
|
|
|
core.info(`Basic caching saved entry with key: ${primaryKey}`)
|
|
return {
|
|
status: 'enabled',
|
|
entries: [
|
|
entryReport({
|
|
primaryKey,
|
|
restoredKey,
|
|
savedKey: primaryKey,
|
|
restoredOutcome: restoredKey
|
|
? '(Entry restored: exact match found)'
|
|
: '(Entry not restored: no match found)',
|
|
savedOutcome: '(Entry saved)'
|
|
})
|
|
]
|
|
}
|
|
} catch (error) {
|
|
core.warning(`Basic caching failed to save entry with key \`${primaryKey}\`: ${error}`)
|
|
return {
|
|
status: 'enabled',
|
|
entries: [
|
|
entryReport({
|
|
primaryKey,
|
|
restoredKey,
|
|
restoredOutcome: restoredKey
|
|
? '(Entry restored: exact match found)'
|
|
: '(Entry not restored: no match found)',
|
|
savedOutcome: `(Entry not saved: ${error})`
|
|
})
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function entryReport(opts: {
|
|
primaryKey: string
|
|
restoredKey?: string
|
|
savedKey?: string
|
|
restoredOutcome: string
|
|
savedOutcome: string
|
|
}): CacheEntryReport {
|
|
return {
|
|
entryName: ENTRY_NAME,
|
|
requestedKey: opts.primaryKey || undefined,
|
|
restoredKey: opts.restoredKey || undefined,
|
|
restoredOutcome: opts.restoredOutcome,
|
|
savedKey: opts.savedKey || undefined,
|
|
savedOutcome: opts.savedOutcome
|
|
}
|
|
}
|
|
|
|
function getCachePaths(gradleUserHome: string): string[] {
|
|
return [path.join(gradleUserHome, 'caches'), path.join(gradleUserHome, 'wrapper')]
|
|
}
|
|
|
|
async function computeCacheKey(): Promise<string> {
|
|
const fileHash = await glob.hashFiles(GRADLE_BUILD_FILE_PATTERNS.join('\n'))
|
|
if (!fileHash) {
|
|
throw new Error(
|
|
`No file in ${process.cwd()} matched to [${GRADLE_BUILD_FILE_PATTERNS}], make sure you have checked out the target repository`
|
|
)
|
|
}
|
|
return `${CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-gradle-${fileHash}`
|
|
}
|