From ac3a4a8d4b82bad9bebe4c3fb78ede7673a3e815 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Thu, 9 Jul 2026 21:00:52 -0400 Subject: [PATCH] Cache Gradle wrapper distribution separately from the dependency cache (#1098) The Gradle wrapper distribution (~/.gradle/wrapper) only depends on gradle-wrapper.properties, which changes rarely, but it was previously cached in the same entry as ~/.gradle/caches, keyed on volatile **/*.gradle* files with no restoreKeys (issue #269). Every dependency change therefore re-downloaded the wrapper. Move ~/.gradle/wrapper into a dedicated `gradle-wrapper` additional cache keyed only on **/gradle-wrapper.properties, reusing the additionalCaches infrastructure introduced for the Maven wrapper fix. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- __tests__/cache.test.ts | 81 +++++++++++++++++++++++++++++++++++++++++ dist/cleanup/index.js | 16 ++++++-- dist/setup/index.js | 16 ++++++-- src/cache.ts | 16 ++++++-- 4 files changed, 117 insertions(+), 12 deletions(-) diff --git a/__tests__/cache.test.ts b/__tests__/cache.test.ts index 332af9f6..f3feba42 100644 --- a/__tests__/cache.test.ts +++ b/__tests__/cache.test.ts @@ -316,6 +316,43 @@ describe('dependency cache', () => { expect(spyWarning).not.toHaveBeenCalled(); expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found'); }); + it('restores the gradle wrapper distribution cache independently of the main cache', async () => { + createFile(join(workspace, 'build.gradle')); + + await restore('gradle', ''); + // Main dependency cache no longer carries the wrapper path. + expect(spyCacheRestore).toHaveBeenCalledWith( + [join(os.homedir(), '.gradle', 'caches')], + expect.any(String) + ); + // Wrapper distribution is restored on its own, keyed only on the + // wrapper properties file. + expect(spyCacheRestore).toHaveBeenCalledWith( + [join(os.homedir(), '.gradle', 'wrapper')], + expect.stringContaining('setup-java-') + ); + expect(spyGlobHashFiles).toHaveBeenCalledWith( + '**/gradle-wrapper.properties' + ); + }); + it('skips the gradle wrapper cache when no wrapper properties exist', async () => { + createFile(join(workspace, 'build.gradle')); + spyGlobHashFiles.mockImplementation((pattern: string) => + Promise.resolve( + pattern === '**/gradle-wrapper.properties' ? '' : 'hash-stub' + ) + ); + + await restore('gradle', ''); + // Only the main dependency cache is restored; the wrapper cache path is + // never touched because the project does not use the gradle wrapper. + expect(spyCacheRestore).toHaveBeenCalledTimes(1); + expect(spyCacheRestore).toHaveBeenCalledWith( + [join(os.homedir(), '.gradle', 'caches')], + expect.any(String) + ); + expect(spyWarning).not.toHaveBeenCalled(); + }); }); describe('for sbt', () => { it('throws error if no build.sbt found', async () => { @@ -587,6 +624,50 @@ describe('dependency cache', () => { expect.stringMatching(/^Cache saved with the key:.*/) ); }); + it('saves the gradle wrapper distribution cache under its own key', async () => { + createFile(join(workspace, 'build.gradle')); + (core.getState as jest.Mock).mockImplementation((name: any) => { + switch (name) { + case 'cache-primary-key': + return 'setup-java-cache-primary-key'; + case 'cache-matched-key': + return 'setup-java-cache-matched-key'; + case 'cache-primary-key-gradle-wrapper': + return 'setup-java-gradle-wrapper-key'; + default: + return ''; + } + }); + + await save('gradle'); + expect(spyCacheSave).toHaveBeenCalledWith( + [join(os.homedir(), '.gradle', 'wrapper')], + 'setup-java-gradle-wrapper-key' + ); + expect(spyWarning).not.toHaveBeenCalled(); + }); + it('does not save the gradle wrapper cache on an exact wrapper hit', async () => { + createFile(join(workspace, 'build.gradle')); + (core.getState as jest.Mock).mockImplementation((name: any) => { + switch (name) { + case 'cache-primary-key': + return 'setup-java-cache-primary-key'; + case 'cache-matched-key': + return 'setup-java-cache-matched-key'; + case 'cache-primary-key-gradle-wrapper': + case 'cache-matched-key-gradle-wrapper': + return 'setup-java-gradle-wrapper-key'; + default: + return ''; + } + }); + + await save('gradle'); + expect(spyCacheSave).not.toHaveBeenCalledWith( + [join(os.homedir(), '.gradle', 'wrapper')], + expect.any(String) + ); + }); }); describe('for sbt', () => { it('uploads cache even if no build.sbt found', async () => { diff --git a/dist/cleanup/index.js b/dist/cleanup/index.js index 8bcea519..d6e09fe4 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -99687,10 +99687,7 @@ const supportedPackageManager = [ }, { id: 'gradle', - path: [ - (0,external_path_.join)(external_os_default().homedir(), '.gradle', 'caches'), - (0,external_path_.join)(external_os_default().homedir(), '.gradle', 'wrapper') - ], + path: [(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'caches')], // https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle pattern: [ '**/*.gradle*', @@ -99699,6 +99696,17 @@ const supportedPackageManager = [ 'buildSrc/**/Dependencies.kt', 'gradle/*.versions.toml', '**/versions.properties' + ], + // The Gradle wrapper distribution only depends on the wrapper properties, + // which change very rarely, so it is cached separately from the Gradle + // caches. This keeps it available across the frequent *.gradle* changes + // that rotate the main cache key. See issue #1095. + additionalCaches: [ + { + name: 'gradle-wrapper', + path: [(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'wrapper')], + pattern: ['**/gradle-wrapper.properties'] + } ] }, { diff --git a/dist/setup/index.js b/dist/setup/index.js index cc8e888e..b88156ce 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -130909,10 +130909,7 @@ const supportedPackageManager = [ }, { id: 'gradle', - path: [ - (0,external_path_.join)(external_os_default().homedir(), '.gradle', 'caches'), - (0,external_path_.join)(external_os_default().homedir(), '.gradle', 'wrapper') - ], + path: [(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'caches')], // https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle pattern: [ '**/*.gradle*', @@ -130921,6 +130918,17 @@ const supportedPackageManager = [ 'buildSrc/**/Dependencies.kt', 'gradle/*.versions.toml', '**/versions.properties' + ], + // The Gradle wrapper distribution only depends on the wrapper properties, + // which change very rarely, so it is cached separately from the Gradle + // caches. This keeps it available across the frequent *.gradle* changes + // that rotate the main cache key. See issue #1095. + additionalCaches: [ + { + name: 'gradle-wrapper', + path: [(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'wrapper')], + pattern: ['**/gradle-wrapper.properties'] + } ] }, { diff --git a/src/cache.ts b/src/cache.ts index c9e67dcc..7bb3fc7e 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -72,10 +72,7 @@ const supportedPackageManager: PackageManager[] = [ }, { id: 'gradle', - path: [ - join(os.homedir(), '.gradle', 'caches'), - join(os.homedir(), '.gradle', 'wrapper') - ], + path: [join(os.homedir(), '.gradle', 'caches')], // https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle pattern: [ '**/*.gradle*', @@ -84,6 +81,17 @@ const supportedPackageManager: PackageManager[] = [ 'buildSrc/**/Dependencies.kt', 'gradle/*.versions.toml', '**/versions.properties' + ], + // The Gradle wrapper distribution only depends on the wrapper properties, + // which change very rarely, so it is cached separately from the Gradle + // caches. This keeps it available across the frequent *.gradle* changes + // that rotate the main cache key. See issue #1095. + additionalCaches: [ + { + name: 'gradle-wrapper', + path: [join(os.homedir(), '.gradle', 'wrapper')], + pattern: ['**/gradle-wrapper.properties'] + } ] }, {