Skip to content

Commit 6937f5e

Browse files
brunoborgesCopilot
andauthored
Centralize OS/architecture capability validation (actions#1178)
* Centralize platform capability validation Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10e35b75-928f-4ef7-984e-605895c5d88e * Address PR review feedback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10e35b75-928f-4ef7-984e-605895c5d88e * Regenerate dist after platform validation updates Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10e35b75-928f-4ef7-984e-605895c5d88e
1 parent 0b56831 commit 6937f5e

17 files changed

Lines changed: 1013 additions & 211 deletions

‎.github/workflows/e2e-versions.yml‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,25 @@ jobs:
495495
run: bash __tests__/verify-java.sh "$JAVA_VERSION" "$JAVA_PATH"
496496
shell: bash
497497

498+
setup-java-unsupported-platform:
499+
name: Reject unsupported Oracle x86 on Linux
500+
runs-on: ubuntu-latest
501+
steps:
502+
- *checkout_step
503+
- name: Attempt unsupported setup
504+
id: unsupported-setup
505+
continue-on-error: true
506+
uses: ./
507+
with:
508+
distribution: oracle
509+
java-version: '21'
510+
architecture: x86
511+
- name: Verify setup was rejected
512+
if: always()
513+
env:
514+
SETUP_OUTCOME: ${{ steps.unsupported-setup.outcome }}
515+
run: test "$SETUP_OUTCOME" = failure
516+
498517
setup-java-version-both-version-inputs-presents:
499518
name: ${{ matrix.distribution }} version (should be from input) - ${{ matrix.os }}
500519
runs-on: ${{ matrix.os }}

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ For more details, see the full release notes on the [releases page](https://git
5050

5151
- `java-package`: The packaging variant of the chosen distribution. Possible values across all distributions are `jdk`, `jre`, `jdk+fx`, `jre+fx`, `jdk+crac`, `jre+crac`, `jdk+jmods`, `jdk+jcef`, `jre+jcef`, `jdk+ft`, and `jre+ft`. Supported values vary by distribution; see the [package compatibility table](docs/advanced-usage.md#package-compatibility). Default value: `jdk`.
5252

53-
- `architecture`: The target architecture of the package. Possible values: `x86`, `x64`, `armv7`, `aarch64`, `ppc64le`. Default value: Derived from the runner machine.
53+
- `architecture`: The target architecture of the package. Canonical values are `x86`, `x64`, `armv7`, `aarch64`, `ppc64le`, `ppc64`, and `s390x`; the aliases `ia32`, `amd64`, `arm`, and `arm64` normalize to `x86`, `x64`, `armv7`, and `aarch64`. Supported values vary by distribution and operating system. Default value: Derived from the runner machine.
5454

5555
- `jdk-file`: If a use-case requires a custom distribution setup-java uses the compressed JDK from the location pointed by this input and will take care of the installation and caching on the VM. Note: `distribution` must be set to 'jdkfile' (case-sensitive; all lowercase) when using this option. (The camelCase `jdkFile` input is still accepted as a deprecated alias and may be removed in a future release.)
5656

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ describe('getAvailableVersions', () => {
260260

261261
it.each([
262262
['amd64', 'x64'],
263+
['arm', 'arm'],
263264
['arm64', 'aarch64']
264265
])(
265266
'defaults to os.arch(): %s mapped to distro arch: %s',

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,19 @@ describe('getAvailableVersions', () => {
299299
expect(availableVersion.url).toBe(expectedLink);
300300
}
301301
);
302+
303+
it('keeps the canonical ARM runner value separate from the vendor value', () => {
304+
jest.spyOn(os, 'arch').mockReturnValue('arm');
305+
const distribution = new CorrettoDistribution({
306+
version: '11',
307+
architecture: '',
308+
packageType: 'jdk',
309+
checkLatest: false
310+
});
311+
312+
expect(distribution['architecture']).toBe('armv7');
313+
expect(distribution['distributionArchitecture']()).toBe('arm');
314+
});
302315
});
303316

304317
const mockPlatform = (

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

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ import {
44
JAVA_PACKAGE_CAPABILITIES,
55
JavaDistribution
66
} from '../../src/distributions/package-types.js';
7+
import os from 'os';
8+
import {validateJavaPlatform} from '../../src/distributions/platform-types.js';
9+
import {normalizeArchitecture} from '../../src/distributions/platform-types.js';
10+
11+
const supportedDistributionsOnCurrentPlatform = Object.values(
12+
JavaDistribution
13+
).filter(distributionName => {
14+
try {
15+
validateJavaPlatform(distributionName, process.platform, 'x64', '25');
16+
return distributionName !== JavaDistribution.JdkFile;
17+
} catch {
18+
return false;
19+
}
20+
});
721

822
const installerOptions = (packageType: string, version = '25') => ({
923
version,
@@ -13,41 +27,29 @@ const installerOptions = (packageType: string, version = '25') => ({
1327
});
1428

1529
describe('getJavaDistribution', () => {
16-
it.each([
17-
'adopt',
18-
'adopt-hotspot',
19-
'adopt-openj9',
20-
'temurin',
21-
'zulu',
22-
'liberica',
23-
'liberica-nik',
24-
'microsoft',
25-
'semeru',
26-
'corretto',
27-
'oracle',
28-
'dragonwell',
29-
'sapmachine',
30-
'graalvm',
31-
'graalvm-community',
32-
'jetbrains',
33-
'kona',
34-
'oracle-openjdk'
35-
])('uses the shared retrying HTTP client for %s', distributionName => {
36-
const distribution = getJavaDistribution(distributionName, {
37-
version: '21',
38-
architecture: 'x64',
39-
packageType: 'jdk',
40-
checkLatest: false
41-
});
30+
it.each(supportedDistributionsOnCurrentPlatform)(
31+
'uses the shared retrying HTTP client for %s',
32+
distributionName => {
33+
const distribution = getJavaDistribution(distributionName, {
34+
version: '25',
35+
architecture: 'x64',
36+
packageType: 'jdk',
37+
checkLatest: false
38+
});
4239

43-
expect(distribution).not.toBeNull();
44-
expect(distribution!['http']).toBeInstanceOf(RetryingHttpClient);
45-
});
40+
expect(distribution).not.toBeNull();
41+
expect(distribution!['http']).toBeInstanceOf(RetryingHttpClient);
42+
}
43+
);
4644

4745
it.each(
4846
Object.entries(JAVA_PACKAGE_CAPABILITIES).flatMap(
4947
([distributionName, packageTypes]) =>
50-
packageTypes.map(packageType => [distributionName, packageType])
48+
supportedDistributionsOnCurrentPlatform.includes(
49+
distributionName as JavaDistribution
50+
) || distributionName === JavaDistribution.JdkFile
51+
? packageTypes.map(packageType => [distributionName, packageType])
52+
: []
5153
)
5254
)('accepts %s with java-package %s', (distributionName, packageType) => {
5355
expect(
@@ -111,4 +113,36 @@ describe('getJavaDistribution', () => {
111113
)
112114
).toBeNull();
113115
});
116+
117+
it.each([
118+
['amd64', 'x64'],
119+
['ia32', 'x86'],
120+
['arm64', 'aarch64']
121+
])('passes normalized architecture %s as %s', (input, expected) => {
122+
const normalized = getJavaDistribution(JavaDistribution.JdkFile, {
123+
...installerOptions('jdk'),
124+
architecture: input
125+
});
126+
127+
expect(normalized!['architecture']).toBe(expected);
128+
});
129+
130+
it('uses the runner architecture when the input is empty', () => {
131+
const distribution = getJavaDistribution(JavaDistribution.Temurin, {
132+
...installerOptions('jdk'),
133+
architecture: ''
134+
});
135+
136+
const expected = normalizeArchitecture(os.arch());
137+
expect(distribution!['architecture']).toBe(expected);
138+
});
139+
140+
it('rejects an unsupported combination before creating an HTTP client', () => {
141+
expect(() =>
142+
getJavaDistribution(JavaDistribution.Oracle, {
143+
...installerOptions('jdk'),
144+
architecture: 'x86'
145+
})
146+
).toThrow(/does not support operating system/);
147+
});
114148
});

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ describe('getAvailableVersions', () => {
291291

292292
it.each([
293293
['amd64', 'x64'],
294+
['arm', 'arm'],
294295
['arm64', 'aarch64']
295296
])(
296297
'defaults to os.arch(): %s mapped to distro arch: %s',
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
import {
4+
JAVA_PLATFORM_CAPABILITIES,
5+
normalizeArchitecture,
6+
validateJavaPlatform
7+
} from '../src/distributions/platform-types.js';
8+
import {JavaDistribution} from '../src/distributions/package-types.js';
9+
10+
describe('Java platform capabilities', () => {
11+
it('declares a capability for every distribution', () => {
12+
expect(Object.keys(JAVA_PLATFORM_CAPABILITIES).sort()).toEqual(
13+
Object.values(JavaDistribution).sort()
14+
);
15+
});
16+
17+
it.each([
18+
['x64', 'x64'],
19+
['amd64', 'x64'],
20+
['x86', 'x86'],
21+
['ia32', 'x86'],
22+
['arm', 'armv7'],
23+
['aarch64', 'aarch64'],
24+
['arm64', 'aarch64'],
25+
['ppc64le', 'ppc64le'],
26+
['s390x', 's390x']
27+
])('normalizes architecture %s to %s', (input, expected) => {
28+
expect(normalizeArchitecture(input)).toBe(expected);
29+
});
30+
31+
it('uses the normalized architecture for validation', () => {
32+
expect(validateJavaPlatform('microsoft', 'linux', 'arm64', '25')).toBe(
33+
'aarch64'
34+
);
35+
});
36+
37+
it('rejects OS-specific restrictions with a consistent diagnostic', () => {
38+
expect(() =>
39+
validateJavaPlatform('oracle', 'win32', 'arm64', '21')
40+
).toThrow(
41+
"Distribution 'oracle' does not support operating system 'windows' with architecture 'aarch64' for Java version '21'. Supported combinations: linux (x64, aarch64); macos (x64, aarch64); windows (x64)."
42+
);
43+
});
44+
45+
it('rejects version-dependent architecture restrictions', () => {
46+
expect(() =>
47+
validateJavaPlatform('corretto', 'linux', 'x86', '17')
48+
).toThrow(/x86 \(<12\)/);
49+
expect(() =>
50+
validateJavaPlatform('corretto', 'linux', 'x86', '17.0.2.8.1')
51+
).toThrow(/x86 \(<12\)/);
52+
expect(validateJavaPlatform('corretto', 'linux', 'x86', '11')).toBe('x86');
53+
});
54+
55+
it.each(['corretto', 'kona'])(
56+
'rejects Windows aarch64 for %s',
57+
distributionName => {
58+
expect(() =>
59+
validateJavaPlatform(distributionName, 'win32', 'arm64', '21')
60+
).toThrow(/does not support operating system 'windows'/);
61+
}
62+
);
63+
64+
it('keeps Adopt HotSpot aliases aligned with the Temurin-first resolver', () => {
65+
expect(validateJavaPlatform('adopt', 'darwin', 'arm64', '21')).toBe(
66+
'aarch64'
67+
);
68+
expect(validateJavaPlatform('adopt-hotspot', 'win32', 'arm64', '21')).toBe(
69+
'aarch64'
70+
);
71+
expect(() =>
72+
validateJavaPlatform('adopt-openj9', 'darwin', 'arm64', '16')
73+
).toThrow(/does not support operating system 'macos'/);
74+
});
75+
76+
it('allows local archives on any platform and architecture', () => {
77+
expect(validateJavaPlatform('jdkfile', 'aix', 'mips64', '21')).toBe(
78+
'mips64'
79+
);
80+
});
81+
82+
it('keeps the documented architecture contract aligned', () => {
83+
const repositoryRoot = process.cwd();
84+
const readRepositoryFile = (filePath: string) =>
85+
fs.readFileSync(path.join(repositoryRoot, filePath), 'utf8');
86+
87+
for (const filePath of ['action.yml', 'README.md']) {
88+
const content = readRepositoryFile(filePath);
89+
for (const architecture of [
90+
'x86',
91+
'x64',
92+
'armv7',
93+
'aarch64',
94+
'ppc64le',
95+
'ppc64',
96+
's390x'
97+
]) {
98+
expect(content).toContain(architecture);
99+
}
100+
}
101+
});
102+
103+
it.each(Object.entries(JAVA_PLATFORM_CAPABILITIES))(
104+
'keeps the advanced compatibility table aligned for %s',
105+
(distributionName, capability) => {
106+
const advancedUsage = fs.readFileSync(
107+
path.join(process.cwd(), 'docs/advanced-usage.md'),
108+
'utf8'
109+
);
110+
const compatibilityTable = advancedUsage.slice(
111+
advancedUsage.indexOf('## Platform and architecture compatibility')
112+
);
113+
const compatibilityRow = compatibilityTable
114+
.split('\n')
115+
.find(
116+
line =>
117+
line.startsWith('|') && line.includes(`\`${distributionName}\``)
118+
);
119+
120+
expect(compatibilityRow).toBeDefined();
121+
if (!('platforms' in capability)) {
122+
expect(compatibilityRow).toContain('Any');
123+
return;
124+
}
125+
126+
const architectures = new Set(
127+
Object.values(capability.platforms)
128+
.flat()
129+
.map(item => (typeof item === 'string' ? item : item.architecture))
130+
);
131+
for (const architecture of architectures) {
132+
expect(compatibilityRow).toContain(`\`${architecture}\``);
133+
}
134+
}
135+
);
136+
});

‎action.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ inputs:
1717
required: false
1818
default: 'jdk'
1919
architecture:
20-
description: "The architecture of the package (defaults to the action runner's architecture)"
20+
description: "The architecture of the package (`x86`, `x64`, `armv7`, `aarch64`, `ppc64le`, `ppc64`, or `s390x`). Aliases `ia32`, `amd64`, `arm`, and `arm64` are normalized to `x86`, `x64`, `armv7`, and `aarch64`. Supported values vary by distribution and operating system. Defaults to the action runner's architecture."
2121
required: false
2222
jdk-file:
2323
description: 'Path to where the compressed JDK is located'

0 commit comments

Comments
 (0)