Skip to content

Commit 9eda6b5

Browse files
authored
feat: implement cache-dependency-path option to control caching dependency (#499)
1 parent 78078da commit 9eda6b5

14 files changed

+277
-58
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
name: Validate cache with cache-dependency-path option
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- releases/*
8+
paths-ignore:
9+
- '**.md'
10+
pull_request:
11+
paths-ignore:
12+
- '**.md'
13+
14+
defaults:
15+
run:
16+
shell: bash
17+
18+
jobs:
19+
gradle1-save:
20+
runs-on: ${{ matrix.os }}
21+
strategy:
22+
fail-fast: false
23+
matrix:
24+
os: [macos-latest, windows-latest, ubuntu-latest]
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v3
28+
- name: Run setup-java with the cache for gradle
29+
uses: ./
30+
id: setup-java
31+
with:
32+
distribution: 'adopt'
33+
java-version: '11'
34+
cache: gradle
35+
cache-dependency-path: __tests__/cache/gradle1/*.gradle*
36+
- name: Create files to cache
37+
# Need to avoid using Gradle daemon to stabilize the save process on Windows
38+
# https://github.com/actions/cache/issues/454#issuecomment-840493935
39+
run: |
40+
gradle downloadDependencies --no-daemon -p __tests__/cache/gradle1
41+
if [ ! -d ~/.gradle/caches ]; then
42+
echo "::error::The ~/.gradle/caches directory does not exist unexpectedly"
43+
exit 1
44+
fi
45+
gradle1-restore:
46+
runs-on: ${{ matrix.os }}
47+
strategy:
48+
fail-fast: false
49+
matrix:
50+
os: [macos-latest, windows-latest, ubuntu-latest]
51+
needs: gradle1-save
52+
steps:
53+
- name: Checkout
54+
uses: actions/checkout@v3
55+
- name: Run setup-java with the cache for gradle
56+
uses: ./
57+
id: setup-java
58+
with:
59+
distribution: 'adopt'
60+
java-version: '11'
61+
cache: gradle
62+
cache-dependency-path: __tests__/cache/gradle1/*.gradle*
63+
- name: Confirm that ~/.gradle/caches directory has been made
64+
run: |
65+
if [ ! -d ~/.gradle/caches ]; then
66+
echo "::error::The ~/.gradle/caches directory does not exist unexpectedly"
67+
exit 1
68+
fi
69+
ls ~/.gradle/caches/
70+
gradle2-restore:
71+
runs-on: ${{ matrix.os }}
72+
strategy:
73+
fail-fast: false
74+
matrix:
75+
os: [macos-latest, windows-latest, ubuntu-latest]
76+
needs: gradle1-save
77+
steps:
78+
- name: Checkout
79+
uses: actions/checkout@v3
80+
- name: Run setup-java with the cache for gradle
81+
uses: ./
82+
id: setup-java
83+
with:
84+
distribution: 'adopt'
85+
java-version: '11'
86+
cache: gradle
87+
cache-dependency-path: __tests__/cache/gradle2/*.gradle*
88+
- name: Confirm that ~/.gradle/caches directory has not been made
89+
run: |
90+
if [ -d ~/.gradle/caches ]; then
91+
echo "::error::The ~/.gradle/caches directory exists unexpectedly"
92+
exit 1
93+
fi

.github/workflows/e2e-cache.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
# Need to avoid using Gradle daemon to stabilize the save process on Windows
3737
# https://github.com/actions/cache/issues/454#issuecomment-840493935
3838
run: |
39-
gradle downloadDependencies --no-daemon -p __tests__/cache/gradle
39+
gradle downloadDependencies --no-daemon -p __tests__/cache/gradle1
4040
if [ ! -d ~/.gradle/caches ]; then
4141
echo "::error::The ~/.gradle/caches directory does not exist unexpectedly"
4242
exit 1

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ This action allows you to work with Java and Scala projects.
4141

4242
- `cache`: Quick [setup caching](#caching-packages-dependencies) for the dependencies managed through one of the predefined package managers. It can be one of "maven", "gradle" or "sbt".
4343

44+
- `cache-dependency-path`: The path to a dependency file: pom.xml, build.gradle, build.sbt, etc. This option can be used with the `cache` option. If this option is omitted, the action searches for the dependency file in the entire repository. This option supports wildcards and a list of file names for caching multiple dependencies.
45+
4446
#### Maven options
4547
The action has a bunch of inputs to generate maven's [settings.xml](https://maven.apache.org/settings.html) on the fly and pass the values to Apache Maven GPG Plugin as well as Apache Maven Toolchains. See [advanced usage](docs/advanced-usage.md) for more.
4648

@@ -115,10 +117,13 @@ Currently, the following distributions are supported:
115117

116118
### Caching packages dependencies
117119
The action has a built-in functionality for caching and restoring dependencies. It uses [toolkit/cache](https://github.com/actions/toolkit/tree/main/packages/cache) under hood for caching dependencies but requires less configuration settings. Supported package managers are gradle, maven and sbt. The format of the used cache key is `setup-java-${{ platform }}-${{ packageManager }}-${{ fileHash }}`, where the hash is based on the following files:
120+
118121
- gradle: `**/*.gradle*`, `**/gradle-wrapper.properties`, `buildSrc/**/Versions.kt`, `buildSrc/**/Dependencies.kt`, `gradle/*.versions.toml`, and `**/versions.properties`
119122
- maven: `**/pom.xml`
120123
- sbt: all sbt build definition files `**/*.sbt`, `**/project/build.properties`, `**/project/**.scala`, `**/project/**.sbt`
121124

125+
When the option `cache-dependency-path` is specified, the hash is based on the matching file. This option supports wildcards and a list of file names, and is especially useful for monorepos.
126+
122127
The workflow output `cache-hit` is set to indicate if an exact match was found for the key [as actions/cache does](https://github.com/actions/cache/tree/main#outputs).
123128

124129
The cache input is optional, and caching is turned off by default.
@@ -132,6 +137,9 @@ steps:
132137
distribution: 'temurin'
133138
java-version: '17'
134139
cache: 'gradle'
140+
cache-dependency-path: | # optional
141+
sub-project/*.gradle*
142+
sub-project/**/gradle-wrapper.properties
135143
- run: ./gradlew build --no-daemon
136144
```
137145

@@ -144,6 +152,7 @@ steps:
144152
distribution: 'temurin'
145153
java-version: '17'
146154
cache: 'maven'
155+
cache-dependency-path: 'sub-project/pom.xml' # optional
147156
- name: Build with Maven
148157
run: mvn -B package --file pom.xml
149158
```
@@ -157,6 +166,9 @@ steps:
157166
distribution: 'temurin'
158167
java-version: '17'
159168
cache: 'sbt'
169+
cache-dependency-path: | # optional
170+
sub-project/build.sbt
171+
sub-project/project/build.properties
160172
- name: Build with SBT
161173
run: sbt package
162174
```

__tests__/cache.test.ts

+87-21
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as fs from 'fs';
66
import * as os from 'os';
77
import * as core from '@actions/core';
88
import * as cache from '@actions/cache';
9+
import * as glob from '@actions/glob';
910

1011
describe('dependency cache', () => {
1112
const ORIGINAL_RUNNER_OS = process.env['RUNNER_OS'];
@@ -64,25 +65,30 @@ describe('dependency cache', () => {
6465
ReturnType<typeof cache.restoreCache>,
6566
Parameters<typeof cache.restoreCache>
6667
>;
68+
let spyGlobHashFiles: jest.SpyInstance<
69+
ReturnType<typeof glob.hashFiles>,
70+
Parameters<typeof glob.hashFiles>
71+
>;
6772

6873
beforeEach(() => {
6974
spyCacheRestore = jest
7075
.spyOn(cache, 'restoreCache')
7176
.mockImplementation((paths: string[], primaryKey: string) =>
7277
Promise.resolve(undefined)
7378
);
79+
spyGlobHashFiles = jest.spyOn(glob, 'hashFiles');
7480
spyWarning.mockImplementation(() => null);
7581
});
7682

7783
it('throws error if unsupported package manager specified', () => {
78-
return expect(restore('ant')).rejects.toThrow(
84+
return expect(restore('ant', '')).rejects.toThrow(
7985
'unknown package manager specified: ant'
8086
);
8187
});
8288

8389
describe('for maven', () => {
8490
it('throws error if no pom.xml found', async () => {
85-
await expect(restore('maven')).rejects.toThrow(
91+
await expect(restore('maven', '')).rejects.toThrow(
8692
`No file in ${projectRoot(
8793
workspace
8894
)} matched to [**/pom.xml], make sure you have checked out the target repository`
@@ -91,15 +97,16 @@ describe('dependency cache', () => {
9197
it('downloads cache', async () => {
9298
createFile(join(workspace, 'pom.xml'));
9399

94-
await restore('maven');
100+
await restore('maven', '');
95101
expect(spyCacheRestore).toHaveBeenCalled();
102+
expect(spyGlobHashFiles).toHaveBeenCalledWith('**/pom.xml');
96103
expect(spyWarning).not.toHaveBeenCalled();
97104
expect(spyInfo).toHaveBeenCalledWith('maven cache is not found');
98105
});
99106
});
100107
describe('for gradle', () => {
101108
it('throws error if no build.gradle found', async () => {
102-
await expect(restore('gradle')).rejects.toThrow(
109+
await expect(restore('gradle', '')).rejects.toThrow(
103110
`No file in ${projectRoot(
104111
workspace
105112
)} matched to [**/*.gradle*,**/gradle-wrapper.properties,buildSrc/**/Versions.kt,buildSrc/**/Dependencies.kt,gradle/*.versions.toml,**/versions.properties], make sure you have checked out the target repository`
@@ -108,41 +115,53 @@ describe('dependency cache', () => {
108115
it('downloads cache based on build.gradle', async () => {
109116
createFile(join(workspace, 'build.gradle'));
110117

111-
await restore('gradle');
118+
await restore('gradle', '');
112119
expect(spyCacheRestore).toHaveBeenCalled();
120+
expect(spyGlobHashFiles).toHaveBeenCalledWith(
121+
'**/*.gradle*\n**/gradle-wrapper.properties\nbuildSrc/**/Versions.kt\nbuildSrc/**/Dependencies.kt\ngradle/*.versions.toml\n**/versions.properties'
122+
);
113123
expect(spyWarning).not.toHaveBeenCalled();
114124
expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found');
115125
});
116126
it('downloads cache based on build.gradle.kts', async () => {
117127
createFile(join(workspace, 'build.gradle.kts'));
118128

119-
await restore('gradle');
129+
await restore('gradle', '');
120130
expect(spyCacheRestore).toHaveBeenCalled();
131+
expect(spyGlobHashFiles).toHaveBeenCalledWith(
132+
'**/*.gradle*\n**/gradle-wrapper.properties\nbuildSrc/**/Versions.kt\nbuildSrc/**/Dependencies.kt\ngradle/*.versions.toml\n**/versions.properties'
133+
);
121134
expect(spyWarning).not.toHaveBeenCalled();
122135
expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found');
123136
});
124137
it('downloads cache based on libs.versions.toml', async () => {
125138
createDirectory(join(workspace, 'gradle'));
126139
createFile(join(workspace, 'gradle', 'libs.versions.toml'));
127140

128-
await restore('gradle');
141+
await restore('gradle', '');
129142
expect(spyCacheRestore).toHaveBeenCalled();
143+
expect(spyGlobHashFiles).toHaveBeenCalledWith(
144+
'**/*.gradle*\n**/gradle-wrapper.properties\nbuildSrc/**/Versions.kt\nbuildSrc/**/Dependencies.kt\ngradle/*.versions.toml\n**/versions.properties'
145+
);
130146
expect(spyWarning).not.toHaveBeenCalled();
131147
expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found');
132148
});
133-
});
134-
it('downloads cache based on buildSrc/Versions.kt', async () => {
135-
createDirectory(join(workspace, 'buildSrc'));
136-
createFile(join(workspace, 'buildSrc', 'Versions.kt'));
149+
it('downloads cache based on buildSrc/Versions.kt', async () => {
150+
createDirectory(join(workspace, 'buildSrc'));
151+
createFile(join(workspace, 'buildSrc', 'Versions.kt'));
137152

138-
await restore('gradle');
139-
expect(spyCacheRestore).toHaveBeenCalled();
140-
expect(spyWarning).not.toHaveBeenCalled();
141-
expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found');
153+
await restore('gradle', '');
154+
expect(spyCacheRestore).toHaveBeenCalled();
155+
expect(spyGlobHashFiles).toHaveBeenCalledWith(
156+
'**/*.gradle*\n**/gradle-wrapper.properties\nbuildSrc/**/Versions.kt\nbuildSrc/**/Dependencies.kt\ngradle/*.versions.toml\n**/versions.properties'
157+
);
158+
expect(spyWarning).not.toHaveBeenCalled();
159+
expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found');
160+
});
142161
});
143162
describe('for sbt', () => {
144163
it('throws error if no build.sbt found', async () => {
145-
await expect(restore('sbt')).rejects.toThrow(
164+
await expect(restore('sbt', '')).rejects.toThrow(
146165
`No file in ${projectRoot(
147166
workspace
148167
)} matched to [**/*.sbt,**/project/build.properties,**/project/**.scala,**/project/**.sbt], make sure you have checked out the target repository`
@@ -151,8 +170,11 @@ describe('dependency cache', () => {
151170
it('downloads cache', async () => {
152171
createFile(join(workspace, 'build.sbt'));
153172

154-
await restore('sbt');
173+
await restore('sbt', '');
155174
expect(spyCacheRestore).toHaveBeenCalled();
175+
expect(spyGlobHashFiles).toHaveBeenCalledWith(
176+
'**/*.sbt\n**/project/build.properties\n**/project/**.scala\n**/project/**.sbt'
177+
);
156178
expect(spyWarning).not.toHaveBeenCalled();
157179
expect(spyInfo).toHaveBeenCalledWith('sbt cache is not found');
158180
});
@@ -161,19 +183,19 @@ describe('dependency cache', () => {
161183
createDirectory(join(workspace, 'project'));
162184
createFile(join(workspace, 'project/DependenciesV1.scala'));
163185

164-
await restore('sbt');
186+
await restore('sbt', '');
165187
const firstCall = spySaveState.mock.calls.toString();
166188

167189
spySaveState.mockClear();
168-
await restore('sbt');
190+
await restore('sbt', '');
169191
const secondCall = spySaveState.mock.calls.toString();
170192

171193
// Make sure multiple restores produce the same cache
172194
expect(firstCall).toBe(secondCall);
173195

174196
spySaveState.mockClear();
175197
createFile(join(workspace, 'project/DependenciesV2.scala'));
176-
await restore('sbt');
198+
await restore('sbt', '');
177199
const thirdCall = spySaveState.mock.calls.toString();
178200

179201
expect(firstCall).not.toBe(thirdCall);
@@ -182,11 +204,55 @@ describe('dependency cache', () => {
182204
it('downloads cache based on versions.properties', async () => {
183205
createFile(join(workspace, 'versions.properties'));
184206

185-
await restore('gradle');
207+
await restore('gradle', '');
186208
expect(spyCacheRestore).toHaveBeenCalled();
209+
expect(spyGlobHashFiles).toHaveBeenCalledWith(
210+
'**/*.gradle*\n**/gradle-wrapper.properties\nbuildSrc/**/Versions.kt\nbuildSrc/**/Dependencies.kt\ngradle/*.versions.toml\n**/versions.properties'
211+
);
187212
expect(spyWarning).not.toHaveBeenCalled();
188213
expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found');
189214
});
215+
describe('cache-dependency-path', () => {
216+
it('throws error if no matching dependency file found', async () => {
217+
createFile(join(workspace, 'build.gradle.kts'));
218+
await expect(
219+
restore('gradle', 'sub-project/**/build.gradle.kts')
220+
).rejects.toThrow(
221+
`No file in ${projectRoot(
222+
workspace
223+
)} matched to [sub-project/**/build.gradle.kts], make sure you have checked out the target repository`
224+
);
225+
});
226+
it('downloads cache based on the specified pattern', async () => {
227+
createFile(join(workspace, 'build.gradle.kts'));
228+
createDirectory(join(workspace, 'sub-project1'));
229+
createFile(join(workspace, 'sub-project1', 'build.gradle.kts'));
230+
createDirectory(join(workspace, 'sub-project2'));
231+
createFile(join(workspace, 'sub-project2', 'build.gradle.kts'));
232+
233+
await restore('gradle', 'build.gradle.kts');
234+
expect(spyCacheRestore).toHaveBeenCalled();
235+
expect(spyGlobHashFiles).toHaveBeenCalledWith('build.gradle.kts');
236+
expect(spyWarning).not.toHaveBeenCalled();
237+
expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found');
238+
239+
await restore('gradle', 'sub-project1/**/*.gradle*\n');
240+
expect(spyCacheRestore).toHaveBeenCalled();
241+
expect(spyGlobHashFiles).toHaveBeenCalledWith(
242+
'sub-project1/**/*.gradle*'
243+
);
244+
expect(spyWarning).not.toHaveBeenCalled();
245+
expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found');
246+
247+
await restore('gradle', '*.gradle*\nsub-project2/**/*.gradle*\n');
248+
expect(spyCacheRestore).toHaveBeenCalled();
249+
expect(spyGlobHashFiles).toHaveBeenCalledWith(
250+
'*.gradle*\nsub-project2/**/*.gradle*'
251+
);
252+
expect(spyWarning).not.toHaveBeenCalled();
253+
expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found');
254+
});
255+
});
190256
});
191257
describe('save', () => {
192258
let spyCacheSave: jest.SpyInstance<
File renamed without changes.
File renamed without changes.

__tests__/cache/gradle2/.gitignore

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.gradle
2+
**/build/
3+
!src/**/build/
4+
5+
# Ignore Gradle GUI config
6+
gradle-app.setting
7+
8+
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
9+
!gradle-wrapper.jar
10+
11+
# Cache of project
12+
.gradletasknamecache

0 commit comments

Comments
 (0)