Add Temurin JMOD installation support

This commit is contained in:
copilot-swe-agent[bot]
2026-07-28 20:46:08 +00:00
committed by GitHub
parent 98b0c8a4ef
commit 7f1a95c821
7 changed files with 197 additions and 30 deletions
@@ -169,6 +169,28 @@ describe('getAvailableVersions', () => {
}
);
it('requests the JMOD image type', async () => {
const distribution = new TemurinDistribution(
{
version: '25',
architecture: 'x64',
packageType: 'jdk',
jmod: true,
checkLatest: false
},
TemurinImplementation.Hotspot
);
distribution['getPlatformOption'] = () => 'linux';
await distribution['getAvailableVersions']('jmods');
expect(spyHttpClient).toHaveBeenCalledWith(
expect.stringContaining(
'os=linux&architecture=x64&image_type=jmods&release_type=ga'
)
);
});
it('load available versions', async () => {
const nextPageUrl =
'https://api.adoptium.net/v3/assets/version/%5B1.0,100.0%5D?page=1&page_size=20';
@@ -228,13 +250,20 @@ describe('getAvailableVersions', () => {
});
it.each([
[TemurinImplementation.Hotspot, 'jdk', 'Java_Temurin-Hotspot_jdk'],
[TemurinImplementation.Hotspot, 'jre', 'Java_Temurin-Hotspot_jre']
[TemurinImplementation.Hotspot, 'jdk', false, 'Java_Temurin-Hotspot_jdk'],
[TemurinImplementation.Hotspot, 'jre', false, 'Java_Temurin-Hotspot_jre'],
[
TemurinImplementation.Hotspot,
'jdk',
true,
'Java_Temurin-Hotspot_jdk_jmods'
]
])(
'find right toolchain folder',
(
impl: TemurinImplementationType,
packageType: string,
jmod: boolean,
expected: string
) => {
const distribution = new TemurinDistribution(
@@ -242,6 +271,7 @@ describe('getAvailableVersions', () => {
version: '8',
architecture: 'x64',
packageType: packageType,
jmod,
checkLatest: false
},
impl
@@ -252,6 +282,24 @@ describe('getAvailableVersions', () => {
}
);
it('rejects JMODs with a non-JDK package', () => {
expect(
() =>
new TemurinDistribution(
{
version: '25',
architecture: 'x64',
packageType: 'jre',
jmod: true,
checkLatest: false
},
TemurinImplementation.Hotspot
)
).toThrow(
"Input 'jmod' is only supported with Temurin java-package 'jdk'."
);
});
it.each([
['amd64', 'x64'],
['arm64', 'aarch64']
@@ -386,6 +434,7 @@ describe('downloadTool', () => {
let spyCacheDir: any;
let spyReadDirSync: any;
let spyRenameWinArchive: any;
let spyCopySync: any;
beforeEach(() => {
spyDownloadTool = tc.downloadTool as jest.Mock;
@@ -400,6 +449,8 @@ describe('downloadTool', () => {
spyReadDirSync.mockReturnValue(['jdk-17'] as any);
spyRenameWinArchive = util.renameWinArchive as jest.Mock;
spyRenameWinArchive.mockReturnValue('/tmp/jdk.tar.gz.zip');
spyCopySync = jest.spyOn(fs, 'cpSync');
spyCopySync.mockImplementation(() => undefined);
});
afterEach(() => {
@@ -433,6 +484,59 @@ describe('downloadTool', () => {
);
});
it('downloads and adds matching JMODs to the JDK', async () => {
spyDownloadTool
.mockResolvedValueOnce('/tmp/jdk.tar.gz')
.mockResolvedValueOnce('/tmp/jmods.tar.gz');
spyExtractJdkFile
.mockResolvedValueOnce('/tmp/extracted')
.mockResolvedValueOnce('/tmp/extracted-jmods');
spyReadDirSync
.mockReturnValueOnce(['jdk-25'] as any)
.mockReturnValueOnce(['jdk-25-jmods'] as any);
jest.spyOn(fs, 'existsSync').mockReturnValue(false);
const distribution = new TemurinDistribution(
{
version: '25',
architecture: 'x64',
packageType: 'jdk',
jmod: true,
checkLatest: false
},
TemurinImplementation.Hotspot
);
distribution['resolvePackage'] = jest.fn().mockResolvedValue({
version: '25.0.3+9',
url: 'https://example.com/jmods.tar.gz'
});
await distribution['downloadTool']({
version: '25.0.3+9',
url: 'https://example.com/jdk.tar.gz'
});
expect(distribution['resolvePackage']).toHaveBeenCalledWith(
'25.0.3+9',
'jmods'
);
expect(spyDownloadTool).toHaveBeenNthCalledWith(
2,
'https://example.com/jmods.tar.gz'
);
expect(spyCopySync).toHaveBeenCalledWith(
'/tmp/extracted-jmods/jdk-25-jmods',
'/tmp/extracted/jdk-25/jmods',
{recursive: true}
);
expect(spyCacheDir).toHaveBeenCalledWith(
'/tmp/extracted/jdk-25',
'Java_Temurin-Hotspot_jdk_jmods',
'25.0.3-9',
'x64'
);
});
it('fails when signature is missing and verification is enabled', async () => {
const distribution = new TemurinDistribution(
{
+4
View File
@@ -16,6 +16,10 @@ inputs:
description: 'The package type (jdk, jre, jdk+fx, jre+fx, jdk+crac, jre+crac)'
required: false
default: 'jdk'
jmod:
description: 'Set this option to true to include JMOD files with Temurin JDK 24 and later'
required: false
default: false
architecture:
description: "The architecture of the package (defaults to the action runner's architecture)"
required: false
+1
View File
@@ -44,6 +44,7 @@ steps:
with:
distribution: 'temurin'
java-version: '25'
jmod: true # optional, includes JMOD files with JDK 24 and later
- run: java --version
```
+1
View File
@@ -3,6 +3,7 @@ export const INPUT_JAVA_VERSION = 'java-version';
export const INPUT_JAVA_VERSION_FILE = 'java-version-file';
export const INPUT_ARCHITECTURE = 'architecture';
export const INPUT_JAVA_PACKAGE = 'java-package';
export const INPUT_JMOD = 'jmod';
export const INPUT_DISTRIBUTION = 'distribution';
export const INPUT_JDK_FILE = 'jdk-file';
export const INPUT_JDK_FILE_DEPRECATED = 'jdkFile';
+1
View File
@@ -2,6 +2,7 @@ export interface JavaInstallerOptions {
version: string;
architecture: string;
packageType: string;
jmod?: boolean;
checkLatest: boolean;
forceDownload?: boolean;
setDefault?: boolean;
+78 -28
View File
@@ -9,6 +9,7 @@ import * as gpg from '../../gpg.js';
import {ADOPTIUM_PUBLIC_KEY} from './adoptium-key.js';
import {JavaBase} from '../base-installer.js';
import {ITemurinAvailableVersions} from './models.js';
import {MACOS_JAVA_CONTENT_POSTFIX} from '../../constants.js';
import {
JavaDownloadRelease,
JavaInstallerOptions,
@@ -31,11 +32,19 @@ export enum TemurinImplementation {
}
export class TemurinDistribution extends JavaBase {
private readonly jmod: boolean;
constructor(
installerOptions: JavaInstallerOptions,
private readonly jvmImpl: TemurinImplementation
) {
super(`Temurin-${jvmImpl}`, installerOptions);
this.jmod = installerOptions.jmod ?? false;
if (this.jmod && this.packageType !== 'jdk') {
throw new Error(
`Input 'jmod' is only supported with Temurin java-package 'jdk'.`
);
}
}
/**
@@ -44,7 +53,14 @@ export class TemurinDistribution extends JavaBase {
public async findPackageForDownload(
version: string
): Promise<JavaDownloadRelease> {
const availableVersionsRaw = await this.getAvailableVersions();
return this.resolvePackage(version, this.packageType);
}
private async resolvePackage(
version: string,
imageType: string
): Promise<JavaDownloadRelease> {
const availableVersionsRaw = await this.getAvailableVersions(imageType);
const availableVersionsWithBinaries = availableVersionsRaw
.filter(item => item.binaries.length > 0)
.map(item => {
@@ -83,30 +99,7 @@ export class TemurinDistribution extends JavaBase {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
if (this.verifySignature) {
if (!javaRelease.signatureUrl) {
throw new Error(
`Input 'verify-signature' is enabled, but no signature URL was found for Temurin version ${javaRelease.version}.`
);
}
core.info(`Verifying Java package signature...`);
try {
await gpg.verifyPackageSignature(
javaArchivePath,
javaRelease.signatureUrl,
this.verifySignaturePublicKey ?? ADOPTIUM_PUBLIC_KEY
);
} catch (error) {
throw new Error(
`Failed to verify signature for Temurin version ${javaRelease.version} from ${javaRelease.signatureUrl}: ${
(error as Error).message
}`,
{cause: error}
);
}
}
let javaArchivePath = await this.downloadPackage(javaRelease);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
@@ -117,6 +110,13 @@ export class TemurinDistribution extends JavaBase {
const archiveName = fs.readdirSync(extractedJavaPath)[0];
const archivePath = path.join(extractedJavaPath, archiveName);
const javaHome =
process.platform === 'darwin'
? path.join(archivePath, MACOS_JAVA_CONTENT_POSTFIX)
: archivePath;
if (this.jmod && !fs.existsSync(path.join(javaHome, 'jmods'))) {
await this.installJmods(javaRelease.version, javaHome);
}
const version = this.getToolcacheVersionName(javaRelease.version);
const javaPath = await tc.cacheDir(
@@ -130,17 +130,67 @@ export class TemurinDistribution extends JavaBase {
}
protected get toolcacheFolderName(): string {
return super.toolcacheFolderName;
return `${super.toolcacheFolderName}${this.jmod ? '_jmods' : ''}`;
}
protected supportsSignatureVerification(): boolean {
return true;
}
private async getAvailableVersions(): Promise<ITemurinAvailableVersions[]> {
private async downloadPackage(release: JavaDownloadRelease): Promise<string> {
const archivePath = await tc.downloadTool(release.url);
if (this.verifySignature) {
if (!release.signatureUrl) {
throw new Error(
`Input 'verify-signature' is enabled, but no signature URL was found for Temurin version ${release.version}.`
);
}
core.info(`Verifying Java package signature...`);
try {
await gpg.verifyPackageSignature(
archivePath,
release.signatureUrl,
this.verifySignaturePublicKey ?? ADOPTIUM_PUBLIC_KEY
);
} catch (error) {
throw new Error(
`Failed to verify signature for Temurin version ${release.version} from ${release.signatureUrl}: ${
(error as Error).message
}`,
{cause: error}
);
}
}
return archivePath;
}
private async installJmods(version: string, javaHome: string): Promise<void> {
const jmodsRelease = await this.resolvePackage(version, 'jmods');
core.info(
`Downloading JMODs ${jmodsRelease.version} (${this.distribution}) from ${jmodsRelease.url} ...`
);
let jmodsArchivePath = await this.downloadPackage(jmodsRelease);
if (process.platform === 'win32') {
jmodsArchivePath = renameWinArchive(jmodsArchivePath);
}
const extractedJmodsPath = await extractJdkFile(
jmodsArchivePath,
getDownloadArchiveExtension()
);
const jmodsDirectory = path.join(
extractedJmodsPath,
fs.readdirSync(extractedJmodsPath)[0]
);
fs.cpSync(jmodsDirectory, path.join(javaHome, 'jmods'), {recursive: true});
}
private async getAvailableVersions(
imageType = this.packageType
): Promise<ITemurinAvailableVersions[]> {
const platform = this.getPlatformOption();
const arch = this.distributionArchitecture();
const imageType = this.packageType;
const versionRange = encodeURI('[1.0,100.0]'); // retrieve all available versions
const releaseType = this.stable ? 'ga' : 'ea';
+6
View File
@@ -23,6 +23,7 @@ async function run() {
const versionFile = core.getInput(constants.INPUT_JAVA_VERSION_FILE);
const architecture = core.getInput(constants.INPUT_ARCHITECTURE);
const packageType = core.getInput(constants.INPUT_JAVA_PACKAGE);
const jmod = getBooleanInput(constants.INPUT_JMOD, false);
const jdkFile = getJdkFileInput();
const cache = core.getInput(constants.INPUT_CACHE);
const cacheDependencyPath = core.getInput(
@@ -86,6 +87,7 @@ async function run() {
const installerInputsOptions: installerInputsOptions = {
architecture,
packageType,
jmod,
checkLatest,
forceDownload,
setDefault,
@@ -106,6 +108,7 @@ async function run() {
const installerInputsOptions: installerInputsOptions = {
architecture,
packageType,
jmod,
checkLatest,
forceDownload,
setDefault,
@@ -164,6 +167,7 @@ async function installVersion(
jdkFile,
architecture,
packageType,
jmod,
checkLatest,
forceDownload,
setDefault,
@@ -175,6 +179,7 @@ async function installVersion(
const installerOptions: JavaInstallerOptions = {
architecture,
packageType,
jmod,
checkLatest,
forceDownload,
setDefault,
@@ -219,6 +224,7 @@ async function installVersion(
interface installerInputsOptions {
architecture: string;
packageType: string;
jmod: boolean;
checkLatest: boolean;
forceDownload: boolean;
setDefault: boolean;