2021-04-05 13:02:27 +03:00
import * as core from '@actions/core' ;
import path from 'path' ;
import fs from 'fs' ;
import semver from 'semver' ;
2026-07-08 14:45:00 +05:30
import { JavaBase } from '../base-installer.js' ;
2026-07-29 04:43:56 -04:00
import { IZuluPackageDetails , IZuluVersions } from './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 ,
getDownloadArchiveExtension ,
2023-04-10 10:29:19 +02:00
convertVersionToSemver ,
2024-10-11 03:02:25 +05:30
isVersionSatisfies ,
renameWinArchive
2026-07-08 14:45:00 +05:30
} from '../../util.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' ;
2021-04-05 13:02:27 +03:00
2026-07-29 04:43:56 -04:00
// The Azul Metadata API only reports the sha256 checksum on the
// package-details endpoint, keyed by package_uuid, so the resolved candidate
// must retain its UUID after sorting until the single follow-up request is made.
interface ZuluResolvedRelease {
version : string ;
url : string ;
packageUuid : string ;
}
2021-04-05 13:02:27 +03:00
export class ZuluDistribution extends JavaBase {
constructor ( installerOptions : JavaInstallerOptions ) {
super ( 'Zulu' , installerOptions );
}
2023-03-09 14:49:35 +02:00
protected async findPackageForDownload (
version : string
) : Promise < JavaDownloadRelease > {
2021-04-05 13:02:27 +03:00
const availableVersionsRaw = await this . getAvailableVersions ();
const availableVersions = availableVersionsRaw . map ( item => {
2026-07-08 05:00:43 -04:00
// The Azul Metadata API reports the JDK build number separately from
// java_version (e.g. java_version=[17,0,7], openjdk_build_number=7).
// Append it so the resulting semver retains the build (e.g. 17.0.7+7).
const javaVersion =
item . openjdk_build_number != null
? [... item . java_version , item . openjdk_build_number ]
: item . java_version ;
2021-04-05 13:02:27 +03:00
return {
2026-07-08 05:00:43 -04:00
version : convertVersionToSemver ( javaVersion ),
url : item.download_url ,
2026-07-29 04:43:56 -04:00
zuluVersion : convertVersionToSemver ( item . distro_version ),
packageUuid : item.package_uuid
2021-04-05 13:02:27 +03:00
};
});
const satisfiedVersions = availableVersions
. filter ( item => isVersionSatisfies ( version , item . version ))
. sort (( a , b ) => {
2026-07-08 05:00:43 -04:00
// Azul provides two versions: java_version and distro_version
2021-04-05 13:02:27 +03:00
// we should sort by both fields by descending
return (
- semver . compareBuild ( a . version , b . version ) ||
- semver . compareBuild ( a . zuluVersion , b . zuluVersion )
);
})
2026-07-29 04:43:56 -04:00
. map (( item ) : ZuluResolvedRelease => ({
version : item.version ,
url : item.url ,
packageUuid : item.packageUuid
}));
2021-04-05 13:02:27 +03:00
2023-03-09 14:49:35 +02:00
const resolvedFullVersion =
satisfiedVersions . length > 0 ? satisfiedVersions [ 0 ] : null ;
2021-04-05 13:02:27 +03:00
if ( ! resolvedFullVersion ) {
2026-04-13 23:14:45 +05:30
const availableVersionStrings = availableVersions . map (
item => item . version
2021-04-05 13:02:27 +03:00
);
2026-04-13 23:14:45 +05:30
throw this . createVersionNotFoundError ( version , availableVersionStrings );
2021-04-05 13:02:27 +03:00
}
2026-07-29 04:43:56 -04:00
const packageDetailsUrl = `https://api.azul.com/metadata/v1/zulu/packages/ ${ resolvedFullVersion . packageUuid } ` ;
const packageDetails = (
await this . http . getJson < IZuluPackageDetails >( packageDetailsUrl )
). result ;
const digest = packageDetails ? . sha256_hash ? . match ( /^[a-f0-9]{64}$/i ) ? .[ 0 ];
if ( ! digest ) {
core . debug (
`No authoritative sha256 checksum is available for Zulu version ${ resolvedFullVersion . version } from ${ packageDetailsUrl } ; skipping checksum verification.`
);
}
return {
version : resolvedFullVersion.version ,
url : resolvedFullVersion.url ,
checksum : digest
? {
algorithm : 'sha256' ,
value : digest ,
source : packageDetailsUrl
}
: undefined
};
2021-04-05 13:02:27 +03:00
}
2023-03-09 14:49:35 +02:00
protected async downloadTool (
javaRelease : JavaDownloadRelease
) : Promise < JavaInstallerResults > {
2021-04-05 13:02:27 +03:00
core . info (
`Downloading Java ${ javaRelease . version } ( ${ this . distribution } ) from ${ javaRelease . url } ...`
);
2026-07-29 04:43:56 -04:00
let javaArchivePath = await this . downloadAndVerify ( javaRelease );
2021-04-05 13:02:27 +03: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 );
2024-02-28 10:41:33 +05:30
}
2023-03-09 14:49:35 +02:00
const extractedJavaPath = await extractJdkFile ( javaArchivePath , extension );
2021-04-05 13:02:27 +03:00
const archiveName = fs . readdirSync ( extractedJavaPath )[ 0 ];
const archivePath = path . join ( extractedJavaPath , archiveName );
2026-08-04 23:05:06 -04:00
const javaPath = await cacheJdkDir (
2021-04-05 13:02:27 +03:00
archivePath ,
this . toolcacheFolderName ,
this . getToolcacheVersionName ( javaRelease . version ),
this . architecture
);
2023-03-09 14:49:35 +02:00
return { version : javaRelease.version , path : javaPath };
2021-04-05 13:02:27 +03:00
}
private async getAvailableVersions () : Promise < IZuluVersions [] > {
2026-07-08 05:00:43 -04:00
const arch = this . getArchitectureOptions ();
2021-04-05 13:02:27 +03:00
const [ bundleType , features ] = this . packageType . split ( '+' );
const platform = this . getPlatformOption ();
const extension = getDownloadArchiveExtension ();
const javafx = features ? . includes ( 'fx' ) ?? false ;
2026-07-08 05:00:43 -04:00
const crac = features ? . includes ( 'crac' ) ?? false ;
2021-04-05 13:02:27 +03:00
const releaseStatus = 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 Zulu took' ); // eslint-disable-line no-console
2022-09-08 15:26:54 +02:00
}
2023-03-09 14:49:35 +02:00
2026-07-08 05:00:43 -04:00
const baseRequestArguments = [
2021-04-05 13:02:27 +03:00
`os= ${ platform } ` ,
2026-07-08 05:00:43 -04:00
`archive_type= ${ extension } ` ,
`java_package_type= ${ bundleType } ` ,
`javafx_bundled= ${ javafx } ` ,
`crac_supported= ${ crac } ` ,
2021-04-05 13:02:27 +03:00
`arch= ${ arch } ` ,
`release_status= ${ releaseStatus } ` ,
2026-07-08 05:00:43 -04:00
`availability_types=ca`
]. join ( '&' );
2021-04-05 13:02:27 +03:00
2026-07-08 05:00:43 -04:00
// Need to iterate through all pages to retrieve the list of all versions.
// The Azul API doesn't return a total page count, so paginate until a page
// comes back empty (or short), guarding against a runaway loop with a cap.
const pageSize = 100 ;
const maxPages = 100 ;
let pageIndex = 1 ;
const availableVersions : IZuluVersions [] = [];
while ( pageIndex <= maxPages ) {
const requestArguments = ` ${ baseRequestArguments } &page= ${ pageIndex } &page_size= ${ pageSize } ` ;
const availableVersionsUrl = `https://api.azul.com/metadata/v1/zulu/packages/? ${ requestArguments } ` ;
if ( core . isDebug () && pageIndex === 1 ) {
// the url is identical except for the page number, so print it once for debug
core . debug (
`Gathering available versions from ' ${ availableVersionsUrl } '`
);
}
2023-03-09 14:49:35 +02:00
2026-07-08 05:00:43 -04:00
const paginationPage = (
await this . http . getJson < IZuluVersions [] >( availableVersionsUrl )
). result ;
if ( ! paginationPage || paginationPage . length === 0 ) {
// stop paginating because we have reached the end of the results
break ;
}
2021-04-05 13:02:27 +03:00
2026-07-08 05:00:43 -04:00
availableVersions . push (... paginationPage );
if ( paginationPage . length < pageSize ) {
// a short page means this was the last one; avoid an extra empty request
break ;
}
pageIndex ++ ;
}
if ( pageIndex > maxPages ) {
core . warning (
`Reached the maximum of ${ maxPages } pages while listing Zulu versions; results may be truncated.`
);
}
2021-04-05 13:02:27 +03: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 Zulu took' ); // eslint-disable-line no-console
core . debug ( `Available versions: [ ${ availableVersions . length } ]` );
core . debug (
2026-07-08 05:00:43 -04:00
availableVersions . map ( item => item . java_version . join ( '.' )). join ( ', ' )
2023-03-09 14:49:35 +02:00
);
2021-04-05 13:02:27 +03:00
core . endGroup ();
}
return availableVersions ;
}
2026-07-08 05:00:43 -04:00
private getArchitectureOptions () : string {
2022-10-10 17:47:17 -06:00
const arch = this . distributionArchitecture ();
switch ( arch ) {
case 'x64' :
2026-07-08 05:00:43 -04:00
return 'x64' ;
2022-10-10 17:47:17 -06:00
case 'x86' :
2026-07-08 09:46:49 -04:00
// The Azul Metadata API's "x86" value returns both 32-bit (i686) and
// 64-bit (x64) packages, which are indistinguishable by version and
// would let a 32-bit request resolve to a 64-bit JDK. Use "i686" to
// target only genuine 32-bit builds, matching the legacy API behavior.
return 'i686' ;
2026-07-29 15:46:29 -04:00
case 'armv7' :
return 'arm' ;
2022-10-10 17:47:17 -06:00
case 'aarch64' :
case 'arm64' :
2026-07-08 05:00:43 -04:00
return 'aarch64' ;
2022-10-10 17:47:17 -06:00
default :
2026-07-08 05:00:43 -04:00
return arch ;
2021-04-05 13:02:27 +03:00
}
}
private getPlatformOption () : string {
// Azul has own platform names so need to map them
switch ( process . platform ) {
case 'darwin' :
return 'macos' ;
case 'win32' :
return 'windows' ;
2026-07-08 05:00:43 -04:00
case 'linux' :
// The new Metadata API's "linux" value returns both glibc and musl packages;
// use "linux_glibc" to target only glibc, which is what standard runners use.
return 'linux_glibc' ;
2021-04-05 13:02:27 +03:00
default :
return process . platform ;
}
}
}