Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
3c7576693f chore(deps): bump actions/checkout from 6 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-13 03:43:27 +00:00
7 changed files with 43 additions and 634 deletions

View File

@@ -680,7 +680,7 @@ jobs:
os: [macos-latest, windows-latest, ubuntu-latest] os: [macos-latest, windows-latest, ubuntu-latest]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Setup Java 17 as default - name: Setup Java 17 as default
uses: ./ uses: ./
id: setup-java-17 id: setup-java-17

View File

@@ -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. 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 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. **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.
#### Caching gradle dependencies #### Caching gradle dependencies
```yaml ```yaml
@@ -180,8 +180,6 @@ steps:
``` ```
Using the `cache: gradle` provides a simple and effective way to cache Gradle dependencies with minimal configuration. 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 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). 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).

View File

@@ -166,7 +166,10 @@ describe('dependency cache', () => {
await restore('maven', ''); await restore('maven', '');
expect(spyCacheRestore).toHaveBeenCalledWith( expect(spyCacheRestore).toHaveBeenCalledWith(
[join(os.homedir(), '.m2', 'repository')], [
join(os.homedir(), '.m2', 'repository'),
join(os.homedir(), '.m2', 'wrapper', 'dists')
],
expect.any(String) expect.any(String)
); );
expect(spyGlobHashFiles).toHaveBeenCalledWith( expect(spyGlobHashFiles).toHaveBeenCalledWith(
@@ -193,7 +196,10 @@ describe('dependency cache', () => {
await restore('maven', ''); await restore('maven', '');
expect(spyCacheRestore).toHaveBeenCalledWith( expect(spyCacheRestore).toHaveBeenCalledWith(
[join(os.homedir(), '.m2', 'repository')], [
join(os.homedir(), '.m2', 'repository'),
join(os.homedir(), '.m2', 'wrapper', 'dists')
],
expect.any(String) expect.any(String)
); );
expect(spyGlobHashFiles).toHaveBeenCalledWith( expect(spyGlobHashFiles).toHaveBeenCalledWith(
@@ -208,7 +214,10 @@ describe('dependency cache', () => {
await restore('maven', ''); await restore('maven', '');
expect(spyCacheRestore).toHaveBeenCalledWith( expect(spyCacheRestore).toHaveBeenCalledWith(
[join(os.homedir(), '.m2', 'repository')], [
join(os.homedir(), '.m2', 'repository'),
join(os.homedir(), '.m2', 'wrapper', 'dists')
],
expect.any(String) expect.any(String)
); );
expect(spyGlobHashFiles).toHaveBeenCalledWith( expect(spyGlobHashFiles).toHaveBeenCalledWith(
@@ -217,47 +226,6 @@ describe('dependency cache', () => {
expect(spyWarning).not.toHaveBeenCalled(); expect(spyWarning).not.toHaveBeenCalled();
expect(spyInfo).toHaveBeenCalledWith('maven cache is not found'); 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', () => { describe('for gradle', () => {
it('throws error if no build.gradle found', async () => { it('throws error if no build.gradle found', async () => {
@@ -314,43 +282,6 @@ describe('dependency cache', () => {
expect(spyWarning).not.toHaveBeenCalled(); expect(spyWarning).not.toHaveBeenCalled();
expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found'); 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', () => { describe('for sbt', () => {
it('throws error if no build.sbt found', async () => { it('throws error if no build.sbt found', async () => {
@@ -526,77 +457,6 @@ describe('dependency cache', () => {
expect.stringMatching(/^Cache saved with the key:.*/) 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', () => { describe('for gradle', () => {
it('uploads cache even if no build.gradle found', async () => { it('uploads cache even if no build.gradle found', async () => {
@@ -649,50 +509,6 @@ describe('dependency cache', () => {
expect.stringMatching(/^Cache saved with the key:.*/) 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', () => { describe('for sbt', () => {
it('uploads cache even if no build.sbt found', async () => { it('uploads cache even if no build.sbt found', async () => {

129
dist/cleanup/index.js vendored
View File

@@ -99666,28 +99666,23 @@ const CACHE_KEY_PREFIX = 'setup-java';
const supportedPackageManager = [ const supportedPackageManager = [
{ {
id: 'maven', id: 'maven',
path: [(0,external_path_.join)(external_os_default().homedir(), '.m2', 'repository')], path: [
(0,external_path_.join)(external_os_default().homedir(), '.m2', 'repository'),
(0,external_path_.join)(external_os_default().homedir(), '.m2', 'wrapper', 'dists')
],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven // https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven
pattern: [ pattern: [
'**/pom.xml', '**/pom.xml',
'**/.mvn/wrapper/maven-wrapper.properties', '**/.mvn/wrapper/maven-wrapper.properties',
'**/.mvn/extensions.xml' '**/.mvn/extensions.xml'
],
// The Maven wrapper distribution only depends on the wrapper properties,
// which change very rarely, so it is cached separately from the local
// repository. This keeps it available across the frequent pom.xml changes
// that rotate the main cache key. See issue #1095.
additionalCaches: [
{
name: 'maven-wrapper',
path: [(0,external_path_.join)(external_os_default().homedir(), '.m2', 'wrapper', 'dists')],
pattern: ['**/.mvn/wrapper/maven-wrapper.properties']
}
] ]
}, },
{ {
id: 'gradle', id: 'gradle',
path: [(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'caches')], path: [
(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'caches'),
(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'wrapper')
],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle // https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle
pattern: [ pattern: [
'**/*.gradle*', '**/*.gradle*',
@@ -99696,17 +99691,6 @@ const supportedPackageManager = [
'buildSrc/**/Dependencies.kt', 'buildSrc/**/Dependencies.kt',
'gradle/*.versions.toml', 'gradle/*.versions.toml',
'**/versions.properties' '**/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 #269.
additionalCaches: [
{
name: 'gradle-wrapper',
path: [(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'wrapper')],
pattern: ['**/gradle-wrapper.properties']
}
] ]
}, },
{ {
@@ -99742,19 +99726,6 @@ function findPackageManager(id) {
} }
return packageManager; return packageManager;
} }
/**
* State keys used to carry an additional cache's restore-time information over
* to the post (save) action, scoped by the additional cache name.
*/
function additionalCachePrimaryKeyState(name) {
return `${STATE_CACHE_PRIMARY_KEY}-${name}`;
}
function additionalCacheMatchedKeyState(name) {
return `${CACHE_MATCHED_KEY}-${name}`;
}
function buildCacheKey(id, fileHash) {
return `${CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-${id}-${fileHash}`;
}
/** /**
* A function that generates a cache key to use. * A function that generates a cache key to use.
* Format of the generated key will be "${{ platform }}-${{ id }}-${{ fileHash }}"". * Format of the generated key will be "${{ platform }}-${{ id }}-${{ fileHash }}"".
@@ -99768,19 +99739,7 @@ async function computeCacheKey(packageManager, cacheDependencyPath) {
if (!fileHash) { if (!fileHash) {
throw new Error(`No file in ${process.cwd()} matched to [${pattern}], make sure you have checked out the target repository`); throw new Error(`No file in ${process.cwd()} matched to [${pattern}], make sure you have checked out the target repository`);
} }
return buildCacheKey(packageManager.id, fileHash); return `${CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-${packageManager.id}-${fileHash}`;
}
/**
* Computes the cache key for an additional cache. Unlike {@link computeCacheKey}
* this returns undefined (instead of throwing) when no file matches the pattern,
* because additional caches are optional features that many projects do not use.
*/
async function computeAdditionalCacheKey(additionalCache) {
const fileHash = await glob.hashFiles(additionalCache.pattern.join('\n'));
if (!fileHash) {
return undefined;
}
return buildCacheKey(additionalCache.name, fileHash);
} }
/** /**
* Restore the dependency cache * Restore the dependency cache
@@ -99804,29 +99763,6 @@ async function restore(id, cacheDependencyPath) {
core.setOutput('cache-hit', false); core.setOutput('cache-hit', false);
core.info(`${packageManager.id} cache is not found`); core.info(`${packageManager.id} cache is not found`);
} }
for (const additionalCache of packageManager.additionalCaches ?? []) {
await restoreAdditionalCache(additionalCache);
}
}
/**
* Restore an additional cache (e.g. a build-tool wrapper distribution) that is
* keyed independently of the main dependency cache so that it survives changes
* to volatile dependency files. Skips silently when the project does not use
* the corresponding feature.
*/
async function restoreAdditionalCache(additionalCache) {
const primaryKey = await computeAdditionalCacheKey(additionalCache);
if (!primaryKey) {
core.debug(`No file matched [${additionalCache.pattern}] for the ${additionalCache.name} cache, skipping.`);
return;
}
core.debug(`${additionalCache.name} primary key is ${primaryKey}`);
core.saveState(additionalCachePrimaryKeyState(additionalCache.name), primaryKey);
const matchedKey = await cache.restoreCache(additionalCache.path, primaryKey);
if (matchedKey) {
core.saveState(additionalCacheMatchedKeyState(additionalCache.name), matchedKey);
core.info(`${additionalCache.name} cache restored from key: ${matchedKey}`);
}
} }
/** /**
* Save the dependency cache * Save the dependency cache
@@ -99837,9 +99773,6 @@ async function save(id) {
const matchedKey = getState(CACHE_MATCHED_KEY); const matchedKey = getState(CACHE_MATCHED_KEY);
// Inputs are re-evaluated before the post action, so we want the original key used for restore // Inputs are re-evaluated before the post action, so we want the original key used for restore
const primaryKey = getState(STATE_CACHE_PRIMARY_KEY); const primaryKey = getState(STATE_CACHE_PRIMARY_KEY);
for (const additionalCache of packageManager.additionalCaches ?? []) {
await saveAdditionalCache(packageManager, additionalCache);
}
if (!primaryKey) { if (!primaryKey) {
warning('Error retrieving key from state.'); warning('Error retrieving key from state.');
return; return;
@@ -99874,50 +99807,6 @@ async function save(id) {
} }
} }
} }
/**
* Save an additional cache under its own key. Skips when no key was recorded at
* restore time (feature unused) or when the exact key was already restored.
*/
async function saveAdditionalCache(packageManager, additionalCache) {
const primaryKey = getState(additionalCachePrimaryKeyState(additionalCache.name));
const matchedKey = getState(additionalCacheMatchedKeyState(additionalCache.name));
if (!primaryKey) {
// The feature is not used by this project, nothing to save.
core_debug(`No primary key for the ${additionalCache.name} cache, not saving cache.`);
return;
}
else if (matchedKey === primaryKey) {
info(`Cache hit occurred on the ${additionalCache.name} primary key ${primaryKey}, not saving cache.`);
return;
}
try {
const cacheId = await cache_saveCache(additionalCache.path, primaryKey);
if (cacheId === -1) {
core_debug(`${additionalCache.name} cache was not saved for the key: ${primaryKey}`);
return;
}
info(`${additionalCache.name} cache saved with the key: ${primaryKey}`);
}
catch (error) {
const err = error;
if (err.name === ValidationError.name) {
// The cache paths did not resolve, e.g. the wrapper distribution was
// never downloaded because a system build tool was used or the download
// failed. Optional wrapper caches must not fail the post step, so skip.
core_debug(`${additionalCache.name} cache paths do not exist, not saving cache: ${err.message}`);
return;
}
if (err.name === ReserveCacheError.name) {
info(err.message);
}
else {
if (isProbablyGradleDaemonProblem(packageManager, err)) {
warning('Failed to save Gradle cache on Windows. If tar.exe reported "Permission denied", try to run Gradle with `--no-daemon` option. Refer to https://github.com/actions/cache/issues/454 for details.');
}
throw error;
}
}
}
/** /**
* @param packageManager the specified package manager by user * @param packageManager the specified package manager by user
* @param error the error thrown by the saveCache * @param error the error thrown by the saveCache

129
dist/setup/index.js vendored
View File

@@ -130888,28 +130888,23 @@ const CACHE_KEY_PREFIX = 'setup-java';
const supportedPackageManager = [ const supportedPackageManager = [
{ {
id: 'maven', id: 'maven',
path: [(0,external_path_.join)(external_os_default().homedir(), '.m2', 'repository')], path: [
(0,external_path_.join)(external_os_default().homedir(), '.m2', 'repository'),
(0,external_path_.join)(external_os_default().homedir(), '.m2', 'wrapper', 'dists')
],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven // https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven
pattern: [ pattern: [
'**/pom.xml', '**/pom.xml',
'**/.mvn/wrapper/maven-wrapper.properties', '**/.mvn/wrapper/maven-wrapper.properties',
'**/.mvn/extensions.xml' '**/.mvn/extensions.xml'
],
// The Maven wrapper distribution only depends on the wrapper properties,
// which change very rarely, so it is cached separately from the local
// repository. This keeps it available across the frequent pom.xml changes
// that rotate the main cache key. See issue #1095.
additionalCaches: [
{
name: 'maven-wrapper',
path: [(0,external_path_.join)(external_os_default().homedir(), '.m2', 'wrapper', 'dists')],
pattern: ['**/.mvn/wrapper/maven-wrapper.properties']
}
] ]
}, },
{ {
id: 'gradle', id: 'gradle',
path: [(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'caches')], path: [
(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'caches'),
(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'wrapper')
],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle // https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle
pattern: [ pattern: [
'**/*.gradle*', '**/*.gradle*',
@@ -130918,17 +130913,6 @@ const supportedPackageManager = [
'buildSrc/**/Dependencies.kt', 'buildSrc/**/Dependencies.kt',
'gradle/*.versions.toml', 'gradle/*.versions.toml',
'**/versions.properties' '**/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 #269.
additionalCaches: [
{
name: 'gradle-wrapper',
path: [(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'wrapper')],
pattern: ['**/gradle-wrapper.properties']
}
] ]
}, },
{ {
@@ -130964,19 +130948,6 @@ function findPackageManager(id) {
} }
return packageManager; return packageManager;
} }
/**
* State keys used to carry an additional cache's restore-time information over
* to the post (save) action, scoped by the additional cache name.
*/
function additionalCachePrimaryKeyState(name) {
return `${STATE_CACHE_PRIMARY_KEY}-${name}`;
}
function additionalCacheMatchedKeyState(name) {
return `${CACHE_MATCHED_KEY}-${name}`;
}
function buildCacheKey(id, fileHash) {
return `${CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-${id}-${fileHash}`;
}
/** /**
* A function that generates a cache key to use. * A function that generates a cache key to use.
* Format of the generated key will be "${{ platform }}-${{ id }}-${{ fileHash }}"". * Format of the generated key will be "${{ platform }}-${{ id }}-${{ fileHash }}"".
@@ -130990,19 +130961,7 @@ async function computeCacheKey(packageManager, cacheDependencyPath) {
if (!fileHash) { if (!fileHash) {
throw new Error(`No file in ${process.cwd()} matched to [${pattern}], make sure you have checked out the target repository`); throw new Error(`No file in ${process.cwd()} matched to [${pattern}], make sure you have checked out the target repository`);
} }
return buildCacheKey(packageManager.id, fileHash); return `${CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-${packageManager.id}-${fileHash}`;
}
/**
* Computes the cache key for an additional cache. Unlike {@link computeCacheKey}
* this returns undefined (instead of throwing) when no file matches the pattern,
* because additional caches are optional features that many projects do not use.
*/
async function computeAdditionalCacheKey(additionalCache) {
const fileHash = await lib_glob_hashFiles(additionalCache.pattern.join('\n'));
if (!fileHash) {
return undefined;
}
return buildCacheKey(additionalCache.name, fileHash);
} }
/** /**
* Restore the dependency cache * Restore the dependency cache
@@ -131026,29 +130985,6 @@ async function restore(id, cacheDependencyPath) {
setOutput('cache-hit', false); setOutput('cache-hit', false);
info(`${packageManager.id} cache is not found`); info(`${packageManager.id} cache is not found`);
} }
for (const additionalCache of packageManager.additionalCaches ?? []) {
await restoreAdditionalCache(additionalCache);
}
}
/**
* Restore an additional cache (e.g. a build-tool wrapper distribution) that is
* keyed independently of the main dependency cache so that it survives changes
* to volatile dependency files. Skips silently when the project does not use
* the corresponding feature.
*/
async function restoreAdditionalCache(additionalCache) {
const primaryKey = await computeAdditionalCacheKey(additionalCache);
if (!primaryKey) {
core_debug(`No file matched [${additionalCache.pattern}] for the ${additionalCache.name} cache, skipping.`);
return;
}
core_debug(`${additionalCache.name} primary key is ${primaryKey}`);
saveState(additionalCachePrimaryKeyState(additionalCache.name), primaryKey);
const matchedKey = await restoreCache(additionalCache.path, primaryKey);
if (matchedKey) {
saveState(additionalCacheMatchedKeyState(additionalCache.name), matchedKey);
info(`${additionalCache.name} cache restored from key: ${matchedKey}`);
}
} }
/** /**
* Save the dependency cache * Save the dependency cache
@@ -131059,9 +130995,6 @@ async function save(id) {
const matchedKey = core.getState(CACHE_MATCHED_KEY); const matchedKey = core.getState(CACHE_MATCHED_KEY);
// Inputs are re-evaluated before the post action, so we want the original key used for restore // Inputs are re-evaluated before the post action, so we want the original key used for restore
const primaryKey = core.getState(STATE_CACHE_PRIMARY_KEY); const primaryKey = core.getState(STATE_CACHE_PRIMARY_KEY);
for (const additionalCache of packageManager.additionalCaches ?? []) {
await saveAdditionalCache(packageManager, additionalCache);
}
if (!primaryKey) { if (!primaryKey) {
core.warning('Error retrieving key from state.'); core.warning('Error retrieving key from state.');
return; return;
@@ -131096,50 +131029,6 @@ async function save(id) {
} }
} }
} }
/**
* Save an additional cache under its own key. Skips when no key was recorded at
* restore time (feature unused) or when the exact key was already restored.
*/
async function saveAdditionalCache(packageManager, additionalCache) {
const primaryKey = core.getState(additionalCachePrimaryKeyState(additionalCache.name));
const matchedKey = core.getState(additionalCacheMatchedKeyState(additionalCache.name));
if (!primaryKey) {
// The feature is not used by this project, nothing to save.
core.debug(`No primary key for the ${additionalCache.name} cache, not saving cache.`);
return;
}
else if (matchedKey === primaryKey) {
core.info(`Cache hit occurred on the ${additionalCache.name} primary key ${primaryKey}, not saving cache.`);
return;
}
try {
const cacheId = await cache.saveCache(additionalCache.path, primaryKey);
if (cacheId === -1) {
core.debug(`${additionalCache.name} cache was not saved for the key: ${primaryKey}`);
return;
}
core.info(`${additionalCache.name} cache saved with the key: ${primaryKey}`);
}
catch (error) {
const err = error;
if (err.name === cache.ValidationError.name) {
// The cache paths did not resolve, e.g. the wrapper distribution was
// never downloaded because a system build tool was used or the download
// failed. Optional wrapper caches must not fail the post step, so skip.
core.debug(`${additionalCache.name} cache paths do not exist, not saving cache: ${err.message}`);
return;
}
if (err.name === cache.ReserveCacheError.name) {
core.info(err.message);
}
else {
if (isProbablyGradleDaemonProblem(packageManager, err)) {
core.warning('Failed to save Gradle cache on Windows. If tar.exe reported "Permission denied", try to run Gradle with `--no-daemon` option. Refer to https://github.com/actions/cache/issues/454 for details.');
}
throw error;
}
}
}
/** /**
* @param packageManager the specified package manager by user * @param packageManager the specified package manager by user
* @param error the error thrown by the saveCache * @param error the error thrown by the saveCache

View File

@@ -289,7 +289,8 @@ To run the JavaFX application in CI:
## Ensuring the Maven cache is complete (plugin dependencies) ## Ensuring the Maven cache is complete (plugin dependencies)
When you enable `cache: maven`, the action caches your local Maven repository When you enable `cache: maven`, the action caches your local Maven repository
(`~/.m2/repository`). The cache key is a hash of your Maven inputs — every (`~/.m2/repository`) and downloaded Maven Wrapper distributions
(`~/.m2/wrapper/dists`). The cache key is a hash of your Maven inputs — every
`**/pom.xml`, plus `**/.mvn/wrapper/maven-wrapper.properties` and `**/pom.xml`, plus `**/.mvn/wrapper/maven-wrapper.properties` and
`**/.mvn/extensions.xml` — so changing any of those files (for example bumping `**/.mvn/extensions.xml` — so changing any of those files (for example bumping
the wrapper version or editing core extensions) produces a new key and the wrapper version or editing core extensions) produces a new key and
@@ -297,14 +298,6 @@ invalidates the cache. At the end of the job the action saves whatever was
downloaded during that run. It does **not** re-save the cache when the key downloaded during that run. It does **not** re-save the cache when the key
already matches (a cache *hit*). already matches (a cache *hit*).
Downloaded Maven Wrapper distributions (`~/.m2/wrapper/dists`) are cached in a
**separate** cache entry keyed only on `**/.mvn/wrapper/maven-wrapper.properties`.
Because the wrapper distribution changes far less often than your `pom.xml`
files, this keeps it available across the frequent dependency changes that
rotate the main cache key, so wrapper-based (`./mvnw`) builds don't re-download
the Maven distribution on every dependency change. See
[issue #1095](https://github.com/actions/setup-java/issues/1095).
Maven resolves **plugin** dependencies lazily: it only downloads the plugins and Maven resolves **plugin** dependencies lazily: it only downloads the plugins and
plugin dependencies required by the goals that actually execute. As a result, the plugin dependencies required by the goals that actually execute. As a result, the
run that first creates the cache determines what is stored. If that run executed a run that first creates the cache determines what is stored. If that run executed a

View File

@@ -12,30 +12,6 @@ const STATE_CACHE_PRIMARY_KEY = 'cache-primary-key';
const CACHE_MATCHED_KEY = 'cache-matched-key'; const CACHE_MATCHED_KEY = 'cache-matched-key';
const CACHE_KEY_PREFIX = 'setup-java'; const CACHE_KEY_PREFIX = 'setup-java';
/**
* An additional cache entry that is restored and saved independently of the
* main dependency cache. Used for build-tool wrapper distributions that rarely
* change (e.g. the Maven wrapper distribution) so that they are not evicted
* every time a volatile dependency file such as pom.xml changes. See
* https://github.com/actions/setup-java/issues/1095.
*/
interface AdditionalCache {
/**
* Short identifier for the cache, used to build its cache key and to scope
* the state keys that carry information from restore to save.
*/
name: string;
/**
* Paths that make up this cache entry.
*/
path: string[];
/**
* Glob patterns whose hash forms the cache key. If no file matches, the
* cache is skipped silently (the project simply does not use this feature).
*/
pattern: string[];
}
interface PackageManager { interface PackageManager {
id: 'maven' | 'gradle' | 'sbt'; id: 'maven' | 'gradle' | 'sbt';
/** /**
@@ -43,36 +19,27 @@ interface PackageManager {
*/ */
path: string[]; path: string[];
pattern: string[]; pattern: string[];
/**
* Additional caches keyed independently of the main dependency cache.
*/
additionalCaches?: AdditionalCache[];
} }
const supportedPackageManager: PackageManager[] = [ const supportedPackageManager: PackageManager[] = [
{ {
id: 'maven', id: 'maven',
path: [join(os.homedir(), '.m2', 'repository')], path: [
join(os.homedir(), '.m2', 'repository'),
join(os.homedir(), '.m2', 'wrapper', 'dists')
],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven // https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven
pattern: [ pattern: [
'**/pom.xml', '**/pom.xml',
'**/.mvn/wrapper/maven-wrapper.properties', '**/.mvn/wrapper/maven-wrapper.properties',
'**/.mvn/extensions.xml' '**/.mvn/extensions.xml'
],
// The Maven wrapper distribution only depends on the wrapper properties,
// which change very rarely, so it is cached separately from the local
// repository. This keeps it available across the frequent pom.xml changes
// that rotate the main cache key. See issue #1095.
additionalCaches: [
{
name: 'maven-wrapper',
path: [join(os.homedir(), '.m2', 'wrapper', 'dists')],
pattern: ['**/.mvn/wrapper/maven-wrapper.properties']
}
] ]
}, },
{ {
id: 'gradle', id: 'gradle',
path: [join(os.homedir(), '.gradle', 'caches')], path: [
join(os.homedir(), '.gradle', 'caches'),
join(os.homedir(), '.gradle', 'wrapper')
],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle // https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle
pattern: [ pattern: [
'**/*.gradle*', '**/*.gradle*',
@@ -81,17 +48,6 @@ const supportedPackageManager: PackageManager[] = [
'buildSrc/**/Dependencies.kt', 'buildSrc/**/Dependencies.kt',
'gradle/*.versions.toml', 'gradle/*.versions.toml',
'**/versions.properties' '**/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 #269.
additionalCaches: [
{
name: 'gradle-wrapper',
path: [join(os.homedir(), '.gradle', 'wrapper')],
pattern: ['**/gradle-wrapper.properties']
}
] ]
}, },
{ {
@@ -131,21 +87,6 @@ function findPackageManager(id: string): PackageManager {
return packageManager; return packageManager;
} }
/**
* State keys used to carry an additional cache's restore-time information over
* to the post (save) action, scoped by the additional cache name.
*/
function additionalCachePrimaryKeyState(name: string): string {
return `${STATE_CACHE_PRIMARY_KEY}-${name}`;
}
function additionalCacheMatchedKeyState(name: string): string {
return `${CACHE_MATCHED_KEY}-${name}`;
}
function buildCacheKey(id: string, fileHash: string): string {
return `${CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-${id}-${fileHash}`;
}
/** /**
* A function that generates a cache key to use. * A function that generates a cache key to use.
* Format of the generated key will be "${{ platform }}-${{ id }}-${{ fileHash }}"". * Format of the generated key will be "${{ platform }}-${{ id }}-${{ fileHash }}"".
@@ -164,22 +105,7 @@ async function computeCacheKey(
`No file in ${process.cwd()} matched to [${pattern}], make sure you have checked out the target repository` `No file in ${process.cwd()} matched to [${pattern}], make sure you have checked out the target repository`
); );
} }
return buildCacheKey(packageManager.id, fileHash); return `${CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-${packageManager.id}-${fileHash}`;
}
/**
* Computes the cache key for an additional cache. Unlike {@link computeCacheKey}
* this returns undefined (instead of throwing) when no file matches the pattern,
* because additional caches are optional features that many projects do not use.
*/
async function computeAdditionalCacheKey(
additionalCache: AdditionalCache
): Promise<string | undefined> {
const fileHash = await glob.hashFiles(additionalCache.pattern.join('\n'));
if (!fileHash) {
return undefined;
}
return buildCacheKey(additionalCache.name, fileHash);
} }
/** /**
@@ -204,40 +130,6 @@ export async function restore(id: string, cacheDependencyPath: string) {
core.setOutput('cache-hit', false); core.setOutput('cache-hit', false);
core.info(`${packageManager.id} cache is not found`); core.info(`${packageManager.id} cache is not found`);
} }
for (const additionalCache of packageManager.additionalCaches ?? []) {
await restoreAdditionalCache(additionalCache);
}
}
/**
* Restore an additional cache (e.g. a build-tool wrapper distribution) that is
* keyed independently of the main dependency cache so that it survives changes
* to volatile dependency files. Skips silently when the project does not use
* the corresponding feature.
*/
async function restoreAdditionalCache(additionalCache: AdditionalCache) {
const primaryKey = await computeAdditionalCacheKey(additionalCache);
if (!primaryKey) {
core.debug(
`No file matched [${additionalCache.pattern}] for the ${additionalCache.name} cache, skipping.`
);
return;
}
core.debug(`${additionalCache.name} primary key is ${primaryKey}`);
core.saveState(
additionalCachePrimaryKeyState(additionalCache.name),
primaryKey
);
const matchedKey = await cache.restoreCache(additionalCache.path, primaryKey);
if (matchedKey) {
core.saveState(
additionalCacheMatchedKeyState(additionalCache.name),
matchedKey
);
core.info(`${additionalCache.name} cache restored from key: ${matchedKey}`);
}
} }
/** /**
@@ -251,10 +143,6 @@ export async function save(id: string) {
// Inputs are re-evaluated before the post action, so we want the original key used for restore // Inputs are re-evaluated before the post action, so we want the original key used for restore
const primaryKey = core.getState(STATE_CACHE_PRIMARY_KEY); const primaryKey = core.getState(STATE_CACHE_PRIMARY_KEY);
for (const additionalCache of packageManager.additionalCaches ?? []) {
await saveAdditionalCache(packageManager, additionalCache);
}
if (!primaryKey) { if (!primaryKey) {
core.warning('Error retrieving key from state.'); core.warning('Error retrieving key from state.');
return; return;
@@ -292,70 +180,6 @@ export async function save(id: string) {
} }
} }
/**
* Save an additional cache under its own key. Skips when no key was recorded at
* restore time (feature unused) or when the exact key was already restored.
*/
async function saveAdditionalCache(
packageManager: PackageManager,
additionalCache: AdditionalCache
) {
const primaryKey = core.getState(
additionalCachePrimaryKeyState(additionalCache.name)
);
const matchedKey = core.getState(
additionalCacheMatchedKeyState(additionalCache.name)
);
if (!primaryKey) {
// The feature is not used by this project, nothing to save.
core.debug(
`No primary key for the ${additionalCache.name} cache, not saving cache.`
);
return;
} else if (matchedKey === primaryKey) {
core.info(
`Cache hit occurred on the ${additionalCache.name} primary key ${primaryKey}, not saving cache.`
);
return;
}
try {
const cacheId = await cache.saveCache(additionalCache.path, primaryKey);
if (cacheId === -1) {
core.debug(
`${additionalCache.name} cache was not saved for the key: ${primaryKey}`
);
return;
}
core.info(
`${additionalCache.name} cache saved with the key: ${primaryKey}`
);
} catch (error) {
const err = error as Error;
if (err.name === cache.ValidationError.name) {
// The cache paths did not resolve, e.g. the wrapper distribution was
// never downloaded because a system build tool was used or the download
// failed. Optional wrapper caches must not fail the post step, so skip.
core.debug(
`${additionalCache.name} cache paths do not exist, not saving cache: ${err.message}`
);
return;
}
if (err.name === cache.ReserveCacheError.name) {
core.info(err.message);
} else {
if (isProbablyGradleDaemonProblem(packageManager, err)) {
core.warning(
'Failed to save Gradle cache on Windows. If tar.exe reported "Permission denied", try to run Gradle with `--no-daemon` option. Refer to https://github.com/actions/cache/issues/454 for details.'
);
}
throw error;
}
}
}
/** /**
* @param packageManager the specified package manager by user * @param packageManager the specified package manager by user
* @param error the error thrown by the saveCache * @param error the error thrown by the saveCache