Skip to content

Commit 47b3648

Browse files
brunoborgesCopilot
andauthored
Support Temurin JEP 322 patch versions like 26.0.2.1+1 (#1270) (#1279)
Accept 4-part versions with build metadata (X.Y.Z.P+B -> X.Y.Z+P.B) and match exact Temurin requests against OpenJDK-derived version keys, since the Adoptium API semver folds the patch into the build number (26.0.2+101) and adds LTS metadata (25.0.4+7.0.LTS). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 74920aa commit 47b3648

14 files changed

Lines changed: 243 additions & 39 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ Additional distribution notes:
233233
| --- | --- |
234234
| Major version | `8`, `11`, `17`, `21`, `25` |
235235
| Specific feature or patch version | `11.0`, `11.0.4`, `17.0`, `8.0.282+8` |
236-
| JEP 322 multi-field versions | `11.0.9.1`, `18.0.1.1` |
236+
| JEP 322 multi-field versions | `11.0.9.1`, `18.0.1.1`, `26.0.2.1+1` |
237237
| Early access | `15-ea`, `15.0.0-ea`, `27-ea` |
238238
| Latest stable GA release | `latest` |
239239

‎__tests__/distributors/base-installer.test.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1758,6 +1758,8 @@ describe('normalizeVersion', () => {
17581758
['11.0.9.1', {version: '11.0.9+1', stable: true, latest: false}],
17591759
['12.0.2.1.0', {version: '12.0.2+1.0', stable: true, latest: false}],
17601760
['18.0.1.1-ea', {version: '18.0.1+1', stable: false, latest: false}],
1761+
['26.0.2.1+1', {version: '26.0.2+1.1', stable: true, latest: false}],
1762+
['25.0.4.1+1', {version: '25.0.4+1.1', stable: true, latest: false}],
17611763
['latest', {version: 'x', stable: true, latest: true}],
17621764
['LATEST', {version: 'x', stable: true, latest: true}],
17631765
[' Latest ', {version: 'x', stable: true, latest: true}]

‎__tests__/distributors/distribution-factory.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ describe('getJavaDistribution', () => {
8282
);
8383
});
8484

85-
it.each(['8', '23.x', '23.0.1.1', '<24'])(
85+
it.each(['8', '23.x', '23.0.1.1', '23.0.1.1+1', '<24'])(
8686
"rejects Temurin java-package 'jdk+jmods' for version %s",
8787
async version => {
8888
await expect(
@@ -96,7 +96,7 @@ describe('getJavaDistribution', () => {
9696
}
9797
);
9898

99-
it.each(['24', '24.0.1.1', '25-ea', '>=21', 'latest'])(
99+
it.each(['24', '24.0.1.1', '25.0.4.1+1', '25-ea', '>=21', 'latest'])(
100100
"accepts Temurin java-package 'jdk+jmods' for version %s",
101101
async version => {
102102
expect(

‎__tests__/distributors/temurin-installer.test.ts‎

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,97 @@ describe('findPackageForDownload', () => {
378378
expect(resolvedVersion.version).toBe('16.0.2+7');
379379
});
380380

381+
describe('OpenJDK patch (respin) versions', () => {
382+
const makeRelease = (
383+
semverVersion: string,
384+
openjdkVersion: string,
385+
versionData: Record<string, number>
386+
) => ({
387+
binaries: [
388+
{
389+
package: {
390+
link: `https://example.com/${openjdkVersion}.tar.gz`,
391+
checksum: `checksum-${openjdkVersion}`,
392+
checksum_link: `https://example.com/${openjdkVersion}.sha256.txt`
393+
}
394+
}
395+
],
396+
version_data: {
397+
semver: semverVersion,
398+
openjdk_version: openjdkVersion,
399+
minor: 0,
400+
...versionData
401+
}
402+
});
403+
404+
const respinManifest = [
405+
makeRelease('26.0.2+101', '26.0.2.1+1', {
406+
major: 26,
407+
security: 2,
408+
patch: 1,
409+
build: 1
410+
}),
411+
makeRelease('26.0.2+10', '26.0.2+10', {
412+
major: 26,
413+
security: 2,
414+
build: 10
415+
}),
416+
makeRelease('25.0.4+101.0.LTS', '25.0.4.1+1-LTS', {
417+
major: 25,
418+
security: 4,
419+
patch: 1,
420+
build: 1
421+
}),
422+
makeRelease('25.0.4+7.0.LTS', '25.0.4+7-LTS', {
423+
major: 25,
424+
security: 4,
425+
build: 7
426+
})
427+
];
428+
429+
it.each([
430+
['26.0.2.1+1', '26.0.2+101'],
431+
['26.0.2+10', '26.0.2+10'],
432+
['26', '26.0.2+101'],
433+
['26.0.2', '26.0.2+101'],
434+
['25.0.4.1+1', '25.0.4+101.0.LTS'],
435+
['25.0.4+7', '25.0.4+7.0.LTS'],
436+
['25.0.4', '25.0.4+101.0.LTS']
437+
])('%s resolves to %s', async (input, expected) => {
438+
const distribution = new TemurinDistribution(
439+
{
440+
version: input,
441+
architecture: 'x64',
442+
packageType: 'jdk',
443+
checkLatest: false
444+
},
445+
TemurinImplementation.Hotspot
446+
);
447+
distribution['getAvailableVersions'] = async () => respinManifest as any;
448+
const resolvedVersion = await distribution['findPackageForDownload'](
449+
distribution['version']
450+
);
451+
expect(resolvedVersion.version).toBe(expected);
452+
expect(resolvedVersion).not.toHaveProperty('openjdkVersion');
453+
});
454+
455+
it('does not match a non-existent respin', async () => {
456+
const distribution = new TemurinDistribution(
457+
{
458+
version: '26.0.2.2+1',
459+
architecture: 'x64',
460+
packageType: 'jdk',
461+
checkLatest: false
462+
},
463+
TemurinImplementation.Hotspot
464+
);
465+
distribution['getAvailableVersions'] = async () => respinManifest as any;
466+
await expect(
467+
distribution['findPackageForDownload'](distribution['version'])
468+
).rejects.toThrow(/No matching version found for SemVer '26.0.2\+2.1'/);
469+
});
470+
});
471+
381472
it('version is found but binaries list is empty', async () => {
382473
const distribution = new TemurinDistribution(
383474
{

‎__tests__/util.test.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const core = await import('@actions/core');
4343

4444
const {
4545
convertVersionToSemver,
46+
normalizeJavaVersionToSemver,
4647
getNextPageUrlFromLinkHeader,
4748
getVersionFromFileContent,
4849
isVersionSatisfies,
@@ -188,6 +189,22 @@ describe('convertVersionToSemver', () => {
188189
});
189190
});
190191

192+
describe('normalizeJavaVersionToSemver', () => {
193+
it.each([
194+
['17', '17'],
195+
['17.0.8', '17.0.8'],
196+
['17.0.8+7', '17.0.8+7'],
197+
['11.0.9.1', '11.0.9+1'],
198+
['12.0.2.1.0', '12.0.2+1.0'],
199+
['26.0.2.1+1', '26.0.2+1.1'],
200+
['17.0.8.1+1080.1', '17.0.8+1.1080.1'],
201+
['>=11.0.9.1', '>=11.0.9.1'],
202+
['17.x', '17.x']
203+
])('%s -> %s', (input: string, expected: string) => {
204+
expect(normalizeJavaVersionToSemver(input)).toBe(expected);
205+
});
206+
});
207+
191208
describe('getNextPageUrlFromLinkHeader', () => {
192209
it.each([
193210
[

‎dist/cleanup/index.js‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30848,7 +30848,7 @@ const DISTRIBUTIONS_ONLY_MAJOR_VERSION = (/* unused pure expression or super */
3084830848
/* harmony export */ Vt: () => (/* binding */ getBooleanInput),
3084930849
/* harmony export */ lN: () => (/* binding */ isJdkCacheEnabled)
3085030850
/* harmony export */ });
30851-
/* unused harmony exports getVersionFromToolcachePath, extractJdkFile, cacheJdkDir, getJavaVersionFromReleaseFile, getDownloadArchiveExtension, isVersionSatisfies, getToolcachePath, isGhes, getVersionFromFileContent, convertVersionToSemver, getArtifactFingerprint, getGitHubToken, getGitHubHttpHeaders, MAX_PAGINATION_PAGES, getNextPageUrlFromLinkHeader, validatePaginationUrl, renameWinArchive, getLatestMajorVersion */
30851+
/* unused harmony exports getVersionFromToolcachePath, extractJdkFile, cacheJdkDir, getJavaVersionFromReleaseFile, getDownloadArchiveExtension, isVersionSatisfies, getToolcachePath, isGhes, getVersionFromFileContent, convertVersionToSemver, normalizeJavaVersionToSemver, getArtifactFingerprint, getGitHubToken, getGitHubHttpHeaders, MAX_PAGINATION_PAGES, getNextPageUrlFromLinkHeader, validatePaginationUrl, renameWinArchive, getLatestMajorVersion */
3085230852
/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_0__ = __nccwpck_require__(857);
3085330853
/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nccwpck_require__.n(os__WEBPACK_IMPORTED_MODULE_0__);
3085430854
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __nccwpck_require__(6928);
@@ -31247,6 +31247,20 @@ function convertVersionToSemver(version) {
3124731247
}
3124831248
return mainVersion;
3124931249
}
31250+
/**
31251+
* Java versions (JEP 322) can contain more numeric fields than SemVer allows,
31252+
* e.g. '11.0.9.1' or Temurin respins such as '26.0.2.1+1'. Move the extra
31253+
* fields into SemVer build metadata ('11.0.9+1', '26.0.2+1.1'). Any other
31254+
* input (ranges, regular SemVer versions) is returned unchanged.
31255+
*/
31256+
function normalizeJavaVersionToSemver(version) {
31257+
const match = /^(\d+(?:\.\d+){3,})(?:\+([0-9A-Za-z.-]+))?$/.exec(version);
31258+
if (!match) {
31259+
return version;
31260+
}
31261+
const converted = convertVersionToSemver(match[1]);
31262+
return match[2] ? `${converted}.${match[2]}` : converted;
31263+
}
3125031264
/**
3125131265
* Builds a validator for the bytes currently served by a URL from the response
3125231266
* headers of a HEAD request. A vendor's `/latest/` URL never changes, so this

‎dist/setup/242.index.js‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -730,12 +730,9 @@ class JavaBase {
730730
}
731731
// Java uses a versioning scheme (JEP 322) that can contain more numeric
732732
// fields than SemVer allows, e.g. '18.0.1.1' or '11.0.9.1'. Convert such
733-
// exact versions to SemVer build notation ('18.0.1+1') so they are
734-
// accepted. Ranges and versions that already carry build metadata are
735-
// left untouched.
736-
if (/^\d+(\.\d+){3,}$/.test(version)) {
737-
version = (0,util/* convertVersionToSemver */.ZY)(version);
738-
}
733+
// exact versions to SemVer build notation ('18.0.1+1', or '26.0.2+1.1'
734+
// for '26.0.2.1+1') so they are accepted. Ranges are left untouched.
735+
version = (0,util/* normalizeJavaVersionToSemver */.zZ)(version);
739736
if (!semver_default().validRange(version)) {
740737
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`);
741738
}

‎dist/setup/463.index.js‎

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ class TemurinDistribution extends base_installer/* JavaBase */.O {
109109
const formattedVersion = this.stable
110110
? item.version_data.semver
111111
: item.version_data.semver.replace('-beta+', '+');
112-
return {
112+
const release = {
113113
version: formattedVersion,
114114
url: item.binaries[0].package.link,
115115
signatureUrl: item.binaries[0].package.signature_link,
@@ -119,15 +119,29 @@ class TemurinDistribution extends base_installer/* JavaBase */.O {
119119
source: item.binaries[0].package.checksum_link
120120
}
121121
};
122+
return {
123+
release,
124+
openjdkVersion: getOpenJdkSemverVersion(item.version_data)
125+
};
122126
});
127+
// The Adoptium API `semver` folds the JEP 322 patch field into the build
128+
// number ('26.0.2.1+1' -> '26.0.2+101') and appends extra metadata for LTS
129+
// releases ('25.0.4+7' -> '25.0.4+7.0.LTS'). Exact versions requested by
130+
// users follow the OpenJDK notation instead, so also match them against a
131+
// key derived from the OpenJDK version fields ('26.0.2+1.1', '25.0.4+7').
132+
const isExactBuildRequest = (semver_default().parse(version)?.build.length ?? 0) > 0;
123133
const satisfiedVersions = availableVersionsWithBinaries
124-
.filter(item => (0,util/* isVersionSatisfies */.y)(version, item.version))
134+
.filter(({ release, openjdkVersion }) => (0,util/* isVersionSatisfies */.y)(version, release.version) ||
135+
(isExactBuildRequest &&
136+
openjdkVersion !== null &&
137+
semver_default().compareBuild(version, openjdkVersion) === 0))
138+
.map(({ release }) => release)
125139
.sort((a, b) => {
126140
return -semver_default().compareBuild(a.version, b.version);
127141
});
128142
const resolvedFullVersion = satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
129143
if (!resolvedFullVersion) {
130-
const availableVersionStrings = availableVersionsWithBinaries.map(item => item.version);
144+
const availableVersionStrings = availableVersionsWithBinaries.map(({ release }) => release.version);
131145
throw this.createVersionNotFoundError(version, availableVersionStrings);
132146
}
133147
return resolvedFullVersion;
@@ -279,6 +293,20 @@ class TemurinDistribution extends base_installer/* JavaBase */.O {
279293
return architecture === 'armv7' ? 'arm' : architecture;
280294
}
281295
}
296+
/**
297+
* Builds a SemVer version from the OpenJDK version fields reported by the
298+
* Adoptium API, e.g. '26.0.2.1+1' -> '26.0.2+1.1' and '25.0.4+7-LTS' ->
299+
* '25.0.4+7'. Returns null if the fields cannot form a valid SemVer version.
300+
*/
301+
function getOpenJdkSemverVersion(versionData) {
302+
const { major, minor, security, patch, build } = versionData;
303+
if (build === undefined || build === null) {
304+
return null;
305+
}
306+
const buildMetadata = patch ? `${patch}.${build}` : `${build}`;
307+
const version = `${major}.${minor}.${security}+${buildMetadata}`;
308+
return semver_default().valid(version) ? version : null;
309+
}
282310

283311

284312
/***/ }),

‎dist/setup/index.js‎

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30975,12 +30975,7 @@ function canResolveTemurinJmods(version) {
3097530975
if (normalizedVersion === 'latest') {
3097630976
return true;
3097730977
}
30978-
let normalizedRange = normalizedVersion
30979-
.replace(/-ea$/, '')
30980-
.replace('-ea.', '+');
30981-
if (/^\d+(\.\d+){3,}$/.test(normalizedRange)) {
30982-
normalizedRange = (0,_util_js__WEBPACK_IMPORTED_MODULE_1__/* .convertVersionToSemver */ .ZY)(normalizedRange);
30983-
}
30978+
const normalizedRange = (0,_util_js__WEBPACK_IMPORTED_MODULE_1__/* .normalizeJavaVersionToSemver */ .zZ)(normalizedVersion.replace(/-ea$/, '').replace('-ea.', '+'));
3098430979
if (!semver__WEBPACK_IMPORTED_MODULE_0___default().validRange(normalizedRange)) {
3098530980
// JavaBase owns general version validation and its targeted error messages.
3098630981
return true;
@@ -31311,7 +31306,8 @@ function validateToolchainIds(versions, versionFile, toolchainIds) {
3131131306
/* harmony export */ rC: () => (/* binding */ getNextPageUrlFromLinkHeader),
3131231307
/* harmony export */ ri: () => (/* binding */ getLatestMajorVersion),
3131331308
/* harmony export */ y: () => (/* binding */ isVersionSatisfies),
31314-
/* harmony export */ yH: () => (/* binding */ getToolcachePath)
31309+
/* harmony export */ yH: () => (/* binding */ getToolcachePath),
31310+
/* harmony export */ zZ: () => (/* binding */ normalizeJavaVersionToSemver)
3131531311
/* harmony export */ });
3131631312
/* unused harmony exports getVersionFromToolcachePath, isJobStatusSuccess */
3131731313
/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_0__ = __nccwpck_require__(857);
@@ -31712,6 +31708,20 @@ function convertVersionToSemver(version) {
3171231708
}
3171331709
return mainVersion;
3171431710
}
31711+
/**
31712+
* Java versions (JEP 322) can contain more numeric fields than SemVer allows,
31713+
* e.g. '11.0.9.1' or Temurin respins such as '26.0.2.1+1'. Move the extra
31714+
* fields into SemVer build metadata ('11.0.9+1', '26.0.2+1.1'). Any other
31715+
* input (ranges, regular SemVer versions) is returned unchanged.
31716+
*/
31717+
function normalizeJavaVersionToSemver(version) {
31718+
const match = /^(\d+(?:\.\d+){3,})(?:\+([0-9A-Za-z.-]+))?$/.exec(version);
31719+
if (!match) {
31720+
return version;
31721+
}
31722+
const converted = convertVersionToSemver(match[1]);
31723+
return match[2] ? `${converted}.${match[2]}` : converted;
31724+
}
3171531725
/**
3171631726
* Builds a validator for the bytes currently served by a URL from the response
3171731727
* headers of a HEAD request. A vendor's `/latest/` URL never changes, so this

‎src/distributions/base-installer.ts‎

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import semver from 'semver';
55
import path from 'path';
66
import * as httpm from '@actions/http-client';
77
import {
8-
convertVersionToSemver,
98
getToolcachePath,
10-
isVersionSatisfies
9+
isVersionSatisfies,
10+
normalizeJavaVersionToSemver
1111
} from '../util.js';
1212
import type {
1313
ChecksumAlgorithm,
@@ -675,12 +675,9 @@ export abstract class JavaBase {
675675

676676
// Java uses a versioning scheme (JEP 322) that can contain more numeric
677677
// fields than SemVer allows, e.g. '18.0.1.1' or '11.0.9.1'. Convert such
678-
// exact versions to SemVer build notation ('18.0.1+1') so they are
679-
// accepted. Ranges and versions that already carry build metadata are
680-
// left untouched.
681-
if (/^\d+(\.\d+){3,}$/.test(version)) {
682-
version = convertVersionToSemver(version);
683-
}
678+
// exact versions to SemVer build notation ('18.0.1+1', or '26.0.2+1.1'
679+
// for '26.0.2.1+1') so they are accepted. Ranges are left untouched.
680+
version = normalizeJavaVersionToSemver(version);
684681

685682
if (!semver.validRange(version)) {
686683
throw new Error(

0 commit comments

Comments
 (0)