mirror of
https://github.com/actions/setup-java.git
synced 2026-11-14 13:10:34 +08:00
5fb63c9bc9
* Initial plan * Fix JetBrains hotfix version selection and cache precedence --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Bruno Borges <brborges@microsoft.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
330 lines
9.7 KiB
TypeScript
330 lines
9.7 KiB
TypeScript
import * as core from '@actions/core';
|
|
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import semver from 'semver';
|
|
|
|
import {JavaBase} from '../base-installer.js';
|
|
import {IJetBrainsRawVersion, IJetBrainsVersion} from './models.js';
|
|
import {
|
|
JavaDownloadRelease,
|
|
JavaInstallerOptions,
|
|
JavaInstallerResults
|
|
} from '../base-models.js';
|
|
import {
|
|
cacheJdkDir,
|
|
extractJdkFile,
|
|
getGitHubToken,
|
|
getNextPageUrlFromLinkHeader,
|
|
MAX_PAGINATION_PAGES,
|
|
normalizeJavaVersionToSemver,
|
|
validatePaginationUrl
|
|
} from '../../util.js';
|
|
import {OutgoingHttpHeaders} from 'http';
|
|
import {HttpCodes} from '@actions/http-client';
|
|
|
|
const JETBRAINS_RELEASES_URL =
|
|
'https://api.github.com/repos/JetBrains/JetBrainsRuntime/releases?per_page=100';
|
|
const GITHUB_API_ORIGIN = 'https://api.github.com';
|
|
|
|
export class JetBrainsDistribution extends JavaBase {
|
|
constructor(installerOptions: JavaInstallerOptions) {
|
|
super('JetBrains', installerOptions);
|
|
}
|
|
|
|
protected async findPackageForDownload(
|
|
range: string
|
|
): Promise<JavaDownloadRelease> {
|
|
const versionsRaw = await this.getAvailableVersions();
|
|
|
|
const versions = versionsRaw.map(v => {
|
|
const formattedVersion = normalizeJavaVersionToSemver(
|
|
`${v.semver}+${v.build}`
|
|
);
|
|
|
|
return {
|
|
version: formattedVersion,
|
|
url: v.url
|
|
} as JavaDownloadRelease;
|
|
});
|
|
|
|
const satisfiedVersions = versions
|
|
.filter(item => this.isVersionSatisfied(range, item.version))
|
|
.sort((a, b) => {
|
|
return -this.compareVersions(a.version, b.version);
|
|
});
|
|
|
|
const resolvedFullVersion =
|
|
satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
|
|
if (!resolvedFullVersion) {
|
|
const availableVersionStrings = versionsRaw.map(
|
|
item => `${item.tag_name} (${item.semver}+${item.build})`
|
|
);
|
|
throw this.createVersionNotFoundError(range, availableVersionStrings);
|
|
}
|
|
|
|
return {
|
|
...resolvedFullVersion,
|
|
// JetBrains' `.checksum` sibling doesn't disclose its algorithm via the
|
|
// filename, and older JBR builds (e.g. JBR 11) publish a SHA-256 digest
|
|
// there while newer builds publish SHA-512. Accept either, preferring
|
|
// the stronger SHA-512 when the digest length is ambiguous.
|
|
checksum: await this.fetchChecksum(
|
|
`${resolvedFullVersion.url}.checksum`,
|
|
['sha512', 'sha256']
|
|
)
|
|
};
|
|
}
|
|
|
|
protected normalizeVersion(version: string) {
|
|
const normalized = super.normalizeVersion(version);
|
|
if (/^\d+\.\d+\.\d+(?:\.\d+)+(?:-ea)?$/.test(version)) {
|
|
// A patch-only request must match any JBR build of that patch.
|
|
normalized.version = version.replace(/-ea$/, '');
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
protected isVersionSatisfied(range: string, version: string): boolean {
|
|
const patchRequest = range.match(/^(\d+\.\d+\.\d+)((?:\.\d+)+)$/);
|
|
if (patchRequest) {
|
|
const candidate = semver.parse(version);
|
|
return (
|
|
candidate !== null &&
|
|
semver.eq(patchRequest[1], candidate) &&
|
|
(candidate.build.slice(0, -2).join('.') || '0') ===
|
|
patchRequest[2].slice(1)
|
|
);
|
|
}
|
|
return super.isVersionSatisfied(range, version);
|
|
}
|
|
|
|
protected compareVersions(a: string, b: string): number {
|
|
const mainComparison = semver.compare(a, b);
|
|
if (mainComparison !== 0) {
|
|
return mainComparison;
|
|
}
|
|
|
|
// JBR builds have two components (one in older releases). Any preceding
|
|
// metadata components are Java patch fields and take priority over builds.
|
|
const first = new semver.SemVer(a).build;
|
|
const second = new semver.SemVer(b).build;
|
|
const firstPatch = first.slice(0, -2);
|
|
const secondPatch = second.slice(0, -2);
|
|
for (
|
|
let index = 0;
|
|
index < Math.max(firstPatch.length, secondPatch.length);
|
|
index++
|
|
) {
|
|
const patchComparison = semver.compareIdentifiers(
|
|
firstPatch[index] ?? '0',
|
|
secondPatch[index] ?? '0'
|
|
);
|
|
if (patchComparison !== 0) {
|
|
return patchComparison;
|
|
}
|
|
}
|
|
|
|
return semver.compareBuild(
|
|
first.length ? `0.0.0+${first.slice(-2).join('.')}` : '0.0.0',
|
|
second.length ? `0.0.0+${second.slice(-2).join('.')}` : '0.0.0'
|
|
);
|
|
}
|
|
|
|
protected async downloadTool(
|
|
javaRelease: JavaDownloadRelease
|
|
): Promise<JavaInstallerResults> {
|
|
core.info(
|
|
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
|
|
);
|
|
|
|
const javaArchivePath = await this.downloadAndVerify(javaRelease);
|
|
|
|
core.info(`Extracting Java archive...`);
|
|
const extractedJavaPath = await extractJdkFile(javaArchivePath, 'tar.gz');
|
|
|
|
const archiveName = fs.readdirSync(extractedJavaPath)[0];
|
|
const archivePath = path.join(extractedJavaPath, archiveName);
|
|
const version = this.getToolcacheVersionName(javaRelease.version);
|
|
|
|
const javaPath = await cacheJdkDir(
|
|
archivePath,
|
|
this.toolcacheFolderName,
|
|
version,
|
|
this.architecture
|
|
);
|
|
|
|
return {version: javaRelease.version, path: javaPath};
|
|
}
|
|
|
|
private async getAvailableVersions(): Promise<IJetBrainsVersion[]> {
|
|
const platform = this.getPlatformOption();
|
|
const arch = this.distributionArchitecture();
|
|
|
|
if (core.isDebug()) {
|
|
console.time('Retrieving available versions for JBR took'); // eslint-disable-line no-console
|
|
}
|
|
|
|
const rawVersions: IJetBrainsRawVersion[] = [];
|
|
const bearerToken = getGitHubToken();
|
|
const requestHeaders: OutgoingHttpHeaders = {
|
|
Accept: 'application/vnd.github+json'
|
|
};
|
|
if (bearerToken) {
|
|
requestHeaders.Authorization = `Bearer ${bearerToken}`;
|
|
}
|
|
let releasesUrl: string | null = JETBRAINS_RELEASES_URL;
|
|
let pageCount = 0;
|
|
|
|
if (core.isDebug()) {
|
|
core.debug(`Gathering available versions from '${releasesUrl}'`);
|
|
}
|
|
|
|
while (releasesUrl) {
|
|
pageCount++;
|
|
const response = await this.http.getJson<IJetBrainsRawVersion[]>(
|
|
releasesUrl,
|
|
requestHeaders
|
|
);
|
|
const paginationPageResult = response.result;
|
|
if (!paginationPageResult || paginationPageResult.length === 0) {
|
|
break;
|
|
}
|
|
|
|
rawVersions.push(
|
|
...paginationPageResult.filter(version =>
|
|
this.stable ? !version.prerelease : version.prerelease
|
|
)
|
|
);
|
|
|
|
const nextUrl = getNextPageUrlFromLinkHeader(response.headers);
|
|
if (nextUrl && !validatePaginationUrl(nextUrl, GITHUB_API_ORIGIN)) {
|
|
core.warning(
|
|
`Ignoring pagination link with unexpected origin: ${nextUrl}`
|
|
);
|
|
releasesUrl = null;
|
|
} else {
|
|
releasesUrl = nextUrl;
|
|
}
|
|
|
|
if (pageCount >= MAX_PAGINATION_PAGES) {
|
|
if (releasesUrl) {
|
|
core.warning(
|
|
`Reached pagination safeguard limit (${MAX_PAGINATION_PAGES} pages) while listing JetBrains Runtime releases.`
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (this.stable) {
|
|
// Add versions not available from the API but are downloadable
|
|
const hidden = ['11_0_10b1145.115', '11_0_11b1341.60'];
|
|
rawVersions.push(
|
|
...hidden.map(tag => ({tag_name: tag, name: tag, prerelease: false}))
|
|
);
|
|
}
|
|
|
|
const versions0 = rawVersions.map(async v => {
|
|
// Release tags look like one of these:
|
|
// jbr-release-21.0.3b465.3
|
|
// jbr17-b87.7
|
|
// jb11_0_11-b87.7
|
|
// jbr11_0_15b2043.56
|
|
// 11_0_11b1536.2
|
|
// 11_0_11-b1522
|
|
const tag = v.tag_name;
|
|
|
|
// Extract version string
|
|
const vstring = tag
|
|
.replace('jbr-release-', '')
|
|
.replace('jbr', '')
|
|
.replace('jb', '')
|
|
.replace('-', '');
|
|
|
|
const vsplit = vstring.split('b');
|
|
let semver = vsplit[0];
|
|
const build = vsplit[1];
|
|
|
|
// Normalize semver
|
|
if (!semver.includes('.') && !semver.includes('_'))
|
|
semver = `${semver}.0.0`;
|
|
|
|
// Construct URL
|
|
let type: string;
|
|
switch (this.packageType ?? '') {
|
|
case 'jre':
|
|
type = 'jbr';
|
|
break;
|
|
case 'jdk+jcef':
|
|
type = 'jbrsdk_jcef';
|
|
break;
|
|
case 'jre+jcef':
|
|
type = 'jbr_jcef';
|
|
break;
|
|
case 'jdk+ft':
|
|
type = 'jbrsdk_ft';
|
|
break;
|
|
case 'jre+ft':
|
|
type = 'jbr_ft';
|
|
break;
|
|
default:
|
|
type = 'jbrsdk';
|
|
break;
|
|
}
|
|
|
|
let url = `https://cache-redirector.jetbrains.com/intellij-jbr/${type}-${semver}-${platform}-${arch}-b${build}.tar.gz`;
|
|
let include = false;
|
|
|
|
const res = await this.http.head(url);
|
|
if (res.message.statusCode === HttpCodes.OK) {
|
|
include = true;
|
|
} else {
|
|
url = `https://cache-redirector.jetbrains.com/intellij-jbr/${type}_nomod-${semver}-${platform}-${arch}-b${build}.tar.gz`;
|
|
const res2 = await this.http.head(url);
|
|
if (res2.message.statusCode === HttpCodes.OK) {
|
|
include = true;
|
|
}
|
|
}
|
|
|
|
const version = {
|
|
tag_name: tag,
|
|
semver: semver.replace(/_/g, '.'),
|
|
build: build,
|
|
url: url
|
|
} as IJetBrainsVersion;
|
|
|
|
return {
|
|
item: version,
|
|
include: include
|
|
};
|
|
});
|
|
|
|
const versions = await Promise.all(versions0).then(res =>
|
|
res.filter(item => item.include).map(item => item.item)
|
|
);
|
|
|
|
if (core.isDebug()) {
|
|
core.startGroup('Print information about available versions');
|
|
console.timeEnd('Retrieving available versions for JBR took'); // eslint-disable-line no-console
|
|
core.debug(`Available versions: [${versions.length}]`);
|
|
core.debug(versions.map(item => item.semver).join(', '));
|
|
core.endGroup();
|
|
}
|
|
|
|
return versions;
|
|
}
|
|
|
|
private getPlatformOption(): string {
|
|
// Jetbrains has own platform names so need to map them
|
|
switch (process.platform) {
|
|
case 'darwin':
|
|
return 'osx';
|
|
case 'win32':
|
|
return 'windows';
|
|
default:
|
|
return process.platform;
|
|
}
|
|
}
|
|
}
|