diff --git a/sources/src/build-results.ts b/sources/src/build-results.ts index 82934cf..9969867 100644 --- a/sources/src/build-results.ts +++ b/sources/src/build-results.ts @@ -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 { diff --git a/sources/src/caching/cache-cleaner.ts b/sources/src/caching/cache-cleaner.ts index e050c3b..91ed908 100644 --- a/sources/src/caching/cache-cleaner.ts +++ b/sources/src/caching/cache-cleaner.ts @@ -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 { + async forceCleanup(buildResults: BuildResults): Promise { + 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 { + 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 { + async forceCleanupFilesOlderThan(cleanTimestamp: string, executable: string): Promise { // 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) diff --git a/sources/src/caching/caches.ts b/sources/src/caching/caches.ts index d3f4356..0ffd4b0 100644 --- a/sources/src/caching/caches.ts +++ b/sources/src/caching/caches.ts @@ -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 { +async function performCacheCleanup(gradleUserHome: string, buildResults: BuildResults): Promise { 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)}`) } diff --git a/sources/test/jest/cache-cleanup.test.ts b/sources/test/jest/cache-cleanup.test.ts index 4bf9f98..5debc5c 100644 --- a/sources/test/jest/cache-cleanup.test.ts +++ b/sources/test/jest/cache-cleanup.test.ts @@ -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)