Skip to content
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ The workflow output `cache-primary-key` exposes the primary cache key computed b

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

**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.
**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.

#### Caching gradle dependencies
Comment thread
brunoborges marked this conversation as resolved.
```yaml
Expand All @@ -180,6 +180,8 @@ steps:
```
Using the `cache: gradle` provides a simple and effective way to cache Gradle dependencies with minimal configuration.

**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.

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).

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).
Expand Down
208 changes: 196 additions & 12 deletions __tests__/cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,7 @@ describe('dependency cache', () => {

await restore('maven', '');
expect(spyCacheRestore).toHaveBeenCalledWith(
[
join(os.homedir(), '.m2', 'repository'),
join(os.homedir(), '.m2', 'wrapper', 'dists')
],
[join(os.homedir(), '.m2', 'repository')],
expect.any(String)
);
expect(spyGlobHashFiles).toHaveBeenCalledWith(
Expand All @@ -196,10 +193,7 @@ describe('dependency cache', () => {

await restore('maven', '');
expect(spyCacheRestore).toHaveBeenCalledWith(
[
join(os.homedir(), '.m2', 'repository'),
join(os.homedir(), '.m2', 'wrapper', 'dists')
],
[join(os.homedir(), '.m2', 'repository')],
expect.any(String)
);
expect(spyGlobHashFiles).toHaveBeenCalledWith(
Expand All @@ -214,10 +208,7 @@ describe('dependency cache', () => {

await restore('maven', '');
expect(spyCacheRestore).toHaveBeenCalledWith(
[
join(os.homedir(), '.m2', 'repository'),
join(os.homedir(), '.m2', 'wrapper', 'dists')
],
[join(os.homedir(), '.m2', 'repository')],
expect.any(String)
);
expect(spyGlobHashFiles).toHaveBeenCalledWith(
Expand All @@ -226,6 +217,47 @@ describe('dependency cache', () => {
expect(spyWarning).not.toHaveBeenCalled();
expect(spyInfo).toHaveBeenCalledWith('maven cache is not found');
});
it('restores the maven wrapper distribution cache independently of the main cache', async () => {
createDirectory(join(workspace, '.mvn'));
createDirectory(join(workspace, '.mvn', 'wrapper'));
createFile(
join(workspace, '.mvn', 'wrapper', 'maven-wrapper.properties')
);

await restore('maven', '');
// Main dependency cache no longer carries the wrapper dists path.
expect(spyCacheRestore).toHaveBeenCalledWith(
[join(os.homedir(), '.m2', 'repository')],
expect.any(String)
);
expect(spyCacheRestore).toHaveBeenCalledWith(
[join(os.homedir(), '.m2', 'wrapper', 'dists')],
expect.stringContaining('maven-wrapper')
);
expect(spyGlobHashFiles).toHaveBeenCalledWith(
'**/.mvn/wrapper/maven-wrapper.properties'
);
});
it('skips the maven wrapper cache when no wrapper properties exist', async () => {
createFile(join(workspace, 'pom.xml'));
spyGlobHashFiles.mockImplementation((pattern: string) =>
Promise.resolve(
pattern === '**/.mvn/wrapper/maven-wrapper.properties'
? ''
: 'hash-stub'
)
);

await restore('maven', '');
// Only the main dependency cache is restored; the wrapper cache path is
// never touched because the project does not use mvnw.
expect(spyCacheRestore).toHaveBeenCalledTimes(1);
expect(spyCacheRestore).toHaveBeenCalledWith(
[join(os.homedir(), '.m2', 'repository')],
expect.any(String)
);
expect(spyWarning).not.toHaveBeenCalled();
});
});
describe('for gradle', () => {
it('throws error if no build.gradle found', async () => {
Expand Down Expand Up @@ -282,6 +314,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 () => {
Expand Down Expand Up @@ -457,6 +526,77 @@ describe('dependency cache', () => {
expect.stringMatching(/^Cache saved with the key:.*/)
);
});
it('saves the maven wrapper distribution cache under its own key', async () => {
createFile(join(workspace, 'pom.xml'));
(core.getState as jest.Mock<any>).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-maven-wrapper':
return 'setup-java-maven-wrapper-key';
default:
return '';
}
});

await save('maven');
expect(spyCacheSave).toHaveBeenCalledWith(
[join(os.homedir(), '.m2', 'wrapper', 'dists')],
'setup-java-maven-wrapper-key'
);
expect(spyWarning).not.toHaveBeenCalled();
});
it('does not save the maven wrapper cache on an exact wrapper hit', async () => {
createFile(join(workspace, 'pom.xml'));
(core.getState as jest.Mock<any>).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-maven-wrapper':
case 'cache-matched-key-maven-wrapper':
return 'setup-java-maven-wrapper-key';
default:
return '';
}
});

await save('maven');
expect(spyCacheSave).not.toHaveBeenCalledWith(
[join(os.homedir(), '.m2', 'wrapper', 'dists')],
expect.any(String)
);
});
it('does not fail the post step when the wrapper distribution path is missing', async () => {
createFile(join(workspace, 'pom.xml'));
(core.getState as jest.Mock<any>).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-maven-wrapper':
return 'setup-java-maven-wrapper-key';
default:
return '';
}
});
spyCacheSave.mockImplementation((paths: string[]) =>
paths.includes(join(os.homedir(), '.m2', 'wrapper', 'dists'))
? Promise.reject(
new cache.ValidationError(
'Path Validation Error: Path(s) specified in the action for caching do(es) not exist'
)
)
: Promise.resolve(0)
);

await expect(save('maven')).resolves.toBeUndefined();
expect(spyWarning).not.toHaveBeenCalled();
});
});
describe('for gradle', () => {
it('uploads cache even if no build.gradle found', async () => {
Expand Down Expand Up @@ -509,6 +649,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<any>).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<any>).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 () => {
Expand Down
Loading
Loading