2021-08-06 12:12:36 +02:00
import * as core from '@actions/core' ;
import fs from 'fs' ;
import path from 'path' ;
import semver from 'semver' ;
2026-07-08 14:45:00 +05:30
import * as gpg from '../../gpg.js' ;
2021-08-06 12:12:36 +02:00
2026-07-08 14:45:00 +05:30
import { ADOPTIUM_PUBLIC_KEY } from './adoptium-key.js' ;
import { JavaBase } from '../base-installer.js' ;
import { ITemurinAvailableVersions } from './models.js' ;
2026-09-03 13:27:24 -04:00
import {
MACOS_JAVA_CONTENT_POSTFIX ,
SIGNATURE_VERIFICATION_FAILURE_HELP
} from '../../constants.js' ;
2023-03-09 14:49:35 +02:00
import {
JavaDownloadRelease ,
JavaInstallerOptions ,
JavaInstallerResults
2026-07-08 14:45:00 +05:30
} from '../base-models.js' ;
2023-03-09 14:49:35 +02:00
import {
2026-08-04 23:05:06 -04:00
cacheJdkDir ,
2023-03-09 14:49:35 +02:00
extractJdkFile ,
2026-06-12 11:50:16 +01:00
getNextPageUrlFromLinkHeader ,
2023-03-09 14:49:35 +02:00
getDownloadArchiveExtension ,
2024-10-11 03:02:25 +05:30
isVersionSatisfies ,
2026-06-12 11:50:16 +01:00
renameWinArchive ,
MAX_PAGINATION_PAGES ,
validatePaginationUrl
2026-07-08 14:45:00 +05:30
} from '../../util.js' ;
2026-08-05 18:45:11 +02:00
import { isAlpineLinux } from '../platform-types.js' ;
2021-08-06 12:12:36 +02:00
2026-07-08 14:45:00 +05:30
export { ADOPTIUM_PUBLIC_KEY } from './adoptium-key.js' ;
2026-06-29 13:19:49 +01:00
2021-08-06 12:12:36 +02:00
export enum TemurinImplementation {
Hotspot = 'Hotspot'
}
export class TemurinDistribution extends JavaBase {
2026-07-28 18:39:15 -04:00
private readonly includeJmods : boolean ;
2021-08-06 12:12:36 +02:00
constructor (
installerOptions : JavaInstallerOptions ,
private readonly jvmImpl : TemurinImplementation
) {
super ( `Temurin- ${ jvmImpl } ` , installerOptions );
2026-07-28 18:39:15 -04:00
this . includeJmods = this . packageType === 'jdk+jmods' ;
2021-08-06 12:12:36 +02:00
}
2026-06-12 16:30:59 +01:00
/**
* @internal For cross-distribution reuse only. Not intended as a public API.
*/
public async findPackageForDownload (
2023-03-09 14:49:35 +02:00
version : string
) : Promise < JavaDownloadRelease > {
2026-07-28 18:39:15 -04:00
return this . resolvePackage (
version ,
this . includeJmods ? 'jdk' : this . packageType
);
}
private async resolvePackage (
version : string ,
imageType : string
) : Promise < JavaDownloadRelease > {
const availableVersionsRaw = await this . getAvailableVersions ( imageType );
2021-08-06 12:12:36 +02:00
const availableVersionsWithBinaries = availableVersionsRaw
. filter ( item => item . binaries . length > 0 )
. map ( item => {
// normalize 17.0.0-beta+33.0.202107301459 to 17.0.0+33.0.202107301459 for earlier access versions
const formattedVersion = this . stable
? item.version_data.semver
: item.version_data.semver.replace ( '-beta+' , '+' );
2026-10-02 00:09:56 -04:00
const release : JavaDownloadRelease = {
2021-08-06 12:12:36 +02:00
version : formattedVersion ,
2026-06-29 13:19:49 +01:00
url : item.binaries [ 0 ]. package . link ,
2026-07-29 04:43:56 -04:00
signatureUrl : item.binaries [ 0 ]. package . signature_link ,
checksum : {
algorithm : 'sha256' ,
value : item.binaries [ 0 ]. package . checksum ,
source : item.binaries [ 0 ]. package . checksum_link
}
2026-10-02 00:09:56 -04:00
};
return {
release ,
openjdkVersion : getOpenJdkSemverVersion ( item . version_data )
};
2021-08-06 12:12:36 +02:00
});
2026-10-02 00:09:56 -04:00
// The Adoptium API `semver` folds the JEP 322 patch field into the build
// number ('26.0.2.1+1' -> '26.0.2+101') and appends extra metadata for LTS
// releases ('25.0.4+7' -> '25.0.4+7.0.LTS'). Exact versions requested by
// users follow the OpenJDK notation instead, so also match them against a
// key derived from the OpenJDK version fields ('26.0.2+1.1', '25.0.4+7').
const isExactBuildRequest = ( semver . parse ( version ) ? . build . length ?? 0 ) > 0 ;
2021-08-06 12:12:36 +02:00
const satisfiedVersions = availableVersionsWithBinaries
2026-10-02 00:09:56 -04:00
. filter (
({ release , openjdkVersion }) =>
isVersionSatisfies ( version , release . version ) ||
( isExactBuildRequest &&
openjdkVersion !== null &&
semver . compareBuild ( version , openjdkVersion ) === 0 )
)
. map (({ release }) => release )
2021-08-06 12:12:36 +02:00
. sort (( a , b ) => {
return - semver . compareBuild ( a . version , b . version );
});
2023-03-09 14:49:35 +02:00
const resolvedFullVersion =
satisfiedVersions . length > 0 ? satisfiedVersions [ 0 ] : null ;
2021-08-06 12:12:36 +02:00
if ( ! resolvedFullVersion ) {
2026-04-13 23:14:45 +05:30
const availableVersionStrings = availableVersionsWithBinaries . map (
2026-10-02 00:09:56 -04:00
({ release }) => release . version
2021-08-06 12:12:36 +02:00
);
2026-04-13 23:14:45 +05:30
throw this . createVersionNotFoundError ( version , availableVersionStrings );
2021-08-06 12:12:36 +02:00
}
return resolvedFullVersion ;
}
2023-03-09 14:49:35 +02:00
protected async downloadTool (
javaRelease : JavaDownloadRelease
) : Promise < JavaInstallerResults > {
2021-08-06 12:12:36 +02:00
core . info (
`Downloading Java ${ javaRelease . version } ( ${ this . distribution } ) from ${ javaRelease . url } ...`
);
2026-07-28 18:39:15 -04:00
let javaArchivePath = await this . downloadPackage ( javaRelease );
2026-06-29 13:19:49 +01:00
2021-08-06 12:12:36 +02:00
core . info ( `Extracting Java archive...` );
2023-03-09 14:49:35 +02:00
const extension = getDownloadArchiveExtension ();
2024-10-11 03:02:25 +05:30
if ( process . platform === 'win32' ) {
javaArchivePath = renameWinArchive ( javaArchivePath );
}
2023-03-09 14:49:35 +02:00
const extractedJavaPath = await extractJdkFile ( javaArchivePath , extension );
2021-08-06 12:12:36 +02:00
const archiveName = fs . readdirSync ( extractedJavaPath )[ 0 ];
const archivePath = path . join ( extractedJavaPath , archiveName );
2026-07-28 18:39:15 -04:00
const javaHome =
process . platform === 'darwin'
? path . join ( archivePath , MACOS_JAVA_CONTENT_POSTFIX )
: archivePath ;
if ( this . includeJmods && ! fs . existsSync ( path . join ( javaHome , 'jmods' ))) {
await this . installJmods ( javaRelease . version , javaHome );
}
2021-08-06 12:12:36 +02:00
const version = this . getToolcacheVersionName ( javaRelease . version );
2026-08-04 23:05:06 -04:00
const javaPath = await cacheJdkDir (
2023-03-09 14:49:35 +02:00
archivePath ,
this . toolcacheFolderName ,
version ,
this . architecture
);
2021-08-06 12:12:36 +02:00
2023-03-09 14:49:35 +02:00
return { version : javaRelease.version , path : javaPath };
2021-08-06 12:12:36 +02:00
}
2026-06-29 13:19:49 +01:00
protected supportsSignatureVerification () : boolean {
return true ;
}
2026-07-28 18:39:15 -04:00
private async downloadPackage ( release : JavaDownloadRelease ) : Promise < string > {
2026-07-29 04:43:56 -04:00
const archivePath = await this . downloadAndVerify ( release );
2026-07-28 18:39:15 -04:00
if ( this . verifySignature ) {
try {
2026-09-03 13:27:24 -04:00
if ( ! ( await gpg . isGpgAvailable ())) {
throw new Error (
"Input 'verify-signature' is enabled, but gpg is not available."
);
}
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 ) {
const verificationError = new Error (
`Failed to verify signature for Temurin version ${ release . version } from ${ release . signatureUrl } : ${ ( error as Error ). message } ${ SIGNATURE_VERIFICATION_FAILURE_HELP } ` ,
{ cause : error }
);
if ( this . verifySignatureExplicitlyRequested ) {
throw verificationError ;
} else {
core . warning ( verificationError . message );
}
}
2026-07-28 18:39:15 -04:00
} catch ( error ) {
2026-09-03 13:27:24 -04:00
if ( this . verifySignatureExplicitlyRequested ) {
throw error ;
}
core . warning (
error instanceof Error ? error . message : `Unknown error: ${ error } `
2026-07-28 18:39:15 -04:00
);
}
}
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 . includeJmods ? 'jdk' : this . packageType
) : Promise < ITemurinAvailableVersions [] > {
2021-08-06 12:12:36 +02:00
const platform = this . getPlatformOption ();
2022-10-10 17:47:17 -06:00
const arch = this . distributionArchitecture ();
2021-08-06 12:12:36 +02:00
const versionRange = encodeURI ( '[1.0,100.0]' ); // retrieve all available versions
const releaseType = this . stable ? 'ga' : 'ea' ;
2022-09-08 15:26:54 +02:00
if ( core . isDebug ()) {
2023-03-09 14:49:35 +02:00
console . time ( 'Retrieving available versions for Temurin took' ); // eslint-disable-line no-console
2022-09-08 15:26:54 +02:00
}
2021-08-06 12:12:36 +02:00
const baseRequestArguments = [
`project=jdk` ,
'vendor=adoptium' ,
`heap_size=normal` ,
'sort_method=DEFAULT' ,
'sort_order=DESC' ,
`os= ${ platform } ` ,
`architecture= ${ arch } ` ,
`image_type= ${ imageType } ` ,
`release_type= ${ releaseType } ` ,
`jvm_impl= ${ this . jvmImpl . toLowerCase () } `
]. join ( '&' );
2026-06-12 11:50:16 +01:00
const requestArguments = ` ${ baseRequestArguments } &page_size=20&page=0` ;
let availableVersionsUrl : string | null =
`https://api.adoptium.net/v3/assets/version/ ${ versionRange } ? ${ requestArguments } ` ;
2021-08-06 12:12:36 +02:00
const availableVersions : ITemurinAvailableVersions [] = [];
2026-06-12 11:50:16 +01:00
let pageCount = 0 ;
if ( core . isDebug ()) {
core . debug ( `Gathering available versions from ' ${ availableVersionsUrl } '` );
}
2021-08-06 12:12:36 +02:00
2026-06-12 11:50:16 +01:00
while ( availableVersionsUrl ) {
pageCount ++ ;
const response =
2023-03-09 14:49:35 +02:00
await this . http . getJson < ITemurinAvailableVersions [] >(
availableVersionsUrl
2026-06-12 11:50:16 +01:00
);
const paginationPage = response . result ;
const nextUrl = getNextPageUrlFromLinkHeader ( response . headers );
if (
nextUrl &&
! validatePaginationUrl ( nextUrl , 'https://api.adoptium.net' )
) {
core . warning (
`Ignoring pagination link with unexpected origin: ${ nextUrl } `
);
availableVersionsUrl = null ;
} else {
availableVersionsUrl = nextUrl ;
}
2021-08-06 12:12:36 +02:00
if ( paginationPage === null || paginationPage . length === 0 ) {
break ;
}
availableVersions . push (... paginationPage );
2026-06-12 11:50:16 +01:00
if ( pageCount >= MAX_PAGINATION_PAGES ) {
core . warning (
`Reached pagination safeguard limit ( ${ MAX_PAGINATION_PAGES } pages) while listing Temurin releases.`
);
break ;
}
2021-08-06 12:12:36 +02:00
}
if ( core . isDebug ()) {
core . startGroup ( 'Print information about available versions' );
2023-03-09 14:49:35 +02:00
console . timeEnd ( 'Retrieving available versions for Temurin took' ); // eslint-disable-line no-console
core . debug ( `Available versions: [ ${ availableVersions . length } ]` );
core . debug (
availableVersions . map ( item => item . version_data . semver ). join ( ', ' )
);
2021-08-06 12:12:36 +02:00
core . endGroup ();
}
return availableVersions ;
}
private getPlatformOption () : string {
// Adoptium has own platform names so need to map them
switch ( process . platform ) {
case 'darwin' :
return 'mac' ;
case 'win32' :
return 'windows' ;
2026-06-12 09:18:56 +01:00
case 'linux' :
2026-08-05 18:45:11 +02:00
if ( isAlpineLinux ()) {
2026-06-12 09:18:56 +01:00
return 'alpine-linux' ;
}
return 'linux' ;
2021-08-06 12:12:36 +02:00
default :
return process . platform ;
}
}
2026-07-29 15:46:29 -04:00
protected distributionArchitecture () : string {
const architecture = super . distributionArchitecture ();
return architecture === 'armv7' ? 'arm' : architecture ;
}
2021-08-06 12:12:36 +02:00
}
2026-10-02 00:09:56 -04:00
/**
* Builds a SemVer version from the OpenJDK version fields reported by the
* Adoptium API, e.g. '26.0.2.1+1' -> '26.0.2+1.1' and '25.0.4+7-LTS' ->
* '25.0.4+7'. Returns null if the fields cannot form a valid SemVer version.
*/
function getOpenJdkSemverVersion (
versionData : ITemurinAvailableVersions [ 'version_data' ]
) : string | null {
const { major , minor , security , patch , build } = versionData ;
if ( build === undefined || build === null ) {
return null ;
}
const buildMetadata = patch ? ` ${ patch } . ${ build } ` : ` ${ build } ` ;
const version = ` ${ major } . ${ minor } . ${ security } + ${ buildMetadata } ` ;
return semver . valid ( version ) ? version : null ;
}