Skip to content

Commit 816722a

Browse files
authored
Fix Liberica SDKMAN patch release resolution (#1291)
* Fix Liberica SDKMAN patch release resolution Preserve BellSoft's fourth version component as SDKMAN patch metadata and cover real API version formats for Java 17 and 25. Fixes #1290 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d5b53868-538f-4c5f-b2aa-c66dc4ad06e2 * Fix Liberica patch ordering and build-less requests Share overridable matching and ordering with toolcache selection. Keep patch-only requests distinct from exact SDKMAN build requests and cover the reported API pair across remote and cached resolution. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d5b53868-538f-4c5f-b2aa-c66dc4ad06e2 --------- Copilot-Session: d5b53868-538f-4c5f-b2aa-c66dc4ad06e2
1 parent b843ebf commit 816722a

6 files changed

Lines changed: 343 additions & 27 deletions

File tree

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

Lines changed: 232 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
} from '../../src/distributions/liberica/models.js';
1616
import {HttpClient} from '@actions/http-client';
1717
import os from 'os';
18+
import path from 'path';
1819

1920
import manifestData from '../data/liberica.json' with {type: 'json'};
2021

@@ -253,35 +254,68 @@ describe('findPackageForDownload', () => {
253254
expect(result.version).toBe(expected);
254255
});
255256

256-
describe('compound build versions', () => {
257+
describe.each([
258+
[17, 20],
259+
[25, 4]
260+
])('patch releases for Java %s.0.%s', (featureVersion, updateVersion) => {
261+
const mainVersion = `${featureVersion}.0.${updateVersion}`;
262+
257263
beforeEach(() => {
258264
distribution['getAvailableVersions'] = async () =>
259-
['1', '1.1', '1.2', '1.10'].map(build => ({
260-
featureVersion: 25,
265+
['+1', '+10', '.1+1', '.1+2', '.1+10', '.2+1', '.10+1'].map(suffix => ({
266+
featureVersion,
261267
interimVersion: 0,
262-
updateVersion: 4,
263-
buildVersion: 1,
264-
version: `25.0.4+${build}`,
265-
downloadUrl: `https://download.bell-sw.com/java/25.0.4+${build}/bellsoft-jdk25.0.4+${build}-macos-aarch64.tar.gz`
268+
updateVersion,
269+
buildVersion: Number(suffix.split('+')[1]),
270+
version: `${mainVersion}${suffix}`,
271+
downloadUrl: `https://download.bell-sw.com/java/${mainVersion}${suffix}/bellsoft-jdk${mainVersion}${suffix}-macos-aarch64.tar.gz`
266272
}));
267273
});
268274

269275
it.each([
270-
['25.0.4+1.1', '25.0.4+1.1'],
271-
['25.0.4+1', '25.0.4+1'],
272-
['25.0.4', '25.0.4+1.10'],
273-
['25', '25.0.4+1.10']
274-
])('version is %s -> %s', async (input, expected) => {
276+
[`${mainVersion}+1.1`, `${mainVersion}+1.1`, '.1+1'],
277+
[`${mainVersion}+1.2`, `${mainVersion}+1.2`, '.1+2'],
278+
[`${mainVersion}+1.10`, `${mainVersion}+1.10`, '.1+10'],
279+
[`${mainVersion}+1`, `${mainVersion}+1`, '+1'],
280+
[mainVersion, `${mainVersion}+10.1`, '.10+1'],
281+
[`${featureVersion}`, `${mainVersion}+10.1`, '.10+1']
282+
])('version is %s -> %s', async (input, expected, suffix) => {
275283
const result = await distribution['findPackageForDownload'](input);
276284
expect(result).toEqual({
277285
version: expected,
278-
url: `https://download.bell-sw.com/java/${expected}/bellsoft-jdk${expected}-macos-aarch64.tar.gz`
286+
url: `https://download.bell-sw.com/java/${mainVersion}${suffix}/bellsoft-jdk${mainVersion}${suffix}-macos-aarch64.tar.gz`
279287
});
280288
});
281289

290+
it('sorts builds within the same patch numerically', async () => {
291+
const versions = await distribution['getAvailableVersions']();
292+
distribution['getAvailableVersions'] = async () =>
293+
versions.filter(item => item.version.startsWith(`${mainVersion}.1+`));
294+
295+
const result = await distribution['findPackageForDownload'](mainVersion);
296+
expect(result.version).toBe(`${mainVersion}+1.10`);
297+
});
298+
299+
it('selects the newest build for a four-component patch-only request', async () => {
300+
const patchDistribution = new LibericaDistributions({
301+
version: `${mainVersion}.1`,
302+
architecture: 'x64',
303+
packageType: 'jdk',
304+
checkLatest: false
305+
});
306+
patchDistribution['getAvailableVersions'] =
307+
distribution['getAvailableVersions'];
308+
309+
await expect(
310+
patchDistribution['findPackageForDownload'](
311+
patchDistribution['version']
312+
)
313+
).resolves.toMatchObject({version: `${mainVersion}+1.10`});
314+
});
315+
282316
it('does not substitute a different compound build', async () => {
283317
await expect(
284-
distribution['findPackageForDownload']('25.0.4+1.3')
318+
distribution['findPackageForDownload'](`${mainVersion}+1.3`)
285319
).rejects.toThrow(/No matching version found for SemVer/);
286320
});
287321
});
@@ -293,6 +327,160 @@ describe('findPackageForDownload', () => {
293327
});
294328
});
295329

330+
describe('patch request resolution from the constructor', () => {
331+
const releases: LibericaVersion[] = ['17.0.20+10', '17.0.20.1+1'].map(
332+
version => ({
333+
featureVersion: 17,
334+
interimVersion: 0,
335+
updateVersion: 20,
336+
buildVersion: Number(version.split('+')[1]),
337+
version,
338+
downloadUrl: `https://download.bell-sw.com/java/${version}/jdk.tar.gz`
339+
})
340+
);
341+
let cacheRoot: string;
342+
let previousToolcache: string | undefined;
343+
344+
beforeEach(async () => {
345+
previousToolcache = process.env['RUNNER_TOOL_CACHE'];
346+
cacheRoot = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'liberica-'));
347+
process.env['RUNNER_TOOL_CACHE'] = cacheRoot;
348+
for (const version of ['17.0.20-10', '17.0.20-1.1']) {
349+
const javaPath = path.join(
350+
cacheRoot,
351+
'Java_Liberica_jdk',
352+
version,
353+
'x64'
354+
);
355+
await fs.promises.mkdir(javaPath, {recursive: true});
356+
await fs.promises.writeFile(`${javaPath}.complete`, '');
357+
}
358+
});
359+
360+
afterEach(async () => {
361+
if (previousToolcache === undefined) {
362+
delete process.env['RUNNER_TOOL_CACHE'];
363+
} else {
364+
process.env['RUNNER_TOOL_CACHE'] = previousToolcache;
365+
}
366+
await fs.promises.rm(cacheRoot, {recursive: true, force: true});
367+
});
368+
369+
it.each([
370+
['17', '17.0.20+1.1', '17.0.20.1+1'],
371+
['17.0.20', '17.0.20+1.1', '17.0.20.1+1'],
372+
['17.0.20+10', '17.0.20+10', '17.0.20+10'],
373+
['17.0.20+1.1', '17.0.20+1.1', '17.0.20.1+1'],
374+
['17.0.20.1', '17.0.20+1.1', '17.0.20.1+1'],
375+
['17.0.20.1+1', '17.0.20+1.1', '17.0.20.1+1'],
376+
['17.0.20.0', '17.0.20+10', '17.0.20+10']
377+
])(
378+
'resolves %s consistently remotely and in cache',
379+
async (input, expected, apiVersion) => {
380+
const distribution = new LibericaDistributions({
381+
version: input,
382+
architecture: 'x64',
383+
packageType: 'jdk',
384+
checkLatest: false
385+
});
386+
distribution['getAvailableVersions'] = async () => releases;
387+
388+
await expect(
389+
distribution['findPackageForDownload'](distribution['version'])
390+
).resolves.toEqual({
391+
version: expected,
392+
url: `https://download.bell-sw.com/java/${apiVersion}/jdk.tar.gz`
393+
});
394+
expect(distribution['findInToolcache']()).toEqual({
395+
version: expected,
396+
path: path.join(
397+
cacheRoot,
398+
'Java_Liberica_jdk',
399+
expected.replace('+', '-'),
400+
'x64'
401+
)
402+
});
403+
}
404+
);
405+
406+
it.each(['17.0.20.2', '17.0.20+1.2'])(
407+
'does not substitute a different patch or exact build for %s',
408+
async version => {
409+
const distribution = new LibericaDistributions({
410+
version,
411+
architecture: 'x64',
412+
packageType: 'jdk',
413+
checkLatest: false
414+
});
415+
distribution['getAvailableVersions'] = async () => releases;
416+
417+
await expect(
418+
distribution['findPackageForDownload'](distribution['version'])
419+
).rejects.toThrow(/No matching version found/);
420+
expect(distribution['findInToolcache']()).toBeNull();
421+
}
422+
);
423+
424+
it('keeps patch-only and exact-build resolution cache identities distinct', () => {
425+
const distribution = new LibericaDistributions({
426+
version: '17.0.20.1',
427+
architecture: 'x64',
428+
packageType: 'jdk',
429+
checkLatest: false
430+
});
431+
expect(distribution['version']).toBe('17.0.20.1');
432+
});
433+
434+
it('orders main versions, patches, and builds consistently in cache', async () => {
435+
for (const version of ['17.0.20-1.2', '17.0.20-1.10', '17.0.21-1']) {
436+
const javaPath = path.join(
437+
cacheRoot,
438+
'Java_Liberica_jdk',
439+
version,
440+
'x64'
441+
);
442+
await fs.promises.mkdir(javaPath, {recursive: true});
443+
await fs.promises.writeFile(`${javaPath}.complete`, '');
444+
}
445+
446+
for (const [version, expected] of [
447+
['17', '17.0.21+1'],
448+
['17.0.20', '17.0.20+1.10'],
449+
['17.0.20.1', '17.0.20+1.10']
450+
]) {
451+
const distribution = new LibericaDistributions({
452+
version,
453+
architecture: 'x64',
454+
packageType: 'jdk',
455+
checkLatest: false
456+
});
457+
expect(distribution['findInToolcache']()?.version).toBe(expected);
458+
}
459+
});
460+
461+
it('preserves early-access handling for patch-only requests', async () => {
462+
const javaPath = path.join(
463+
cacheRoot,
464+
'Java_Liberica_jdk',
465+
'17.0.20-ea.1.2',
466+
'x64'
467+
);
468+
await fs.promises.mkdir(javaPath, {recursive: true});
469+
await fs.promises.writeFile(`${javaPath}.complete`, '');
470+
const distribution = new LibericaDistributions({
471+
version: '17.0.20.1-ea',
472+
architecture: 'x64',
473+
packageType: 'jdk',
474+
checkLatest: false
475+
});
476+
expect(distribution['stable']).toBe(false);
477+
expect(distribution['findInToolcache']()).toEqual({
478+
version: '17.0.20+1.2',
479+
path: javaPath
480+
});
481+
});
482+
});
483+
296484
describe('getPlatformOption', () => {
297485
beforeEach(() => {
298486
// The linux row below is glibc, so pin the Alpine probe rather than
@@ -369,6 +557,36 @@ describe('convertVersionToSemver', () => {
369557
},
370558
'11.0.0+13'
371559
],
560+
[
561+
{
562+
version: '17.0.20.1+1',
563+
featureVersion: 17,
564+
interimVersion: 0,
565+
updateVersion: 20,
566+
buildVersion: 1
567+
},
568+
'17.0.20+1.1'
569+
],
570+
[
571+
{
572+
version: '25.0.4.1+1',
573+
featureVersion: 25,
574+
interimVersion: 0,
575+
updateVersion: 4,
576+
buildVersion: 1
577+
},
578+
'25.0.4+1.1'
579+
],
580+
[
581+
{
582+
version: '17.0.20.1+2',
583+
featureVersion: 17,
584+
interimVersion: 0,
585+
updateVersion: 20,
586+
buildVersion: 2
587+
},
588+
'17.0.20+1.2'
589+
],
372590
[
373591
{
374592
version: '25.0.4+1.1',

‎dist/setup/242.index.js‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ class JavaBase {
378378
core/* info */.pq(`Java ${foundJava.version} was downloaded`);
379379
if (javaRelease.floating) {
380380
if (!semver_default().valid(foundJava.version) ||
381-
!(0,util/* isVersionSatisfies */.y)(this.version, foundJava.version)) {
381+
!this.isVersionSatisfied(this.version, foundJava.version)) {
382382
throw new Error(`The downloaded ${this.distribution} artifact reported Java ${foundJava.version}, which does not satisfy '${this.version}'.`);
383383
}
384384
javaRelease = { ...javaRelease, version: foundJava.version };
@@ -501,7 +501,7 @@ class JavaBase {
501501
return javaRelease;
502502
}
503503
if (!semver_default().valid(restored.release.version) ||
504-
!(0,util/* isVersionSatisfies */.y)(this.version, restored.release.version)) {
504+
!this.isVersionSatisfied(this.version, restored.release.version)) {
505505
core/* debug */.Yz(`Ignoring the cached concrete version '${restored.release.version}' for ${this.distribution} ${this.version}.`);
506506
return javaRelease;
507507
}
@@ -684,10 +684,10 @@ class JavaBase {
684684
})
685685
.filter(item => item.stable === this.stable);
686686
const satisfiedVersions = availableVersions
687-
.filter(item => (0,util/* isVersionSatisfies */.y)(this.version, item.version))
687+
.filter(item => this.isVersionSatisfied(this.version, item.version))
688688
.filter(item => item.path)
689689
.sort((a, b) => {
690-
return -semver_default().compareBuild(a.version, b.version);
690+
return -this.compareVersions(a.version, b.version);
691691
});
692692
if (!satisfiedVersions || satisfiedVersions.length === 0) {
693693
return null;
@@ -697,6 +697,12 @@ class JavaBase {
697697
path: satisfiedVersions[0].path
698698
};
699699
}
700+
isVersionSatisfied(range, version) {
701+
return (0,util/* isVersionSatisfies */.y)(range, version);
702+
}
703+
compareVersions(a, b) {
704+
return semver_default().compareBuild(a, b);
705+
}
700706
normalizeVersion(version) {
701707
let stable = true;
702708
const latest = false;

‎dist/setup/63.index.js‎

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,46 @@ class LibericaDistributions extends _base_installer_js__WEBPACK_IMPORTED_MODULE_
5252
version: this.convertVersionToSemver(item)
5353
}));
5454
const satisfiedVersion = availableVersions
55-
.filter(item => (0,_util_js__WEBPACK_IMPORTED_MODULE_2__/* .isVersionSatisfies */ .y)(range, item.version))
56-
.sort((a, b) => -semver__WEBPACK_IMPORTED_MODULE_1___default().compareBuild(a.version, b.version))[0];
55+
.filter(item => this.isVersionSatisfied(range, item.version))
56+
.sort((a, b) => -this.compareVersions(a.version, b.version))[0];
5757
if (!satisfiedVersion) {
5858
const availableVersionStrings = availableVersions.map(item => item.version);
5959
throw this.createVersionNotFoundError(range, availableVersionStrings);
6060
}
6161
return satisfiedVersion;
6262
}
63+
normalizeVersion(version) {
64+
const normalized = super.normalizeVersion(version);
65+
if (/^\d+\.\d+\.\d+\.\d+(?:-ea)?$/.test(version)) {
66+
// Keep patch-only requests distinct from exact SDKMAN build requests.
67+
normalized.version = version.replace(/-ea$/, '');
68+
}
69+
return normalized;
70+
}
71+
isVersionSatisfied(range, version) {
72+
const patchRequest = range.match(/^(\d+\.\d+\.\d+)\.(\d+)$/);
73+
if (patchRequest) {
74+
const candidate = semver__WEBPACK_IMPORTED_MODULE_1___default().parse(version);
75+
if (!candidate || !semver__WEBPACK_IMPORTED_MODULE_1___default().eq(patchRequest[1], candidate)) {
76+
return false;
77+
}
78+
const patch = candidate.build.length > 1 ? candidate.build[0] : '0';
79+
return patch === patchRequest[2];
80+
}
81+
return super.isVersionSatisfied(range, version);
82+
}
83+
compareVersions(a, b) {
84+
const mainComparison = semver__WEBPACK_IMPORTED_MODULE_1___default().compare(a, b);
85+
if (mainComparison !== 0) {
86+
return mainComparison;
87+
}
88+
// SDKMAN encodes patch.build, but unpatched releases contain only build.
89+
const patchAndBuild = (version) => {
90+
const build = new (semver__WEBPACK_IMPORTED_MODULE_1___default().SemVer)(version).build;
91+
return (build.length > 1 ? build : ['0', ...build]).join('.');
92+
};
93+
return semver__WEBPACK_IMPORTED_MODULE_1___default().compareBuild(`0.0.0+${patchAndBuild(a)}`, `0.0.0+${patchAndBuild(b)}`);
94+
}
6395
async getAvailableVersions() {
6496
if (_actions_core__WEBPACK_IMPORTED_MODULE_3__/* .isDebug */ ._o()) {
6597
console.time('Retrieving available versions for Liberica took'); // eslint-disable-line no-console
@@ -131,6 +163,10 @@ class LibericaDistributions extends _base_installer_js__WEBPACK_IMPORTED_MODULE_
131163
const { featureVersion, interimVersion, updateVersion } = version;
132164
const buildVersion = version.version.split('+')[1] || version.buildVersion;
133165
const mainVersion = [featureVersion, interimVersion, updateVersion].join('.');
166+
const patchVersion = version.version.split('+')[0].split('.')[3];
167+
if (patchVersion) {
168+
return `${mainVersion}+${patchVersion}.${buildVersion}`;
169+
}
134170
if (buildVersion != 0) {
135171
return `${mainVersion}+${buildVersion}`;
136172
}

0 commit comments

Comments
 (0)