Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 03ad4de

Browse filesBrowse files
brunoborgesCopilot
andauthored
Backport #1097/#1098: cache Maven and Gradle wrapper distributions separately (#1122)
Backports the wrapper caching fix to the v5 release line. The Maven wrapper distribution (~/.m2/wrapper/dists) and Gradle wrapper distribution (~/.gradle/wrapper) were cached in the same entry as the dependency cache, keyed on the volatile dependency-file hashes (**/pom.xml, **/*.gradle*). With no restoreKeys fallback (#269), almost every dependency change was a full cache miss, forcing ./mvnw and ./gradlew to re-download the build tool distribution and hitting intermittent rate-limit failures. Each wrapper distribution now lives in its own additional cache entry keyed only on the rarely-changing wrapper properties file (**/.mvn/wrapper/maven-wrapper.properties, **/gradle-wrapper.properties), so it survives dependency changes. Optional-cache saves swallow ValidationError and ReserveCacheError so a project without a wrapper never fails the post step. Fixes: #1095 Copilot-Session: ea015caa-980a-4e0a-af20-3fc9f39c77fd Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent d229d2e commit 03ad4de
Copy full SHA for 03ad4de

5 files changed

+581-40Lines changed: 581 additions & 40 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎README.md‎

Copy file name to clipboardExpand all lines: README.md
+3-1Lines changed: 3 additions & 1 deletion
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ The workflow output `cache-primary-key` exposes the primary cache key computed b
149149

150150
The cache input is optional, and caching is turned off by default.
151151

152-
**Maven Wrapper:** when `cache: 'maven'` is enabled, the action also caches and restores the Maven Wrapper distribution downloaded to `~/.m2/wrapper/dists` (in addition to the local repository), so wrapper-based (`./mvnw`) builds don't re-download the wrapper on every run. This is keyed on `**/.mvn/wrapper/maven-wrapper.properties` as shown above.
152+
**Maven Wrapper:** when `cache: 'maven'` is enabled, the action also caches and restores the Maven Wrapper distribution downloaded to `~/.m2/wrapper/dists` (in addition to the local repository), so wrapper-based (`./mvnw`) builds don't re-download the Maven distribution. The wrapper distribution is stored in a **separate** cache entry keyed only on `**/.mvn/wrapper/maven-wrapper.properties`, so it stays cached across the frequent `pom.xml` changes that rotate the main dependency cache key.
153153

154154
#### Caching gradle dependencies
155155
```yaml
@@ -167,6 +167,8 @@ steps:
167167
```
168168
Using the `cache: gradle` provides a simple and effective way to cache Gradle dependencies with minimal configuration.
169169

170+
**Gradle Wrapper:** when `cache: 'gradle'` is enabled, the action also caches and restores the Gradle Wrapper distribution downloaded to `~/.gradle/wrapper` (in addition to the Gradle caches), so wrapper-based (`./gradlew`) builds don't re-download the Gradle distribution. The wrapper distribution is stored in a **separate** cache entry keyed only on `**/gradle-wrapper.properties`, so it stays cached across the frequent `*.gradle*` changes that rotate the main dependency cache key.
171+
170172
For projects that require more advanced `Gradle` caching features, such as caching build outputs, support for Gradle configuration cache, encrypted cache storage, fine-grained cache control (including options to enable or disable the cache, set it to read-only or write-only, perform automated cleanup, and define custom cache rules), or optimized performance for complex CI workflows, consider using [`gradle/actions/setup-gradle`](https://github.com/gradle/actions/tree/main/setup-gradle).
171173

172174
For setup details and a comprehensive overview of all available features, visit the [setup-gradle documentation](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md).
Collapse file

‎__tests__/cache.test.ts‎

Copy file name to clipboardExpand all lines: __tests__/cache.test.ts
+137-12Lines changed: 137 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,7 @@ describe('dependency cache', () => {
113113

114114
await restore('maven', '');
115115
expect(spyCacheRestore).toHaveBeenCalledWith(
116-
[
117-
join(os.homedir(), '.m2', 'repository'),
118-
join(os.homedir(), '.m2', 'wrapper', 'dists')
119-
],
116+
[join(os.homedir(), '.m2', 'repository')],
120117
expect.any(String)
121118
);
122119
expect(spyGlobHashFiles).toHaveBeenCalledWith(
@@ -143,10 +140,7 @@ describe('dependency cache', () => {
143140

144141
await restore('maven', '');
145142
expect(spyCacheRestore).toHaveBeenCalledWith(
146-
[
147-
join(os.homedir(), '.m2', 'repository'),
148-
join(os.homedir(), '.m2', 'wrapper', 'dists')
149-
],
143+
[join(os.homedir(), '.m2', 'repository')],
150144
expect.any(String)
151145
);
152146
expect(spyGlobHashFiles).toHaveBeenCalledWith(
@@ -161,10 +155,7 @@ describe('dependency cache', () => {
161155

162156
await restore('maven', '');
163157
expect(spyCacheRestore).toHaveBeenCalledWith(
164-
[
165-
join(os.homedir(), '.m2', 'repository'),
166-
join(os.homedir(), '.m2', 'wrapper', 'dists')
167-
],
158+
[join(os.homedir(), '.m2', 'repository')],
168159
expect.any(String)
169160
);
170161
expect(spyGlobHashFiles).toHaveBeenCalledWith(
@@ -173,6 +164,39 @@ describe('dependency cache', () => {
173164
expect(spyWarning).not.toHaveBeenCalled();
174165
expect(spyInfo).toHaveBeenCalledWith('maven cache is not found');
175166
});
167+
it('restores the maven wrapper distribution cache independently of the main cache', async () => {
168+
createFile(join(workspace, 'pom.xml'));
169+
createDirectory(join(workspace, '.mvn'));
170+
createDirectory(join(workspace, '.mvn', 'wrapper'));
171+
createFile(
172+
join(workspace, '.mvn', 'wrapper', 'maven-wrapper.properties')
173+
);
174+
175+
await restore('maven', '');
176+
// Main cache no longer includes the wrapper distribution.
177+
expect(spyCacheRestore).toHaveBeenCalledWith(
178+
[join(os.homedir(), '.m2', 'repository')],
179+
expect.any(String)
180+
);
181+
// Wrapper distribution is restored on its own, keyed only on the
182+
// maven-wrapper.properties hash.
183+
expect(spyCacheRestore).toHaveBeenCalledWith(
184+
[join(os.homedir(), '.m2', 'wrapper', 'dists')],
185+
expect.any(String)
186+
);
187+
expect(spyGlobHashFiles).toHaveBeenCalledWith(
188+
'**/.mvn/wrapper/maven-wrapper.properties'
189+
);
190+
});
191+
it('skips the maven wrapper cache when no maven-wrapper.properties exists', async () => {
192+
createFile(join(workspace, 'pom.xml'));
193+
194+
await restore('maven', '');
195+
expect(spyCacheRestore).not.toHaveBeenCalledWith(
196+
[join(os.homedir(), '.m2', 'wrapper', 'dists')],
197+
expect.any(String)
198+
);
199+
});
176200
});
177201
describe('for gradle', () => {
178202
it('throws error if no build.gradle found', async () => {
@@ -228,6 +252,30 @@ describe('dependency cache', () => {
228252
expect(spyWarning).not.toHaveBeenCalled();
229253
expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found');
230254
});
255+
it('restores the gradle wrapper distribution cache independently of the main cache', async () => {
256+
createFile(join(workspace, 'build.gradle'));
257+
createDirectory(join(workspace, 'gradle'));
258+
createDirectory(join(workspace, 'gradle', 'wrapper'));
259+
createFile(
260+
join(workspace, 'gradle', 'wrapper', 'gradle-wrapper.properties')
261+
);
262+
263+
await restore('gradle', '');
264+
// Main cache no longer includes the wrapper distribution.
265+
expect(spyCacheRestore).toHaveBeenCalledWith(
266+
[join(os.homedir(), '.gradle', 'caches')],
267+
expect.any(String)
268+
);
269+
// Wrapper distribution is restored on its own, keyed only on the
270+
// gradle-wrapper.properties hash.
271+
expect(spyCacheRestore).toHaveBeenCalledWith(
272+
[join(os.homedir(), '.gradle', 'wrapper')],
273+
expect.any(String)
274+
);
275+
expect(spyGlobHashFiles).toHaveBeenCalledWith(
276+
'**/gradle-wrapper.properties'
277+
);
278+
});
231279
});
232280
describe('for sbt', () => {
233281
it('throws error if no build.sbt found', async () => {
@@ -399,6 +447,40 @@ describe('dependency cache', () => {
399447
expect.stringMatching(/^Cache saved with the key:.*/)
400448
);
401449
});
450+
it('saves the maven wrapper distribution cache under its own key', async () => {
451+
createFile(join(workspace, 'pom.xml'));
452+
createStateForWrapperRestore('maven-wrapper', false);
453+
454+
await save('maven');
455+
expect(spyCacheSave).toHaveBeenCalledWith(
456+
[join(os.homedir(), '.m2', 'wrapper', 'dists')],
457+
'setup-java-maven-wrapper-primary-key'
458+
);
459+
});
460+
it('does not save the maven wrapper cache on an exact wrapper hit', async () => {
461+
createFile(join(workspace, 'pom.xml'));
462+
createStateForWrapperRestore('maven-wrapper', true);
463+
464+
await save('maven');
465+
expect(spyCacheSave).not.toHaveBeenCalledWith(
466+
[join(os.homedir(), '.m2', 'wrapper', 'dists')],
467+
expect.any(String)
468+
);
469+
});
470+
it('does not fail the post step when the wrapper distribution path is missing', async () => {
471+
createFile(join(workspace, 'pom.xml'));
472+
createStateForWrapperRestore('maven-wrapper', false);
473+
spyCacheSave.mockImplementation((paths: string[], key: string) => {
474+
if (paths.includes(join(os.homedir(), '.m2', 'wrapper', 'dists'))) {
475+
return Promise.reject(
476+
new cache.ValidationError('Path Validation Error')
477+
);
478+
}
479+
return Promise.resolve(0);
480+
});
481+
482+
await expect(save('maven')).resolves.not.toThrow();
483+
});
402484
});
403485
describe('for gradle', () => {
404486
it('uploads cache even if no build.gradle found', async () => {
@@ -451,6 +533,26 @@ describe('dependency cache', () => {
451533
expect.stringMatching(/^Cache saved with the key:.*/)
452534
);
453535
});
536+
it('saves the gradle wrapper distribution cache under its own key', async () => {
537+
createFile(join(workspace, 'build.gradle'));
538+
createStateForWrapperRestore('gradle-wrapper', false);
539+
540+
await save('gradle');
541+
expect(spyCacheSave).toHaveBeenCalledWith(
542+
[join(os.homedir(), '.gradle', 'wrapper')],
543+
'setup-java-gradle-wrapper-primary-key'
544+
);
545+
});
546+
it('does not save the gradle wrapper cache on an exact wrapper hit', async () => {
547+
createFile(join(workspace, 'build.gradle'));
548+
createStateForWrapperRestore('gradle-wrapper', true);
549+
550+
await save('gradle');
551+
expect(spyCacheSave).not.toHaveBeenCalledWith(
552+
[join(os.homedir(), '.gradle', 'wrapper')],
553+
expect.any(String)
554+
);
555+
});
454556
});
455557
describe('for sbt', () => {
456558
it('uploads cache even if no build.sbt found', async () => {
@@ -528,6 +630,29 @@ function createStateForSuccessfulRestore() {
528630
});
529631
}
530632

633+
/**
634+
* Create states to emulate a restore process where an additional (wrapper)
635+
* cache was restored. When `hit` is true the matched key equals the primary
636+
* key, emulating an exact wrapper cache hit.
637+
*/
638+
function createStateForWrapperRestore(wrapperName: string, hit: boolean) {
639+
const primaryKey = `setup-java-${wrapperName}-primary-key`;
640+
jest.spyOn(core, 'getState').mockImplementation(name => {
641+
switch (name) {
642+
case 'cache-primary-key':
643+
return 'setup-java-cache-primary-key';
644+
case 'cache-matched-key':
645+
return 'setup-java-cache-matched-key';
646+
case `cache-primary-key-${wrapperName}`:
647+
return primaryKey;
648+
case `cache-matched-key-${wrapperName}`:
649+
return hit ? primaryKey : '';
650+
default:
651+
return '';
652+
}
653+
});
654+
}
655+
531656
function createFile(path: string) {
532657
core.info(`created a file at ${path}`);
533658
fs.writeFileSync(path, '');

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.