Attempt to provision best gradle version for cache-cleanup

This commit is contained in:
daz 2025-01-23 17:37:15 -07:00
parent edf9e3c8c7
commit c6e631b4a7
No known key found for this signature in database
4 changed files with 47 additions and 11 deletions

View file

@ -1,5 +1,6 @@
import * as fs from 'fs'
import * as path from 'path'
import {versionIsAtLeast} from './execution/gradle'
export interface BuildResult {
get rootProjectName(): string
@ -32,6 +33,18 @@ export class BuildResults {
const allHomes = this.results.map(buildResult => buildResult.gradleHomeDir)
return Array.from(new Set(allHomes))
}
highestGradleVersion(): string | null {
if (this.results.length === 0) {
return null
}
return this.results
.map(result => result.gradleVersion)
.reduce((maxVersion: string, currentVersion: string) => {
if (!maxVersion) return currentVersion
return versionIsAtLeast(currentVersion, maxVersion) ? currentVersion : maxVersion
})
}
}
export function loadBuildResults(): BuildResults {

View file

@ -4,6 +4,8 @@ import * as exec from '@actions/exec'
import fs from 'fs'
import path from 'path'
import * as provisioner from '../execution/provision'
import {BuildResults} from '../build-results'
import {versionIsAtLeast} from '../execution/gradle'
export class CacheCleaner {
private readonly gradleUserHome: string
@ -21,13 +23,37 @@ export class CacheCleaner {
return timestamp
}
async forceCleanup(): Promise<void> {
async forceCleanup(buildResults: BuildResults): Promise<void> {
const executable = await this.gradleExecutableForCleanup(buildResults)
const cleanTimestamp = core.getState('clean-timestamp')
await this.forceCleanupFilesOlderThan(cleanTimestamp)
await this.forceCleanupFilesOlderThan(cleanTimestamp, executable)
}
/**
* Attempt to use the newest Gradle version that was used to run a build, at least 8.11.
*
* This will avoid the need to provision a Gradle version for the cleanup when not necessary.
*/
private async gradleExecutableForCleanup(buildResults: BuildResults): Promise<string> {
const preferredVersion = buildResults.highestGradleVersion()
if (preferredVersion && versionIsAtLeast(preferredVersion, '8.11')) {
try {
return await provisioner.provisionGradleAtLeast(preferredVersion)
} catch (e) {
// Ignore the case where the preferred version cannot be located in https://services.gradle.org/versions/all.
// This can happen for snapshot Gradle versions.
core.info(
`Failed to provision Gradle ${preferredVersion} for cache cleanup. Falling back to default version.`
)
}
}
// Fallback to the minimum version required for cache-cleanup
return await provisioner.provisionGradleAtLeast('8.11')
}
// Visible for testing
async forceCleanupFilesOlderThan(cleanTimestamp: string): Promise<void> {
async forceCleanupFilesOlderThan(cleanTimestamp: string, executable: string): Promise<void> {
// Run a dummy Gradle build to trigger cache cleanup
const cleanupProjectDir = path.resolve(this.tmpDir, 'dummy-cleanup-project')
fs.mkdirSync(cleanupProjectDir, {recursive: true})
@ -55,9 +81,6 @@ export class CacheCleaner {
)
fs.writeFileSync(path.resolve(cleanupProjectDir, 'build.gradle'), 'task("noop") {}')
// TODO: This is ineffective: we should be using the newest version of Gradle that ran a build, or a newer version if it's available on PATH.
const executable = await provisioner.provisionGradleAtLeast('8.12')
await core.group('Executing Gradle to clean up caches', async () => {
core.info(`Cleaning up caches last used before ${cleanTimestamp}`)
await this.executeCleanupBuild(executable, cleanupProjectDir)

View file

@ -102,7 +102,7 @@ export async function save(
cacheListener.setCacheCleanupDisabled(CLEANUP_DISABLED_DUE_TO_CONFIG_CACHE_HIT)
} else if (cacheConfig.shouldPerformCacheCleanup(buildResults.anyFailed())) {
cacheListener.setCacheCleanupEnabled()
await performCacheCleanup(gradleUserHome)
await performCacheCleanup(gradleUserHome, buildResults)
} else {
core.info('Not performing cache-cleanup due to build failure')
cacheListener.setCacheCleanupDisabled(CLEANUP_DISABLED_DUE_TO_FAILURE)
@ -114,10 +114,10 @@ export async function save(
})
}
async function performCacheCleanup(gradleUserHome: string): Promise<void> {
async function performCacheCleanup(gradleUserHome: string, buildResults: BuildResults): Promise<void> {
const cacheCleaner = new CacheCleaner(gradleUserHome, process.env['RUNNER_TEMP']!)
try {
await cacheCleaner.forceCleanup()
await cacheCleaner.forceCleanup(buildResults)
} catch (e) {
core.warning(`Cache cleanup failed. Will continue. ${String(e)}`)
}

View file

@ -28,7 +28,7 @@ test('will cleanup unused dependency jars and build-cache entries', async () =>
expect(fs.existsSync(commonsMath311)).toBe(true)
expect(fs.readdirSync(buildCacheDir).length).toBe(4) // gc.properties, build-cache-1.lock, and 2 task entries
await cacheCleaner.forceCleanupFilesOlderThan(timestamp)
await cacheCleaner.forceCleanupFilesOlderThan(timestamp, 'gradle')
expect(fs.existsSync(commonsMath31)).toBe(false)
expect(fs.existsSync(commonsMath311)).toBe(true)
@ -68,7 +68,7 @@ test('will cleanup unused gradle versions', async () => {
// The wrapper won't be removed if it was recently downloaded. Age it.
setUtimes(wrapper802, new Date(Date.now() - 48 * 60 * 60 * 1000))
await cacheCleaner.forceCleanupFilesOlderThan(timestamp)
await cacheCleaner.forceCleanupFilesOlderThan(timestamp, 'gradle')
expect(fs.existsSync(gradle802)).toBe(false)
expect(fs.existsSync(transforms3)).toBe(false)