2021-04-05 13:02:27 +03:00
|
|
|
import * as tc from '@actions/tool-cache';
|
|
|
|
|
import * as core from '@actions/core';
|
|
|
|
|
import * as fs from 'fs';
|
|
|
|
|
import semver from 'semver';
|
|
|
|
|
import path from 'path';
|
|
|
|
|
import * as httpm from '@actions/http-client';
|
2023-03-09 14:49:35 +02:00
|
|
|
import {getToolcachePath, isVersionSatisfies} from '../util';
|
|
|
|
|
import {
|
|
|
|
|
JavaDownloadRelease,
|
|
|
|
|
JavaInstallerOptions,
|
|
|
|
|
JavaInstallerResults
|
|
|
|
|
} from './base-models';
|
|
|
|
|
import {MACOS_JAVA_CONTENT_POSTFIX} from '../constants';
|
2022-10-10 17:47:17 -06:00
|
|
|
import os from 'os';
|
2021-04-05 13:02:27 +03:00
|
|
|
|
|
|
|
|
export abstract class JavaBase {
|
|
|
|
|
protected http: httpm.HttpClient;
|
|
|
|
|
protected version: string;
|
|
|
|
|
protected architecture: string;
|
|
|
|
|
protected packageType: string;
|
|
|
|
|
protected stable: boolean;
|
|
|
|
|
protected checkLatest: boolean;
|
|
|
|
|
|
2023-03-09 14:49:35 +02:00
|
|
|
constructor(
|
|
|
|
|
protected distribution: string,
|
|
|
|
|
installerOptions: JavaInstallerOptions
|
|
|
|
|
) {
|
2021-04-05 13:02:27 +03:00
|
|
|
this.http = new httpm.HttpClient('actions/setup-java', undefined, {
|
|
|
|
|
allowRetries: true,
|
|
|
|
|
maxRetries: 3
|
|
|
|
|
});
|
|
|
|
|
|
2023-03-09 14:49:35 +02:00
|
|
|
({version: this.version, stable: this.stable} = this.normalizeVersion(
|
2021-04-05 13:02:27 +03:00
|
|
|
installerOptions.version
|
|
|
|
|
));
|
2022-10-10 17:47:17 -06:00
|
|
|
this.architecture = installerOptions.architecture || os.arch();
|
2021-04-05 13:02:27 +03:00
|
|
|
this.packageType = installerOptions.packageType;
|
|
|
|
|
this.checkLatest = installerOptions.checkLatest;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-09 14:49:35 +02:00
|
|
|
protected abstract downloadTool(
|
|
|
|
|
javaRelease: JavaDownloadRelease
|
|
|
|
|
): Promise<JavaInstallerResults>;
|
|
|
|
|
protected abstract findPackageForDownload(
|
|
|
|
|
range: string
|
|
|
|
|
): Promise<JavaDownloadRelease>;
|
2021-04-05 13:02:27 +03:00
|
|
|
|
|
|
|
|
public async setupJava(): Promise<JavaInstallerResults> {
|
|
|
|
|
let foundJava = this.findInToolcache();
|
|
|
|
|
if (foundJava && !this.checkLatest) {
|
|
|
|
|
core.info(`Resolved Java ${foundJava.version} from tool-cache`);
|
|
|
|
|
} else {
|
|
|
|
|
core.info('Trying to resolve the latest version from remote');
|
2025-11-14 01:04:50 +05:30
|
|
|
const MAX_RETRIES = 4;
|
|
|
|
|
const RETRY_DELAY_MS = 2000;
|
|
|
|
|
const retryableCodes = [
|
|
|
|
|
'ETIMEDOUT',
|
|
|
|
|
'ECONNRESET',
|
|
|
|
|
'ENOTFOUND',
|
|
|
|
|
'ECONNREFUSED'
|
|
|
|
|
];
|
|
|
|
|
let retries = MAX_RETRIES;
|
|
|
|
|
while (retries > 0) {
|
|
|
|
|
try {
|
|
|
|
|
// Clear console timers before each attempt to prevent conflicts
|
|
|
|
|
if (retries < MAX_RETRIES && core.isDebug()) {
|
|
|
|
|
const consoleAny = console as any;
|
|
|
|
|
consoleAny._times?.clear?.();
|
|
|
|
|
}
|
|
|
|
|
const javaRelease = await this.findPackageForDownload(this.version);
|
|
|
|
|
core.info(`Resolved latest version as ${javaRelease.version}`);
|
|
|
|
|
if (foundJava?.version === javaRelease.version) {
|
|
|
|
|
core.info(`Resolved Java ${foundJava.version} from tool-cache`);
|
2025-06-23 23:02:03 +05:30
|
|
|
} else {
|
2025-11-14 01:04:50 +05:30
|
|
|
core.info('Trying to download...');
|
|
|
|
|
foundJava = await this.downloadTool(javaRelease);
|
|
|
|
|
core.info(`Java ${foundJava.version} was downloaded`);
|
2025-06-23 23:02:03 +05:30
|
|
|
}
|
2025-11-14 01:04:50 +05:30
|
|
|
break;
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
retries--;
|
|
|
|
|
// Check if error is retryable (including aggregate errors)
|
|
|
|
|
const isRetryable =
|
|
|
|
|
(error instanceof tc.HTTPError &&
|
|
|
|
|
error.httpStatusCode &&
|
|
|
|
|
[429, 502, 503, 504].includes(error.httpStatusCode)) ||
|
|
|
|
|
retryableCodes.includes(error?.code) ||
|
|
|
|
|
(error?.errors &&
|
|
|
|
|
Array.isArray(error.errors) &&
|
|
|
|
|
error.errors.some((err: any) =>
|
|
|
|
|
retryableCodes.includes(err?.code)
|
|
|
|
|
));
|
|
|
|
|
if (retries > 0 && isRetryable) {
|
|
|
|
|
core.debug(
|
|
|
|
|
`Attempt failed due to network or timeout issues, initiating retry... (${retries} attempts left)`
|
|
|
|
|
);
|
|
|
|
|
await new Promise(r => setTimeout(r, RETRY_DELAY_MS));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (error instanceof tc.HTTPError) {
|
|
|
|
|
if (error.httpStatusCode === 403) {
|
|
|
|
|
core.error('HTTP 403: Permission denied or access restricted.');
|
|
|
|
|
} else if (error.httpStatusCode === 429) {
|
|
|
|
|
core.warning(
|
|
|
|
|
'HTTP 429: Rate limit exceeded. Please retry later.'
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
core.error(`HTTP ${error.httpStatusCode}: ${error.message}`);
|
|
|
|
|
}
|
|
|
|
|
} else if (error && error.errors && Array.isArray(error.errors)) {
|
|
|
|
|
core.error(
|
|
|
|
|
`Java setup failed due to network or configuration error(s)`
|
|
|
|
|
);
|
|
|
|
|
if (error instanceof Error && error.stack) {
|
|
|
|
|
core.debug(error.stack);
|
|
|
|
|
}
|
|
|
|
|
for (const err of error.errors) {
|
|
|
|
|
const endpoint = err?.address || err?.hostname || '';
|
|
|
|
|
const port = err?.port ? `:${err.port}` : '';
|
|
|
|
|
const message = err?.message || 'Aggregate error';
|
|
|
|
|
const endpointInfo = !message.includes(endpoint)
|
|
|
|
|
? ` ${endpoint}${port}`
|
|
|
|
|
: '';
|
|
|
|
|
const localInfo =
|
|
|
|
|
err.localAddress && err.localPort
|
|
|
|
|
? ` - Local (${err.localAddress}:${err.localPort})`
|
|
|
|
|
: '';
|
|
|
|
|
const logMessage = `${message}${endpointInfo}${localInfo}`;
|
|
|
|
|
core.error(logMessage);
|
|
|
|
|
core.debug(`${err.stack || err.message}`);
|
|
|
|
|
Object.entries(err).forEach(([key, value]) => {
|
|
|
|
|
core.debug(`"${key}": ${JSON.stringify(value)}`);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const message =
|
|
|
|
|
error instanceof Error ? error.message : JSON.stringify(error);
|
|
|
|
|
core.error(`Java setup process failed due to: ${message}`);
|
|
|
|
|
if (typeof error?.code === 'string') {
|
|
|
|
|
core.debug(error.stack);
|
|
|
|
|
}
|
|
|
|
|
const errorDetails = {
|
|
|
|
|
name: error.name,
|
|
|
|
|
message: error.message,
|
|
|
|
|
...Object.getOwnPropertyNames(error)
|
|
|
|
|
.filter(prop => !['name', 'message', 'stack'].includes(prop))
|
|
|
|
|
.reduce<{[key: string]: any}>((acc, prop) => {
|
|
|
|
|
acc[prop] = error[prop];
|
|
|
|
|
return acc;
|
|
|
|
|
}, {})
|
|
|
|
|
};
|
|
|
|
|
Object.entries(errorDetails).forEach(([key, value]) => {
|
|
|
|
|
core.debug(`"${key}": ${JSON.stringify(value)}`);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
throw error;
|
2025-06-23 23:02:03 +05:30
|
|
|
}
|
2021-04-05 13:02:27 +03:00
|
|
|
}
|
|
|
|
|
}
|
2025-11-14 01:04:50 +05:30
|
|
|
if (!foundJava) {
|
|
|
|
|
throw new Error('Failed to resolve Java version');
|
|
|
|
|
}
|
2021-04-05 13:02:27 +03:00
|
|
|
// JDK folder may contain postfix "Contents/Home" on macOS
|
2023-03-09 14:49:35 +02:00
|
|
|
const macOSPostfixPath = path.join(
|
|
|
|
|
foundJava.path,
|
|
|
|
|
MACOS_JAVA_CONTENT_POSTFIX
|
|
|
|
|
);
|
2021-04-05 13:02:27 +03:00
|
|
|
if (process.platform === 'darwin' && fs.existsSync(macOSPostfixPath)) {
|
|
|
|
|
foundJava.path = macOSPostfixPath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
core.info(`Setting Java ${foundJava.version} as the default`);
|
|
|
|
|
this.setJavaDefault(foundJava.version, foundJava.path);
|
|
|
|
|
|
|
|
|
|
return foundJava;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected get toolcacheFolderName(): string {
|
|
|
|
|
return `Java_${this.distribution}_${this.packageType}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected getToolcacheVersionName(version: string): string {
|
|
|
|
|
if (!this.stable) {
|
|
|
|
|
if (version.includes('+')) {
|
|
|
|
|
return version.replace('+', '-ea.');
|
|
|
|
|
} else {
|
|
|
|
|
return `${version}-ea`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Kotlin and some Java dependencies don't work properly when Java path contains "+" sign
|
|
|
|
|
// so replace "/hostedtoolcache/Java/11.0.3+4/x64" to "/hostedtoolcache/Java/11.0.3-4/x64" when saves to cache
|
|
|
|
|
// related issue: https://github.com/actions/virtual-environments/issues/3014
|
|
|
|
|
return version.replace('+', '-');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
const availableVersions = tc
|
|
|
|
|
.findAllVersions(this.toolcacheFolderName, this.architecture)
|
|
|
|
|
.map(item => {
|
|
|
|
|
return {
|
|
|
|
|
version: item
|
|
|
|
|
.replace('-ea.', '+')
|
|
|
|
|
.replace(/-ea$/, '')
|
|
|
|
|
// Kotlin and some Java dependencies don't work properly when Java path contains "+" sign
|
|
|
|
|
// so replace "/hostedtoolcache/Java/11.0.3-4/x64" to "/hostedtoolcache/Java/11.0.3+4/x64" when retrieves to cache
|
|
|
|
|
// related issue: https://github.com/actions/virtual-environments/issues/3014
|
|
|
|
|
.replace('-', '+'),
|
2023-03-09 14:49:35 +02:00
|
|
|
path:
|
|
|
|
|
getToolcachePath(
|
|
|
|
|
this.toolcacheFolderName,
|
|
|
|
|
item,
|
|
|
|
|
this.architecture
|
|
|
|
|
) || '',
|
2021-04-05 13:02:27 +03:00
|
|
|
stable: !item.includes('-ea')
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
.filter(item => item.stable === this.stable);
|
|
|
|
|
|
|
|
|
|
const satisfiedVersions = availableVersions
|
|
|
|
|
.filter(item => isVersionSatisfies(this.version, item.version))
|
|
|
|
|
.filter(item => item.path)
|
|
|
|
|
.sort((a, b) => {
|
|
|
|
|
return -semver.compareBuild(a.version, b.version);
|
|
|
|
|
});
|
|
|
|
|
if (!satisfiedVersions || satisfiedVersions.length === 0) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
version: satisfiedVersions[0].version,
|
|
|
|
|
path: satisfiedVersions[0].path
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected normalizeVersion(version: string) {
|
|
|
|
|
let stable = true;
|
|
|
|
|
|
|
|
|
|
if (version.endsWith('-ea')) {
|
|
|
|
|
version = version.replace(/-ea$/, '');
|
|
|
|
|
stable = false;
|
|
|
|
|
} else if (version.includes('-ea.')) {
|
|
|
|
|
// transform '11.0.3-ea.2' -> '11.0.3+2'
|
|
|
|
|
version = version.replace('-ea.', '+');
|
|
|
|
|
stable = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!semver.validRange(version)) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`The string '${version}' is not valid SemVer notation for a Java version. Please check README file for code snippets and more detailed information`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
version,
|
|
|
|
|
stable
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected setJavaDefault(version: string, toolPath: string) {
|
2022-09-09 19:35:58 +08:00
|
|
|
const majorVersion = version.split('.')[0];
|
2021-04-05 13:02:27 +03:00
|
|
|
core.exportVariable('JAVA_HOME', toolPath);
|
|
|
|
|
core.addPath(path.join(toolPath, 'bin'));
|
|
|
|
|
core.setOutput('distribution', this.distribution);
|
|
|
|
|
core.setOutput('path', toolPath);
|
|
|
|
|
core.setOutput('version', version);
|
2023-03-09 14:49:35 +02:00
|
|
|
core.exportVariable(
|
|
|
|
|
`JAVA_HOME_${majorVersion}_${this.architecture.toUpperCase()}`,
|
|
|
|
|
toolPath
|
|
|
|
|
);
|
2021-04-05 13:02:27 +03:00
|
|
|
}
|
2022-10-10 17:47:17 -06:00
|
|
|
|
|
|
|
|
protected distributionArchitecture(): string {
|
|
|
|
|
// default mappings of config architectures to distribution architectures
|
|
|
|
|
// override if a distribution uses any different names; see liberica for an example
|
|
|
|
|
|
|
|
|
|
// node's os.arch() - which this defaults to - can return any of:
|
|
|
|
|
// 'arm', 'arm64', 'ia32', 'mips', 'mipsel', 'ppc', 'ppc64', 's390', 's390x', and 'x64'
|
|
|
|
|
// so we need to map these to java distribution architectures
|
|
|
|
|
// 'amd64' is included here too b/c it's a common alias for 'x64' people might use explicitly
|
|
|
|
|
switch (this.architecture) {
|
|
|
|
|
case 'amd64':
|
|
|
|
|
return 'x64';
|
|
|
|
|
case 'ia32':
|
|
|
|
|
return 'x86';
|
|
|
|
|
case 'arm64':
|
|
|
|
|
return 'aarch64';
|
|
|
|
|
default:
|
|
|
|
|
return this.architecture;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-04-05 13:02:27 +03:00
|
|
|
}
|