From bdd5dddec4b47ea258591aeffc3ed890ec3af985 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Wed, 29 Jul 2026 15:28:58 -0400 Subject: [PATCH 1/3] Centralize platform capability validation Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10e35b75-928f-4ef7-984e-605895c5d88e --- .github/workflows/e2e-versions.yml | 19 + README.md | 2 +- .../distributors/adopt-installer.test.ts | 1 + .../distributors/corretto-installer.test.ts | 13 + .../distributors/distribution-factory.test.ts | 93 ++- .../distributors/temurin-installer.test.ts | 1 + __tests__/java-platform-contract.test.ts | 134 +++++ action.yml | 2 +- dist/setup/index.js | 530 +++++++++++++----- docs/advanced-usage.md | 32 ++ src/distributions/adopt/installer.ts | 5 + src/distributions/base-installer.ts | 23 +- src/distributions/corretto/installer.ts | 5 + src/distributions/distribution-factory.ts | 48 +- src/distributions/platform-types.ts | 306 ++++++++++ src/distributions/temurin/installer.ts | 5 + src/distributions/zulu/installer.ts | 2 + 17 files changed, 1010 insertions(+), 211 deletions(-) create mode 100644 __tests__/java-platform-contract.test.ts create mode 100644 src/distributions/platform-types.ts diff --git a/.github/workflows/e2e-versions.yml b/.github/workflows/e2e-versions.yml index 197a13c15..5d02fe3e5 100644 --- a/.github/workflows/e2e-versions.yml +++ b/.github/workflows/e2e-versions.yml @@ -495,6 +495,25 @@ jobs: run: bash __tests__/verify-java.sh "$JAVA_VERSION" "$JAVA_PATH" shell: bash + setup-java-unsupported-platform: + name: Reject unsupported Oracle x86 on Linux + runs-on: ubuntu-latest + steps: + - *checkout_step + - name: Attempt unsupported setup + id: unsupported-setup + continue-on-error: true + uses: ./ + with: + distribution: oracle + java-version: '21' + architecture: x86 + - name: Verify setup was rejected + if: always() + env: + SETUP_OUTCOME: ${{ steps.unsupported-setup.outcome }} + run: test "$SETUP_OUTCOME" = failure + setup-java-version-both-version-inputs-presents: name: ${{ matrix.distribution }} version (should be from input) - ${{ matrix.os }} runs-on: ${{ matrix.os }} diff --git a/README.md b/README.md index 38d93876f..4a039c65f 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ For more details, see the full release notes on the [releases page](https://git - `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`. - - `architecture`: The target architecture of the package. Possible values: `x86`, `x64`, `armv7`, `aarch64`, `ppc64le`. Default value: Derived from the runner machine. + - `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. - `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.) diff --git a/__tests__/distributors/adopt-installer.test.ts b/__tests__/distributors/adopt-installer.test.ts index b59451b26..5bcf80834 100644 --- a/__tests__/distributors/adopt-installer.test.ts +++ b/__tests__/distributors/adopt-installer.test.ts @@ -260,6 +260,7 @@ describe('getAvailableVersions', () => { it.each([ ['amd64', 'x64'], + ['arm', 'arm'], ['arm64', 'aarch64'] ])( 'defaults to os.arch(): %s mapped to distro arch: %s', diff --git a/__tests__/distributors/corretto-installer.test.ts b/__tests__/distributors/corretto-installer.test.ts index 371ac86c5..0fa3a8217 100644 --- a/__tests__/distributors/corretto-installer.test.ts +++ b/__tests__/distributors/corretto-installer.test.ts @@ -299,6 +299,19 @@ describe('getAvailableVersions', () => { expect(availableVersion.url).toBe(expectedLink); } ); + + it('keeps the canonical ARM runner value separate from the vendor value', () => { + jest.spyOn(os, 'arch').mockReturnValue('arm'); + const distribution = new CorrettoDistribution({ + version: '11', + architecture: '', + packageType: 'jdk', + checkLatest: false + }); + + expect(distribution['architecture']).toBe('armv7'); + expect(distribution['distributionArchitecture']()).toBe('arm'); + }); }); const mockPlatform = ( diff --git a/__tests__/distributors/distribution-factory.test.ts b/__tests__/distributors/distribution-factory.test.ts index 7c04da52d..1d3f4ff33 100644 --- a/__tests__/distributors/distribution-factory.test.ts +++ b/__tests__/distributors/distribution-factory.test.ts @@ -4,6 +4,19 @@ import { JAVA_PACKAGE_CAPABILITIES, JavaDistribution } from '../../src/distributions/package-types.js'; +import os from 'os'; +import {validateJavaPlatform} from '../../src/distributions/platform-types.js'; + +const supportedDistributionsOnCurrentPlatform = Object.values( + JavaDistribution +).filter(distributionName => { + try { + validateJavaPlatform(distributionName, process.platform, 'x64', '25'); + return distributionName !== JavaDistribution.JdkFile; + } catch { + return false; + } +}); const installerOptions = (packageType: string, version = '25') => ({ version, @@ -13,41 +26,29 @@ const installerOptions = (packageType: string, version = '25') => ({ }); describe('getJavaDistribution', () => { - it.each([ - 'adopt', - 'adopt-hotspot', - 'adopt-openj9', - 'temurin', - 'zulu', - 'liberica', - 'liberica-nik', - 'microsoft', - 'semeru', - 'corretto', - 'oracle', - 'dragonwell', - 'sapmachine', - 'graalvm', - 'graalvm-community', - 'jetbrains', - 'kona', - 'oracle-openjdk' - ])('uses the shared retrying HTTP client for %s', distributionName => { - const distribution = getJavaDistribution(distributionName, { - version: '21', - architecture: 'x64', - packageType: 'jdk', - checkLatest: false - }); + it.each(supportedDistributionsOnCurrentPlatform)( + 'uses the shared retrying HTTP client for %s', + distributionName => { + const distribution = getJavaDistribution(distributionName, { + version: '25', + architecture: 'x64', + packageType: 'jdk', + checkLatest: false + }); - expect(distribution).not.toBeNull(); - expect(distribution!['http']).toBeInstanceOf(RetryingHttpClient); - }); + expect(distribution).not.toBeNull(); + expect(distribution!['http']).toBeInstanceOf(RetryingHttpClient); + } + ); it.each( Object.entries(JAVA_PACKAGE_CAPABILITIES).flatMap( ([distributionName, packageTypes]) => - packageTypes.map(packageType => [distributionName, packageType]) + supportedDistributionsOnCurrentPlatform.includes( + distributionName as JavaDistribution + ) || distributionName === JavaDistribution.JdkFile + ? packageTypes.map(packageType => [distributionName, packageType]) + : [] ) )('accepts %s with java-package %s', (distributionName, packageType) => { expect( @@ -111,4 +112,36 @@ describe('getJavaDistribution', () => { ) ).toBeNull(); }); + + it.each([ + ['amd64', 'x64'], + ['ia32', 'x86'], + ['arm64', 'aarch64'] + ])('passes normalized architecture %s as %s', (input, expected) => { + const normalized = getJavaDistribution(JavaDistribution.JdkFile, { + ...installerOptions('jdk'), + architecture: input + }); + + expect(normalized!['architecture']).toBe(expected); + }); + + it('uses the runner architecture when the input is empty', () => { + const distribution = getJavaDistribution(JavaDistribution.Temurin, { + ...installerOptions('jdk'), + architecture: '' + }); + + const expected = os.arch() === 'arm64' ? 'aarch64' : os.arch(); + expect(distribution!['architecture']).toBe(expected); + }); + + it('rejects an unsupported combination before creating an HTTP client', () => { + expect(() => + getJavaDistribution(JavaDistribution.Oracle, { + ...installerOptions('jdk'), + architecture: 'x86' + }) + ).toThrow(/does not support operating system/); + }); }); diff --git a/__tests__/distributors/temurin-installer.test.ts b/__tests__/distributors/temurin-installer.test.ts index d32c01142..793c3778b 100644 --- a/__tests__/distributors/temurin-installer.test.ts +++ b/__tests__/distributors/temurin-installer.test.ts @@ -291,6 +291,7 @@ describe('getAvailableVersions', () => { it.each([ ['amd64', 'x64'], + ['arm', 'arm'], ['arm64', 'aarch64'] ])( 'defaults to os.arch(): %s mapped to distro arch: %s', diff --git a/__tests__/java-platform-contract.test.ts b/__tests__/java-platform-contract.test.ts new file mode 100644 index 000000000..5433a4a6d --- /dev/null +++ b/__tests__/java-platform-contract.test.ts @@ -0,0 +1,134 @@ +import fs from 'fs'; +import path from 'path'; +import { + JAVA_PLATFORM_CAPABILITIES, + normalizeArchitecture, + validateJavaPlatform +} from '../src/distributions/platform-types.js'; +import {JavaDistribution} from '../src/distributions/package-types.js'; + +describe('Java platform capabilities', () => { + it('declares a capability for every distribution', () => { + expect(Object.keys(JAVA_PLATFORM_CAPABILITIES).sort()).toEqual( + Object.values(JavaDistribution).sort() + ); + }); + + it.each([ + ['x64', 'x64'], + ['amd64', 'x64'], + ['x86', 'x86'], + ['ia32', 'x86'], + ['arm', 'armv7'], + ['aarch64', 'aarch64'], + ['arm64', 'aarch64'], + ['ppc64le', 'ppc64le'], + ['s390x', 's390x'] + ])('normalizes architecture %s to %s', (input, expected) => { + expect(normalizeArchitecture(input)).toBe(expected); + }); + + it('uses the normalized architecture for validation', () => { + expect(validateJavaPlatform('microsoft', 'linux', 'arm64', '25')).toBe( + 'aarch64' + ); + }); + + it('rejects OS-specific restrictions with a consistent diagnostic', () => { + expect(() => + validateJavaPlatform('oracle', 'win32', 'arm64', '21') + ).toThrow( + "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)." + ); + }); + + it('rejects version-dependent architecture restrictions', () => { + expect(() => + validateJavaPlatform('corretto', 'linux', 'x86', '17') + ).toThrow(/x86 \(<=11\)/); + expect(() => + validateJavaPlatform('corretto', 'linux', 'x86', '17.0.2.8.1') + ).toThrow(/x86 \(<=11\)/); + expect(validateJavaPlatform('corretto', 'linux', 'x86', '11')).toBe('x86'); + }); + + it.each(['corretto', 'kona'])( + 'rejects Windows aarch64 for %s', + distributionName => { + expect(() => + validateJavaPlatform(distributionName, 'win32', 'arm64', '21') + ).toThrow(/does not support operating system 'windows'/); + } + ); + + it('keeps Adopt HotSpot aliases aligned with the Temurin-first resolver', () => { + expect(validateJavaPlatform('adopt', 'darwin', 'arm64', '21')).toBe( + 'aarch64' + ); + expect(validateJavaPlatform('adopt-hotspot', 'win32', 'arm64', '21')).toBe( + 'aarch64' + ); + expect(() => + validateJavaPlatform('adopt-openj9', 'darwin', 'arm64', '16') + ).toThrow(/does not support operating system 'macos'/); + }); + + it('allows local archives on any platform and architecture', () => { + expect(validateJavaPlatform('jdkfile', 'aix', 'mips64', '21')).toBe( + 'mips64' + ); + }); + + it('keeps the documented architecture contract aligned', () => { + const repositoryRoot = process.cwd(); + const readRepositoryFile = (filePath: string) => + fs.readFileSync(path.join(repositoryRoot, filePath), 'utf8'); + + for (const filePath of ['action.yml', 'README.md']) { + const content = readRepositoryFile(filePath); + for (const architecture of [ + 'x86', + 'x64', + 'armv7', + 'aarch64', + 'ppc64le' + ]) { + expect(content).toContain(architecture); + } + } + }); + + it.each(Object.entries(JAVA_PLATFORM_CAPABILITIES))( + 'keeps the advanced compatibility table aligned for %s', + (distributionName, capability) => { + const advancedUsage = fs.readFileSync( + path.join(process.cwd(), 'docs/advanced-usage.md'), + 'utf8' + ); + const compatibilityTable = advancedUsage.slice( + advancedUsage.indexOf('## Platform and architecture compatibility') + ); + const compatibilityRow = compatibilityTable + .split('\n') + .find( + line => + line.startsWith('|') && line.includes(`\`${distributionName}\``) + ); + + expect(compatibilityRow).toBeDefined(); + if (!('platforms' in capability)) { + expect(compatibilityRow).toContain('Any'); + return; + } + + const architectures = new Set( + Object.values(capability.platforms) + .flat() + .map(item => (typeof item === 'string' ? item : item.architecture)) + ); + for (const architecture of architectures) { + expect(compatibilityRow).toContain(`\`${architecture}\``); + } + } + ); +}); diff --git a/action.yml b/action.yml index 2b4072ea9..280f83c1e 100644 --- a/action.yml +++ b/action.yml @@ -17,7 +17,7 @@ inputs: required: false default: 'jdk' architecture: - description: "The architecture of the package (defaults to the action runner's architecture)" + 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." required: false jdk-file: description: 'Path to where the compressed JDK is located' diff --git a/dist/setup/index.js b/dist/setup/index.js index 6e88b9c69..1d5cf6687 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -12040,7 +12040,7 @@ exports.NodeListStaticImpl = NodeListStaticImpl; /***/ }), -/***/ 9875: +/***/ 2256: /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { @@ -13485,7 +13485,7 @@ const NodeListImpl_1 = __nccwpck_require__(5788); Object.defineProperty(exports, "NodeList", ({ enumerable: true, get: function () { return NodeListImpl_1.NodeListImpl; } })); const NodeListStaticImpl_1 = __nccwpck_require__(7654); Object.defineProperty(exports, "NodeListStatic", ({ enumerable: true, get: function () { return NodeListStaticImpl_1.NodeListStaticImpl; } })); -const NonDocumentTypeChildNodeImpl_1 = __nccwpck_require__(9875); +const NonDocumentTypeChildNodeImpl_1 = __nccwpck_require__(2256); const NonElementParentNodeImpl_1 = __nccwpck_require__(5325); const ParentNodeImpl_1 = __nccwpck_require__(1824); const ProcessingInstructionImpl_1 = __nccwpck_require__(2755); @@ -129510,6 +129510,350 @@ async function verifyChecksum(filePath, checksum, context) { } } +;// CONCATENATED MODULE: ./src/distributions/package-types.ts + + +var JavaDistribution; +(function (JavaDistribution) { + JavaDistribution["Adopt"] = "adopt"; + JavaDistribution["AdoptHotspot"] = "adopt-hotspot"; + JavaDistribution["AdoptOpenJ9"] = "adopt-openj9"; + JavaDistribution["Temurin"] = "temurin"; + JavaDistribution["Zulu"] = "zulu"; + JavaDistribution["Liberica"] = "liberica"; + JavaDistribution["LibericaNik"] = "liberica-nik"; + JavaDistribution["JdkFile"] = "jdkfile"; + JavaDistribution["Microsoft"] = "microsoft"; + JavaDistribution["Semeru"] = "semeru"; + JavaDistribution["Corretto"] = "corretto"; + JavaDistribution["Oracle"] = "oracle"; + JavaDistribution["Dragonwell"] = "dragonwell"; + JavaDistribution["SapMachine"] = "sapmachine"; + JavaDistribution["GraalVM"] = "graalvm"; + JavaDistribution["GraalVMCommunity"] = "graalvm-community"; + JavaDistribution["JetBrains"] = "jetbrains"; + JavaDistribution["Kona"] = "kona"; + JavaDistribution["OracleOpenJdk"] = "oracle-openjdk"; +})(JavaDistribution || (JavaDistribution = {})); +const JAVA_PACKAGE_CAPABILITIES = { + [JavaDistribution.Adopt]: ['jdk', 'jre'], + [JavaDistribution.AdoptHotspot]: ['jdk', 'jre'], + [JavaDistribution.AdoptOpenJ9]: ['jdk', 'jre'], + [JavaDistribution.Temurin]: ['jdk', 'jre', 'jdk+jmods'], + [JavaDistribution.Zulu]: [ + 'jdk', + 'jre', + 'jdk+fx', + 'jre+fx', + 'jdk+crac', + 'jre+crac' + ], + [JavaDistribution.Liberica]: ['jdk', 'jre', 'jdk+fx', 'jre+fx'], + [JavaDistribution.LibericaNik]: ['jdk', 'jdk+fx'], + [JavaDistribution.JdkFile]: ['jdk'], + [JavaDistribution.Microsoft]: ['jdk'], + [JavaDistribution.Semeru]: ['jdk', 'jre'], + [JavaDistribution.Corretto]: ['jdk', 'jre'], + [JavaDistribution.Oracle]: ['jdk'], + [JavaDistribution.Dragonwell]: ['jdk'], + [JavaDistribution.SapMachine]: ['jdk', 'jre'], + [JavaDistribution.GraalVM]: ['jdk'], + [JavaDistribution.GraalVMCommunity]: ['jdk'], + [JavaDistribution.JetBrains]: [ + 'jdk', + 'jre', + 'jdk+jcef', + 'jre+jcef', + 'jdk+ft', + 'jre+ft' + ], + [JavaDistribution.Kona]: ['jdk'], + [JavaDistribution.OracleOpenJdk]: ['jdk'] +}; +function validateJavaPackage(distributionName, packageType, version) { + if (!isJavaDistribution(distributionName)) { + return; + } + const supportedPackages = JAVA_PACKAGE_CAPABILITIES[distributionName]; + if (!supportedPackages.includes(packageType)) { + throw createUnsupportedPackageError(distributionName, packageType, supportedPackages); + } + if (distributionName === JavaDistribution.Temurin && + packageType === 'jdk+jmods' && + !canResolveTemurinJmods(version)) { + throw createUnsupportedPackageError(distributionName, packageType, supportedPackages, `Package 'jdk+jmods' requires Java 24 or later; requested version '${version}'.`); + } +} +function isJavaDistribution(value) { + return Object.prototype.hasOwnProperty.call(JAVA_PACKAGE_CAPABILITIES, value); +} +function canResolveTemurinJmods(version) { + const normalizedVersion = version.trim().toLowerCase(); + if (normalizedVersion === 'latest') { + return true; + } + let normalizedRange = normalizedVersion + .replace(/-ea$/, '') + .replace('-ea.', '+'); + if (/^\d+(\.\d+){3,}$/.test(normalizedRange)) { + normalizedRange = convertVersionToSemver(normalizedRange); + } + if (!semver_default().validRange(normalizedRange)) { + // JavaBase owns general version validation and its targeted error messages. + return true; + } + return semver_default().intersects(normalizedRange, '>=24.0.0', { + includePrerelease: true + }); +} +function createUnsupportedPackageError(distributionName, packageType, supportedPackages, detail) { + const message = [ + `Java package '${packageType}' is not supported for distribution '${distributionName}'.`, + `Supported package types: ${supportedPackages.join(', ')}.` + ]; + if (detail) { + message.push(detail); + } + return new Error(message.join(' ')); +} + +;// CONCATENATED MODULE: ./src/distributions/platform-types.ts + + +const X64_ARM64 = ['x64', 'aarch64']; +const X64_X86 = ['x64', 'x86']; +const STANDARD_LINUX = ['x64', 'x86', 'aarch64', 'ppc64le', 's390x']; +const JAVA_PLATFORM_CAPABILITIES = { + [JavaDistribution.Adopt]: { + platforms: { + linux: [...STANDARD_LINUX, { architecture: 'armv7', versionRange: '<=17' }], + macos: X64_ARM64, + windows: ['x64', 'x86', 'aarch64'] + } + }, + [JavaDistribution.AdoptHotspot]: { + platforms: { + linux: [...STANDARD_LINUX, { architecture: 'armv7', versionRange: '<=17' }], + macos: X64_ARM64, + windows: ['x64', 'x86', 'aarch64'] + } + }, + [JavaDistribution.AdoptOpenJ9]: { + platforms: { + linux: STANDARD_LINUX, + macos: ['x64'], + windows: X64_X86 + } + }, + [JavaDistribution.Temurin]: { + platforms: { + linux: [...STANDARD_LINUX, { architecture: 'armv7', versionRange: '<=17' }], + macos: X64_ARM64, + windows: ['x64', 'x86', 'aarch64'] + } + }, + [JavaDistribution.Zulu]: { + platforms: { + linux: ['x64', 'x86', 'armv7', 'aarch64'], + macos: X64_ARM64, + windows: ['x64', 'x86', 'aarch64'] + } + }, + [JavaDistribution.Liberica]: { + platforms: { + linux: ['x64', 'x86', 'armv7', 'aarch64', 'ppc64le'], + macos: X64_ARM64, + windows: ['x64', 'x86', 'aarch64'], + solaris: ['x64'] + } + }, + [JavaDistribution.LibericaNik]: { + platforms: { + linux: X64_ARM64, + macos: X64_ARM64, + windows: X64_ARM64 + } + }, + [JavaDistribution.JdkFile]: { + unrestricted: true + }, + [JavaDistribution.Microsoft]: { + platforms: { + linux: X64_ARM64, + macos: X64_ARM64, + windows: X64_ARM64 + } + }, + [JavaDistribution.Semeru]: { + platforms: { + linux: ['x64', 'x86', 'ppc64le', 'ppc64', 's390x', 'aarch64'], + macos: X64_ARM64, + windows: ['x64', 'aarch64'] + } + }, + [JavaDistribution.Corretto]: { + platforms: { + linux: [ + 'x64', + { architecture: 'x86', versionRange: '<=11' }, + { architecture: 'armv7', versionRange: '11' }, + 'aarch64' + ], + macos: X64_ARM64, + windows: ['x64', { architecture: 'x86', versionRange: '<=11' }] + } + }, + [JavaDistribution.Oracle]: { + platforms: { + linux: X64_ARM64, + macos: X64_ARM64, + windows: ['x64'] + } + }, + [JavaDistribution.Dragonwell]: { + platforms: { + linux: X64_ARM64, + windows: ['x64'] + } + }, + [JavaDistribution.SapMachine]: { + platforms: { + linux: ['x64', 'aarch64', 'ppc64le'], + macos: X64_ARM64, + windows: X64_ARM64 + } + }, + [JavaDistribution.GraalVM]: { + platforms: { + linux: X64_ARM64, + macos: X64_ARM64, + windows: ['x64'] + } + }, + [JavaDistribution.GraalVMCommunity]: { + platforms: { + linux: X64_ARM64, + macos: X64_ARM64, + windows: ['x64'] + } + }, + [JavaDistribution.JetBrains]: { + platforms: { + linux: X64_ARM64, + macos: X64_ARM64, + windows: X64_ARM64 + } + }, + [JavaDistribution.Kona]: { + platforms: { + linux: X64_ARM64, + macos: X64_ARM64, + windows: ['x64'] + } + }, + [JavaDistribution.OracleOpenJdk]: { + platforms: { + linux: X64_ARM64, + macos: X64_ARM64, + windows: ['x64'] + } + } +}; +const ARCHITECTURE_ALIASES = { + amd64: 'x64', + arm: 'armv7', + ia32: 'x86', + arm64: 'aarch64' +}; +const CANONICAL_ARCHITECTURES = [ + 'x86', + 'x64', + 'armv7', + 'aarch64', + 'ppc64le', + 'ppc64', + 's390x' +]; +const PLATFORM_ALIASES = { + darwin: 'macos', + linux: 'linux', + sunos: 'solaris', + win32: 'windows' +}; +function normalizeArchitecture(architecture) { + const trimmedArchitecture = architecture.trim(); + const normalizedArchitecture = trimmedArchitecture.toLowerCase(); + return (ARCHITECTURE_ALIASES[normalizedArchitecture] ?? + (CANONICAL_ARCHITECTURES.includes(normalizedArchitecture) + ? normalizedArchitecture + : trimmedArchitecture)); +} +function normalizePlatform(platform) { + return PLATFORM_ALIASES[platform]; +} +function validateJavaPlatform(distributionName, platform, architecture, version) { + const normalizedArchitecture = normalizeArchitecture(architecture); + if (!platform_types_isJavaDistribution(distributionName)) { + return normalizedArchitecture; + } + const capability = JAVA_PLATFORM_CAPABILITIES[distributionName]; + if ('unrestricted' in capability) { + return normalizedArchitecture; + } + const normalizedPlatform = normalizePlatform(platform); + const architectures = normalizedPlatform + ? capability.platforms[normalizedPlatform] + : undefined; + const supported = architectures?.some(item => { + const architectureCapability = typeof item === 'string' ? { architecture: item } : item; + return (architectureCapability.architecture === normalizedArchitecture && + (!('versionRange' in architectureCapability) || + isVersionCompatible(version, architectureCapability.versionRange))); + }); + if (!supported) { + throw new Error(`Distribution '${distributionName}' does not support operating system '${normalizedPlatform ?? platform}' with architecture '${normalizedArchitecture}' for Java version '${version}'. Supported combinations: ${formatSupportedCombinations(capability)}.`); + } + return normalizedArchitecture; +} +function platform_types_isJavaDistribution(value) { + return Object.prototype.hasOwnProperty.call(JAVA_PLATFORM_CAPABILITIES, value); +} +function isVersionCompatible(version, supportedRange) { + let normalizedVersion = version.trim().toLowerCase(); + if (normalizedVersion === 'latest') { + return true; + } + if (/^\d+(\.\d+){3,}$/.test(normalizedVersion)) { + normalizedVersion = normalizeExtendedVersionToSemver(normalizedVersion); + } + const requestedRange = semver_default().validRange(normalizedVersion.replace(/-ea$/, '')); + const capabilityRange = semver_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_default().intersects(requestedRange, capabilityRange, { + includePrerelease: true + }); +} +function formatSupportedCombinations(capability) { + return Object.entries(capability.platforms) + .map(([platform, architectures]) => { + const values = architectures.map(item => typeof item === 'string' + ? item + : `${item.architecture} (${item.versionRange})`); + return `${platform} (${values.join(', ')})`; + }) + .join('; '); +} + ;// CONCATENATED MODULE: ./src/distributions/base-installer.ts @@ -129522,6 +129866,7 @@ async function verifyChecksum(filePath, checksum, context) { + class JavaBase { distribution; http; @@ -129543,7 +129888,7 @@ class JavaBase { stable: this.stable, latest: this.latest } = this.normalizeVersion(installerOptions.version)); - this.architecture = installerOptions.architecture || external_os_default().arch(); + this.architecture = normalizeArchitecture(installerOptions.architecture || external_os_default().arch()); this.packageType = installerOptions.packageType; this.checkLatest = installerOptions.checkLatest; this.forceDownload = installerOptions.forceDownload ?? false; @@ -129867,22 +130212,7 @@ class JavaBase { exportVariable(`JAVA_HOME_${majorVersion}_${this.architecture.toUpperCase()}`, toolPath); } distributionArchitecture() { - // default mappings of config architectures to distribution architectures - // override if a distribution uses any different names; see liberica for an example - // node's os.arch() - which this defaults to - can return any of: - // 'arm', 'arm64', 'ia32', 'mips', 'mipsel', 'ppc', 'ppc64', 's390', 's390x', and 'x64' - // so we need to map these to java distribution architectures - // 'amd64' is included here too b/c it's a common alias for 'x64' people might use explicitly - switch (this.architecture) { - case 'amd64': - return 'x64'; - case 'ia32': - return 'x86'; - case 'arm64': - return 'aarch64'; - default: - return this.architecture; - } + return this.architecture; } } @@ -130102,6 +130432,8 @@ class ZuluDistribution extends JavaBase { // would let a 32-bit request resolve to a 64-bit JDK. Use "i686" to // target only genuine 32-bit builds, matching the legacy API behavior. return 'i686'; + case 'armv7': + return 'arm'; case 'aarch64': case 'arm64': return 'aarch64'; @@ -130348,6 +130680,10 @@ class TemurinDistribution extends JavaBase { return process.platform; } } + distributionArchitecture() { + const architecture = super.distributionArchitecture(); + return architecture === 'armv7' ? 'arm' : architecture; + } } ;// CONCATENATED MODULE: ./src/distributions/adopt/installer.ts @@ -130526,6 +130862,10 @@ class AdoptDistribution extends JavaBase { return process.platform; } } + distributionArchitecture() { + const architecture = super.distributionArchitecture(); + return architecture === 'armv7' ? 'arm' : architecture; + } } ;// CONCATENATED MODULE: ./src/distributions/liberica/installer.ts @@ -131194,6 +131534,10 @@ class CorrettoDistribution extends JavaBase { return process.platform; } } + distributionArchitecture() { + const architecture = super.distributionArchitecture(); + return architecture === 'armv7' ? 'arm' : architecture; + } getCorrettoVersion(resource) { const regex = /(\d+.+)\//; const match = regex.exec(resource); @@ -132376,114 +132720,9 @@ class OpenJdkDistribution extends JavaBase { } } -;// CONCATENATED MODULE: ./src/distributions/package-types.ts - +;// CONCATENATED MODULE: ./src/distributions/distribution-factory.ts -var JavaDistribution; -(function (JavaDistribution) { - JavaDistribution["Adopt"] = "adopt"; - JavaDistribution["AdoptHotspot"] = "adopt-hotspot"; - JavaDistribution["AdoptOpenJ9"] = "adopt-openj9"; - JavaDistribution["Temurin"] = "temurin"; - JavaDistribution["Zulu"] = "zulu"; - JavaDistribution["Liberica"] = "liberica"; - JavaDistribution["LibericaNik"] = "liberica-nik"; - JavaDistribution["JdkFile"] = "jdkfile"; - JavaDistribution["Microsoft"] = "microsoft"; - JavaDistribution["Semeru"] = "semeru"; - JavaDistribution["Corretto"] = "corretto"; - JavaDistribution["Oracle"] = "oracle"; - JavaDistribution["Dragonwell"] = "dragonwell"; - JavaDistribution["SapMachine"] = "sapmachine"; - JavaDistribution["GraalVM"] = "graalvm"; - JavaDistribution["GraalVMCommunity"] = "graalvm-community"; - JavaDistribution["JetBrains"] = "jetbrains"; - JavaDistribution["Kona"] = "kona"; - JavaDistribution["OracleOpenJdk"] = "oracle-openjdk"; -})(JavaDistribution || (JavaDistribution = {})); -const JAVA_PACKAGE_CAPABILITIES = { - [JavaDistribution.Adopt]: ['jdk', 'jre'], - [JavaDistribution.AdoptHotspot]: ['jdk', 'jre'], - [JavaDistribution.AdoptOpenJ9]: ['jdk', 'jre'], - [JavaDistribution.Temurin]: ['jdk', 'jre', 'jdk+jmods'], - [JavaDistribution.Zulu]: [ - 'jdk', - 'jre', - 'jdk+fx', - 'jre+fx', - 'jdk+crac', - 'jre+crac' - ], - [JavaDistribution.Liberica]: ['jdk', 'jre', 'jdk+fx', 'jre+fx'], - [JavaDistribution.LibericaNik]: ['jdk', 'jdk+fx'], - [JavaDistribution.JdkFile]: ['jdk'], - [JavaDistribution.Microsoft]: ['jdk'], - [JavaDistribution.Semeru]: ['jdk', 'jre'], - [JavaDistribution.Corretto]: ['jdk', 'jre'], - [JavaDistribution.Oracle]: ['jdk'], - [JavaDistribution.Dragonwell]: ['jdk'], - [JavaDistribution.SapMachine]: ['jdk', 'jre'], - [JavaDistribution.GraalVM]: ['jdk'], - [JavaDistribution.GraalVMCommunity]: ['jdk'], - [JavaDistribution.JetBrains]: [ - 'jdk', - 'jre', - 'jdk+jcef', - 'jre+jcef', - 'jdk+ft', - 'jre+ft' - ], - [JavaDistribution.Kona]: ['jdk'], - [JavaDistribution.OracleOpenJdk]: ['jdk'] -}; -function validateJavaPackage(distributionName, packageType, version) { - if (!isJavaDistribution(distributionName)) { - return; - } - const supportedPackages = JAVA_PACKAGE_CAPABILITIES[distributionName]; - if (!supportedPackages.includes(packageType)) { - throw createUnsupportedPackageError(distributionName, packageType, supportedPackages); - } - if (distributionName === JavaDistribution.Temurin && - packageType === 'jdk+jmods' && - !canResolveTemurinJmods(version)) { - throw createUnsupportedPackageError(distributionName, packageType, supportedPackages, `Package 'jdk+jmods' requires Java 24 or later; requested version '${version}'.`); - } -} -function isJavaDistribution(value) { - return Object.prototype.hasOwnProperty.call(JAVA_PACKAGE_CAPABILITIES, value); -} -function canResolveTemurinJmods(version) { - const normalizedVersion = version.trim().toLowerCase(); - if (normalizedVersion === 'latest') { - return true; - } - let normalizedRange = normalizedVersion - .replace(/-ea$/, '') - .replace('-ea.', '+'); - if (/^\d+(\.\d+){3,}$/.test(normalizedRange)) { - normalizedRange = convertVersionToSemver(normalizedRange); - } - if (!semver_default().validRange(normalizedRange)) { - // JavaBase owns general version validation and its targeted error messages. - return true; - } - return semver_default().intersects(normalizedRange, '>=24.0.0', { - includePrerelease: true - }); -} -function createUnsupportedPackageError(distributionName, packageType, supportedPackages, detail) { - const message = [ - `Java package '${packageType}' is not supported for distribution '${distributionName}'.`, - `Supported package types: ${supportedPackages.join(', ')}.` - ]; - if (detail) { - message.push(detail); - } - return new Error(message.join(' ')); -} -;// CONCATENATED MODULE: ./src/distributions/distribution-factory.ts @@ -132503,44 +132742,49 @@ function createUnsupportedPackageError(distributionName, packageType, supportedP function getJavaDistribution(distributionName, installerOptions, jdkFile) { validateJavaPackage(distributionName, installerOptions.packageType, installerOptions.version); + const architecture = validateJavaPlatform(distributionName, process.platform, installerOptions.architecture || external_os_default().arch(), installerOptions.version); + const normalizedInstallerOptions = { + ...installerOptions, + architecture + }; switch (distributionName) { case JavaDistribution.JdkFile: - return new LocalDistribution(installerOptions, jdkFile); + return new LocalDistribution(normalizedInstallerOptions, jdkFile); case JavaDistribution.Adopt: case JavaDistribution.AdoptHotspot: - return new AdoptDistribution(installerOptions, AdoptImplementation.Hotspot); + return new AdoptDistribution(normalizedInstallerOptions, AdoptImplementation.Hotspot); case JavaDistribution.AdoptOpenJ9: - return new AdoptDistribution(installerOptions, AdoptImplementation.OpenJ9); + return new AdoptDistribution(normalizedInstallerOptions, AdoptImplementation.OpenJ9); case JavaDistribution.Temurin: - return new TemurinDistribution(installerOptions, TemurinImplementation.Hotspot); + return new TemurinDistribution(normalizedInstallerOptions, TemurinImplementation.Hotspot); case JavaDistribution.Zulu: - return new ZuluDistribution(installerOptions); + return new ZuluDistribution(normalizedInstallerOptions); case JavaDistribution.Liberica: - return new LibericaDistributions(installerOptions); + return new LibericaDistributions(normalizedInstallerOptions); case JavaDistribution.LibericaNik: - return new LibericaNikDistributions(installerOptions); + return new LibericaNikDistributions(normalizedInstallerOptions); case JavaDistribution.Microsoft: - return new MicrosoftDistributions(installerOptions); + return new MicrosoftDistributions(normalizedInstallerOptions); case JavaDistribution.Semeru: - return new SemeruDistribution(installerOptions); + return new SemeruDistribution(normalizedInstallerOptions); case JavaDistribution.Corretto: - return new CorrettoDistribution(installerOptions); + return new CorrettoDistribution(normalizedInstallerOptions); case JavaDistribution.Oracle: - return new OracleDistribution(installerOptions); + return new OracleDistribution(normalizedInstallerOptions); case JavaDistribution.Dragonwell: - return new DragonwellDistribution(installerOptions); + return new DragonwellDistribution(normalizedInstallerOptions); case JavaDistribution.SapMachine: - return new SapMachineDistribution(installerOptions); + return new SapMachineDistribution(normalizedInstallerOptions); case JavaDistribution.GraalVM: - return new GraalVMDistribution(installerOptions); + return new GraalVMDistribution(normalizedInstallerOptions); case JavaDistribution.GraalVMCommunity: - return new GraalVMCommunityDistribution(installerOptions); + return new GraalVMCommunityDistribution(normalizedInstallerOptions); case JavaDistribution.JetBrains: - return new JetBrainsDistribution(installerOptions); + return new JetBrainsDistribution(normalizedInstallerOptions); case JavaDistribution.Kona: - return new KonaDistribution(installerOptions); + return new KonaDistribution(normalizedInstallerOptions); case JavaDistribution.OracleOpenJdk: - return new OpenJdkDistribution(installerOptions); + return new OpenJdkDistribution(normalizedInstallerOptions); default: return null; } diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 1a429ad67..24f707159 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -484,6 +484,38 @@ jobs: > which provides purpose-built caching (see the > [setup-gradle documentation](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md)). +## Platform and architecture compatibility + +The `architecture` input is normalized before setup-java checks the tool cache +or contacts a vendor. `amd64`, `ia32`, `arm`, and `arm64` are accepted aliases +for `x64`, `x86`, `armv7`, and `aarch64`. The table lists the combinations +setup-java validates up front; an individual Java patch release can still be +absent from a vendor catalog. + +| Distribution | Linux | macOS | Windows | Other / version restrictions | +| --- | --- | --- | --- | --- | +| `temurin` | `x64`, `x86`, `armv7`, `aarch64`, `ppc64le`, `s390x` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | Linux `armv7` is available through Java 17. | +| `adopt`, `adopt-hotspot` | `x64`, `x86`, `armv7`, `aarch64`, `ppc64le`, `s390x` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | HotSpot requests try Temurin before the archived catalog; Linux `armv7` is available through Java 17. | +| `adopt-openj9` | `x64`, `x86`, `aarch64`, `ppc64le`, `s390x` | `x64` | `x64`, `x86` | Uses the archived AdoptOpenJDK OpenJ9 catalog. | +| `zulu` | `x64`, `x86`, `armv7`, `aarch64` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | | +| `liberica` | `x64`, `x86`, `armv7`, `aarch64`, `ppc64le` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | Solaris: `x64`. | +| `liberica-nik` | `x64`, `aarch64` | `x64`, `aarch64` | `x64`, `aarch64` | | +| `microsoft` | `x64`, `aarch64` | `x64`, `aarch64` | `x64`, `aarch64` | | +| `semeru` | `x64`, `x86`, `ppc64le`, `ppc64`, `s390x`, `aarch64` | `x64`, `aarch64` | `x64`, `aarch64` | | +| `corretto` | `x64`, `x86`, `armv7`, `aarch64` | `x64`, `aarch64` | `x64`, `x86` | `x86` is limited to Java 11 or earlier; Linux `armv7` is available for Java 11. | +| `oracle` | `x64`, `aarch64` | `x64`, `aarch64` | `x64` | | +| `oracle-openjdk` | `x64`, `aarch64` | `x64`, `aarch64` | `x64` | | +| `dragonwell` | `x64`, `aarch64` | — | `x64` | | +| `sapmachine` | `x64`, `aarch64`, `ppc64le` | `x64`, `aarch64` | `x64`, `aarch64` | | +| `graalvm`, `graalvm-community` | `x64`, `aarch64` | `x64`, `aarch64` | `x64` | | +| `jetbrains` | `x64`, `aarch64` | `x64`, `aarch64` | `x64`, `aarch64` | | +| `kona` | `x64`, `aarch64` | `x64`, `aarch64` | `x64` | | +| `jdkfile` | Any | Any | Any | Local archives are not restricted because setup-java does not inspect their contents. | + +Unsupported combinations fail with a platform-capability error before a cache +lookup or vendor request. A supported combination can still produce a +version-not-found error when the requested release was not published. + ## Installing custom Java architecture ```yaml diff --git a/src/distributions/adopt/installer.ts b/src/distributions/adopt/installer.ts index 84bb4f2c5..aa97c6825 100644 --- a/src/distributions/adopt/installer.ts +++ b/src/distributions/adopt/installer.ts @@ -260,4 +260,9 @@ export class AdoptDistribution extends JavaBase { return process.platform; } } + + protected distributionArchitecture(): string { + const architecture = super.distributionArchitecture(); + return architecture === 'armv7' ? 'arm' : architecture; + } } diff --git a/src/distributions/base-installer.ts b/src/distributions/base-installer.ts index 2c8753fb6..d771b9271 100644 --- a/src/distributions/base-installer.ts +++ b/src/distributions/base-installer.ts @@ -20,6 +20,7 @@ import {MACOS_JAVA_CONTENT_POSTFIX} from '../constants.js'; import {RetryingHttpClient} from '../retrying-http-client.js'; import os from 'os'; import {expectedDigestLength, verifyChecksum} from '../checksum.js'; +import {normalizeArchitecture} from './platform-types.js'; export abstract class JavaBase { protected http: httpm.HttpClient; @@ -45,7 +46,9 @@ export abstract class JavaBase { stable: this.stable, latest: this.latest } = this.normalizeVersion(installerOptions.version)); - this.architecture = installerOptions.architecture || os.arch(); + this.architecture = normalizeArchitecture( + installerOptions.architecture || os.arch() + ); this.packageType = installerOptions.packageType; this.checkLatest = installerOptions.checkLatest; this.forceDownload = installerOptions.forceDownload ?? false; @@ -451,22 +454,6 @@ export abstract class JavaBase { } protected distributionArchitecture(): string { - // default mappings of config architectures to distribution architectures - // override if a distribution uses any different names; see liberica for an example - - // node's os.arch() - which this defaults to - can return any of: - // 'arm', 'arm64', 'ia32', 'mips', 'mipsel', 'ppc', 'ppc64', 's390', 's390x', and 'x64' - // so we need to map these to java distribution architectures - // 'amd64' is included here too b/c it's a common alias for 'x64' people might use explicitly - switch (this.architecture) { - case 'amd64': - return 'x64'; - case 'ia32': - return 'x86'; - case 'arm64': - return 'aarch64'; - default: - return this.architecture; - } + return this.architecture; } } diff --git a/src/distributions/corretto/installer.ts b/src/distributions/corretto/installer.ts index 065420007..bd8436f63 100644 --- a/src/distributions/corretto/installer.ts +++ b/src/distributions/corretto/installer.ts @@ -194,6 +194,11 @@ export class CorrettoDistribution extends JavaBase { } } + protected distributionArchitecture(): string { + const architecture = super.distributionArchitecture(); + return architecture === 'armv7' ? 'arm' : architecture; + } + private getCorrettoVersion(resource: string): string { const regex = /(\d+.+)\//; const match = regex.exec(resource); diff --git a/src/distributions/distribution-factory.ts b/src/distributions/distribution-factory.ts index 7cc578ca3..4932965f4 100644 --- a/src/distributions/distribution-factory.ts +++ b/src/distributions/distribution-factory.ts @@ -23,6 +23,8 @@ import {JetBrainsDistribution} from './jetbrains/installer.js'; import {KonaDistribution} from './kona/installer.js'; import {OpenJdkDistribution} from './openjdk/installer.js'; import {JavaDistribution, validateJavaPackage} from './package-types.js'; +import os from 'os'; +import {validateJavaPlatform} from './platform-types.js'; export function getJavaDistribution( distributionName: string, @@ -34,54 +36,64 @@ export function getJavaDistribution( installerOptions.packageType, installerOptions.version ); + const architecture = validateJavaPlatform( + distributionName, + process.platform, + installerOptions.architecture || os.arch(), + installerOptions.version + ); + const normalizedInstallerOptions = { + ...installerOptions, + architecture + }; switch (distributionName) { case JavaDistribution.JdkFile: - return new LocalDistribution(installerOptions, jdkFile); + return new LocalDistribution(normalizedInstallerOptions, jdkFile); case JavaDistribution.Adopt: case JavaDistribution.AdoptHotspot: return new AdoptDistribution( - installerOptions, + normalizedInstallerOptions, AdoptImplementation.Hotspot ); case JavaDistribution.AdoptOpenJ9: return new AdoptDistribution( - installerOptions, + normalizedInstallerOptions, AdoptImplementation.OpenJ9 ); case JavaDistribution.Temurin: return new TemurinDistribution( - installerOptions, + normalizedInstallerOptions, TemurinImplementation.Hotspot ); case JavaDistribution.Zulu: - return new ZuluDistribution(installerOptions); + return new ZuluDistribution(normalizedInstallerOptions); case JavaDistribution.Liberica: - return new LibericaDistributions(installerOptions); + return new LibericaDistributions(normalizedInstallerOptions); case JavaDistribution.LibericaNik: - return new LibericaNikDistributions(installerOptions); + return new LibericaNikDistributions(normalizedInstallerOptions); case JavaDistribution.Microsoft: - return new MicrosoftDistributions(installerOptions); + return new MicrosoftDistributions(normalizedInstallerOptions); case JavaDistribution.Semeru: - return new SemeruDistribution(installerOptions); + return new SemeruDistribution(normalizedInstallerOptions); case JavaDistribution.Corretto: - return new CorrettoDistribution(installerOptions); + return new CorrettoDistribution(normalizedInstallerOptions); case JavaDistribution.Oracle: - return new OracleDistribution(installerOptions); + return new OracleDistribution(normalizedInstallerOptions); case JavaDistribution.Dragonwell: - return new DragonwellDistribution(installerOptions); + return new DragonwellDistribution(normalizedInstallerOptions); case JavaDistribution.SapMachine: - return new SapMachineDistribution(installerOptions); + return new SapMachineDistribution(normalizedInstallerOptions); case JavaDistribution.GraalVM: - return new GraalVMDistribution(installerOptions); + return new GraalVMDistribution(normalizedInstallerOptions); case JavaDistribution.GraalVMCommunity: - return new GraalVMCommunityDistribution(installerOptions); + return new GraalVMCommunityDistribution(normalizedInstallerOptions); case JavaDistribution.JetBrains: - return new JetBrainsDistribution(installerOptions); + return new JetBrainsDistribution(normalizedInstallerOptions); case JavaDistribution.Kona: - return new KonaDistribution(installerOptions); + return new KonaDistribution(normalizedInstallerOptions); case JavaDistribution.OracleOpenJdk: - return new OpenJdkDistribution(installerOptions); + return new OpenJdkDistribution(normalizedInstallerOptions); default: return null; } diff --git a/src/distributions/platform-types.ts b/src/distributions/platform-types.ts new file mode 100644 index 000000000..fefb8d189 --- /dev/null +++ b/src/distributions/platform-types.ts @@ -0,0 +1,306 @@ +import semver from 'semver'; +import {JavaDistribution} from './package-types.js'; + +export type JavaPlatform = 'linux' | 'macos' | 'windows' | 'solaris'; + +export type JavaArchitecture = + 'x86' | 'x64' | 'armv7' | 'aarch64' | 'ppc64le' | 'ppc64' | 's390x'; + +interface VersionedArchitecture { + architecture: JavaArchitecture; + versionRange: string; +} + +type ArchitectureCapability = JavaArchitecture | VersionedArchitecture; + +interface RestrictedPlatformCapability { + unrestricted?: false; + platforms: Partial>; +} + +interface UnrestrictedPlatformCapability { + unrestricted: true; +} + +export type JavaPlatformCapability = + RestrictedPlatformCapability | UnrestrictedPlatformCapability; + +const X64_ARM64 = ['x64', 'aarch64'] as const; +const X64_X86 = ['x64', 'x86'] as const; +const STANDARD_LINUX = ['x64', 'x86', 'aarch64', 'ppc64le', 's390x'] as const; + +export const JAVA_PLATFORM_CAPABILITIES: Record< + JavaDistribution, + JavaPlatformCapability +> = { + [JavaDistribution.Adopt]: { + platforms: { + linux: [...STANDARD_LINUX, {architecture: 'armv7', versionRange: '<=17'}], + macos: X64_ARM64, + windows: ['x64', 'x86', 'aarch64'] + } + }, + [JavaDistribution.AdoptHotspot]: { + platforms: { + linux: [...STANDARD_LINUX, {architecture: 'armv7', versionRange: '<=17'}], + macos: X64_ARM64, + windows: ['x64', 'x86', 'aarch64'] + } + }, + [JavaDistribution.AdoptOpenJ9]: { + platforms: { + linux: STANDARD_LINUX, + macos: ['x64'], + windows: X64_X86 + } + }, + [JavaDistribution.Temurin]: { + platforms: { + linux: [...STANDARD_LINUX, {architecture: 'armv7', versionRange: '<=17'}], + macos: X64_ARM64, + windows: ['x64', 'x86', 'aarch64'] + } + }, + [JavaDistribution.Zulu]: { + platforms: { + linux: ['x64', 'x86', 'armv7', 'aarch64'], + macos: X64_ARM64, + windows: ['x64', 'x86', 'aarch64'] + } + }, + [JavaDistribution.Liberica]: { + platforms: { + linux: ['x64', 'x86', 'armv7', 'aarch64', 'ppc64le'], + macos: X64_ARM64, + windows: ['x64', 'x86', 'aarch64'], + solaris: ['x64'] + } + }, + [JavaDistribution.LibericaNik]: { + platforms: { + linux: X64_ARM64, + macos: X64_ARM64, + windows: X64_ARM64 + } + }, + [JavaDistribution.JdkFile]: { + unrestricted: true + }, + [JavaDistribution.Microsoft]: { + platforms: { + linux: X64_ARM64, + macos: X64_ARM64, + windows: X64_ARM64 + } + }, + [JavaDistribution.Semeru]: { + platforms: { + linux: ['x64', 'x86', 'ppc64le', 'ppc64', 's390x', 'aarch64'], + macos: X64_ARM64, + windows: ['x64', 'aarch64'] + } + }, + [JavaDistribution.Corretto]: { + platforms: { + linux: [ + 'x64', + {architecture: 'x86', versionRange: '<=11'}, + {architecture: 'armv7', versionRange: '11'}, + 'aarch64' + ], + macos: X64_ARM64, + windows: ['x64', {architecture: 'x86', versionRange: '<=11'}] + } + }, + [JavaDistribution.Oracle]: { + platforms: { + linux: X64_ARM64, + macos: X64_ARM64, + windows: ['x64'] + } + }, + [JavaDistribution.Dragonwell]: { + platforms: { + linux: X64_ARM64, + windows: ['x64'] + } + }, + [JavaDistribution.SapMachine]: { + platforms: { + linux: ['x64', 'aarch64', 'ppc64le'], + macos: X64_ARM64, + windows: X64_ARM64 + } + }, + [JavaDistribution.GraalVM]: { + platforms: { + linux: X64_ARM64, + macos: X64_ARM64, + windows: ['x64'] + } + }, + [JavaDistribution.GraalVMCommunity]: { + platforms: { + linux: X64_ARM64, + macos: X64_ARM64, + windows: ['x64'] + } + }, + [JavaDistribution.JetBrains]: { + platforms: { + linux: X64_ARM64, + macos: X64_ARM64, + windows: X64_ARM64 + } + }, + [JavaDistribution.Kona]: { + platforms: { + linux: X64_ARM64, + macos: X64_ARM64, + windows: ['x64'] + } + }, + [JavaDistribution.OracleOpenJdk]: { + platforms: { + linux: X64_ARM64, + macos: X64_ARM64, + windows: ['x64'] + } + } +}; + +const ARCHITECTURE_ALIASES: Readonly> = { + amd64: 'x64', + arm: 'armv7', + ia32: 'x86', + arm64: 'aarch64' +}; +const CANONICAL_ARCHITECTURES: readonly JavaArchitecture[] = [ + 'x86', + 'x64', + 'armv7', + 'aarch64', + 'ppc64le', + 'ppc64', + 's390x' +]; + +const PLATFORM_ALIASES: Readonly< + Partial> +> = { + darwin: 'macos', + linux: 'linux', + sunos: 'solaris', + win32: 'windows' +}; + +export function normalizeArchitecture(architecture: string): string { + const trimmedArchitecture = architecture.trim(); + const normalizedArchitecture = trimmedArchitecture.toLowerCase(); + return ( + ARCHITECTURE_ALIASES[normalizedArchitecture] ?? + (CANONICAL_ARCHITECTURES.includes( + normalizedArchitecture as JavaArchitecture + ) + ? normalizedArchitecture + : trimmedArchitecture) + ); +} + +export function normalizePlatform( + platform: NodeJS.Platform +): JavaPlatform | undefined { + return PLATFORM_ALIASES[platform]; +} + +export function validateJavaPlatform( + distributionName: string, + platform: NodeJS.Platform, + architecture: string, + version: string +): string { + const normalizedArchitecture = normalizeArchitecture(architecture); + if (!isJavaDistribution(distributionName)) { + return normalizedArchitecture; + } + + const capability = JAVA_PLATFORM_CAPABILITIES[distributionName]; + if ('unrestricted' in capability) { + return normalizedArchitecture; + } + + const normalizedPlatform = normalizePlatform(platform); + const architectures = normalizedPlatform + ? capability.platforms[normalizedPlatform] + : undefined; + const supported = architectures?.some(item => { + const architectureCapability = + typeof item === 'string' ? {architecture: item} : item; + return ( + architectureCapability.architecture === normalizedArchitecture && + (!('versionRange' in architectureCapability) || + isVersionCompatible(version, architectureCapability.versionRange)) + ); + }); + + if (!supported) { + throw new Error( + `Distribution '${distributionName}' does not support operating system '${normalizedPlatform ?? platform}' with architecture '${normalizedArchitecture}' for Java version '${version}'. Supported combinations: ${formatSupportedCombinations(capability)}.` + ); + } + + return normalizedArchitecture; +} + +function isJavaDistribution(value: string): value is JavaDistribution { + return Object.prototype.hasOwnProperty.call( + JAVA_PLATFORM_CAPABILITIES, + value + ); +} + +function isVersionCompatible(version: string, supportedRange: string): boolean { + let 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$/, '') + ); + 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 + }); +} + +function formatSupportedCombinations( + capability: RestrictedPlatformCapability +): string { + return Object.entries(capability.platforms) + .map(([platform, architectures]) => { + const values = architectures.map(item => + typeof item === 'string' + ? item + : `${item.architecture} (${item.versionRange})` + ); + return `${platform} (${values.join(', ')})`; + }) + .join('; '); +} diff --git a/src/distributions/temurin/installer.ts b/src/distributions/temurin/installer.ts index 4762b0cae..6f1f23704 100644 --- a/src/distributions/temurin/installer.ts +++ b/src/distributions/temurin/installer.ts @@ -282,4 +282,9 @@ export class TemurinDistribution extends JavaBase { return process.platform; } } + + protected distributionArchitecture(): string { + const architecture = super.distributionArchitecture(); + return architecture === 'armv7' ? 'arm' : architecture; + } } diff --git a/src/distributions/zulu/installer.ts b/src/distributions/zulu/installer.ts index aa0e0e79b..1c78961ec 100644 --- a/src/distributions/zulu/installer.ts +++ b/src/distributions/zulu/installer.ts @@ -221,6 +221,8 @@ export class ZuluDistribution extends JavaBase { // would let a 32-bit request resolve to a 64-bit JDK. Use "i686" to // target only genuine 32-bit builds, matching the legacy API behavior. return 'i686'; + case 'armv7': + return 'arm'; case 'aarch64': case 'arm64': return 'aarch64'; From f1373f4179da13c30f661012cd1ef5fc2e898639 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Wed, 29 Jul 2026 15:38:00 -0400 Subject: [PATCH 2/3] Address PR review feedback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10e35b75-928f-4ef7-984e-605895c5d88e --- __tests__/distributors/distribution-factory.test.ts | 3 ++- __tests__/java-platform-contract.test.ts | 8 +++++--- src/distributions/platform-types.ts | 12 ++++++------ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/__tests__/distributors/distribution-factory.test.ts b/__tests__/distributors/distribution-factory.test.ts index 1d3f4ff33..2dfeb6ffb 100644 --- a/__tests__/distributors/distribution-factory.test.ts +++ b/__tests__/distributors/distribution-factory.test.ts @@ -6,6 +6,7 @@ import { } from '../../src/distributions/package-types.js'; import os from 'os'; import {validateJavaPlatform} from '../../src/distributions/platform-types.js'; +import {normalizeArchitecture} from '../../src/distributions/platform-types.js'; const supportedDistributionsOnCurrentPlatform = Object.values( JavaDistribution @@ -132,7 +133,7 @@ describe('getJavaDistribution', () => { architecture: '' }); - const expected = os.arch() === 'arm64' ? 'aarch64' : os.arch(); + const expected = normalizeArchitecture(os.arch()); expect(distribution!['architecture']).toBe(expected); }); diff --git a/__tests__/java-platform-contract.test.ts b/__tests__/java-platform-contract.test.ts index 5433a4a6d..afe964d9b 100644 --- a/__tests__/java-platform-contract.test.ts +++ b/__tests__/java-platform-contract.test.ts @@ -45,10 +45,10 @@ describe('Java platform capabilities', () => { it('rejects version-dependent architecture restrictions', () => { expect(() => validateJavaPlatform('corretto', 'linux', 'x86', '17') - ).toThrow(/x86 \(<=11\)/); + ).toThrow(/x86 \(<12\)/); expect(() => validateJavaPlatform('corretto', 'linux', 'x86', '17.0.2.8.1') - ).toThrow(/x86 \(<=11\)/); + ).toThrow(/x86 \(<12\)/); expect(validateJavaPlatform('corretto', 'linux', 'x86', '11')).toBe('x86'); }); @@ -91,7 +91,9 @@ describe('Java platform capabilities', () => { 'x64', 'armv7', 'aarch64', - 'ppc64le' + 'ppc64le', + 'ppc64', + 's390x' ]) { expect(content).toContain(architecture); } diff --git a/src/distributions/platform-types.ts b/src/distributions/platform-types.ts index fefb8d189..4a4aed952 100644 --- a/src/distributions/platform-types.ts +++ b/src/distributions/platform-types.ts @@ -35,14 +35,14 @@ export const JAVA_PLATFORM_CAPABILITIES: Record< > = { [JavaDistribution.Adopt]: { platforms: { - linux: [...STANDARD_LINUX, {architecture: 'armv7', versionRange: '<=17'}], + linux: [...STANDARD_LINUX, {architecture: 'armv7', versionRange: '<18'}], macos: X64_ARM64, windows: ['x64', 'x86', 'aarch64'] } }, [JavaDistribution.AdoptHotspot]: { platforms: { - linux: [...STANDARD_LINUX, {architecture: 'armv7', versionRange: '<=17'}], + linux: [...STANDARD_LINUX, {architecture: 'armv7', versionRange: '<18'}], macos: X64_ARM64, windows: ['x64', 'x86', 'aarch64'] } @@ -56,7 +56,7 @@ export const JAVA_PLATFORM_CAPABILITIES: Record< }, [JavaDistribution.Temurin]: { platforms: { - linux: [...STANDARD_LINUX, {architecture: 'armv7', versionRange: '<=17'}], + linux: [...STANDARD_LINUX, {architecture: 'armv7', versionRange: '<18'}], macos: X64_ARM64, windows: ['x64', 'x86', 'aarch64'] } @@ -104,12 +104,12 @@ export const JAVA_PLATFORM_CAPABILITIES: Record< platforms: { linux: [ 'x64', - {architecture: 'x86', versionRange: '<=11'}, + {architecture: 'x86', versionRange: '<12'}, {architecture: 'armv7', versionRange: '11'}, 'aarch64' ], macos: X64_ARM64, - windows: ['x64', {architecture: 'x86', versionRange: '<=11'}] + windows: ['x64', {architecture: 'x86', versionRange: '<12'}] } }, [JavaDistribution.Oracle]: { @@ -225,7 +225,7 @@ export function validateJavaPlatform( } const capability = JAVA_PLATFORM_CAPABILITIES[distributionName]; - if ('unrestricted' in capability) { + if ('unrestricted' in capability && capability.unrestricted === true) { return normalizedArchitecture; } From 10bde1b52c8f6e6ca260dd6ed8b1201cfece4cd6 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Wed, 29 Jul 2026 15:42:21 -0400 Subject: [PATCH 3/3] Regenerate dist after platform validation updates Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dist/setup/index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dist/setup/index.js b/dist/setup/index.js index 1d5cf6687..a8f9406d2 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -129626,14 +129626,14 @@ const STANDARD_LINUX = ['x64', 'x86', 'aarch64', 'ppc64le', 's390x']; const JAVA_PLATFORM_CAPABILITIES = { [JavaDistribution.Adopt]: { platforms: { - linux: [...STANDARD_LINUX, { architecture: 'armv7', versionRange: '<=17' }], + linux: [...STANDARD_LINUX, { architecture: 'armv7', versionRange: '<18' }], macos: X64_ARM64, windows: ['x64', 'x86', 'aarch64'] } }, [JavaDistribution.AdoptHotspot]: { platforms: { - linux: [...STANDARD_LINUX, { architecture: 'armv7', versionRange: '<=17' }], + linux: [...STANDARD_LINUX, { architecture: 'armv7', versionRange: '<18' }], macos: X64_ARM64, windows: ['x64', 'x86', 'aarch64'] } @@ -129647,7 +129647,7 @@ const JAVA_PLATFORM_CAPABILITIES = { }, [JavaDistribution.Temurin]: { platforms: { - linux: [...STANDARD_LINUX, { architecture: 'armv7', versionRange: '<=17' }], + linux: [...STANDARD_LINUX, { architecture: 'armv7', versionRange: '<18' }], macos: X64_ARM64, windows: ['x64', 'x86', 'aarch64'] } @@ -129695,12 +129695,12 @@ const JAVA_PLATFORM_CAPABILITIES = { platforms: { linux: [ 'x64', - { architecture: 'x86', versionRange: '<=11' }, + { architecture: 'x86', versionRange: '<12' }, { architecture: 'armv7', versionRange: '11' }, 'aarch64' ], macos: X64_ARM64, - windows: ['x64', { architecture: 'x86', versionRange: '<=11' }] + windows: ['x64', { architecture: 'x86', versionRange: '<12' }] } }, [JavaDistribution.Oracle]: { @@ -129797,7 +129797,7 @@ function validateJavaPlatform(distributionName, platform, architecture, version) return normalizedArchitecture; } const capability = JAVA_PLATFORM_CAPABILITIES[distributionName]; - if ('unrestricted' in capability) { + if ('unrestricted' in capability && capability.unrestricted === true) { return normalizedArchitecture; } const normalizedPlatform = normalizePlatform(platform);