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 { 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 { 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 { 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 { 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'` ); } } }