5
0
mirror of https://gitea.com/actions/setup-java.git synced 2026-08-07 02:31:20 +00:00

Fix JDK resolution cache platform identity (#1210)

* Fix JDK resolution cache platform identity

Include the effective Linux libc platform in JDK resolution cache keys so Alpine/musl and glibc runners cannot restore each other's release metadata. Bump the cache namespace and share Alpine detection with affected distributors.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: 4f95577c-567c-47a8-92f2-b4dced527866

* Update generated action bundles

Regenerate setup and cleanup distributions for the platform-aware JDK resolution cache.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: 4f95577c-567c-47a8-92f2-b4dced527866

* Cover the platform-identity fallback and Alpine short-circuit

getJavaPlatformIdentity's `?? platform` fallback and the alias path for
platforms other than linux/darwin/win32 had no coverage, and isAlpineLinux
had no direct test at all.

Verified by mutation: replacing the fallback with a constant, and dropping
the `platform === 'linux'` short-circuit from isAlpineLinux, both left the
existing suite fully green. The added cases fail on each.

The short-circuit case matters beyond coverage bookkeeping: it is what keeps
the /etc/alpine-release probe from running on non-Linux runners, so a stray
file can never make Windows or macOS resolve as musl.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 74248bb0-72af-41d8-b85d-b0f5836e68db

* Carry the platform identity into the floating resolution request

Merging main brought in #1219, which added getFloatingResolutionRequest
as a second construction site for JdkResolutionRequest. It predates the
required `platform` field, so the merged tree did not compile.

The floating request already carries `source`, which pins the artifact
bytes, so this changes no lookup behaviour on its own -- it keeps the two
request builders consistent and the tree building.

Also refreshes dist/, which the textual merge left stale.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 74248bb0-72af-41d8-b85d-b0f5836e68db

---------

Co-authored-by: Bruno Borges <brborges@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 74248bb0-72af-41d8-b85d-b0f5836e68db
This commit is contained in:
Julien Dubois
2026-08-05 18:45:11 +02:00
committed by GitHub
parent fb58a661f3
commit d0e61fe743
14 changed files with 149 additions and 38 deletions
@@ -101,6 +101,8 @@ const tc = await import('@actions/tool-cache');
const util = await import('../../src/util.js');
const jdkCache = await import('../../src/jdk-cache.js');
const jdkResolutionCache = await import('../../src/jdk-resolution-cache.js');
const {getJavaPlatformIdentity} =
await import('../../src/distributions/platform-types.js');
const {JavaBase} = await import('../../src/distributions/base-installer.js');
class EmptyJavaBase extends JavaBase {
@@ -1290,6 +1292,7 @@ describe('setupJava', () => {
const expectedRequest = {
distribution: 'Empty',
packageType: 'jdk',
platform: getJavaPlatformIdentity(),
architecture: 'x86',
versionSpec: '11.0.9',
stable: true
@@ -1406,6 +1409,7 @@ describe('setupJava', () => {
{
distribution: 'Empty',
packageType: 'jdk',
platform: getJavaPlatformIdentity(),
architecture: 'x86',
versionSpec: '11.0.9',
stable: true,
+34
View File
@@ -1,6 +1,8 @@
import fs from 'fs';
import path from 'path';
import {
getJavaPlatformIdentity,
isAlpineLinux,
JAVA_PLATFORM_CAPABILITIES,
normalizeArchitecture,
validateJavaPlatform
@@ -28,6 +30,38 @@ describe('Java platform capabilities', () => {
expect(normalizeArchitecture(input)).toBe(expected);
});
it.each([
['linux', false, 'linux-glibc'],
['linux', true, 'linux-musl'],
['darwin', false, 'macos'],
['win32', false, 'windows'],
// Exercises the normalizePlatform alias path and the `?? platform`
// fallback for a platform that has no Java alias.
['sunos', false, 'solaris'],
['aix', false, 'aix']
] as const)(
'identifies %s with Alpine release %s as %s',
(platform, alpineReleaseExists, expected) => {
expect(getJavaPlatformIdentity(platform, alpineReleaseExists)).toBe(
expected
);
}
);
// The platform check has to short-circuit before the filesystem probe, so a
// stray /etc/alpine-release can never make a non-Linux runner look like musl.
it.each([
['linux', true, true],
['linux', false, false],
['darwin', true, false],
['win32', true, false]
] as const)(
'treats %s with Alpine release %s as Alpine: %s',
(platform, alpineReleaseExists, expected) => {
expect(isAlpineLinux(platform, alpineReleaseExists)).toBe(expected);
}
);
it('uses the normalized architecture for validation', () => {
expect(validateJavaPlatform('microsoft', 'linux', 'arm64', '25')).toBe(
'aarch64'
+23 -8
View File
@@ -25,6 +25,7 @@ const {restoreJdkResolution, registerJdkResolution, saveJdkResolutionCaches} =
const request = {
distribution: 'Temurin-Hotspot',
packageType: 'jdk',
platform: 'linux-glibc',
architecture: 'x64',
versionSpec: '21',
stable: true
@@ -102,10 +103,24 @@ describe('JDK resolution cache', () => {
expect(paths[0]).not.toContain(bucket());
expect(primaryKey).toBe(`${restoreKeys[0]}${bucket()}`);
expect(restoreKeys[0]).toMatch(
/^setup-java-jdkres-v1-Linux-x64-[0-9a-f]{64}-$/
/^setup-java-jdkres-v2-Linux-x64-[0-9a-f]{64}-$/
);
});
it('separates glibc and musl Linux resolutions', async () => {
createRunnerTemp();
await restoreJdkResolution(request);
const [glibcPaths, glibcKey] = jest.mocked(cache.restoreCache).mock
.calls[0] as [string[], string];
await restoreJdkResolution({...request, platform: 'linux-musl'});
const [muslPaths, muslKey] = jest.mocked(cache.restoreCache).mock
.calls[1] as [string[], string];
expect(muslKey).not.toBe(glibcKey);
expect(muslPaths).not.toEqual(glibcPaths);
});
it('holds the key steady for a week and then rolls it', async () => {
createRunnerTemp();
const nowSpy = jest.spyOn(Date, 'now');
@@ -129,7 +144,7 @@ describe('JDK resolution cache', () => {
it('reports a hit on the current bucket as fresh', async () => {
createRunnerTemp();
const key = `setup-java-jdkres-v1-Linux-x64-${'0'.repeat(64)}-${bucket()}`;
const key = `setup-java-jdkres-v2-Linux-x64-${'0'.repeat(64)}-${bucket()}`;
restoreWith(JSON.stringify(release), key);
// The key the module computes is the one it passes to restoreCache, so
@@ -152,7 +167,7 @@ describe('JDK resolution cache', () => {
it('reports a hit on an older bucket as stale', async () => {
createRunnerTemp();
restoreWith(JSON.stringify(release), 'setup-java-jdkres-v1-old');
restoreWith(JSON.stringify(release), 'setup-java-jdkres-v2-old');
const restored = await restoreJdkResolution(request);
expect(restored?.fresh).toBe(false);
@@ -233,7 +248,7 @@ describe('JDK resolution cache', () => {
]
])('rejects an entry with %s', async (_name, contents) => {
createRunnerTemp();
restoreWith(contents, 'setup-java-jdkres-v1-old');
restoreWith(contents, 'setup-java-jdkres-v2-old');
await expect(restoreJdkResolution(request)).resolves.toBeUndefined();
});
@@ -251,7 +266,7 @@ describe('JDK resolution cache', () => {
},
floating: true
};
restoreWith(JSON.stringify(full), 'setup-java-jdkres-v1-old');
restoreWith(JSON.stringify(full), 'setup-java-jdkres-v2-old');
const restored = await restoreJdkResolution(request);
expect(restored?.release).toEqual(full);
@@ -261,7 +276,7 @@ describe('JDK resolution cache', () => {
createRunnerTemp();
restoreWith(
JSON.stringify({...release, evil: 'payload'}),
'setup-java-jdkres-v1-old'
'setup-java-jdkres-v2-old'
);
const restored = await restoreJdkResolution(request);
@@ -332,7 +347,7 @@ describe('JDK resolution cache', () => {
const stateFor = (cachePath: string) =>
JSON.stringify([
{
key: 'setup-java-jdkres-v1-key',
key: 'setup-java-jdkres-v2-key',
path: cachePath,
release: JSON.stringify(release)
}
@@ -350,7 +365,7 @@ describe('JDK resolution cache', () => {
await saveJdkResolutionCaches();
expect(cache.saveCache).toHaveBeenCalledWith(
[root],
'setup-java-jdkres-v1-key'
'setup-java-jdkres-v2-key'
);
});