mirror of
https://github.com/gradle/actions.git
synced 2025-08-24 10:51:27 +08:00
Use Gradle 8.8 features for cleanup
Gradle 8.8 introduces new features that allow us to avoid using timestamp manipulation to force the cleanup of the Gradle User Home directory. This solution is simpler and more robust, but relies on Gradle 8.8+ always being used for the cache cleanup operation. Fixes #24
This commit is contained in:
parent
169bec5d8b
commit
95ef72241e
@ -1,5 +1,4 @@
|
|||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import * as glob from '@actions/glob'
|
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import {provisionAndMaybeExecute} from '../execution/gradle'
|
import {provisionAndMaybeExecute} from '../execution/gradle'
|
||||||
@ -13,25 +12,20 @@ export class CacheCleaner {
|
|||||||
this.tmpDir = tmpDir
|
this.tmpDir = tmpDir
|
||||||
}
|
}
|
||||||
|
|
||||||
async prepare(): Promise<void> {
|
async prepare(): Promise<string> {
|
||||||
// Reset the file-access journal so that files appear not to have been used recently
|
// Save the current timestamp
|
||||||
fs.rmSync(path.resolve(this.gradleUserHome, 'caches/journal-1'), {recursive: true, force: true})
|
const timestamp = Date.now().toString()
|
||||||
fs.mkdirSync(path.resolve(this.gradleUserHome, 'caches/journal-1'), {recursive: true})
|
core.saveState('clean-timestamp', timestamp)
|
||||||
fs.writeFileSync(
|
return timestamp
|
||||||
path.resolve(this.gradleUserHome, 'caches/journal-1/file-access.properties'),
|
|
||||||
'inceptionTimestamp=0'
|
|
||||||
)
|
|
||||||
|
|
||||||
// Set the modification time of all files to the past: this timestamp is used when there is no matching entry in the journal
|
|
||||||
await this.ageAllFiles()
|
|
||||||
|
|
||||||
// Touch all 'gc' files so that cache cleanup won't run immediately.
|
|
||||||
await this.touchAllFiles('gc.properties')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async forceCleanup(): Promise<void> {
|
async forceCleanup(): Promise<void> {
|
||||||
// Age all 'gc' files so that cache cleanup will run immediately.
|
const cleanTimestamp = core.getState('clean-timestamp')
|
||||||
await this.ageAllFiles('gc.properties')
|
await this.forceCleanupFilesOlderThan(cleanTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
async forceCleanupFilesOlderThan(cleanTimestamp: string): Promise<void> {
|
||||||
|
core.info(`Cleaning up caches before ${cleanTimestamp}`)
|
||||||
|
|
||||||
// Run a dummy Gradle build to trigger cache cleanup
|
// Run a dummy Gradle build to trigger cache cleanup
|
||||||
const cleanupProjectDir = path.resolve(this.tmpDir, 'dummy-cleanup-project')
|
const cleanupProjectDir = path.resolve(this.tmpDir, 'dummy-cleanup-project')
|
||||||
@ -40,11 +34,31 @@ export class CacheCleaner {
|
|||||||
path.resolve(cleanupProjectDir, 'settings.gradle'),
|
path.resolve(cleanupProjectDir, 'settings.gradle'),
|
||||||
'rootProject.name = "dummy-cleanup-project"'
|
'rootProject.name = "dummy-cleanup-project"'
|
||||||
)
|
)
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.resolve(cleanupProjectDir, 'init.gradle'),
|
||||||
|
`
|
||||||
|
beforeSettings { settings ->
|
||||||
|
def cleanupTime = ${cleanTimestamp}
|
||||||
|
|
||||||
|
settings.caches {
|
||||||
|
cleanup = Cleanup.ALWAYS
|
||||||
|
|
||||||
|
releasedWrappers.removeUnusedEntriesOlderThan.set(cleanupTime)
|
||||||
|
snapshotWrappers.removeUnusedEntriesOlderThan.set(cleanupTime)
|
||||||
|
downloadedResources.removeUnusedEntriesOlderThan.set(cleanupTime)
|
||||||
|
createdResources.removeUnusedEntriesOlderThan.set(cleanupTime)
|
||||||
|
buildCache.removeUnusedEntriesOlderThan.set(cleanupTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)
|
||||||
fs.writeFileSync(path.resolve(cleanupProjectDir, 'build.gradle'), 'task("noop") {}')
|
fs.writeFileSync(path.resolve(cleanupProjectDir, 'build.gradle'), 'task("noop") {}')
|
||||||
|
|
||||||
await provisionAndMaybeExecute('current', cleanupProjectDir, [
|
await provisionAndMaybeExecute('current', cleanupProjectDir, [
|
||||||
'-g',
|
'-g',
|
||||||
this.gradleUserHome,
|
this.gradleUserHome,
|
||||||
|
'-I',
|
||||||
|
'init.gradle',
|
||||||
'--quiet',
|
'--quiet',
|
||||||
'--no-daemon',
|
'--no-daemon',
|
||||||
'--no-scan',
|
'--no-scan',
|
||||||
@ -53,23 +67,4 @@ export class CacheCleaner {
|
|||||||
'noop'
|
'noop'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ageAllFiles(fileName = '*'): Promise<void> {
|
|
||||||
core.debug(`Aging all files in Gradle User Home with name ${fileName}`)
|
|
||||||
await this.setUtimes(`${this.gradleUserHome}/**/${fileName}`, new Date(0))
|
|
||||||
}
|
|
||||||
|
|
||||||
private async touchAllFiles(fileName = '*'): Promise<void> {
|
|
||||||
core.debug(`Touching all files in Gradle User Home with name ${fileName}`)
|
|
||||||
await this.setUtimes(`${this.gradleUserHome}/**/${fileName}`, new Date())
|
|
||||||
}
|
|
||||||
|
|
||||||
private async setUtimes(pattern: string, timestamp: Date): Promise<void> {
|
|
||||||
const globber = await glob.create(pattern, {
|
|
||||||
implicitDescendants: false
|
|
||||||
})
|
|
||||||
for await (const file of globber.globGenerator()) {
|
|
||||||
fs.utimesSync(file, timestamp, timestamp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import * as exec from '@actions/exec'
|
import * as exec from '@actions/exec'
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
|
import * as glob from '@actions/glob'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import {CacheCleaner} from '../../src/caching/cache-cleaner'
|
import {CacheCleaner} from '../../src/caching/cache-cleaner'
|
||||||
@ -14,7 +15,7 @@ test('will cleanup unused dependency jars and build-cache entries', async () =>
|
|||||||
|
|
||||||
await runGradleBuild(projectRoot, 'build', '3.1')
|
await runGradleBuild(projectRoot, 'build', '3.1')
|
||||||
|
|
||||||
await cacheCleaner.prepare()
|
const timestamp = await cacheCleaner.prepare()
|
||||||
|
|
||||||
await runGradleBuild(projectRoot, 'build', '3.1.1')
|
await runGradleBuild(projectRoot, 'build', '3.1.1')
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ test('will cleanup unused dependency jars and build-cache entries', async () =>
|
|||||||
expect(fs.existsSync(commonsMath311)).toBe(true)
|
expect(fs.existsSync(commonsMath311)).toBe(true)
|
||||||
expect(fs.readdirSync(buildCacheDir).length).toBe(4) // gc.properties, build-cache-1.lock, and 2 task entries
|
expect(fs.readdirSync(buildCacheDir).length).toBe(4) // gc.properties, build-cache-1.lock, and 2 task entries
|
||||||
|
|
||||||
await cacheCleaner.forceCleanup()
|
await cacheCleaner.forceCleanupFilesOlderThan(timestamp)
|
||||||
|
|
||||||
expect(fs.existsSync(commonsMath31)).toBe(false)
|
expect(fs.existsSync(commonsMath31)).toBe(false)
|
||||||
expect(fs.existsSync(commonsMath311)).toBe(true)
|
expect(fs.existsSync(commonsMath311)).toBe(true)
|
||||||
@ -42,25 +43,39 @@ test('will cleanup unused gradle versions', async () => {
|
|||||||
// Initialize HOME with 2 different Gradle versions
|
// Initialize HOME with 2 different Gradle versions
|
||||||
await runGradleWrapperBuild(projectRoot, 'build')
|
await runGradleWrapperBuild(projectRoot, 'build')
|
||||||
await runGradleBuild(projectRoot, 'build')
|
await runGradleBuild(projectRoot, 'build')
|
||||||
|
|
||||||
await cacheCleaner.prepare()
|
const timestamp = await cacheCleaner.prepare()
|
||||||
|
|
||||||
// Run with only one of these versions
|
// Run with only one of these versions
|
||||||
await runGradleBuild(projectRoot, 'build')
|
await runGradleBuild(projectRoot, 'build')
|
||||||
|
|
||||||
const gradle802 = path.resolve(gradleUserHome, "caches/8.0.2")
|
const gradle802 = path.resolve(gradleUserHome, "caches/8.0.2")
|
||||||
|
const transforms3 = path.resolve(gradleUserHome, "caches/transforms-3")
|
||||||
|
const metadata100 = path.resolve(gradleUserHome, "caches/modules-2/metadata-2.100")
|
||||||
const wrapper802 = path.resolve(gradleUserHome, "wrapper/dists/gradle-8.0.2-bin")
|
const wrapper802 = path.resolve(gradleUserHome, "wrapper/dists/gradle-8.0.2-bin")
|
||||||
const gradleCurrent = path.resolve(gradleUserHome, "caches/8.8")
|
const gradleCurrent = path.resolve(gradleUserHome, "caches/8.8")
|
||||||
|
const metadataCurrent = path.resolve(gradleUserHome, "caches/modules-2/metadata-2.106")
|
||||||
|
|
||||||
expect(fs.existsSync(gradle802)).toBe(true)
|
expect(fs.existsSync(gradle802)).toBe(true)
|
||||||
|
expect(fs.existsSync(transforms3)).toBe(true)
|
||||||
|
expect(fs.existsSync(metadata100)).toBe(true)
|
||||||
expect(fs.existsSync(wrapper802)).toBe(true)
|
expect(fs.existsSync(wrapper802)).toBe(true)
|
||||||
expect(fs.existsSync(gradleCurrent)).toBe(true)
|
|
||||||
|
|
||||||
await cacheCleaner.forceCleanup()
|
expect(fs.existsSync(gradleCurrent)).toBe(true)
|
||||||
|
expect(fs.existsSync(metadataCurrent)).toBe(true)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
expect(fs.existsSync(gradle802)).toBe(false)
|
expect(fs.existsSync(gradle802)).toBe(false)
|
||||||
|
expect(fs.existsSync(transforms3)).toBe(false)
|
||||||
|
expect(fs.existsSync(metadata100)).toBe(false)
|
||||||
expect(fs.existsSync(wrapper802)).toBe(false)
|
expect(fs.existsSync(wrapper802)).toBe(false)
|
||||||
|
|
||||||
expect(fs.existsSync(gradleCurrent)).toBe(true)
|
expect(fs.existsSync(gradleCurrent)).toBe(true)
|
||||||
|
expect(fs.existsSync(metadataCurrent)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
async function runGradleBuild(projectRoot: string, args: string, version: string = '3.1'): Promise<void> {
|
async function runGradleBuild(projectRoot: string, args: string, version: string = '3.1'): Promise<void> {
|
||||||
@ -86,3 +101,9 @@ function prepareTestProject(): string {
|
|||||||
return projectRoot
|
return projectRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function setUtimes(pattern: string, timestamp: Date): Promise<void> {
|
||||||
|
const globber = await glob.create(pattern)
|
||||||
|
for await (const file of globber.globGenerator()) {
|
||||||
|
fs.utimesSync(file, timestamp, timestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user