From 74920aab4a9e7a33c5bd4fd3c8d5a7ba260ae795 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Fri, 2 Oct 2026 00:07:25 -0400 Subject: [PATCH] Prefer Zulu 4-segment hotfix builds when resolving version ranges (#1278) Azul reports hotfix releases like 25.0.4.1 as java_version=[25,0,4,1] with openjdk_build_number=1 (SDKMAN '25.0.4+1.1'). Sorting candidates via semver.compareBuild ranked 25.0.4+7 above 25.0.4+1.1, so ranges such as '25' resolved to the older build. Compare java_version, build number and distro_version numerically instead. Fixes: #1275 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- __tests__/distributors/zulu-installer.test.ts | 63 +++++++++++++++++++ __tests__/util.test.ts | 1 + dist/setup/978.index.js | 52 +++++++++------ src/distributions/zulu/installer.ts | 27 ++++++-- 4 files changed, 117 insertions(+), 26 deletions(-) diff --git a/__tests__/distributors/zulu-installer.test.ts b/__tests__/distributors/zulu-installer.test.ts index 20939cb9..7b7b1856 100644 --- a/__tests__/distributors/zulu-installer.test.ts +++ b/__tests__/distributors/zulu-installer.test.ts @@ -334,6 +334,69 @@ describe('findPackageForDownload', () => { ); }); + describe('hotfix builds with a 4-segment java_version', () => { + // Mirrors the Azul Metadata API: 25.0.4.1 hotfix is reported as + // java_version=[25,0,4,1], openjdk_build_number=1 (SDKMAN '25.0.4+1.1'). + const hotfixManifest = [ + { + package_uuid: 'uuid-25.0.4+7', + name: 'zulu25.36.15-ca-jdk25.0.4-linux_x64.tar.gz', + download_url: + 'https://cdn.azul.com/zulu/bin/zulu25.36.15-ca-jdk25.0.4-linux_x64.tar.gz', + java_version: [25, 0, 4], + openjdk_build_number: 7, + distro_version: [25, 36, 15, 0], + latest: false, + availability_type: 'ca' + }, + { + package_uuid: 'uuid-25.0.4+1.1', + name: 'zulu25.36.205-ca-jdk25.0.4.1-linux_x64.tar.gz', + download_url: + 'https://cdn.azul.com/zulu/bin/zulu25.36.205-ca-jdk25.0.4.1-linux_x64.tar.gz', + java_version: [25, 0, 4, 1], + openjdk_build_number: 1, + distro_version: [25, 36, 205, 0], + latest: true, + availability_type: 'ca' + }, + { + package_uuid: 'uuid-25.0.3+9', + name: 'zulu25.34.17-ca-jdk25.0.3-linux_x64.tar.gz', + download_url: + 'https://cdn.azul.com/zulu/bin/zulu25.34.17-ca-jdk25.0.3-linux_x64.tar.gz', + java_version: [25, 0, 3], + openjdk_build_number: 9, + distro_version: [25, 34, 17, 0], + latest: false, + availability_type: 'ca' + } + ] as IZuluVersions[]; + + it.each([ + ['25.0.4+1.1', '25.0.4+1.1', 'uuid-25.0.4+1.1'], + ['25', '25.0.4+1.1', 'uuid-25.0.4+1.1'], + ['25.0.4', '25.0.4+1.1', 'uuid-25.0.4+1.1'], + ['25.0.4+7', '25.0.4+7', 'uuid-25.0.4+7'], + ['25.0.3', '25.0.3+9', 'uuid-25.0.3+9'] + ])('version is %s -> %s', async (input, expected, uuid) => { + const distribution = new ZuluDistribution({ + version: input, + architecture: 'x64', + packageType: 'jdk', + checkLatest: false + }); + distribution['getAvailableVersions'] = async () => hotfixManifest; + const result = await distribution['findPackageForDownload']( + distribution['version'] + ); + expect(result.version).toBe(expected); + expect(result.url).toBe( + hotfixManifest.find(item => item.package_uuid === uuid)!.download_url + ); + }); + }); + it('should throw an error', async () => { const distribution = new ZuluDistribution({ version: '18', diff --git a/__tests__/util.test.ts b/__tests__/util.test.ts index 97f932fb..5ebd5344 100644 --- a/__tests__/util.test.ts +++ b/__tests__/util.test.ts @@ -277,6 +277,7 @@ describe('getVersionFromFileContent', () => { ['java=17.0.9-graalce', '17.0.9', 'graalvm'], ['java=11.0.25-librca', '11.0.25', 'liberica'], ['java=25.0.4+1.1-librca', '25.0.4+1.1', 'liberica'], + ['java=25.0.4+1.1-zulu', '25.0.4+1.1', 'zulu'], ['java=11.0.25-ms', '11.0.25', 'microsoft'], ['java=21.0.5-oracle', '21.0.5', 'oracle'], ['java=11.0.25-sapmchn', '11.0.25', 'sapmachine'], diff --git a/dist/setup/978.index.js b/dist/setup/978.index.js index 23b363d3..fe57a00e 100644 --- a/dist/setup/978.index.js +++ b/dist/setup/978.index.js @@ -13,19 +13,26 @@ export const modules = { /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9896); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2088); -/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(semver__WEBPACK_IMPORTED_MODULE_3__); -/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6242); -/* harmony import */ var _platform_types_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(7444); -/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(4527); +/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6242); +/* harmony import */ var _platform_types_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(7444); +/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(4527); - -class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/* .JavaBase */ .O { +function compareNumberArrays(a, b) { + const length = Math.max(a.length, b.length); + for (let i = 0; i < length; i++) { + const diff = (a[i] ?? 0) - (b[i] ?? 0); + if (diff !== 0) { + return diff; + } + } + return 0; +} +class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaBase */ .O { constructor(installerOptions) { super('Zulu', installerOptions); } @@ -39,19 +46,24 @@ class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/* ? [...item.java_version, item.openjdk_build_number] : item.java_version; return { - version: (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .convertVersionToSemver */ .ZY)(javaVersion), + version: (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .convertVersionToSemver */ .ZY)(javaVersion), url: item.download_url, - zuluVersion: (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .convertVersionToSemver */ .ZY)(item.distro_version), + javaVersion: item.java_version, + buildNumber: item.openjdk_build_number ?? 0, + distroVersion: item.distro_version, packageUuid: item.package_uuid }; }); const satisfiedVersions = availableVersions - .filter(item => (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .isVersionSatisfies */ .y)(version, item.version)) + .filter(item => (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .isVersionSatisfies */ .y)(version, item.version)) .sort((a, b) => { - // Azul provides two versions: java_version and distro_version - // we should sort by both fields by descending - return (-semver__WEBPACK_IMPORTED_MODULE_3___default().compareBuild(a.version, b.version) || - -semver__WEBPACK_IMPORTED_MODULE_3___default().compareBuild(a.zuluVersion, b.zuluVersion)); + // Compare numerically rather than via semver build metadata: Azul + // hotfix releases carry a 4th java_version segment (e.g. 25.0.4.1+1, + // rendered as '25.0.4+1.1') that must rank above 25.0.4+7, whereas + // semver.compareBuild would order the build identifiers '7' > '1'. + return (-compareNumberArrays(a.javaVersion, b.javaVersion) || + b.buildNumber - a.buildNumber || + -compareNumberArrays(a.distroVersion, b.distroVersion)); }) .map((item) => ({ version: item.version, @@ -85,21 +97,21 @@ class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/* _actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`); let javaArchivePath = await this.downloadAndVerify(javaRelease); _actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Extracting Java archive...`); - const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getDownloadArchiveExtension */ .ag)(); + const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getDownloadArchiveExtension */ .ag)(); if (process.platform === 'win32') { - javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .renameWinArchive */ .n2)(javaArchivePath); + javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .renameWinArchive */ .n2)(javaArchivePath); } - const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .extractJdkFile */ .PE)(javaArchivePath, extension); + const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .extractJdkFile */ .PE)(javaArchivePath, extension); const archiveName = fs__WEBPACK_IMPORTED_MODULE_2___default().readdirSync(extractedJavaPath)[0]; const archivePath = path__WEBPACK_IMPORTED_MODULE_1___default().join(extractedJavaPath, archiveName); - const javaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .cacheJdkDir */ .Vj)(archivePath, this.toolcacheFolderName, this.getToolcacheVersionName(javaRelease.version), this.architecture); + const javaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .cacheJdkDir */ .Vj)(archivePath, this.toolcacheFolderName, this.getToolcacheVersionName(javaRelease.version), this.architecture); return { version: javaRelease.version, path: javaPath }; } async getAvailableVersions() { const arch = this.getArchitectureOptions(); const [bundleType, features] = this.packageType.split('+'); const platform = this.getPlatformOption(); - const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getDownloadArchiveExtension */ .ag)(); + const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getDownloadArchiveExtension */ .ag)(); const javafx = features?.includes('fx') ?? false; const crac = features?.includes('crac') ?? false; const releaseStatus = this.stable ? 'ga' : 'ea'; @@ -185,7 +197,7 @@ class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/* // The new Metadata API's "linux" value returns both glibc and musl // packages, so target the libc the runner actually has. A glibc JDK // cannot run on Alpine. - return (0,_platform_types_js__WEBPACK_IMPORTED_MODULE_5__/* .isAlpineLinux */ .G6)() ? 'linux_musl' : 'linux_glibc'; + return (0,_platform_types_js__WEBPACK_IMPORTED_MODULE_4__/* .isAlpineLinux */ .G6)() ? 'linux_musl' : 'linux_glibc'; default: return process.platform; } diff --git a/src/distributions/zulu/installer.ts b/src/distributions/zulu/installer.ts index 2c05567c..0107dd24 100644 --- a/src/distributions/zulu/installer.ts +++ b/src/distributions/zulu/installer.ts @@ -2,7 +2,6 @@ import * as core from '@actions/core'; import path from 'path'; import fs from 'fs'; -import semver from 'semver'; import {JavaBase} from '../base-installer.js'; import {IZuluPackageDetails, IZuluVersions} from './models.js'; @@ -30,6 +29,17 @@ interface ZuluResolvedRelease { packageUuid: string; } +function compareNumberArrays(a: number[], b: number[]): number { + const length = Math.max(a.length, b.length); + for (let i = 0; i < length; i++) { + const diff = (a[i] ?? 0) - (b[i] ?? 0); + if (diff !== 0) { + return diff; + } + } + return 0; +} + export class ZuluDistribution extends JavaBase { constructor(installerOptions: JavaInstallerOptions) { super('Zulu', installerOptions); @@ -50,7 +60,9 @@ export class ZuluDistribution extends JavaBase { return { version: convertVersionToSemver(javaVersion), url: item.download_url, - zuluVersion: convertVersionToSemver(item.distro_version), + javaVersion: item.java_version, + buildNumber: item.openjdk_build_number ?? 0, + distroVersion: item.distro_version, packageUuid: item.package_uuid }; }); @@ -58,11 +70,14 @@ export class ZuluDistribution extends JavaBase { const satisfiedVersions = availableVersions .filter(item => isVersionSatisfies(version, item.version)) .sort((a, b) => { - // Azul provides two versions: java_version and distro_version - // we should sort by both fields by descending + // Compare numerically rather than via semver build metadata: Azul + // hotfix releases carry a 4th java_version segment (e.g. 25.0.4.1+1, + // rendered as '25.0.4+1.1') that must rank above 25.0.4+7, whereas + // semver.compareBuild would order the build identifiers '7' > '1'. return ( - -semver.compareBuild(a.version, b.version) || - -semver.compareBuild(a.zuluVersion, b.zuluVersion) + -compareNumberArrays(a.javaVersion, b.javaVersion) || + b.buildNumber - a.buildNumber || + -compareNumberArrays(a.distroVersion, b.distroVersion) ); }) .map((item): ZuluResolvedRelease => ({