diff --git a/README.md b/README.md index 5fe1cca9..1d1377e6 100644 --- a/README.md +++ b/README.md @@ -233,10 +233,13 @@ Additional distribution notes: | --- | --- | | Major version | `8`, `11`, `17`, `21`, `25` | | Specific feature or patch version | `11.0`, `11.0.4`, `17.0`, `8.0.282+8` | +| Exact build, with optional `jdk-` prefix | `27+35`, `27.0+35`, `27.0.0+35`, `jdk-27+35` | | JEP 322 multi-field versions | `11.0.9.1`, `18.0.1.1`, `26.0.2.1+1` | | Early access | `15-ea`, `15.0.0-ea`, `27-ea` | | Latest stable GA release | `latest` | +Numeric Java versions can use a `jdk-` prefix. For example, `jdk-27+35`, `27+35`, and `27.0+35` all normalize to `27.0.0+35` and pin that exact build; `jdk-27` and `27` remain major-version ranges. The prefix does not select a distribution or package type. Release availability depends on the selected distribution and platform. + When `check-latest` is `false`, the action first tries the runner tool cache for the requested distribution, package type, architecture, and version range. It downloads Java only when no matching cached version is found. When `check-latest` is `true`, the action checks remote metadata first and downloads if the cached version is not current. GitHub-hosted runners primarily pre-cache Eclipse Temurin JDKs. See the installed Java versions for [Ubuntu](https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md#java), [Windows](https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-Readme.md#java), and [macOS](https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md#java). On a fresh GitHub-hosted runner, requests for other distributions usually miss the tool cache and resolve from remote metadata. For broad version ranges such as a major version (`21`, `25`), this often behaves similarly to `check-latest: true` because the action downloads the latest available release that satisfies the range. diff --git a/__tests__/distributors/base-installer.test.ts b/__tests__/distributors/base-installer.test.ts index 1016a8fe..86796f72 100644 --- a/__tests__/distributors/base-installer.test.ts +++ b/__tests__/distributors/base-installer.test.ts @@ -374,6 +374,49 @@ describe('findInToolcache', () => { ); }); +describe('exact build requests in tool-cache', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it.each(['jdk-27+35', '27+35', '27.0+35', '27.0.0+35'])( + 'only matches the requested build for %s', + input => { + const findAllVersions = jest.mocked(tc.findAllVersions); + findAllVersions.mockReturnValue([ + '27.0.1-35', + '27.0.0-36', + '27.0.0-ea.35', + '27.0.0-35' + ]); + jest + .mocked(util.getToolcachePath) + .mockImplementation( + (toolname, version, architecture) => + `/hostedtoolcache/${toolname}/${version}/${architecture}` + ); + const distribution = new EmptyJavaBase({ + version: input, + architecture: 'x64', + packageType: 'jdk', + checkLatest: false + }); + + expect(distribution['findInToolcache']()).toEqual({ + version: '27.0.0+35', + path: '/hostedtoolcache/Java_Empty_jdk/27.0.0-35/x64' + }); + + findAllVersions.mockReturnValue([ + '27.0.1-35', + '27.0.0-36', + '27.0.0-ea.35' + ]); + expect(distribution['findInToolcache']()).toBeNull(); + } + ); +}); + describe('setupJava', () => { const actualJavaVersion = '11.0.9'; const installedJavaVersion = '11.0.8'; @@ -1749,6 +1792,15 @@ describe('normalizeVersion', () => { const DummyJavaBase = JavaBase as any; it.each([ + ['jdk-27+35', {version: '27.0.0+35', stable: true, latest: false}], + ['27+35', {version: '27.0.0+35', stable: true, latest: false}], + ['27.0+35', {version: '27.0.0+35', stable: true, latest: false}], + ['jdk-27', {version: '27', stable: true, latest: false}], + ['jdk-27.0', {version: '27.0', stable: true, latest: false}], + ['jdk-27-ea', {version: '27', stable: false, latest: false}], + ['jdk-27+35-ea', {version: '27.0.0+35', stable: false, latest: false}], + ['jdk-27-ea.35', {version: '27.0.0+35', stable: false, latest: false}], + ['jdk-26.0.2.1+1', {version: '26.0.2+1.1', stable: true, latest: false}], ['11', {version: '11', stable: true, latest: false}], ['11.0', {version: '11.0', stable: true, latest: false}], ['11.0.10', {version: '11.0.10', stable: true, latest: false}], @@ -1778,6 +1830,24 @@ describe('normalizeVersion', () => { ); }); + it.each([ + 'jdk-', + 'jdk-latest', + 'jdk-27.x', + 'jdk->=27', + 'jdk8u442-b06', + 'temurin-27+35', + 'jdk-27-ea+35', + 'jdk-27+', + 'jdk-27+35..1', + '27+35..1', + 'jdk-27..0+35' + ])('rejects malformed or unsupported release tags: %s', input => { + expect(() => + DummyJavaBase.prototype.normalizeVersion.call(null, input) + ).toThrow(/is not valid SemVer notation for a Java version/); + }); + it.each(['latest-ea', 'latest.1', 'LATEST-EA', ' latest-ea '])( 'normalizeVersion should throw a targeted error for latest combined with a qualifier (%s)', version => { diff --git a/__tests__/distributors/distribution-factory.test.ts b/__tests__/distributors/distribution-factory.test.ts index c60e7f75..4facb9ef 100644 --- a/__tests__/distributors/distribution-factory.test.ts +++ b/__tests__/distributors/distribution-factory.test.ts @@ -82,7 +82,7 @@ describe('getJavaDistribution', () => { ); }); - it.each(['8', '23.x', '23.0.1.1', '23.0.1.1+1', '<24'])( + it.each(['8', '23.x', '23.0.1.1', '23.0.1.1+1', '<24', 'jdk-23+35'])( "rejects Temurin java-package 'jdk+jmods' for version %s", async version => { await expect( @@ -96,7 +96,15 @@ describe('getJavaDistribution', () => { } ); - it.each(['24', '24.0.1.1', '25.0.4.1+1', '25-ea', '>=21', 'latest'])( + it.each([ + '24', + '24.0.1.1', + '25.0.4.1+1', + '25-ea', + '>=21', + 'latest', + 'jdk-27+35' + ])( "accepts Temurin java-package 'jdk+jmods' for version %s", async version => { expect( @@ -117,6 +125,17 @@ describe('getJavaDistribution', () => { ).toBeNull(); }); + it('does not change the distribution or package for a jdk-prefixed version', async () => { + const distribution = await getJavaDistribution( + JavaDistribution.Zulu, + installerOptions('jre', 'jdk-27+35') + ); + + expect(distribution!['distribution']).toBe('Zulu'); + expect(distribution!['packageType']).toBe('jre'); + expect(distribution!['version']).toBe('27.0.0+35'); + }); + it.each(['adopt', 'adopt-hotspot', 'adopt-openj9'])( 'does not support legacy Adopt distribution %s', async distributionName => { diff --git a/__tests__/distributors/temurin-installer.test.ts b/__tests__/distributors/temurin-installer.test.ts index 2d424b33..7f700c93 100644 --- a/__tests__/distributors/temurin-installer.test.ts +++ b/__tests__/distributors/temurin-installer.test.ts @@ -378,7 +378,7 @@ describe('findPackageForDownload', () => { expect(resolvedVersion.version).toBe('16.0.2+7'); }); - describe('OpenJDK patch (respin) versions', () => { + describe('OpenJDK versions and release tags', () => { const makeRelease = ( semverVersion: string, openjdkVersion: string, @@ -402,6 +402,21 @@ describe('findPackageForDownload', () => { }); const respinManifest = [ + makeRelease('27.0.1+35', '27.0.1+35', { + major: 27, + security: 1, + build: 35 + }), + makeRelease('27.0.0+36', '27+36', { + major: 27, + security: 0, + build: 36 + }), + makeRelease('27.0.0+35', '27+35', { + major: 27, + security: 0, + build: 35 + }), makeRelease('26.0.2+101', '26.0.2.1+1', { major: 26, security: 2, @@ -427,6 +442,13 @@ describe('findPackageForDownload', () => { ]; it.each([ + ['jdk-27+35', '27.0.0+35'], + ['27+35', '27.0.0+35'], + ['27.0+35', '27.0.0+35'], + ['jdk-27.0.0+35', '27.0.0+35'], + ['jdk-27', '27.0.1+35'], + ['27', '27.0.1+35'], + ['jdk-26.0.2.1+1', '26.0.2+101'], ['26.0.2.1+1', '26.0.2+101'], ['26.0.2+10', '26.0.2+10'], ['26', '26.0.2+101'], @@ -452,21 +474,27 @@ describe('findPackageForDownload', () => { expect(resolvedVersion).not.toHaveProperty('openjdkVersion'); }); - it('does not match a non-existent respin', async () => { - const distribution = new TemurinDistribution( - { - version: '26.0.2.2+1', - architecture: 'x64', - packageType: 'jdk', - checkLatest: false - }, - TemurinImplementation.Hotspot - ); - distribution['getAvailableVersions'] = async () => respinManifest as any; - await expect( - distribution['findPackageForDownload'](distribution['version']) - ).rejects.toThrow(/No matching version found for SemVer '26.0.2\+2.1'/); - }); + it.each(['26.0.2.2+1', 'jdk-27+34', '27+34'])( + 'does not match a non-existent build or respin: %s', + async input => { + const distribution = new TemurinDistribution( + { + version: input, + architecture: 'x64', + packageType: 'jdk', + checkLatest: false + }, + TemurinImplementation.Hotspot + ); + distribution['getAvailableVersions'] = async () => + respinManifest as any; + await expect( + distribution['findPackageForDownload'](distribution['version']) + ).rejects.toThrow( + `No matching version found for SemVer '${distribution['version']}'` + ); + } + ); }); it('version is found but binaries list is empty', async () => { diff --git a/__tests__/java-platform-contract.test.ts b/__tests__/java-platform-contract.test.ts index 57a0df65..04e4a548 100644 --- a/__tests__/java-platform-contract.test.ts +++ b/__tests__/java-platform-contract.test.ts @@ -102,6 +102,24 @@ describe('Java platform capabilities', () => { } ); + it.each(['jdk-27+35', '27+35', 'jdk-27-ea.35'])( + 'enforces version-dependent architecture restrictions for %s', + version => { + expect(() => + validateJavaPlatform('corretto', 'linux', 'x86', version) + ).toThrow(/x86 \(<12\)/); + } + ); + + it.each(['jdk-11+9', '11+9', 'jdk-11-ea.9'])( + 'accepts compatible numeric release tags: %s', + version => { + expect(validateJavaPlatform('corretto', 'linux', 'x86', version)).toBe( + 'x86' + ); + } + ); + it('allows local archives on any platform and architecture', () => { expect(validateJavaPlatform('jdkfile', 'aix', 'mips64', '21')).toBe( 'mips64' diff --git a/__tests__/util.test.ts b/__tests__/util.test.ts index 87b10b74..a58167a6 100644 --- a/__tests__/util.test.ts +++ b/__tests__/util.test.ts @@ -191,6 +191,17 @@ describe('convertVersionToSemver', () => { describe('normalizeJavaVersionToSemver', () => { it.each([ + ['jdk-27+35', '27.0.0+35'], + ['27+35', '27.0.0+35'], + ['27.0+35', '27.0.0+35'], + ['jdk-27.0+35', '27.0.0+35'], + ['jdk-27.0.0+35', '27.0.0+35'], + ['jdk-27', '27'], + ['jdk-27.0', '27.0'], + ['jdk-27.0.1', '27.0.1'], + ['27+35.0.LTS', '27.0.0+35.0.LTS'], + ['jdk-26.0.2.1+1', '26.0.2+1.1'], + ['jdk-11.0.9.1', '11.0.9+1'], ['17', '17'], ['17.0.8', '17.0.8'], ['17.0.8+7', '17.0.8+7'], @@ -199,7 +210,11 @@ describe('normalizeJavaVersionToSemver', () => { ['26.0.2.1+1', '26.0.2+1.1'], ['17.0.8.1+1080.1', '17.0.8+1.1080.1'], ['>=11.0.9.1', '>=11.0.9.1'], - ['17.x', '17.x'] + ['17.x', '17.x'], + ['>=17 <28', '>=17 <28'], + ['jdk-27.x', 'jdk-27.x'], + ['jdk-latest', 'jdk-latest'], + ['temurin-27+35', 'temurin-27+35'] ])('%s -> %s', (input: string, expected: string) => { expect(normalizeJavaVersionToSemver(input)).toBe(expected); }); diff --git a/dist/cleanup/index.js b/dist/cleanup/index.js index 43b8c0ac..e96fa2d5 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -31514,18 +31514,26 @@ function convertVersionToSemver(version) { return mainVersion; } /** - * Java versions (JEP 322) can contain more numeric fields than SemVer allows, - * e.g. '11.0.9.1' or Temurin respins such as '26.0.2.1+1'. Move the extra - * fields into SemVer build metadata ('11.0.9+1', '26.0.2+1.1'). Any other - * input (ranges, regular SemVer versions) is returned unchanged. + * Normalize numeric Java versions, optionally prefixed with 'jdk-'. Pad + * abbreviated build requests ('27+35' -> '27.0.0+35') to preserve exact + * matching, and move extra JEP 322 fields into build metadata + * ('26.0.2.1+1' -> '26.0.2+1.1'). Leave ranges unchanged. */ function normalizeJavaVersionToSemver(version) { - const match = /^(\d+(?:\.\d+){3,})(?:\+([0-9A-Za-z.-]+))?$/.exec(version); + const match = /^(?:jdk-)?(\d+(?:\.\d+)*)(?:\+([0-9A-Za-z.-]+))?$/.exec(version); if (!match) { return version; } - const converted = convertVersionToSemver(match[1]); - return match[2] ? `${converted}.${match[2]}` : converted; + const versionParts = match[1].split('.'); + if (match[2]) { + while (versionParts.length < 3) { + versionParts.push('0'); + } + } + const converted = convertVersionToSemver(versionParts.join('.')); + return match[2] + ? `${converted}${versionParts.length > 3 ? '.' : '+'}${match[2]}` + : converted; } /** * Builds a validator for the bytes currently served by a URL from the response diff --git a/dist/setup/242.index.js b/dist/setup/242.index.js index 1b0369a6..5edde5fe 100644 --- a/dist/setup/242.index.js +++ b/dist/setup/242.index.js @@ -728,10 +728,8 @@ class JavaBase { version = version.replace('-ea.', '+'); stable = false; } - // Java uses a versioning scheme (JEP 322) that can contain more numeric - // fields than SemVer allows, e.g. '18.0.1.1' or '11.0.9.1'. Convert such - // exact versions to SemVer build notation ('18.0.1+1', or '26.0.2+1.1' - // for '26.0.2.1+1') so they are accepted. Ranges are left untouched. + // Normalize Java release tags and JEP 322 versions without losing exact + // build constraints ('jdk-27+35' -> '27.0.0+35'). Leave ranges unchanged. version = (0,util/* normalizeJavaVersionToSemver */.zZ)(version); if (!semver_default().validRange(version)) { throw new Error(`The string '${version}' is not valid SemVer notation for a Java version. Please check README file for code snippets and more detailed information`); diff --git a/dist/setup/index.js b/dist/setup/index.js index 18e223de..074f0e8b 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -31278,7 +31278,9 @@ function createUnsupportedPackageError(distributionName, packageType, supportedP /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nccwpck_require__.n(fs__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_1__ = __nccwpck_require__(2088); /* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__nccwpck_require__.n(semver__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var _package_types_js__WEBPACK_IMPORTED_MODULE_2__ = __nccwpck_require__(7835); +/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_2__ = __nccwpck_require__(4527); +/* harmony import */ var _package_types_js__WEBPACK_IMPORTED_MODULE_3__ = __nccwpck_require__(7835); + @@ -31292,21 +31294,21 @@ const STANDARD_LINUX = [ 's390x' ]; const JAVA_PLATFORM_CAPABILITIES = { - [_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.Temurin]: { + [_package_types_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaDistribution */ .zS.Temurin]: { platforms: { linux: [...STANDARD_LINUX, { architecture: 'armv7', versionRange: '<18' }], macos: X64_ARM64, windows: ['x64', 'x86', 'aarch64'] } }, - [_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.Zulu]: { + [_package_types_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaDistribution */ .zS.Zulu]: { platforms: { linux: ['x64', 'x86', 'armv7', 'aarch64'], macos: X64_ARM64, windows: ['x64', 'x86', 'aarch64'] } }, - [_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.Liberica]: { + [_package_types_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaDistribution */ .zS.Liberica]: { platforms: { linux: ['x64', 'x86', 'armv7', 'aarch64', 'ppc64le'], macos: X64_ARM64, @@ -31314,31 +31316,31 @@ const JAVA_PLATFORM_CAPABILITIES = { solaris: ['x64'] } }, - [_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.LibericaNik]: { + [_package_types_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaDistribution */ .zS.LibericaNik]: { platforms: { linux: X64_ARM64, macos: X64_ARM64, windows: X64_ARM64 } }, - [_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.JdkFile]: { + [_package_types_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaDistribution */ .zS.JdkFile]: { unrestricted: true }, - [_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.Microsoft]: { + [_package_types_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaDistribution */ .zS.Microsoft]: { platforms: { linux: X64_ARM64, macos: X64_ARM64, windows: X64_ARM64 } }, - [_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.Semeru]: { + [_package_types_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaDistribution */ .zS.Semeru]: { platforms: { linux: ['x64', 'x86', 'ppc64le', 'ppc64', 's390x', 'aarch64'], macos: X64_ARM64, windows: ['x64', 'aarch64'] } }, - [_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.Corretto]: { + [_package_types_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaDistribution */ .zS.Corretto]: { platforms: { linux: [ 'x64', @@ -31350,62 +31352,62 @@ const JAVA_PLATFORM_CAPABILITIES = { windows: ['x64', { architecture: 'x86', versionRange: '<12' }] } }, - [_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.Oracle]: { + [_package_types_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaDistribution */ .zS.Oracle]: { platforms: { linux: X64_ARM64, macos: X64_ARM64, windows: ['x64'] } }, - [_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.Dragonwell]: { + [_package_types_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaDistribution */ .zS.Dragonwell]: { platforms: { linux: X64_ARM64, windows: ['x64'] } }, - [_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.SapMachine]: { + [_package_types_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaDistribution */ .zS.SapMachine]: { platforms: { linux: ['x64', 'aarch64', 'ppc64le'], macos: X64_ARM64, windows: X64_ARM64 } }, - [_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.GraalVM]: { + [_package_types_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaDistribution */ .zS.GraalVM]: { platforms: { linux: X64_ARM64, macos: X64_ARM64, windows: ['x64'] } }, - [_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.GraalVMCommunity]: { + [_package_types_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaDistribution */ .zS.GraalVMCommunity]: { platforms: { linux: X64_ARM64, macos: X64_ARM64, windows: ['x64'] } }, - [_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.JetBrains]: { + [_package_types_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaDistribution */ .zS.JetBrains]: { platforms: { linux: X64_ARM64, macos: X64_ARM64, windows: X64_ARM64 } }, - [_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.Kona]: { + [_package_types_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaDistribution */ .zS.Kona]: { platforms: { linux: X64_ARM64, macos: X64_ARM64, windows: ['x64'] } }, - [_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.OracleOpenJdk]: { + [_package_types_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaDistribution */ .zS.OracleOpenJdk]: { platforms: { linux: X64_ARM64, macos: X64_ARM64, windows: ['x64'] } }, - [_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.RedHat]: { + [_package_types_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaDistribution */ .zS.RedHat]: { platforms: { linux: [ 'x64', @@ -31492,26 +31494,15 @@ function isJavaDistribution(value) { return Object.prototype.hasOwnProperty.call(JAVA_PLATFORM_CAPABILITIES, value); } function isVersionCompatible(version, supportedRange) { - let normalizedVersion = version.trim().toLowerCase(); + const normalizedVersion = version.trim().toLowerCase(); if (normalizedVersion === 'latest') { return true; } - if (/^\d+(\.\d+){3,}$/.test(normalizedVersion)) { - normalizedVersion = normalizeExtendedVersionToSemver(normalizedVersion); - } - const requestedRange = semver__WEBPACK_IMPORTED_MODULE_1___default().validRange(normalizedVersion.replace(/-ea$/, '')); + const requestedRange = semver__WEBPACK_IMPORTED_MODULE_1___default().validRange((0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .normalizeJavaVersionToSemver */ .zZ)(normalizedVersion.replace(/-ea$/, '').replace('-ea.', '+'))); const capabilityRange = semver__WEBPACK_IMPORTED_MODULE_1___default().validRange(supportedRange); if (!requestedRange || !capabilityRange) { return true; } - function normalizeExtendedVersionToSemver(version) { - const versionParts = version.split('.'); - const mainVersion = versionParts.slice(0, 3).join('.'); - if (versionParts.length > 3) { - return `${mainVersion}+${versionParts.slice(3).join('.')}`; - } - return version; - } return semver__WEBPACK_IMPORTED_MODULE_1___default().intersects(requestedRange, capabilityRange, { includePrerelease: true }); @@ -31975,18 +31966,26 @@ function convertVersionToSemver(version) { return mainVersion; } /** - * Java versions (JEP 322) can contain more numeric fields than SemVer allows, - * e.g. '11.0.9.1' or Temurin respins such as '26.0.2.1+1'. Move the extra - * fields into SemVer build metadata ('11.0.9+1', '26.0.2+1.1'). Any other - * input (ranges, regular SemVer versions) is returned unchanged. + * Normalize numeric Java versions, optionally prefixed with 'jdk-'. Pad + * abbreviated build requests ('27+35' -> '27.0.0+35') to preserve exact + * matching, and move extra JEP 322 fields into build metadata + * ('26.0.2.1+1' -> '26.0.2+1.1'). Leave ranges unchanged. */ function normalizeJavaVersionToSemver(version) { - const match = /^(\d+(?:\.\d+){3,})(?:\+([0-9A-Za-z.-]+))?$/.exec(version); + const match = /^(?:jdk-)?(\d+(?:\.\d+)*)(?:\+([0-9A-Za-z.-]+))?$/.exec(version); if (!match) { return version; } - const converted = convertVersionToSemver(match[1]); - return match[2] ? `${converted}.${match[2]}` : converted; + const versionParts = match[1].split('.'); + if (match[2]) { + while (versionParts.length < 3) { + versionParts.push('0'); + } + } + const converted = convertVersionToSemver(versionParts.join('.')); + return match[2] + ? `${converted}${versionParts.length > 3 ? '.' : '+'}${match[2]}` + : converted; } /** * Builds a validator for the bytes currently served by a URL from the response diff --git a/src/distributions/base-installer.ts b/src/distributions/base-installer.ts index 32c7455c..e3bc32f4 100644 --- a/src/distributions/base-installer.ts +++ b/src/distributions/base-installer.ts @@ -673,10 +673,8 @@ export abstract class JavaBase { stable = false; } - // Java uses a versioning scheme (JEP 322) that can contain more numeric - // fields than SemVer allows, e.g. '18.0.1.1' or '11.0.9.1'. Convert such - // exact versions to SemVer build notation ('18.0.1+1', or '26.0.2+1.1' - // for '26.0.2.1+1') so they are accepted. Ranges are left untouched. + // Normalize Java release tags and JEP 322 versions without losing exact + // build constraints ('jdk-27+35' -> '27.0.0+35'). Leave ranges unchanged. version = normalizeJavaVersionToSemver(version); if (!semver.validRange(version)) { diff --git a/src/distributions/platform-types.ts b/src/distributions/platform-types.ts index 5e65c726..34ee0498 100644 --- a/src/distributions/platform-types.ts +++ b/src/distributions/platform-types.ts @@ -1,5 +1,6 @@ import fs from 'fs'; import semver from 'semver'; +import {normalizeJavaVersionToSemver} from '../util.js'; import {JavaDistribution} from './package-types.js'; export type JavaPlatform = 'linux' | 'macos' | 'windows' | 'solaris'; @@ -289,31 +290,20 @@ function isJavaDistribution(value: string): value is JavaDistribution { } function isVersionCompatible(version: string, supportedRange: string): boolean { - let normalizedVersion = version.trim().toLowerCase(); + const normalizedVersion = version.trim().toLowerCase(); if (normalizedVersion === 'latest') { return true; } - if (/^\d+(\.\d+){3,}$/.test(normalizedVersion)) { - normalizedVersion = normalizeExtendedVersionToSemver(normalizedVersion); - } - const requestedRange = semver.validRange( - normalizedVersion.replace(/-ea$/, '') + normalizeJavaVersionToSemver( + normalizedVersion.replace(/-ea$/, '').replace('-ea.', '+') + ) ); const capabilityRange = semver.validRange(supportedRange); if (!requestedRange || !capabilityRange) { return true; } - function normalizeExtendedVersionToSemver(version: string): string { - const versionParts = version.split('.'); - const mainVersion = versionParts.slice(0, 3).join('.'); - if (versionParts.length > 3) { - return `${mainVersion}+${versionParts.slice(3).join('.')}`; - } - return version; - } - return semver.intersects(requestedRange, capabilityRange, { includePrerelease: true }); diff --git a/src/util.ts b/src/util.ts index b71a7362..89242ac1 100644 --- a/src/util.ts +++ b/src/util.ts @@ -511,18 +511,28 @@ export function convertVersionToSemver(version: number[] | string) { } /** - * Java versions (JEP 322) can contain more numeric fields than SemVer allows, - * e.g. '11.0.9.1' or Temurin respins such as '26.0.2.1+1'. Move the extra - * fields into SemVer build metadata ('11.0.9+1', '26.0.2+1.1'). Any other - * input (ranges, regular SemVer versions) is returned unchanged. + * Normalize numeric Java versions, optionally prefixed with 'jdk-'. Pad + * abbreviated build requests ('27+35' -> '27.0.0+35') to preserve exact + * matching, and move extra JEP 322 fields into build metadata + * ('26.0.2.1+1' -> '26.0.2+1.1'). Leave ranges unchanged. */ export function normalizeJavaVersionToSemver(version: string): string { - const match = /^(\d+(?:\.\d+){3,})(?:\+([0-9A-Za-z.-]+))?$/.exec(version); + const match = /^(?:jdk-)?(\d+(?:\.\d+)*)(?:\+([0-9A-Za-z.-]+))?$/.exec( + version + ); if (!match) { return version; } - const converted = convertVersionToSemver(match[1]); - return match[2] ? `${converted}.${match[2]}` : converted; + const versionParts = match[1].split('.'); + if (match[2]) { + while (versionParts.length < 3) { + versionParts.push('0'); + } + } + const converted = convertVersionToSemver(versionParts.join('.')); + return match[2] + ? `${converted}${versionParts.length > 3 ? '.' : '+'}${match[2]}` + : converted; } /**