Skip to content

Commit a8e0c55

Browse files
committed
feat: implement cache-dependency-path option to control caching dependency
1 parent cd89f46 commit a8e0c55

File tree

7 files changed

+138
-44
lines changed

7 files changed

+138
-44
lines changed

README.md

Lines changed: 12 additions & 0 deletions
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 predifined 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

@@ -114,10 +116,13 @@ Currently, the following distributions are supported:
114116

115117
### Caching packages dependencies
116118
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:
119+
117120
- gradle: `**/*.gradle*`, `**/gradle-wrapper.properties`, `buildSrc/**/Versions.kt`, `buildSrc/**/Dependencies.kt`, `gradle/*.versions.toml`, and `**/versions.properties`
118121
- maven: `**/pom.xml`
119122
- sbt: all sbt build definition files `**/*.sbt`, `**/project/build.properties`, `**/project/**.scala`, `**/project/**.sbt`
120123

124+
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.
125+
121126
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).
122127

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

@@ -143,6 +151,7 @@ steps:
143151
distribution: 'temurin'
144152
java-version: '17'
145153
cache: 'maven'
154+
cache-dependency-path: 'sub-project/pom.xml' # optional
146155
- name: Build with Maven
147156
run: mvn -B package --file pom.xml
148157
```
@@ -156,6 +165,9 @@ steps:
156165
distribution: 'temurin'
157166
java-version: '17'
158167
cache: 'sbt'
168+
cache-dependency-path: | # optional
169+
sub-project/build.sbt
170+
sub-project/project/build.properties
159171
- name: Build with SBT
160172
run: sbt package
161173
```

__tests__/cache.test.ts

Lines changed: 74 additions & 8 deletions
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,13 +65,18 @@ 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

@@ -93,6 +99,7 @@ describe('dependency cache', () => {
9399

94100
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
});
@@ -110,6 +117,9 @@ describe('dependency cache', () => {
110117

111118
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
});
@@ -118,6 +128,9 @@ describe('dependency cache', () => {
118128

119129
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
});
@@ -127,18 +140,24 @@ describe('dependency cache', () => {
127140

128141
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 () => {
@@ -153,6 +172,9 @@ describe('dependency cache', () => {
153172

154173
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
});
@@ -184,9 +206,53 @@ describe('dependency cache', () => {
184206

185207
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<

dist/cleanup/index.js

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66672,28 +66672,31 @@ function findPackageManager(id) {
6667266672
/**
6667366673
* A function that generates a cache key to use.
6667466674
* Format of the generated key will be "${{ platform }}-${{ id }}-${{ fileHash }}"".
66675-
* If there is no file matched to {@link PackageManager.path}, the generated key ends with a dash (-).
6667666675
* @see {@link https://docs.github.com/en/actions/guides/caching-dependencies-to-speed-up-workflows#matching-a-cache-key|spec of cache key}
6667766676
*/
66678-
function computeCacheKey(packageManager) {
66677+
function computeCacheKey(packageManager, cacheDependencyPath) {
6667966678
return __awaiter(this, void 0, void 0, function* () {
66680-
const hash = yield glob.hashFiles(packageManager.pattern.join('\n'));
66681-
return `${CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${packageManager.id}-${hash}`;
66679+
const pattern = cacheDependencyPath
66680+
? cacheDependencyPath.trim().split('\n')
66681+
: packageManager.pattern;
66682+
const fileHash = yield glob.hashFiles(pattern.join('\n'));
66683+
if (!fileHash) {
66684+
throw new Error(`No file in ${process.cwd()} matched to [${pattern}], make sure you have checked out the target repository`);
66685+
}
66686+
return `${CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${packageManager.id}-${fileHash}`;
6668266687
});
6668366688
}
6668466689
/**
6668566690
* Restore the dependency cache
6668666691
* @param id ID of the package manager, should be "maven" or "gradle"
66692+
* @param cacheDependencyPath The path to a dependency file
6668766693
*/
66688-
function restore(id) {
66694+
function restore(id, cacheDependencyPath) {
6668966695
return __awaiter(this, void 0, void 0, function* () {
6669066696
const packageManager = findPackageManager(id);
66691-
const primaryKey = yield computeCacheKey(packageManager);
66697+
const primaryKey = yield computeCacheKey(packageManager, cacheDependencyPath);
6669266698
core.debug(`primary key is ${primaryKey}`);
6669366699
core.saveState(STATE_CACHE_PRIMARY_KEY, primaryKey);
66694-
if (primaryKey.endsWith('-')) {
66695-
throw new Error(`No file in ${process.cwd()} matched to [${packageManager.pattern}], make sure you have checked out the target repository`);
66696-
}
6669766700
// No "restoreKeys" is set, to start with a clear cache after dependency update (see https://github.com/actions/setup-java/issues/269)
6669866701
const matchedKey = yield cache.restoreCache(packageManager.path, primaryKey);
6669966702
if (matchedKey) {
@@ -66870,7 +66873,7 @@ else {
6687066873
"use strict";
6687166874

6687266875
Object.defineProperty(exports, "__esModule", ({ value: true }));
66873-
exports.DISTRIBUTIONS_ONLY_MAJOR_VERSION = exports.INPUT_MVN_TOOLCHAIN_VENDOR = exports.INPUT_MVN_TOOLCHAIN_ID = exports.MVN_TOOLCHAINS_FILE = exports.MVN_SETTINGS_FILE = exports.M2_DIR = exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = exports.INPUT_JOB_STATUS = exports.INPUT_CACHE = exports.INPUT_DEFAULT_GPG_PASSPHRASE = exports.INPUT_DEFAULT_GPG_PRIVATE_KEY = exports.INPUT_GPG_PASSPHRASE = exports.INPUT_GPG_PRIVATE_KEY = exports.INPUT_OVERWRITE_SETTINGS = exports.INPUT_SETTINGS_PATH = exports.INPUT_SERVER_PASSWORD = exports.INPUT_SERVER_USERNAME = exports.INPUT_SERVER_ID = exports.INPUT_CHECK_LATEST = exports.INPUT_JDK_FILE = exports.INPUT_DISTRIBUTION = exports.INPUT_JAVA_PACKAGE = exports.INPUT_ARCHITECTURE = exports.INPUT_JAVA_VERSION_FILE = exports.INPUT_JAVA_VERSION = exports.MACOS_JAVA_CONTENT_POSTFIX = void 0;
66876+
exports.DISTRIBUTIONS_ONLY_MAJOR_VERSION = exports.INPUT_MVN_TOOLCHAIN_VENDOR = exports.INPUT_MVN_TOOLCHAIN_ID = exports.MVN_TOOLCHAINS_FILE = exports.MVN_SETTINGS_FILE = exports.M2_DIR = exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = exports.INPUT_JOB_STATUS = exports.INPUT_CACHE_DEPENDENCY_PATH = exports.INPUT_CACHE = exports.INPUT_DEFAULT_GPG_PASSPHRASE = exports.INPUT_DEFAULT_GPG_PRIVATE_KEY = exports.INPUT_GPG_PASSPHRASE = exports.INPUT_GPG_PRIVATE_KEY = exports.INPUT_OVERWRITE_SETTINGS = exports.INPUT_SETTINGS_PATH = exports.INPUT_SERVER_PASSWORD = exports.INPUT_SERVER_USERNAME = exports.INPUT_SERVER_ID = exports.INPUT_CHECK_LATEST = exports.INPUT_JDK_FILE = exports.INPUT_DISTRIBUTION = exports.INPUT_JAVA_PACKAGE = exports.INPUT_ARCHITECTURE = exports.INPUT_JAVA_VERSION_FILE = exports.INPUT_JAVA_VERSION = exports.MACOS_JAVA_CONTENT_POSTFIX = void 0;
6687466877
exports.MACOS_JAVA_CONTENT_POSTFIX = 'Contents/Home';
6687566878
exports.INPUT_JAVA_VERSION = 'java-version';
6687666879
exports.INPUT_JAVA_VERSION_FILE = 'java-version-file';
@@ -66889,6 +66892,7 @@ exports.INPUT_GPG_PASSPHRASE = 'gpg-passphrase';
6688966892
exports.INPUT_DEFAULT_GPG_PRIVATE_KEY = undefined;
6689066893
exports.INPUT_DEFAULT_GPG_PASSPHRASE = 'GPG_PASSPHRASE';
6689166894
exports.INPUT_CACHE = 'cache';
66895+
exports.INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path';
6689266896
exports.INPUT_JOB_STATUS = 'job-status';
6689366897
exports.STATE_GPG_PRIVATE_KEY_FINGERPRINT = 'gpg-private-key-fingerprint';
6689466898
exports.M2_DIR = '.m2';

0 commit comments

Comments
 (0)