5
0
mirror of https://gitea.com/actions/setup-java.git synced 2026-08-05 02:31:18 +00:00

Add JDK caching

Cache resolved JDK tool-cache entries by exact platform and release identity, with a default-on cache-jdk input and explicit opt-out.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Bruno Borges
2026-08-04 19:02:19 -04:00
parent 60b1ab8234
commit ee3e6d82d3
21 changed files with 1695 additions and 804 deletions
+9 -2
View File
@@ -147,9 +147,10 @@ steps:
| `verify-signature-public-key` | ASCII-armored GPG public key to use for signature verification. Overrides the bundled key. | |
| `token` | Token for fetching GitHub.com-hosted version manifests, useful on GitHub Enterprise Server when unauthenticated requests are rate-limited. | `${{ github.token }}` on GitHub.com; empty string on GHES |
| `cache` | Enable dependency caching for `maven`, `gradle`, or `sbt`. | |
| `cache-jdk` | Cache downloaded JDK installations between jobs. Set to `false` to opt out. | `true` |
| `cache-dependency-path` | Dependency file paths used for cache key hashing. Supports globs and multiline values. | Auto-detected by package manager |
| `cache-path` | Cache paths to use instead of the package manager's default dependency cache path. Supports multiline values and exclusions. | |
| `cache-read-only` | Restore caches without saving changes in the post step. | `false` |
| `cache-read-only` | Restore dependency, wrapper, and JDK caches without saving changes in the post step. | `false` |
| `server-id` | Maven repository ID used in generated `settings.xml`. | `github` |
| `server-username-env-var` | Environment variable name for Maven repository username. | `GITHUB_ACTOR` |
| `server-password-env-var` | Environment variable name for Maven repository password or token. | `GITHUB_TOKEN` |
@@ -230,6 +231,12 @@ Use `verify-signature: true` to verify package signatures for distributions that
## Caching dependencies
Downloaded JDK installations are cached by default, independently of dependency
caching. The JDK cache key includes the runner OS and platform, normalized
architecture, distribution, package type, exact resolved version, and release
identity, preventing incompatible JDKs from sharing an entry. Local `jdk-file`
archives are keyed by their content hash. Set `cache-jdk: false` to opt out.
Set `cache` to `maven`, `gradle`, or `sbt` to cache dependencies with minimal configuration.
```yaml
@@ -282,7 +289,7 @@ Use `cache-path` when the build tool stores dependencies outside the default loc
### Read-only caches
Set `cache-read-only: true` to restore dependency caches without saving changes in the post action. This is useful for pull requests, merge queues, short-lived branches, and matrix fan-out jobs that should only consume caches produced elsewhere.
Set `cache-read-only: true` to restore dependency, wrapper, and JDK caches without saving changes in the post action. This is useful for pull requests, merge queues, short-lived branches, and matrix fan-out jobs that should only consume caches produced elsewhere.
```yaml
- uses: actions/setup-java@v5
+27
View File
@@ -8,6 +8,7 @@ import {
beforeAll,
afterAll
} from '@jest/globals';
import fs from 'fs';
// Mock @actions/cache before importing source modules
const real_cache_module = await import('@actions/cache');
@@ -163,6 +164,32 @@ describe('cleanup', () => {
expect(spyCacheSave).toHaveBeenCalled();
});
it('saves the JDK cache without dependency caching', async () => {
const key = 'setup-java-jdk-v1-Linux-x64-key';
(core.getInput as jest.Mock).mockReturnValue('');
(core.getState as jest.Mock<any>).mockImplementation((name: string) =>
name === 'jdk-caches'
? JSON.stringify([{key, path: '/toolcache/java'}])
: ''
);
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
spyCacheSave.mockResolvedValue(1);
await cleanup();
expect(spyCacheSave).toHaveBeenCalledWith(['/toolcache/java'], key);
});
it('does not save a JDK cache when cache-jdk is disabled', async () => {
(core.getInput as jest.Mock<any>).mockImplementation((name: string) =>
name === 'cache-jdk' ? 'false' : ''
);
await cleanup();
expect(spyCacheSave).not.toHaveBeenCalled();
});
});
function resetState() {
@@ -70,6 +70,10 @@ jest.unstable_mockModule('@actions/tool-cache', () => ({
}
}));
jest.unstable_mockModule('../../src/jdk-cache.js', () => ({
restoreJdk: jest.fn()
}));
const real_util_module = await import('../../src/util.js');
jest.unstable_mockModule('../../src/util.js', () => ({
...real_util_module,
@@ -86,6 +90,7 @@ jest.unstable_mockModule('../../src/util.js', () => ({
const core = await import('@actions/core');
const tc = await import('@actions/tool-cache');
const util = await import('../../src/util.js');
const jdkCache = await import('../../src/jdk-cache.js');
const {JavaBase} = await import('../../src/distributions/base-installer.js');
class EmptyJavaBase extends JavaBase {
@@ -465,6 +470,7 @@ describe('setupJava', () => {
checkLatest: false,
forceDownload: true
});
const findInToolcache = jest.fn(() => ({
version: actualJavaVersion,
path: javaPathInstalled
@@ -486,6 +492,37 @@ describe('setupJava', () => {
);
});
it('restores the exact resolved JDK before downloading', async () => {
mockJavaBase = new EmptyJavaBase({
version: '11',
architecture: 'x86',
packageType: 'jdk',
checkLatest: true,
cacheJdk: true
});
const downloadTool = jest.spyOn(mockJavaBase as any, 'downloadTool');
(jdkCache.restoreJdk as jest.Mock).mockResolvedValue(true);
jest
.spyOn(mockJavaBase as any, 'getRestoredJdkPath')
.mockReturnValue(javaPathInstalled);
await expect(mockJavaBase.setupJava()).resolves.toEqual({
version: actualJavaVersion,
path: javaPathInstalled
});
expect(jdkCache.restoreJdk).toHaveBeenCalledWith({
distribution: 'Empty',
packageType: 'jdk',
architecture: 'x86',
version: actualJavaVersion,
source: `some/random_url/java/${actualJavaVersion}`,
path: path.join('Java_Empty_jdk', actualJavaVersion)
});
expect(downloadTool).not.toHaveBeenCalled();
expect(spyCoreInfo).not.toHaveBeenCalledWith('Trying to download...');
});
it.each([
[
{
+114
View File
@@ -0,0 +1,114 @@
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
import fs from 'fs';
import path from 'path';
jest.unstable_mockModule('@actions/cache', () => ({
restoreCache: jest.fn(),
saveCache: jest.fn(),
ReserveCacheError: class ReserveCacheError extends Error {}
}));
jest.unstable_mockModule('@actions/core', () => ({
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
saveState: jest.fn(),
getState: jest.fn()
}));
jest.unstable_mockModule('../src/cache-feature.js', () => ({
isCacheFeatureAvailable: jest.fn()
}));
const cache = await import('@actions/cache');
const core = await import('@actions/core');
const cacheFeature = await import('../src/cache-feature.js');
const {buildJdkCacheKey, restoreJdk, saveJdkCaches} =
await import('../src/jdk-cache.js');
const jdk = {
distribution: 'temurin',
packageType: 'jdk',
architecture: 'x64',
version: '21.0.8+9',
source: 'sha256:abc123',
path: '/toolcache/Java_temurin_jdk/21.0.8-9'
};
describe('JDK cache', () => {
beforeEach(() => {
jest.resetAllMocks();
(cacheFeature.isCacheFeatureAvailable as jest.Mock).mockReturnValue(true);
process.env['RUNNER_OS'] = 'Linux';
});
afterEach(() => {
jest.restoreAllMocks();
delete process.env['RUNNER_OS'];
});
it('builds distinct keys for incompatible JDK identities', () => {
const key = buildJdkCacheKey(jdk);
expect(key).toMatch(/^setup-java-jdk-v1-Linux-x64-[a-f0-9]{64}$/);
expect(buildJdkCacheKey({...jdk, architecture: 'aarch64'})).not.toBe(key);
expect(buildJdkCacheKey({...jdk, distribution: 'zulu'})).not.toBe(key);
expect(buildJdkCacheKey({...jdk, packageType: 'jre'})).not.toBe(key);
expect(buildJdkCacheKey({...jdk, version: '21.0.7+6'})).not.toBe(key);
expect(buildJdkCacheKey({...jdk, source: 'sha256:def456'})).not.toBe(key);
});
it('restores and records an exact JDK cache hit', async () => {
(cache.restoreCache as jest.Mock).mockResolvedValue(buildJdkCacheKey(jdk));
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
await expect(restoreJdk(jdk)).resolves.toBe(true);
expect(cache.restoreCache).toHaveBeenCalledWith(
[jdk.path],
buildJdkCacheKey(jdk)
);
const architecturePath = path.join(jdk.path, 'x64');
expect(fs.existsSync).toHaveBeenCalledWith(architecturePath);
expect(fs.existsSync).toHaveBeenCalledWith(`${architecturePath}.complete`);
expect(core.saveState).toHaveBeenCalledWith(
'jdk-caches',
expect.stringContaining(buildJdkCacheKey(jdk))
);
});
it('falls back to download when restoration fails', async () => {
(cache.restoreCache as jest.Mock).mockRejectedValue(
new Error('cache unavailable')
);
await expect(restoreJdk(jdk)).resolves.toBe(false);
expect(core.warning).toHaveBeenCalledWith(
'Failed to restore JDK cache: cache unavailable'
);
});
it('saves a downloaded JDK recorded during restore', async () => {
const key = buildJdkCacheKey(jdk);
(core.getState as jest.Mock).mockReturnValue(
JSON.stringify([{key, path: jdk.path}])
);
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
(cache.saveCache as jest.Mock).mockResolvedValue(1);
await saveJdkCaches();
expect(cache.saveCache).toHaveBeenCalledWith([jdk.path], key);
});
it('does not save an exact JDK cache hit again', async () => {
const key = buildJdkCacheKey(jdk);
(core.getState as jest.Mock).mockReturnValue(
JSON.stringify([{key, path: jdk.path, matchedKey: key}])
);
await saveJdkCaches();
expect(cache.saveCache).not.toHaveBeenCalled();
});
});
+7
View File
@@ -217,6 +217,7 @@ describe('setup action orchestration', () => {
packageType: 'jdk',
checkLatest: true,
forceDownload: true,
cacheJdk: true,
setDefault: false,
verifySignature: true,
verifySignaturePublicKey: 'public-key'
@@ -457,6 +458,7 @@ describe('setup action orchestration', () => {
it('does not initialize cache modules when cache input is absent', async () => {
inputs.set('distribution', 'temurin');
multilineInputs.set('java-version', ['21']);
booleanInputs.set('cache-jdk', false);
(factory.getJavaDistribution as jest.Mock).mockReturnValue({
setupJava: jest.fn(async () => ({
version: '21.0.4+7',
@@ -468,6 +470,11 @@ describe('setup action orchestration', () => {
expect(cacheFeature.isCacheFeatureAvailable).not.toHaveBeenCalled();
expect(cache.restore).not.toHaveBeenCalled();
expect(factory.getJavaDistribution).toHaveBeenCalledWith(
'temurin',
expect.objectContaining({cacheJdk: false}),
''
);
});
it('reports unsupported distributions through core.setFailed', async () => {
+5 -1
View File
@@ -84,6 +84,10 @@ inputs:
cache:
description: 'Name of the build platform to cache dependencies. It can be "maven", "gradle" or "sbt".'
required: false
cache-jdk:
description: 'Cache downloaded JDK installations between jobs. Set to "false" to disable JDK caching.'
required: false
default: true
cache-dependency-path:
description: '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.'
required: false
@@ -91,7 +95,7 @@ inputs:
description: 'The path to cache instead of the default dependency cache path for the selected package manager. This option can be used with the `cache` option and supports a list of paths and exclusion patterns.'
required: false
cache-read-only:
description: 'Restore dependency caches without saving cache changes in the post action.'
description: 'Restore caches without saving cache changes in the post action.'
required: false
default: false
job-status:
+146
View File
@@ -0,0 +1,146 @@
export const id = 314;
export const ids = [314];
export const modules = {
/***/ 2314:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
saveJdkCaches: () => (/* binding */ saveJdkCaches)
});
// UNUSED EXPORTS: buildJdkCacheKey, restoreJdk
// EXTERNAL MODULE: external "crypto"
var external_crypto_ = __webpack_require__(6982);
// EXTERNAL MODULE: external "fs"
var external_fs_ = __webpack_require__(9896);
var external_fs_default = /*#__PURE__*/__webpack_require__.n(external_fs_);
// EXTERNAL MODULE: external "path"
var external_path_ = __webpack_require__(6928);
// EXTERNAL MODULE: ./node_modules/@actions/cache/lib/cache.js + 291 modules
var lib_cache = __webpack_require__(5767);
// EXTERNAL MODULE: ./node_modules/@actions/core/lib/core.js + 7 modules
var lib_core = __webpack_require__(3838);
// EXTERNAL MODULE: ./src/util.ts
var util = __webpack_require__(4527);
;// CONCATENATED MODULE: ./src/cache-feature.ts
function cache_feature_isCacheFeatureAvailable() {
if (cache.isFeatureAvailable()) {
return true;
}
if (isGhes()) {
core.warning('Caching is only supported on GHES version >= 3.5. If you are on a version >= 3.5, please check with your GHES admin if the Actions cache service is enabled or not.');
return false;
}
core.warning('The runner was not able to contact the cache service. Caching will be skipped');
return false;
}
;// CONCATENATED MODULE: ./src/jdk-cache.ts
const STATE_JDK_CACHES = 'jdk-caches';
const JDK_CACHE_KEY_VERSION = 1;
const restoredCaches = (/* unused pure expression or super */ null && ([]));
async function restoreJdk(jdk) {
if (!jdk.path || !isCacheFeatureAvailable()) {
return false;
}
const key = buildJdkCacheKey(jdk);
let matchedKey;
try {
matchedKey = await cache.restoreCache([jdk.path], key);
}
catch (error) {
core.warning(`Failed to restore JDK cache: ${error.message}`);
}
const architecturePath = path.join(jdk.path, jdk.architecture);
if (matchedKey &&
(!fs.existsSync(architecturePath) ||
!fs.existsSync(`${architecturePath}.complete`))) {
core.warning(`JDK cache key ${matchedKey} was restored without the expected tool-cache path; downloading the JDK instead.`);
matchedKey = undefined;
}
restoredCaches.push({ key, path: jdk.path, matchedKey });
core.saveState(STATE_JDK_CACHES, JSON.stringify(restoredCaches));
if (matchedKey) {
core.info(`JDK cache restored from key: ${matchedKey}`);
return true;
}
core.info(`JDK cache is not found for ${jdk.distribution} ${jdk.version}`);
return false;
}
async function saveJdkCaches() {
const state = lib_core/* getState */.Gu(STATE_JDK_CACHES);
if (!state) {
return;
}
const caches = parseJdkCacheState(state);
for (const jdk of caches) {
if (jdk.matchedKey === jdk.key) {
lib_core/* info */.pq(`Cache hit occurred on the JDK primary key ${jdk.key}, not saving cache.`);
continue;
}
if (!external_fs_default().existsSync(jdk.path)) {
lib_core/* debug */.Yz(`JDK cache path does not exist, not saving: ${jdk.path}`);
continue;
}
try {
const cacheId = await lib_cache/* saveCache */.Io([jdk.path], jdk.key);
if (cacheId !== -1) {
lib_core/* info */.pq(`JDK cache saved with the key: ${jdk.key}`);
}
}
catch (error) {
const err = error;
if (err.name === lib_cache/* ReserveCacheError */.Zh.name) {
lib_core/* info */.pq(err.message);
}
else {
throw error;
}
}
}
}
function buildJdkCacheKey(jdk) {
const identity = JSON.stringify({
keyVersion: JDK_CACHE_KEY_VERSION,
runnerOs: process.env['RUNNER_OS'] ?? process.platform,
platform: process.platform,
distribution: jdk.distribution.toLowerCase(),
packageType: jdk.packageType.toLowerCase(),
architecture: jdk.architecture.toLowerCase(),
version: jdk.version,
source: jdk.source
});
const digest = createHash('sha256').update(identity).digest('hex');
return `setup-java-jdk-v${JDK_CACHE_KEY_VERSION}-${process.env['RUNNER_OS'] ?? process.platform}-${jdk.architecture}-${digest}`;
}
function parseJdkCacheState(state) {
const value = JSON.parse(state);
if (!Array.isArray(value) ||
!value.every(item => typeof item === 'object' &&
item !== null &&
typeof item.key === 'string' &&
typeof item.path === 'string' &&
(item.matchedKey === undefined ||
typeof item.matchedKey === 'string'))) {
throw new Error('Invalid JDK cache information retrieved from state.');
}
return value;
}
/***/ })
};
+648 -578
View File
File diff suppressed because it is too large Load Diff
+38 -2
View File
@@ -16,7 +16,11 @@ export const modules = {
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6242);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(4527);
/* harmony import */ var _constants_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(7242);
/* harmony import */ var _constants_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(7242);
/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(6982);
/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(crypto__WEBPACK_IMPORTED_MODULE_6__);
@@ -48,6 +52,30 @@ class LocalDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/
if (!stats.isFile()) {
throw new Error(`JDK file was not found in path '${jdkFilePath}'`);
}
if (!this.forceDownload && this.cacheJdk) {
const [{ restoreJdk }, source] = await Promise.all([
Promise.all(/* import() */[__webpack_require__.e(824), __webpack_require__.e(971), __webpack_require__.e(779)]).then(__webpack_require__.bind(__webpack_require__, 5779)),
hashFile(jdkFilePath)
]);
const restored = await restoreJdk({
distribution: this.distribution,
packageType: this.packageType,
architecture: this.architecture,
version: this.version,
source,
path: this.getJdkCachePath(this.version)
});
const restoredPath = restored
? this.getRestoredJdkPath(this.version)
: undefined;
if (restoredPath) {
foundJava = {
version: this.version,
path: restoredPath
};
}
}
if (!foundJava) {
_actions_core__WEBPACK_IMPORTED_MODULE_1__/* .info */ .pq(`Extracting Java from '${jdkFilePath}'`);
const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .extractJdkFile */ .PE)(jdkFilePath);
const archiveName = fs__WEBPACK_IMPORTED_MODULE_2___default().readdirSync(extractedJavaPath)[0];
@@ -59,8 +87,9 @@ class LocalDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/
path: javaPath
};
}
}
// JDK folder may contain postfix "Contents/Home" on macOS
const macOSPostfixPath = path__WEBPACK_IMPORTED_MODULE_3___default().join(foundJava.path, _constants_js__WEBPACK_IMPORTED_MODULE_6__/* .MACOS_JAVA_CONTENT_POSTFIX */ .PG);
const macOSPostfixPath = path__WEBPACK_IMPORTED_MODULE_3___default().join(foundJava.path, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MACOS_JAVA_CONTENT_POSTFIX */ .PG);
if (process.platform === 'darwin' && fs__WEBPACK_IMPORTED_MODULE_2___default().existsSync(macOSPostfixPath)) {
foundJava.path = macOSPostfixPath;
}
@@ -83,6 +112,13 @@ class LocalDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/
throw new Error('This method should not be implemented in local file provider');
}
}
async function hashFile(file) {
const hash = (0,crypto__WEBPACK_IMPORTED_MODULE_6__.createHash)('sha256');
for await (const chunk of (0,fs__WEBPACK_IMPORTED_MODULE_2__.createReadStream)(file)) {
hash.update(chunk);
}
return hash.digest('hex');
}
/***/ })
+46
View File
@@ -217,6 +217,7 @@ class JavaBase {
latest;
checkLatest;
forceDownload;
cacheJdk;
setDefault;
verifySignature;
verifySignaturePublicKey;
@@ -232,6 +233,7 @@ class JavaBase {
this.packageType = installerOptions.packageType;
this.checkLatest = installerOptions.checkLatest;
this.forceDownload = installerOptions.forceDownload ?? false;
this.cacheJdk = installerOptions.cacheJdk ?? false;
this.setDefault =
installerOptions.setDefault !== undefined
? installerOptions.setDefault
@@ -326,11 +328,33 @@ class JavaBase {
core/* info */.pq(`Resolved Java ${foundJava.version} from tool-cache`);
}
else {
if (!this.forceDownload && this.cacheJdk) {
const { restoreJdk } = await Promise.all(/* import() */[__webpack_require__.e(824), __webpack_require__.e(971), __webpack_require__.e(779)]).then(__webpack_require__.bind(__webpack_require__, 5779));
const restored = await restoreJdk({
distribution: this.distribution,
packageType: this.packageType,
architecture: this.architecture,
version: javaRelease.version,
source: this.getJdkReleaseIdentity(javaRelease),
path: this.getJdkCachePath(javaRelease.version)
});
if (restored) {
const restoredPath = this.getRestoredJdkPath(javaRelease.version);
if (restoredPath) {
foundJava = {
version: javaRelease.version,
path: restoredPath
};
}
}
}
if (!foundJava || foundJava.version !== javaRelease.version) {
core/* info */.pq('Trying to download...');
foundJava = await this.downloadTool(javaRelease);
core/* info */.pq(`Java ${foundJava.version} was downloaded`);
}
}
}
catch (error) {
this.logSetupError(error);
throw error;
@@ -435,6 +459,28 @@ class JavaBase {
// related issue: https://github.com/actions/virtual-environments/issues/3014
return version.replace('+', '-');
}
getJdkCachePath(version) {
return external_path_default().join(process.env['RUNNER_TOOL_CACHE'] ?? '', this.toolcacheFolderName, this.getToolcacheVersionName(version));
}
getRestoredJdkPath(version) {
const architecturePath = external_path_default().join(this.getJdkCachePath(version), this.architecture);
return external_fs_.existsSync(architecturePath) &&
external_fs_.existsSync(`${architecturePath}.complete`)
? architecturePath
: null;
}
getJdkReleaseIdentity(javaRelease) {
if (javaRelease.checksum) {
return `${javaRelease.checksum.algorithm}:${javaRelease.checksum.value}`;
}
try {
const url = new URL(javaRelease.url);
return `${url.origin}${url.pathname}`;
}
catch {
return javaRelease.url;
}
}
findInToolcache() {
// we can't use tc.find directly because firstly, we need to filter versions by stability flag
// if *-ea is provided, take only ea versions from toolcache, otherwise - only stable versions
+150
View File
@@ -0,0 +1,150 @@
export const id = 779;
export const ids = [779,394];
export const modules = {
/***/ 1394:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ isCacheFeatureAvailable: () => (/* binding */ isCacheFeatureAvailable)
/* harmony export */ });
/* harmony import */ var _actions_cache__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6971);
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3838);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4527);
function isCacheFeatureAvailable() {
if (_actions_cache__WEBPACK_IMPORTED_MODULE_0__/* .isFeatureAvailable */ .w3()) {
return true;
}
if ((0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .isGhes */ .aT)()) {
_actions_core__WEBPACK_IMPORTED_MODULE_1__/* .warning */ .$e('Caching is only supported on GHES version >= 3.5. If you are on a version >= 3.5, please check with your GHES admin if the Actions cache service is enabled or not.');
return false;
}
_actions_core__WEBPACK_IMPORTED_MODULE_1__/* .warning */ .$e('The runner was not able to contact the cache service. Caching will be skipped');
return false;
}
/***/ }),
/***/ 5779:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ buildJdkCacheKey: () => (/* binding */ buildJdkCacheKey),
/* harmony export */ restoreJdk: () => (/* binding */ restoreJdk),
/* harmony export */ saveJdkCaches: () => (/* binding */ saveJdkCaches)
/* harmony export */ });
/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6982);
/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(crypto__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9896);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6928);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _actions_cache__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6971);
/* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(3838);
/* harmony import */ var _cache_feature_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(1394);
const STATE_JDK_CACHES = 'jdk-caches';
const JDK_CACHE_KEY_VERSION = 1;
const restoredCaches = [];
async function restoreJdk(jdk) {
if (!jdk.path || !(0,_cache_feature_js__WEBPACK_IMPORTED_MODULE_5__.isCacheFeatureAvailable)()) {
return false;
}
const key = buildJdkCacheKey(jdk);
let matchedKey;
try {
matchedKey = await _actions_cache__WEBPACK_IMPORTED_MODULE_3__/* .restoreCache */ .P3([jdk.path], key);
}
catch (error) {
_actions_core__WEBPACK_IMPORTED_MODULE_4__/* .warning */ .$e(`Failed to restore JDK cache: ${error.message}`);
}
const architecturePath = path__WEBPACK_IMPORTED_MODULE_2___default().join(jdk.path, jdk.architecture);
if (matchedKey &&
(!fs__WEBPACK_IMPORTED_MODULE_1___default().existsSync(architecturePath) ||
!fs__WEBPACK_IMPORTED_MODULE_1___default().existsSync(`${architecturePath}.complete`))) {
_actions_core__WEBPACK_IMPORTED_MODULE_4__/* .warning */ .$e(`JDK cache key ${matchedKey} was restored without the expected tool-cache path; downloading the JDK instead.`);
matchedKey = undefined;
}
restoredCaches.push({ key, path: jdk.path, matchedKey });
_actions_core__WEBPACK_IMPORTED_MODULE_4__/* .saveState */ .LZ(STATE_JDK_CACHES, JSON.stringify(restoredCaches));
if (matchedKey) {
_actions_core__WEBPACK_IMPORTED_MODULE_4__/* .info */ .pq(`JDK cache restored from key: ${matchedKey}`);
return true;
}
_actions_core__WEBPACK_IMPORTED_MODULE_4__/* .info */ .pq(`JDK cache is not found for ${jdk.distribution} ${jdk.version}`);
return false;
}
async function saveJdkCaches() {
const state = _actions_core__WEBPACK_IMPORTED_MODULE_4__/* .getState */ .Gu(STATE_JDK_CACHES);
if (!state) {
return;
}
const caches = parseJdkCacheState(state);
for (const jdk of caches) {
if (jdk.matchedKey === jdk.key) {
_actions_core__WEBPACK_IMPORTED_MODULE_4__/* .info */ .pq(`Cache hit occurred on the JDK primary key ${jdk.key}, not saving cache.`);
continue;
}
if (!fs__WEBPACK_IMPORTED_MODULE_1___default().existsSync(jdk.path)) {
_actions_core__WEBPACK_IMPORTED_MODULE_4__/* .debug */ .Yz(`JDK cache path does not exist, not saving: ${jdk.path}`);
continue;
}
try {
const cacheId = await _actions_cache__WEBPACK_IMPORTED_MODULE_3__/* .saveCache */ .Io([jdk.path], jdk.key);
if (cacheId !== -1) {
_actions_core__WEBPACK_IMPORTED_MODULE_4__/* .info */ .pq(`JDK cache saved with the key: ${jdk.key}`);
}
}
catch (error) {
const err = error;
if (err.name === _actions_cache__WEBPACK_IMPORTED_MODULE_3__/* .ReserveCacheError */ .Zh.name) {
_actions_core__WEBPACK_IMPORTED_MODULE_4__/* .info */ .pq(err.message);
}
else {
throw error;
}
}
}
}
function buildJdkCacheKey(jdk) {
const identity = JSON.stringify({
keyVersion: JDK_CACHE_KEY_VERSION,
runnerOs: process.env['RUNNER_OS'] ?? process.platform,
platform: process.platform,
distribution: jdk.distribution.toLowerCase(),
packageType: jdk.packageType.toLowerCase(),
architecture: jdk.architecture.toLowerCase(),
version: jdk.version,
source: jdk.source
});
const digest = (0,crypto__WEBPACK_IMPORTED_MODULE_0__.createHash)('sha256').update(identity).digest('hex');
return `setup-java-jdk-v${JDK_CACHE_KEY_VERSION}-${process.env['RUNNER_OS'] ?? process.platform}-${jdk.architecture}-${digest}`;
}
function parseJdkCacheState(state) {
const value = JSON.parse(state);
if (!Array.isArray(value) ||
!value.every(item => typeof item === 'object' &&
item !== null &&
typeof item.key === 'string' &&
typeof item.path === 'string' &&
(item.matchedKey === undefined ||
typeof item.matchedKey === 'string'))) {
throw new Error('Invalid JDK cache information retrieved from state.');
}
return value;
}
/***/ })
};
+180 -181
View File
File diff suppressed because it is too large Load Diff
+9 -2
View File
@@ -30770,6 +30770,7 @@ module.exports = {
/* harmony export */ __nccwpck_require__.d(__webpack_exports__, {
/* harmony export */ At: () => (/* binding */ INPUT_CACHE_DEPENDENCY_PATH),
/* harmony export */ E8: () => (/* binding */ INPUT_SET_DEFAULT),
/* harmony export */ GL: () => (/* binding */ INPUT_CACHE_JDK),
/* harmony export */ I9: () => (/* binding */ INPUT_FORCE_DOWNLOAD),
/* harmony export */ K$: () => (/* binding */ GPG_PASSPHRASE_PROFILE_ID),
/* harmony export */ LS: () => (/* binding */ INPUT_ARCHITECTURE),
@@ -30849,6 +30850,7 @@ const MAVEN_GPG_PASSPHRASE_DEFAULT_ENV = 'MAVEN_GPG_PASSPHRASE';
// Id of the settings.xml profile used to set `gpg.passphraseEnvName`.
const GPG_PASSPHRASE_PROFILE_ID = 'setup-java-gpg';
const INPUT_CACHE = 'cache';
const INPUT_CACHE_JDK = 'cache-jdk';
const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path';
const INPUT_CACHE_PATH = 'cache-path';
const INPUT_CACHE_READ_ONLY = 'cache-read-only';
@@ -31890,6 +31892,7 @@ __nccwpck_require__.d(__webpack_exports__, {
dN: () => (/* binding */ exportVariable),
V4: () => (/* binding */ getInput),
q3: () => (/* binding */ getMultilineInput),
Gu: () => (/* binding */ getState),
pq: () => (/* binding */ info),
_o: () => (/* binding */ isDebug),
LZ: () => (/* binding */ saveState),
@@ -31900,7 +31903,7 @@ __nccwpck_require__.d(__webpack_exports__, {
$e: () => (/* binding */ warning)
});
// UNUSED EXPORTS: ExitCode, getBooleanInput, getIDToken, getState, group, markdownSummary, notice, platform, setCommandEcho, summary, toPlatformPath, toPosixPath, toWin32Path
// UNUSED EXPORTS: ExitCode, getBooleanInput, getIDToken, group, markdownSummary, notice, platform, setCommandEcho, summary, toPlatformPath, toPosixPath, toWin32Path
// EXTERNAL MODULE: external "os"
var external_os_ = __nccwpck_require__(857);
@@ -36119,6 +36122,7 @@ async function run() {
const packageType = setup_java_core/* getInput */.V4(constants/* INPUT_JAVA_PACKAGE */.p1);
const jdkFile = getJdkFileInput();
const cache = setup_java_core/* getInput */.V4(constants/* INPUT_CACHE */.gk);
const cacheJdk = (0,util/* getBooleanInput */.Vt)(constants/* INPUT_CACHE_JDK */.GL, true);
const cacheDependencyPath = setup_java_core/* getInput */.V4(constants/* INPUT_CACHE_DEPENDENCY_PATH */.At);
const cachePath = setup_java_core/* getMultilineInput */.q3(constants/* INPUT_CACHE_PATH */.uW);
const checkLatest = (0,util/* getBooleanInput */.Vt)(constants/* INPUT_CHECK_LATEST */.YM, false);
@@ -36157,6 +36161,7 @@ async function run() {
packageType,
checkLatest,
forceDownload,
cacheJdk,
setDefault,
verifySignature,
verifySignaturePublicKey,
@@ -36179,6 +36184,7 @@ async function run() {
packageType,
checkLatest,
forceDownload,
cacheJdk,
setDefault,
verifySignature,
verifySignaturePublicKey,
@@ -36231,12 +36237,13 @@ function getJdkFileInput() {
return jdkFile || deprecatedJdkFile;
}
async function installVersion(version, options, toolchainId = 0) {
const { distributionName, jdkFile, architecture, packageType, checkLatest, forceDownload, setDefault, verifySignature, verifySignaturePublicKey, toolchainIds } = options;
const { distributionName, jdkFile, architecture, packageType, checkLatest, forceDownload, cacheJdk, setDefault, verifySignature, verifySignaturePublicKey, toolchainIds } = options;
const installerOptions = {
architecture,
packageType,
checkLatest,
forceDownload,
cacheJdk,
setDefault,
verifySignature,
verifySignaturePublicKey,
+1 -1
View File
@@ -18,7 +18,7 @@
"fix": "npm run format && npm run lint:fix && npm run build",
"prepare": "husky install",
"prerelease": "npm run-script build",
"release": "git add -f dist/setup/*.js dist/setup/package.json dist/cleanup/index.js",
"release": "git add -f dist/setup/*.js dist/setup/package.json dist/cleanup/*.js",
"test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --runInBand --coverage"
},
"lint-staged": {
+13 -4
View File
@@ -24,10 +24,11 @@ async function removePrivateKeyFromKeychain() {
* Check given input and run a save process for the specified package manager
* @returns Promise that will be resolved when the save process finishes
*/
async function saveCache() {
async function saveCaches() {
const jobStatus = isJobStatusSuccess();
const cache = core.getInput(constants.INPUT_CACHE);
if (!jobStatus || !cache) {
const cacheJdk = getBooleanInput(constants.INPUT_CACHE_JDK, true);
if (!jobStatus || (!cache && !cacheJdk)) {
return;
}
@@ -36,8 +37,16 @@ async function saveCache() {
return;
}
const saves: Promise<void>[] = [];
if (cache) {
const {save} = await import('./cache.js');
await save(cache);
saves.push(save(cache));
}
if (cacheJdk) {
const {saveJdkCaches} = await import('./jdk-cache.js');
saves.push(saveJdkCaches());
}
await Promise.all(saves);
}
/**
@@ -59,7 +68,7 @@ async function ignoreError(promise: Promise<void>) {
export async function run() {
await removePrivateKeyFromKeychain();
await ignoreError(saveCache());
await ignoreError(saveCaches());
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
+1
View File
@@ -37,6 +37,7 @@ export const MAVEN_GPG_PASSPHRASE_DEFAULT_ENV = 'MAVEN_GPG_PASSPHRASE';
export const GPG_PASSPHRASE_PROFILE_ID = 'setup-java-gpg';
export const INPUT_CACHE = 'cache';
export const INPUT_CACHE_JDK = 'cache-jdk';
export const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path';
export const INPUT_CACHE_PATH = 'cache-path';
export const INPUT_CACHE_READ_ONLY = 'cache-read-only';
+55
View File
@@ -31,6 +31,7 @@ export abstract class JavaBase {
protected latest: boolean;
protected checkLatest: boolean;
protected forceDownload: boolean;
protected cacheJdk: boolean;
protected setDefault: boolean;
protected verifySignature: boolean;
protected verifySignaturePublicKey: string | undefined;
@@ -52,6 +53,7 @@ export abstract class JavaBase {
this.packageType = installerOptions.packageType;
this.checkLatest = installerOptions.checkLatest;
this.forceDownload = installerOptions.forceDownload ?? false;
this.cacheJdk = installerOptions.cacheJdk ?? false;
this.setDefault =
installerOptions.setDefault !== undefined
? installerOptions.setDefault
@@ -180,10 +182,32 @@ export abstract class JavaBase {
if (!this.forceDownload && foundJava?.version === javaRelease.version) {
core.info(`Resolved Java ${foundJava.version} from tool-cache`);
} else {
if (!this.forceDownload && this.cacheJdk) {
const {restoreJdk} = await import('../jdk-cache.js');
const restored = await restoreJdk({
distribution: this.distribution,
packageType: this.packageType,
architecture: this.architecture,
version: javaRelease.version,
source: this.getJdkReleaseIdentity(javaRelease),
path: this.getJdkCachePath(javaRelease.version)
});
if (restored) {
const restoredPath = this.getRestoredJdkPath(javaRelease.version);
if (restoredPath) {
foundJava = {
version: javaRelease.version,
path: restoredPath
};
}
}
}
if (!foundJava || foundJava.version !== javaRelease.version) {
core.info('Trying to download...');
foundJava = await this.downloadTool(javaRelease);
core.info(`Java ${foundJava.version} was downloaded`);
}
}
} catch (error: any) {
this.logSetupError(error);
throw error;
@@ -299,6 +323,37 @@ export abstract class JavaBase {
return version.replace('+', '-');
}
protected getJdkCachePath(version: string): string {
return path.join(
process.env['RUNNER_TOOL_CACHE'] ?? '',
this.toolcacheFolderName,
this.getToolcacheVersionName(version)
);
}
protected getRestoredJdkPath(version: string): string | null {
const architecturePath = path.join(
this.getJdkCachePath(version),
this.architecture
);
return fs.existsSync(architecturePath) &&
fs.existsSync(`${architecturePath}.complete`)
? architecturePath
: null;
}
private getJdkReleaseIdentity(javaRelease: JavaDownloadRelease): string {
if (javaRelease.checksum) {
return `${javaRelease.checksum.algorithm}:${javaRelease.checksum.value}`;
}
try {
const url = new URL(javaRelease.url);
return `${url.origin}${url.pathname}`;
} catch {
return javaRelease.url;
}
}
protected findInToolcache(): JavaInstallerResults | null {
// we can't use tc.find directly because firstly, we need to filter versions by stability flag
// if *-ea is provided, take only ea versions from toolcache, otherwise - only stable versions
+1
View File
@@ -4,6 +4,7 @@ export interface JavaInstallerOptions {
packageType: string;
checkLatest: boolean;
forceDownload?: boolean;
cacheJdk?: boolean;
setDefault?: boolean;
verifySignature?: boolean;
verifySignaturePublicKey?: string;
+36
View File
@@ -12,6 +12,8 @@ import {
} from '../base-models.js';
import {extractJdkFile} from '../../util.js';
import {MACOS_JAVA_CONTENT_POSTFIX} from '../../constants.js';
import {createReadStream} from 'fs';
import {createHash} from 'crypto';
export class LocalDistribution extends JavaBase {
constructor(
@@ -46,6 +48,31 @@ export class LocalDistribution extends JavaBase {
throw new Error(`JDK file was not found in path '${jdkFilePath}'`);
}
if (!this.forceDownload && this.cacheJdk) {
const [{restoreJdk}, source] = await Promise.all([
import('../../jdk-cache.js'),
hashFile(jdkFilePath)
]);
const restored = await restoreJdk({
distribution: this.distribution,
packageType: this.packageType,
architecture: this.architecture,
version: this.version,
source,
path: this.getJdkCachePath(this.version)
});
const restoredPath = restored
? this.getRestoredJdkPath(this.version)
: undefined;
if (restoredPath) {
foundJava = {
version: this.version,
path: restoredPath
};
}
}
if (!foundJava) {
core.info(`Extracting Java from '${jdkFilePath}'`);
const extractedJavaPath = await extractJdkFile(jdkFilePath);
@@ -65,6 +92,7 @@ export class LocalDistribution extends JavaBase {
path: javaPath
};
}
}
// JDK folder may contain postfix "Contents/Home" on macOS
const macOSPostfixPath = path.join(
@@ -103,3 +131,11 @@ export class LocalDistribution extends JavaBase {
);
}
}
async function hashFile(file: string): Promise<string> {
const hash = createHash('sha256');
for await (const chunk of createReadStream(file)) {
hash.update(chunk);
}
return hash.digest('hex');
}
+133
View File
@@ -0,0 +1,133 @@
import {createHash} from 'crypto';
import fs from 'fs';
import path from 'path';
import * as cache from '@actions/cache';
import * as core from '@actions/core';
import {isCacheFeatureAvailable} from './cache-feature.js';
const STATE_JDK_CACHES = 'jdk-caches';
const JDK_CACHE_KEY_VERSION = 1;
export interface JdkCache {
distribution: string;
packageType: string;
architecture: string;
version: string;
source: string;
path: string;
}
interface JdkCacheState {
key: string;
path: string;
matchedKey?: string;
}
const restoredCaches: JdkCacheState[] = [];
export async function restoreJdk(jdk: JdkCache): Promise<boolean> {
if (!jdk.path || !isCacheFeatureAvailable()) {
return false;
}
const key = buildJdkCacheKey(jdk);
let matchedKey: string | undefined;
try {
matchedKey = await cache.restoreCache([jdk.path], key);
} catch (error) {
core.warning(`Failed to restore JDK cache: ${(error as Error).message}`);
}
const architecturePath = path.join(jdk.path, jdk.architecture);
if (
matchedKey &&
(!fs.existsSync(architecturePath) ||
!fs.existsSync(`${architecturePath}.complete`))
) {
core.warning(
`JDK cache key ${matchedKey} was restored without the expected tool-cache path; downloading the JDK instead.`
);
matchedKey = undefined;
}
restoredCaches.push({key, path: jdk.path, matchedKey});
core.saveState(STATE_JDK_CACHES, JSON.stringify(restoredCaches));
if (matchedKey) {
core.info(`JDK cache restored from key: ${matchedKey}`);
return true;
}
core.info(`JDK cache is not found for ${jdk.distribution} ${jdk.version}`);
return false;
}
export async function saveJdkCaches(): Promise<void> {
const state = core.getState(STATE_JDK_CACHES);
if (!state) {
return;
}
const caches = parseJdkCacheState(state);
for (const jdk of caches) {
if (jdk.matchedKey === jdk.key) {
core.info(
`Cache hit occurred on the JDK primary key ${jdk.key}, not saving cache.`
);
continue;
}
if (!fs.existsSync(jdk.path)) {
core.debug(`JDK cache path does not exist, not saving: ${jdk.path}`);
continue;
}
try {
const cacheId = await cache.saveCache([jdk.path], jdk.key);
if (cacheId !== -1) {
core.info(`JDK cache saved with the key: ${jdk.key}`);
}
} catch (error) {
const err = error as Error;
if (err.name === cache.ReserveCacheError.name) {
core.info(err.message);
} else {
throw error;
}
}
}
}
export function buildJdkCacheKey(jdk: JdkCache): string {
const identity = JSON.stringify({
keyVersion: JDK_CACHE_KEY_VERSION,
runnerOs: process.env['RUNNER_OS'] ?? process.platform,
platform: process.platform,
distribution: jdk.distribution.toLowerCase(),
packageType: jdk.packageType.toLowerCase(),
architecture: jdk.architecture.toLowerCase(),
version: jdk.version,
source: jdk.source
});
const digest = createHash('sha256').update(identity).digest('hex');
return `setup-java-jdk-v${JDK_CACHE_KEY_VERSION}-${process.env['RUNNER_OS'] ?? process.platform}-${jdk.architecture}-${digest}`;
}
function parseJdkCacheState(state: string): JdkCacheState[] {
const value: unknown = JSON.parse(state);
if (
!Array.isArray(value) ||
!value.every(
item =>
typeof item === 'object' &&
item !== null &&
typeof (item as JdkCacheState).key === 'string' &&
typeof (item as JdkCacheState).path === 'string' &&
((item as JdkCacheState).matchedKey === undefined ||
typeof (item as JdkCacheState).matchedKey === 'string')
)
) {
throw new Error('Invalid JDK cache information retrieved from state.');
}
return value as JdkCacheState[];
}
+6
View File
@@ -17,6 +17,7 @@ export async function run() {
const packageType = core.getInput(constants.INPUT_JAVA_PACKAGE);
const jdkFile = getJdkFileInput();
const cache = core.getInput(constants.INPUT_CACHE);
const cacheJdk = getBooleanInput(constants.INPUT_CACHE_JDK, true);
const cacheDependencyPath = core.getInput(
constants.INPUT_CACHE_DEPENDENCY_PATH
);
@@ -80,6 +81,7 @@ export async function run() {
packageType,
checkLatest,
forceDownload,
cacheJdk,
setDefault,
verifySignature,
verifySignaturePublicKey,
@@ -105,6 +107,7 @@ export async function run() {
packageType,
checkLatest,
forceDownload,
cacheJdk,
setDefault,
verifySignature,
verifySignaturePublicKey,
@@ -183,6 +186,7 @@ async function installVersion(
packageType,
checkLatest,
forceDownload,
cacheJdk,
setDefault,
verifySignature,
verifySignaturePublicKey,
@@ -194,6 +198,7 @@ async function installVersion(
packageType,
checkLatest,
forceDownload,
cacheJdk,
setDefault,
verifySignature,
verifySignaturePublicKey,
@@ -238,6 +243,7 @@ interface installerInputsOptions {
packageType: string;
checkLatest: boolean;
forceDownload: boolean;
cacheJdk: boolean;
setDefault: boolean;
verifySignature: boolean;
verifySignaturePublicKey: string | undefined;