mirror of
https://github.com/actions/setup-java.git
synced 2026-08-22 19:12:57 +00:00
885218c5e4
Two wall-clock optimizations on the JDK install path. `tc.cacheDir` recursively copies the extracted tree into RUNNER_TOOL_CACHE, so a 200-600MB JDK is written to disk twice. The extraction directory and the tool-cache normally share a filesystem, so `cacheJdkDir` renames it instead and writes the `.complete` marker itself, mirroring the destination layout `tc.cacheDir` produces. It falls back to the copy when the tool-cache location is unknown, when the source is not a real directory (a symlinked source would otherwise leave a dangling entry once RUNNER_TEMP is cleaned), or when the rename fails - a cross-device tool-cache, or anti-virus holding a handle on Windows. The rename is atomic, so the source is still intact for the fallback. Extraction now uses `pigz` for tarballs when the runner provides it, and Windows zips go through the bundled `tar.exe` rather than `tc.extractZip`, which shells out to PowerShell's much slower `Expand-Archive`. Both fall back to the stock extraction and clean up the abandoned directory first. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b76d8cb0-f629-46e1-bf9a-ffde06948644
186 lines
5.5 KiB
TypeScript
186 lines
5.5 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 {
|
|
JavaDownloadRelease,
|
|
JavaInstallerOptions,
|
|
JavaInstallerResults
|
|
} from '../base-models.js';
|
|
import {
|
|
cacheJdkDir,
|
|
convertVersionToSemver,
|
|
extractJdkFile,
|
|
isVersionSatisfies,
|
|
renameWinArchive
|
|
} from '../../util.js';
|
|
|
|
const OPENJDK_BASE_URL = 'https://jdk.java.net';
|
|
|
|
export class OpenJdkDistribution extends JavaBase {
|
|
constructor(installerOptions: JavaInstallerOptions) {
|
|
super('Oracle OpenJDK', installerOptions);
|
|
}
|
|
|
|
protected async findPackageForDownload(
|
|
range: string
|
|
): Promise<JavaDownloadRelease> {
|
|
if (this.packageType !== 'jdk') {
|
|
throw new Error('Oracle OpenJDK provides only the `jdk` package type');
|
|
}
|
|
|
|
const arch = this.distributionArchitecture();
|
|
if (!['x64', 'aarch64'].includes(arch)) {
|
|
throw new Error(`Unsupported architecture: ${this.architecture}`);
|
|
}
|
|
|
|
const platform = this.getPlatform();
|
|
const releases = await this.getAvailableVersions(platform, arch);
|
|
const matchingReleases = releases
|
|
.filter(release => isVersionSatisfies(range, release.version))
|
|
.sort((left, right) => -semver.compareBuild(left.version, right.version));
|
|
|
|
if (!matchingReleases.length) {
|
|
throw this.createVersionNotFoundError(
|
|
range,
|
|
releases.map(release => release.version),
|
|
`Platform: ${platform}`
|
|
);
|
|
}
|
|
|
|
const release = matchingReleases[0];
|
|
return {
|
|
...release,
|
|
checksum: await this.fetchChecksum(`${release.url}.sha256`, 'sha256')
|
|
};
|
|
}
|
|
|
|
protected async downloadTool(
|
|
javaRelease: JavaDownloadRelease
|
|
): Promise<JavaInstallerResults> {
|
|
core.info(
|
|
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
|
|
);
|
|
let javaArchivePath = await this.downloadAndVerify(javaRelease);
|
|
|
|
core.info(`Extracting Java archive...`);
|
|
const extension = javaRelease.url.endsWith('.zip') ? 'zip' : 'tar.gz';
|
|
if (extension === 'zip') {
|
|
javaArchivePath = renameWinArchive(javaArchivePath);
|
|
}
|
|
const extractedJavaPath = await extractJdkFile(javaArchivePath, extension);
|
|
|
|
const archiveName = fs.readdirSync(extractedJavaPath)[0];
|
|
const archivePath = path.join(extractedJavaPath, archiveName);
|
|
const javaPath = await cacheJdkDir(
|
|
archivePath,
|
|
this.toolcacheFolderName,
|
|
this.getToolcacheVersionName(javaRelease.version),
|
|
this.architecture
|
|
);
|
|
|
|
return {version: javaRelease.version, path: javaPath};
|
|
}
|
|
|
|
private async getAvailableVersions(
|
|
platform: string,
|
|
arch: string
|
|
): Promise<JavaDownloadRelease[]> {
|
|
const homePage = await this.fetchPage(`${OPENJDK_BASE_URL}/`);
|
|
const releasePageUrls = Array.from(
|
|
homePage.matchAll(/href="\/(\d+)\/">JDK\s+\d+/g),
|
|
match => `${OPENJDK_BASE_URL}/${match[1]}/`
|
|
);
|
|
|
|
const pages = await Promise.all(
|
|
releasePageUrls.map(url => this.fetchPage(url))
|
|
);
|
|
if (this.stable) {
|
|
pages.push(await this.fetchPage(`${OPENJDK_BASE_URL}/archive/`));
|
|
}
|
|
|
|
const releases = pages.flatMap(page =>
|
|
this.parseReleases(page, platform, arch)
|
|
);
|
|
|
|
return releases.filter(
|
|
release => release.url.includes('/early_access/') !== this.stable
|
|
);
|
|
}
|
|
|
|
private async fetchPage(url: string): Promise<string> {
|
|
const response = await this.http.get(url);
|
|
return response.readBody();
|
|
}
|
|
|
|
private parseReleases(
|
|
html: string,
|
|
platform: string,
|
|
arch: string
|
|
): JavaDownloadRelease[] {
|
|
const platformPattern = platform === 'macos' ? '(?:macos|osx)' : platform;
|
|
const extensionPattern =
|
|
platform === 'windows' ? '(?:zip|tar\\.gz)' : 'tar\\.gz';
|
|
const pattern = new RegExp(
|
|
`href="(https://download\\.java\\.net/[^"]+/openjdk-([^"_]+)_${platformPattern}-${arch}_bin\\.${extensionPattern})"`,
|
|
'g'
|
|
);
|
|
|
|
return Array.from(html.matchAll(pattern), match => {
|
|
const url = match[1];
|
|
const build =
|
|
url.match(/\/(\d+)\/(?:GPL\/)?openjdk-/)?.[1] ??
|
|
this.findBuildInArchiveHeading(html, match.index, match[2]);
|
|
return {
|
|
version: this.toSemver(match[2], build),
|
|
url
|
|
};
|
|
});
|
|
}
|
|
|
|
private findBuildInArchiveHeading(
|
|
html: string,
|
|
assetIndex: number,
|
|
version: string
|
|
): string | undefined {
|
|
const headings = Array.from(
|
|
html.slice(0, assetIndex).matchAll(/\(build\s+([^)]+)\)/g)
|
|
);
|
|
const headingVersion = headings.at(-1)?.[1];
|
|
if (!headingVersion) {
|
|
return undefined;
|
|
}
|
|
|
|
const [javaVersion, build] = headingVersion.split('+');
|
|
return javaVersion === version ? build : undefined;
|
|
}
|
|
|
|
private toSemver(version: string, urlBuild?: string): string {
|
|
const [javaVersion, filenameBuild] = version.replace('-ea', '').split('+');
|
|
const versionParts = javaVersion.split('.');
|
|
const normalizedVersion = convertVersionToSemver(
|
|
versionParts.length === 1 ? `${javaVersion}.0.0` : javaVersion
|
|
);
|
|
const build =
|
|
filenameBuild ?? (versionParts.length <= 3 ? urlBuild : undefined);
|
|
return build ? `${normalizedVersion}+${build}` : normalizedVersion;
|
|
}
|
|
|
|
private getPlatform(platform: NodeJS.Platform = process.platform): string {
|
|
switch (platform) {
|
|
case 'darwin':
|
|
return 'macos';
|
|
case 'linux':
|
|
return 'linux';
|
|
case 'win32':
|
|
return 'windows';
|
|
default:
|
|
throw new Error(
|
|
`Platform '${platform}' is not supported. Supported platforms: 'linux', 'macos', 'windows'`
|
|
);
|
|
}
|
|
}
|
|
}
|