From d9cbbc45bbebc1638a23610d94df2c4921b848e3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 02:02:38 +0000 Subject: [PATCH 1/9] Initial plan From a7c4828ceba458467c96d20a2a3adcf5a8c5d7ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 02:11:53 +0000 Subject: [PATCH 2/9] Add OpenJDK distribution --- README.md | 1 + .../distributors/openjdk-installer.test.ts | 161 ++++++++++++++++++ dist/setup/index.js | 92 ++++++++++ src/distributions/distribution-factory.ts | 6 +- src/distributions/openjdk/installer.ts | 153 +++++++++++++++++ 5 files changed, 412 insertions(+), 1 deletion(-) create mode 100644 __tests__/distributors/openjdk-installer.test.ts create mode 100644 src/distributions/openjdk/installer.ts diff --git a/README.md b/README.md index 461338a9b..2986da3e2 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,7 @@ Currently, the following distributions are supported: | `corretto` | [Amazon Corretto Build of OpenJDK](https://aws.amazon.com/corretto/) | [`corretto` license](https://aws.amazon.com/corretto/faqs/) | `semeru` | [IBM Semeru Runtime Open Edition](https://developer.ibm.com/languages/java/semeru-runtimes/downloads/) | [`semeru` license](https://openjdk.java.net/legal/gplv2+ce.html) | | `oracle` | [Oracle JDK](https://www.oracle.com/java/technologies/downloads/) | [`oracle` license](https://java.com/freeuselicense) +| `openjdk` | [OpenJDK](https://jdk.java.net/) | [`openjdk` license](https://openjdk.org/legal/gplv2+ce.html) | `dragonwell` | [Alibaba Dragonwell JDK](https://dragonwell-jdk.io/) | [`dragonwell` license](https://www.aliyun.com/product/dragonwell/) | `sapmachine` | [SAP SapMachine JDK/JRE](https://sapmachine.io/) | [`sapmachine` license](https://github.com/SAP/SapMachine/blob/sapmachine/LICENSE) | `graalvm` | [Oracle GraalVM](https://www.graalvm.org/) | [`graalvm` license](https://www.oracle.com/downloads/licenses/graal-free-license.html) diff --git a/__tests__/distributors/openjdk-installer.test.ts b/__tests__/distributors/openjdk-installer.test.ts new file mode 100644 index 000000000..f6714fd76 --- /dev/null +++ b/__tests__/distributors/openjdk-installer.test.ts @@ -0,0 +1,161 @@ +import {afterEach, beforeEach, describe, expect, it, jest} from '@jest/globals'; +import {HttpClient} from '@actions/http-client'; + +jest.unstable_mockModule('@actions/core', () => ({ + info: jest.fn(), + warning: jest.fn(), + debug: jest.fn(), + error: jest.fn(), + notice: jest.fn(), + setFailed: jest.fn(), + setOutput: jest.fn(), + getInput: jest.fn(), + getBooleanInput: jest.fn(), + getMultilineInput: jest.fn(), + addPath: jest.fn(), + exportVariable: jest.fn(), + saveState: jest.fn(), + getState: jest.fn(), + setSecret: jest.fn(), + isDebug: jest.fn(() => false), + startGroup: jest.fn(), + endGroup: jest.fn(), + group: jest.fn((_name: string, fn: () => Promise) => fn()), + toPlatformPath: jest.fn((value: string) => value), + toWin32Path: jest.fn((value: string) => value), + toPosixPath: jest.fn((value: string) => value) +})); + +const {OpenJdkDistribution} = + await import('../../src/distributions/openjdk/installer.js'); +const {getJavaDistribution} = + await import('../../src/distributions/distribution-factory.js'); + +const homePage = ` + JDK 26 + JDK + 27 +`; +const currentPage = ` + tar.gz + tar.gz +`; +const earlyAccessPage = ` + tar.gz +`; +const archivePage = ` + tar.gz + tar.gz +`; + +function createDistribution( + version = '26', + architecture = 'x64', + packageType = 'jdk' +) { + return new OpenJdkDistribution({ + version, + architecture, + packageType, + checkLatest: false + }); +} + +describe('OpenJdkDistribution', () => { + let getSpy: jest.SpiedFunction; + + beforeEach(() => { + getSpy = jest + .spyOn(HttpClient.prototype, 'get') + .mockImplementation(async url => { + const pages: Record = { + 'https://jdk.java.net/': homePage, + 'https://jdk.java.net/26/': currentPage, + 'https://jdk.java.net/27/': earlyAccessPage, + 'https://jdk.java.net/archive/': archivePage + }; + return { + readBody: async () => pages[url] ?? '' + } as Awaited>; + }); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('resolves the newest matching GA release', async () => { + const result = await createDistribution()['findPackageForDownload']('26'); + + expect(result).toEqual({ + version: '26.0.2', + url: 'https://download.java.net/java/GA/jdk26.0.2/hash/10/GPL/openjdk-26.0.2_linux-x64_bin.tar.gz' + }); + }); + + it('resolves an archived GA release', async () => { + const result = + await createDistribution('26.0.1')['findPackageForDownload']('26.0.1'); + + expect(result.version).toBe('26.0.1'); + expect(result.url).toContain('/openjdk-26.0.1_linux-x64_bin.tar.gz'); + }); + + it('resolves an early-access release without requesting the archive', async () => { + const result = + await createDistribution('27-ea')['findPackageForDownload']('27'); + + expect(result).toEqual({ + version: '27.0.0+32', + url: 'https://download.java.net/java/early_access/jdk27/32/GPL/openjdk-27-ea+32_linux-x64_bin.tar.gz' + }); + expect(getSpy).not.toHaveBeenCalledWith('https://jdk.java.net/archive/'); + }); + + it('reports available versions when no release matches', async () => { + await expect( + createDistribution()['findPackageForDownload']('24') + ).rejects.toThrow( + "No matching version found for SemVer '24'.\nDistribution: OpenJDK" + ); + }); + + it.each([ + ['jre', 'OpenJDK provides only the `jdk` package type'], + ['jdk+fx', 'OpenJDK provides only the `jdk` package type'] + ])('rejects the %s package type', async (packageType, message) => { + await expect( + createDistribution('26', 'x64', packageType)['findPackageForDownload']( + '26' + ) + ).rejects.toThrow(message); + }); + + it('rejects unsupported architectures', async () => { + await expect( + createDistribution('26', 'x86')['findPackageForDownload']('26') + ).rejects.toThrow('Unsupported architecture: x86'); + }); + + it('maps supported platforms', () => { + const distribution = createDistribution(); + + expect(distribution['getPlatform']('linux')).toBe('linux'); + expect(distribution['getPlatform']('darwin')).toBe('macos'); + expect(distribution['getPlatform']('win32')).toBe('windows'); + expect(() => distribution['getPlatform']('freebsd')).toThrow( + "Platform 'freebsd' is not supported" + ); + }); + + it('is registered in the distribution factory', () => { + const distribution = getJavaDistribution('openjdk', { + version: '26', + architecture: 'x64', + packageType: 'jdk', + checkLatest: false + }); + + expect(distribution).toBeInstanceOf(OpenJdkDistribution); + }); +}); diff --git a/dist/setup/index.js b/dist/setup/index.js index 89e5b84e9..58dbe7b6c 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -131874,6 +131874,94 @@ class KonaDistribution extends JavaBase { } } +;// CONCATENATED MODULE: ./src/distributions/openjdk/installer.ts + + + + + + + +const OPENJDK_BASE_URL = 'https://jdk.java.net'; +class OpenJdkDistribution extends JavaBase { + constructor(installerOptions) { + super('OpenJDK', installerOptions); + } + async findPackageForDownload(range) { + if (this.packageType !== 'jdk') { + throw new Error('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_default().compareBuild(left.version, right.version)); + if (!matchingReleases.length) { + throw this.createVersionNotFoundError(range, releases.map(release => release.version), `Platform: ${platform}`); + } + return matchingReleases[0]; + } + async downloadTool(javaRelease) { + info(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`); + let javaArchivePath = await downloadTool(javaRelease.url); + info(`Extracting Java archive...`); + const extension = getDownloadArchiveExtension(); + if (process.platform === 'win32') { + javaArchivePath = renameWinArchive(javaArchivePath); + } + const extractedJavaPath = await extractJdkFile(javaArchivePath, extension); + const archiveName = external_fs_default().readdirSync(extractedJavaPath)[0]; + const archivePath = external_path_default().join(extractedJavaPath, archiveName); + const javaPath = await cacheDir(archivePath, this.toolcacheFolderName, this.getToolcacheVersionName(javaRelease.version), this.architecture); + return { version: javaRelease.version, path: javaPath }; + } + async getAvailableVersions(platform, arch) { + 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); + } + async fetchPage(url) { + const response = await this.http.get(url); + return response.readBody(); + } + parseReleases(html, platform, arch) { + const extension = platform === 'windows' ? 'zip' : 'tar.gz'; + const pattern = new RegExp(`href="(https://download\\.java\\.net/[^"]+/openjdk-([^"_]+)_${platform}-${arch}_bin\\.${extension.replaceAll('.', '\\.')})"`, 'g'); + return Array.from(html.matchAll(pattern), match => ({ + version: this.toSemver(match[2]), + url: match[1] + })); + } + toSemver(version) { + const [javaVersion, build] = version.replace('-ea', '').split('+'); + const normalizedVersion = javaVersion.includes('.') + ? javaVersion + : `${javaVersion}.0.0`; + return build ? `${normalizedVersion}+${build}` : normalizedVersion; + } + getPlatform(platform = process.platform) { + 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'`); + } + } +} + ;// CONCATENATED MODULE: ./src/distributions/distribution-factory.ts @@ -131890,6 +131978,7 @@ class KonaDistribution extends JavaBase { + var JavaDistribution; (function (JavaDistribution) { JavaDistribution["Adopt"] = "adopt"; @@ -131910,6 +131999,7 @@ var JavaDistribution; JavaDistribution["GraalVMCommunity"] = "graalvm-community"; JavaDistribution["JetBrains"] = "jetbrains"; JavaDistribution["Kona"] = "kona"; + JavaDistribution["OpenJdk"] = "openjdk"; })(JavaDistribution || (JavaDistribution = {})); function getJavaDistribution(distributionName, installerOptions, jdkFile) { switch (distributionName) { @@ -131948,6 +132038,8 @@ function getJavaDistribution(distributionName, installerOptions, jdkFile) { return new JetBrainsDistribution(installerOptions); case JavaDistribution.Kona: return new KonaDistribution(installerOptions); + case JavaDistribution.OpenJdk: + return new OpenJdkDistribution(installerOptions); default: return null; } diff --git a/src/distributions/distribution-factory.ts b/src/distributions/distribution-factory.ts index 7ad5db03f..fe53b2e95 100644 --- a/src/distributions/distribution-factory.ts +++ b/src/distributions/distribution-factory.ts @@ -21,6 +21,7 @@ import { } from './graalvm/installer.js'; import {JetBrainsDistribution} from './jetbrains/installer.js'; import {KonaDistribution} from './kona/installer.js'; +import {OpenJdkDistribution} from './openjdk/installer.js'; enum JavaDistribution { Adopt = 'adopt', @@ -40,7 +41,8 @@ enum JavaDistribution { GraalVM = 'graalvm', GraalVMCommunity = 'graalvm-community', JetBrains = 'jetbrains', - Kona = 'kona' + Kona = 'kona', + OpenJdk = 'openjdk' } export function getJavaDistribution( @@ -93,6 +95,8 @@ export function getJavaDistribution( return new JetBrainsDistribution(installerOptions); case JavaDistribution.Kona: return new KonaDistribution(installerOptions); + case JavaDistribution.OpenJdk: + return new OpenJdkDistribution(installerOptions); default: return null; } diff --git a/src/distributions/openjdk/installer.ts b/src/distributions/openjdk/installer.ts new file mode 100644 index 000000000..7c225172f --- /dev/null +++ b/src/distributions/openjdk/installer.ts @@ -0,0 +1,153 @@ +import * as core from '@actions/core'; +import * as tc from '@actions/tool-cache'; +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 { + extractJdkFile, + getDownloadArchiveExtension, + isVersionSatisfies, + renameWinArchive +} from '../../util.js'; + +const OPENJDK_BASE_URL = 'https://jdk.java.net'; + +export class OpenJdkDistribution extends JavaBase { + constructor(installerOptions: JavaInstallerOptions) { + super('OpenJDK', installerOptions); + } + + protected async findPackageForDownload( + range: string + ): Promise { + if (this.packageType !== 'jdk') { + throw new Error('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}` + ); + } + + return matchingReleases[0]; + } + + protected async downloadTool( + javaRelease: JavaDownloadRelease + ): Promise { + core.info( + `Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...` + ); + let javaArchivePath = await tc.downloadTool(javaRelease.url); + + core.info(`Extracting Java archive...`); + const extension = getDownloadArchiveExtension(); + if (process.platform === 'win32') { + javaArchivePath = renameWinArchive(javaArchivePath); + } + const extractedJavaPath = await extractJdkFile(javaArchivePath, extension); + + const archiveName = fs.readdirSync(extractedJavaPath)[0]; + const archivePath = path.join(extractedJavaPath, archiveName); + const javaPath = await tc.cacheDir( + 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 extension = platform === 'windows' ? 'zip' : 'tar.gz'; + const pattern = new RegExp( + `href="(https://download\\.java\\.net/[^"]+/openjdk-([^"_]+)_${platform}-${arch}_bin\\.${extension.replaceAll('.', '\\.')})"`, + 'g' + ); + + return Array.from(html.matchAll(pattern), match => ({ + version: this.toSemver(match[2]), + url: match[1] + })); + } + + private toSemver(version: string): string { + const [javaVersion, build] = version.replace('-ea', '').split('+'); + const normalizedVersion = javaVersion.includes('.') + ? javaVersion + : `${javaVersion}.0.0`; + 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'` + ); + } + } +} From 2c4a2dda70f301abffea10f395f1172ffcfe5e3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 02:28:21 +0000 Subject: [PATCH 3/9] Support archived OpenJDK release formats --- .../distributors/openjdk-installer.test.ts | 41 ++++++++++++++++++- dist/setup/index.js | 31 ++++++++------ src/distributions/openjdk/installer.ts | 37 ++++++++++------- 3 files changed, 80 insertions(+), 29 deletions(-) diff --git a/__tests__/distributors/openjdk-installer.test.ts b/__tests__/distributors/openjdk-installer.test.ts index f6714fd76..5c47f2189 100644 --- a/__tests__/distributors/openjdk-installer.test.ts +++ b/__tests__/distributors/openjdk-installer.test.ts @@ -46,6 +46,7 @@ const earlyAccessPage = ` const archivePage = ` tar.gz tar.gz + tar.gz `; function createDistribution( @@ -88,7 +89,7 @@ describe('OpenJdkDistribution', () => { const result = await createDistribution()['findPackageForDownload']('26'); expect(result).toEqual({ - version: '26.0.2', + version: '26.0.2+10', url: 'https://download.java.net/java/GA/jdk26.0.2/hash/10/GPL/openjdk-26.0.2_linux-x64_bin.tar.gz' }); }); @@ -97,10 +98,29 @@ describe('OpenJdkDistribution', () => { const result = await createDistribution('26.0.1')['findPackageForDownload']('26.0.1'); - expect(result.version).toBe('26.0.1'); + expect(result.version).toBe('26.0.1+8'); expect(result.url).toContain('/openjdk-26.0.1_linux-x64_bin.tar.gz'); }); + it('resolves an exact GA build', async () => { + const result = + await createDistribution('26.0.2+10')['findPackageForDownload']( + '26.0.2+10' + ); + + expect(result.version).toBe('26.0.2+10'); + }); + + it('resolves a four-field Java version', async () => { + const result = + await createDistribution('18.0.1.1')['findPackageForDownload']( + '18.0.1+1' + ); + + expect(result.version).toBe('18.0.1+1'); + expect(result.url).toContain('/openjdk-18.0.1.1_linux-x64_bin.tar.gz'); + }); + it('resolves an early-access release without requesting the archive', async () => { const result = await createDistribution('27-ea')['findPackageForDownload']('27'); @@ -148,6 +168,23 @@ describe('OpenJdkDistribution', () => { ); }); + it('parses legacy platform names and archive formats', () => { + const distribution = createDistribution(); + const macRelease = distribution['parseReleases']( + 'tar.gz', + 'macos', + 'x64' + ); + const windowsRelease = distribution['parseReleases']( + 'tar.gz', + 'windows', + 'x64' + ); + + expect(macRelease[0].version).toBe('16.0.0+7'); + expect(windowsRelease[0].url.endsWith('.tar.gz')).toBe(true); + }); + it('is registered in the distribution factory', () => { const distribution = getJavaDistribution('openjdk', { version: '26', diff --git a/dist/setup/index.js b/dist/setup/index.js index 58dbe7b6c..55831b10f 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -131909,8 +131909,8 @@ class OpenJdkDistribution extends JavaBase { info(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`); let javaArchivePath = await downloadTool(javaRelease.url); info(`Extracting Java archive...`); - const extension = getDownloadArchiveExtension(); - if (process.platform === 'win32') { + const extension = javaRelease.url.endsWith('.zip') ? 'zip' : 'tar.gz'; + if (extension === 'zip') { javaArchivePath = renameWinArchive(javaArchivePath); } const extractedJavaPath = await extractJdkFile(javaArchivePath, extension); @@ -131934,18 +131934,23 @@ class OpenJdkDistribution extends JavaBase { return response.readBody(); } parseReleases(html, platform, arch) { - const extension = platform === 'windows' ? 'zip' : 'tar.gz'; - const pattern = new RegExp(`href="(https://download\\.java\\.net/[^"]+/openjdk-([^"_]+)_${platform}-${arch}_bin\\.${extension.replaceAll('.', '\\.')})"`, 'g'); - return Array.from(html.matchAll(pattern), match => ({ - version: this.toSemver(match[2]), - url: match[1] - })); + 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]; + return { + version: this.toSemver(match[2], build), + url + }; + }); } - toSemver(version) { - const [javaVersion, build] = version.replace('-ea', '').split('+'); - const normalizedVersion = javaVersion.includes('.') - ? javaVersion - : `${javaVersion}.0.0`; + toSemver(version, urlBuild) { + 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; } getPlatform(platform = process.platform) { diff --git a/src/distributions/openjdk/installer.ts b/src/distributions/openjdk/installer.ts index 7c225172f..21b4f7f73 100644 --- a/src/distributions/openjdk/installer.ts +++ b/src/distributions/openjdk/installer.ts @@ -11,8 +11,8 @@ import { JavaInstallerResults } from '../base-models.js'; import { + convertVersionToSemver, extractJdkFile, - getDownloadArchiveExtension, isVersionSatisfies, renameWinArchive } from '../../util.js'; @@ -62,8 +62,8 @@ export class OpenJdkDistribution extends JavaBase { let javaArchivePath = await tc.downloadTool(javaRelease.url); core.info(`Extracting Java archive...`); - const extension = getDownloadArchiveExtension(); - if (process.platform === 'win32') { + const extension = javaRelease.url.endsWith('.zip') ? 'zip' : 'tar.gz'; + if (extension === 'zip') { javaArchivePath = renameWinArchive(javaArchivePath); } const extractedJavaPath = await extractJdkFile(javaArchivePath, extension); @@ -116,23 +116,32 @@ export class OpenJdkDistribution extends JavaBase { platform: string, arch: string ): JavaDownloadRelease[] { - const extension = platform === 'windows' ? 'zip' : 'tar.gz'; + 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-([^"_]+)_${platform}-${arch}_bin\\.${extension.replaceAll('.', '\\.')})"`, + `href="(https://download\\.java\\.net/[^"]+/openjdk-([^"_]+)_${platformPattern}-${arch}_bin\\.${extensionPattern})"`, 'g' ); - return Array.from(html.matchAll(pattern), match => ({ - version: this.toSemver(match[2]), - url: match[1] - })); + return Array.from(html.matchAll(pattern), match => { + const url = match[1]; + const build = url.match(/\/(\d+)\/GPL\/openjdk-/)?.[1]; + return { + version: this.toSemver(match[2], build), + url + }; + }); } - private toSemver(version: string): string { - const [javaVersion, build] = version.replace('-ea', '').split('+'); - const normalizedVersion = javaVersion.includes('.') - ? javaVersion - : `${javaVersion}.0.0`; + 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; } From cb48866ec38a9f0ac2d71e4ae4adcfb0fc69c303 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 02:35:20 +0000 Subject: [PATCH 4/9] Handle legacy OpenJDK URL layout --- __tests__/distributors/openjdk-installer.test.ts | 3 ++- dist/setup/index.js | 2 +- src/distributions/openjdk/installer.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/__tests__/distributors/openjdk-installer.test.ts b/__tests__/distributors/openjdk-installer.test.ts index 5c47f2189..b2799e613 100644 --- a/__tests__/distributors/openjdk-installer.test.ts +++ b/__tests__/distributors/openjdk-installer.test.ts @@ -176,12 +176,13 @@ describe('OpenJdkDistribution', () => { 'x64' ); const windowsRelease = distribution['parseReleases']( - 'tar.gz', + 'tar.gz', 'windows', 'x64' ); expect(macRelease[0].version).toBe('16.0.0+7'); + expect(windowsRelease[0].version).toBe('10.0.2+13'); expect(windowsRelease[0].url.endsWith('.tar.gz')).toBe(true); }); diff --git a/dist/setup/index.js b/dist/setup/index.js index 55831b10f..269d760fb 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -131939,7 +131939,7 @@ class OpenJdkDistribution extends JavaBase { 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]; + const build = url.match(/\/(\d+)\/(?:GPL\/)?openjdk-/)?.[1]; return { version: this.toSemver(match[2], build), url diff --git a/src/distributions/openjdk/installer.ts b/src/distributions/openjdk/installer.ts index 21b4f7f73..5776cddc9 100644 --- a/src/distributions/openjdk/installer.ts +++ b/src/distributions/openjdk/installer.ts @@ -126,7 +126,7 @@ export class OpenJdkDistribution extends JavaBase { return Array.from(html.matchAll(pattern), match => { const url = match[1]; - const build = url.match(/\/(\d+)\/GPL\/openjdk-/)?.[1]; + const build = url.match(/\/(\d+)\/(?:GPL\/)?openjdk-/)?.[1]; return { version: this.toSemver(match[2], build), url From 333d9ce35a0bb902d48720da8a0d9fe9647b0ba9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 02:40:37 +0000 Subject: [PATCH 5/9] Resolve legacy OpenJDK build metadata --- .../distributors/openjdk-installer.test.ts | 12 +++++++++++ dist/setup/index.js | 12 ++++++++++- src/distributions/openjdk/installer.ts | 21 ++++++++++++++++++- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/__tests__/distributors/openjdk-installer.test.ts b/__tests__/distributors/openjdk-installer.test.ts index b2799e613..7abf64ed4 100644 --- a/__tests__/distributors/openjdk-installer.test.ts +++ b/__tests__/distributors/openjdk-installer.test.ts @@ -47,6 +47,8 @@ const archivePage = ` tar.gz tar.gz tar.gz + 9.0.4 (build 9.0.4+11) + tar.gz `; function createDistribution( @@ -111,6 +113,16 @@ describe('OpenJdkDistribution', () => { expect(result.version).toBe('26.0.2+10'); }); + it('resolves an exact build from a legacy archive heading', async () => { + const result = + await createDistribution('9.0.4+11')['findPackageForDownload']( + '9.0.4+11' + ); + + expect(result.version).toBe('9.0.4+11'); + expect(result.url).toContain('/binaries/openjdk-9.0.4_linux-x64_bin'); + }); + it('resolves a four-field Java version', async () => { const result = await createDistribution('18.0.1.1')['findPackageForDownload']( diff --git a/dist/setup/index.js b/dist/setup/index.js index 269d760fb..76f07358e 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -131939,13 +131939,23 @@ class OpenJdkDistribution extends JavaBase { 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]; + const build = url.match(/\/(\d+)\/(?:GPL\/)?openjdk-/)?.[1] ?? + this.findBuildInArchiveHeading(html, match.index, match[2]); return { version: this.toSemver(match[2], build), url }; }); } + findBuildInArchiveHeading(html, assetIndex, version) { + 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; + } toSemver(version, urlBuild) { const [javaVersion, filenameBuild] = version.replace('-ea', '').split('+'); const versionParts = javaVersion.split('.'); diff --git a/src/distributions/openjdk/installer.ts b/src/distributions/openjdk/installer.ts index 5776cddc9..3041a5630 100644 --- a/src/distributions/openjdk/installer.ts +++ b/src/distributions/openjdk/installer.ts @@ -126,7 +126,9 @@ export class OpenJdkDistribution extends JavaBase { return Array.from(html.matchAll(pattern), match => { const url = match[1]; - const build = url.match(/\/(\d+)\/(?:GPL\/)?openjdk-/)?.[1]; + const build = + url.match(/\/(\d+)\/(?:GPL\/)?openjdk-/)?.[1] ?? + this.findBuildInArchiveHeading(html, match.index, match[2]); return { version: this.toSemver(match[2], build), url @@ -134,6 +136,23 @@ export class OpenJdkDistribution extends JavaBase { }); } + 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('.'); From 25b4db53ec407ee23b7a3e98db2b719ff34ff562 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 28 Jul 2026 15:21:33 -0400 Subject: [PATCH 6/9] Rename OpenJDK distribution to oracle-openjdk Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2c93ae0c-bbf5-40f5-bf6e-40168d0e267f --- README.md | 3 ++- __tests__/distributors/openjdk-installer.test.ts | 8 ++++---- dist/setup/index.js | 8 ++++---- src/distributions/distribution-factory.ts | 4 ++-- src/distributions/openjdk/installer.ts | 4 ++-- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 96b92b6c7..89d774467 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ Currently, the following distributions are supported: | `corretto` | [Amazon Corretto Build of OpenJDK](https://aws.amazon.com/corretto/) | [`corretto` license](https://aws.amazon.com/corretto/faqs/) | `semeru` | [IBM Semeru Runtime Open Edition](https://developer.ibm.com/languages/java/semeru-runtimes/downloads/) | [`semeru` license](https://openjdk.java.net/legal/gplv2+ce.html) | | `oracle` | [Oracle JDK](https://www.oracle.com/java/technologies/downloads/) | [`oracle` license](https://java.com/freeuselicense) -| `openjdk` | [OpenJDK](https://jdk.java.net/) | [`openjdk` license](https://openjdk.org/legal/gplv2+ce.html) +| `oracle-openjdk` | [Oracle OpenJDK](https://jdk.java.net/) | [`oracle-openjdk` license](https://openjdk.org/legal/gplv2+ce.html) | `dragonwell` | [Alibaba Dragonwell JDK](https://dragonwell-jdk.io/) | [`dragonwell` license](https://www.aliyun.com/product/dragonwell/) | `sapmachine` | [SAP SapMachine JDK/JRE](https://sapmachine.io/) | [`sapmachine` license](https://github.com/SAP/SapMachine/blob/sapmachine/LICENSE) | `graalvm` | [Oracle GraalVM](https://www.graalvm.org/) | [`graalvm` license](https://www.oracle.com/downloads/licenses/graal-free-license.html) @@ -160,6 +160,7 @@ Currently, the following distributions are supported: > [!NOTE] > - The different distributors can provide discrepant list of available versions / supported configurations. Please refer to the official documentation to see the list of supported versions. > - AdoptOpenJDK got moved to Eclipse Temurin and won't be updated anymore. It is highly recommended to migrate workflows from `adopt` and `adopt-openj9`, to `temurin` and `semeru` respectively, to keep receiving software and security updates. See more details in the [Good-bye AdoptOpenJDK post](https://blog.adoptopenjdk.net/2021/08/goodbye-adoptopenjdk-hello-adoptium/). +> - Oracle OpenJDK builds are created and hosted by Oracle under GPLv2+CE. After a limited number of releases, Oracle archives these builds and no longer provides security updates. To continue receiving security patches, users must move to Oracle JDK or choose a different vendor. > - For Azul Zulu OpenJDK, architecture `arm64` is mapped to `aarch64` when querying the Azul Metadata API. > - To comply with the GraalVM Free Terms and Conditions (GFTC) license, it is recommended to use GraalVM JDK 17 version 17.0.12, as this is the only version of GraalVM JDK 17 available under the GFTC license. Additionally, it is encouraged to consider upgrading to GraalVM JDK 21, which offers the latest features and improvements. > - GraalVM Community is available as `distribution: 'graalvm-community'` for stable JDK 17 and later releases published on GitHub. diff --git a/__tests__/distributors/openjdk-installer.test.ts b/__tests__/distributors/openjdk-installer.test.ts index 7abf64ed4..dfd3f8656 100644 --- a/__tests__/distributors/openjdk-installer.test.ts +++ b/__tests__/distributors/openjdk-installer.test.ts @@ -148,13 +148,13 @@ describe('OpenJdkDistribution', () => { await expect( createDistribution()['findPackageForDownload']('24') ).rejects.toThrow( - "No matching version found for SemVer '24'.\nDistribution: OpenJDK" + "No matching version found for SemVer '24'.\nDistribution: Oracle OpenJDK" ); }); it.each([ - ['jre', 'OpenJDK provides only the `jdk` package type'], - ['jdk+fx', 'OpenJDK provides only the `jdk` package type'] + ['jre', 'Oracle OpenJDK provides only the `jdk` package type'], + ['jdk+fx', 'Oracle OpenJDK provides only the `jdk` package type'] ])('rejects the %s package type', async (packageType, message) => { await expect( createDistribution('26', 'x64', packageType)['findPackageForDownload']( @@ -199,7 +199,7 @@ describe('OpenJdkDistribution', () => { }); it('is registered in the distribution factory', () => { - const distribution = getJavaDistribution('openjdk', { + const distribution = getJavaDistribution('oracle-openjdk', { version: '26', architecture: 'x64', packageType: 'jdk', diff --git a/dist/setup/index.js b/dist/setup/index.js index fa43d0435..d311f7fe7 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -131893,11 +131893,11 @@ class KonaDistribution extends JavaBase { const OPENJDK_BASE_URL = 'https://jdk.java.net'; class OpenJdkDistribution extends JavaBase { constructor(installerOptions) { - super('OpenJDK', installerOptions); + super('Oracle OpenJDK', installerOptions); } async findPackageForDownload(range) { if (this.packageType !== 'jdk') { - throw new Error('OpenJDK provides only the `jdk` package type'); + throw new Error('Oracle OpenJDK provides only the `jdk` package type'); } const arch = this.distributionArchitecture(); if (!['x64', 'aarch64'].includes(arch)) { @@ -132022,7 +132022,7 @@ var JavaDistribution; JavaDistribution["GraalVMCommunity"] = "graalvm-community"; JavaDistribution["JetBrains"] = "jetbrains"; JavaDistribution["Kona"] = "kona"; - JavaDistribution["OpenJdk"] = "openjdk"; + JavaDistribution["OracleOpenJdk"] = "oracle-openjdk"; })(JavaDistribution || (JavaDistribution = {})); function getJavaDistribution(distributionName, installerOptions, jdkFile) { switch (distributionName) { @@ -132061,7 +132061,7 @@ function getJavaDistribution(distributionName, installerOptions, jdkFile) { return new JetBrainsDistribution(installerOptions); case JavaDistribution.Kona: return new KonaDistribution(installerOptions); - case JavaDistribution.OpenJdk: + case JavaDistribution.OracleOpenJdk: return new OpenJdkDistribution(installerOptions); default: return null; diff --git a/src/distributions/distribution-factory.ts b/src/distributions/distribution-factory.ts index fe53b2e95..d27844eed 100644 --- a/src/distributions/distribution-factory.ts +++ b/src/distributions/distribution-factory.ts @@ -42,7 +42,7 @@ enum JavaDistribution { GraalVMCommunity = 'graalvm-community', JetBrains = 'jetbrains', Kona = 'kona', - OpenJdk = 'openjdk' + OracleOpenJdk = 'oracle-openjdk' } export function getJavaDistribution( @@ -95,7 +95,7 @@ export function getJavaDistribution( return new JetBrainsDistribution(installerOptions); case JavaDistribution.Kona: return new KonaDistribution(installerOptions); - case JavaDistribution.OpenJdk: + case JavaDistribution.OracleOpenJdk: return new OpenJdkDistribution(installerOptions); default: return null; diff --git a/src/distributions/openjdk/installer.ts b/src/distributions/openjdk/installer.ts index 3041a5630..8c8abea69 100644 --- a/src/distributions/openjdk/installer.ts +++ b/src/distributions/openjdk/installer.ts @@ -21,14 +21,14 @@ const OPENJDK_BASE_URL = 'https://jdk.java.net'; export class OpenJdkDistribution extends JavaBase { constructor(installerOptions: JavaInstallerOptions) { - super('OpenJDK', installerOptions); + super('Oracle OpenJDK', installerOptions); } protected async findPackageForDownload( range: string ): Promise { if (this.packageType !== 'jdk') { - throw new Error('OpenJDK provides only the `jdk` package type'); + throw new Error('Oracle OpenJDK provides only the `jdk` package type'); } const arch = this.distributionArchitecture(); From 3f1ca98f01541b29ac3c98f5dda2315e57ca52b6 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 28 Jul 2026 15:22:45 -0400 Subject: [PATCH 7/9] Make OpenJDK tests platform independent Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2c93ae0c-bbf5-40f5-bf6e-40168d0e267f --- __tests__/distributors/openjdk-installer.test.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/__tests__/distributors/openjdk-installer.test.ts b/__tests__/distributors/openjdk-installer.test.ts index dfd3f8656..48720a410 100644 --- a/__tests__/distributors/openjdk-installer.test.ts +++ b/__tests__/distributors/openjdk-installer.test.ts @@ -54,14 +54,19 @@ const archivePage = ` function createDistribution( version = '26', architecture = 'x64', - packageType = 'jdk' + packageType = 'jdk', + useFixturePlatform = true ) { - return new OpenJdkDistribution({ + const distribution = new OpenJdkDistribution({ version, architecture, packageType, checkLatest: false }); + if (useFixturePlatform) { + distribution['getPlatform'] = jest.fn(() => 'linux'); + } + return distribution; } describe('OpenJdkDistribution', () => { @@ -170,7 +175,7 @@ describe('OpenJdkDistribution', () => { }); it('maps supported platforms', () => { - const distribution = createDistribution(); + const distribution = createDistribution('26', 'x64', 'jdk', false); expect(distribution['getPlatform']('linux')).toBe('linux'); expect(distribution['getPlatform']('darwin')).toBe('macos'); From 8bad58b3f5f475364df864da18a44505f9d235bb Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 28 Jul 2026 15:32:55 -0400 Subject: [PATCH 8/9] Document Oracle OpenJDK early access builds Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2c93ae0c-bbf5-40f5-bf6e-40168d0e267f --- docs/advanced-usage.md | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 36549c668..6ef042a0f 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -154,6 +154,21 @@ steps: - run: java --version ``` +### Oracle OpenJDK +Oracle OpenJDK builds are created and hosted by Oracle under GPLv2+CE. To install the latest early-access build for a feature release, append `-ea` to the Java version: + +```yaml +steps: + - uses: actions/checkout@v7 + - uses: actions/setup-java@v6 + with: + distribution: 'oracle-openjdk' + java-version: '27-ea' + - run: java --version +``` + +Using `27` without the `-ea` suffix selects a stable (GA) release. Oracle archives OpenJDK builds after a limited number of releases and no longer provides security updates for them. To continue receiving security patches, move to Oracle JDK or choose a different vendor. + ### Alibaba Dragonwell **NOTE:** Alibaba Dragonwell only provides jdk. @@ -469,7 +484,7 @@ In this example, `JAVA_HOME` and `java` on `PATH` point to Java 17, while Java 2 If your use-case requires a custom distribution or a version that is not provided by setup-java, you can download it manually and setup-java will take care of the installation and caching on the VM: > [!NOTE] -> This approach also lets you use builds that setup-java does not provide directly, such as **Early Access (EA)** or other unreleased JDK builds (for example, an upcoming feature release or a Loom/Valhalla preview build). Download the desired archive in a prior step and point `jdk-file` at it; setup-java will extract, install, and cache it just like a supported distribution. When targeting multiple architectures, select the correct binary per architecture in your workflow (for example, with a build matrix). +> This approach also lets you use builds that setup-java does not provide directly, such as unreleased Loom/Valhalla preview builds or early-access builds not exposed by a supported distribution. Download the desired archive in a prior step and point `jdk-file` at it; setup-java will extract, install, and cache it just like a supported distribution. When targeting multiple architectures, select the correct binary per architecture in your workflow (for example, with a build matrix). ```yaml steps: @@ -486,23 +501,6 @@ steps: - run: java --version ``` -For example, to use an **Early Access** build from [jdk.java.net](https://jdk.java.net/), download the archive for your runner OS/architecture and install it via `distribution: 'jdkfile'` (example below assumes Linux x64): - -```yaml -steps: - - run: | - download_url="https://download.java.net/java/early_access/jdk25/36/GPL/openjdk-25-ea+36_linux-x64_bin.tar.gz" - wget -O $RUNNER_TEMP/java_package.tar.gz $download_url - - uses: actions/setup-java@v6 - with: - distribution: 'jdkfile' - jdk-file: ${{ runner.temp }}/java_package.tar.gz - java-version: '25.0.0-ea.36' - architecture: x64 - - - run: java --version -``` - If your use-case requires a custom distribution (in the example, alpine-linux is used) or a version that is not provided by setup-java and you want to always install the latest version during runtime, then you can use the following code to auto-download the latest JDK, determine the semver needed for setup-java, and setup-java will take care of the installation and caching on the VM: ```yaml From c9ff0d564a347a8d1311dbeb8b733256d5cbedc8 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 28 Jul 2026 15:49:36 -0400 Subject: [PATCH 9/9] Clarify Oracle OpenJDK security note Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2c93ae0c-bbf5-40f5-bf6e-40168d0e267f --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 89d774467..ded2fa82d 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ Currently, the following distributions are supported: > [!NOTE] > - The different distributors can provide discrepant list of available versions / supported configurations. Please refer to the official documentation to see the list of supported versions. > - AdoptOpenJDK got moved to Eclipse Temurin and won't be updated anymore. It is highly recommended to migrate workflows from `adopt` and `adopt-openj9`, to `temurin` and `semeru` respectively, to keep receiving software and security updates. See more details in the [Good-bye AdoptOpenJDK post](https://blog.adoptopenjdk.net/2021/08/goodbye-adoptopenjdk-hello-adoptium/). -> - Oracle OpenJDK builds are created and hosted by Oracle under GPLv2+CE. After a limited number of releases, Oracle archives these builds and no longer provides security updates. To continue receiving security patches, users must move to Oracle JDK or choose a different vendor. +> - Oracle OpenJDK builds are created and hosted by Oracle. After a limited number of releases, Oracle archives these builds and no longer provides security updates. To continue receiving security patches, users must move to Oracle JDK or choose a different vendor. > - For Azul Zulu OpenJDK, architecture `arm64` is mapped to `aarch64` when querying the Azul Metadata API. > - To comply with the GraalVM Free Terms and Conditions (GFTC) license, it is recommended to use GraalVM JDK 17 version 17.0.12, as this is the only version of GraalVM JDK 17 available under the GFTC license. Additionally, it is encouraged to consider upgrading to GraalVM JDK 21, which offers the latest features and improvements. > - GraalVM Community is available as `distribution: 'graalvm-community'` for stable JDK 17 and later releases published on GitHub.