Skip to content

Commit 1d56e31

Browse files
Copilotbrunoborges
andauthored
dist: Add GraalVM Community distribution support (#1042)
* Initial plan * feat: add graalvm community distribution support * build: update bundled dist for graalvm community support * chore: address GraalVM community review feedback * fix: tidy graalvm community validation follow-ups * refactor: simplify GraalVM Community release resolution * refactor: address review feedback on Community resolver * refactor: rename pagination index for clarity * test: fix graalvm installer test formatting --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Bruno Borges <brborges@microsoft.com>
1 parent 1d25252 commit 1d56e31

6 files changed

Lines changed: 482 additions & 45 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ Currently, the following distributions are supported:
112112
| `dragonwell` | [Alibaba Dragonwell JDK](https://dragonwell-jdk.io/) | [`dragonwell` license](https://www.aliyun.com/product/dragonwell/)
113113
| `sapmachine` | [SAP SapMachine JDK/JRE](https://sapmachine.io/) | [`sapmachine` license](https://github.com/SAP/SapMachine/blob/sapmachine/LICENSE)
114114
| `graalvm` | [Oracle GraalVM](https://www.graalvm.org/) | [`graalvm` license](https://www.oracle.com/downloads/licenses/graal-free-license.html)
115+
| `graalvm-community` | [GraalVM Community](https://github.com/graalvm/graalvm-ce-builds/releases) | [`graalvm-community` license](https://github.com/oracle/graal/blob/master/LICENSE)
115116
| `jetbrains` | [JetBrains Runtime](https://github.com/JetBrains/JetBrainsRuntime/) | [`jetbrains` license](https://github.com/JetBrains/JetBrainsRuntime/blob/main/LICENSE)
116117
| `jdkfile` | Custom JDK Installation | |
117118

@@ -120,6 +121,7 @@ Currently, the following distributions are supported:
120121
> - AdoptOpenJDK got moved to Eclipse Temurin and won't be updated anymore. It is highly recommended to migrate workflows from `adopt` and `adopt-openj9`, to `temurin` and `semeru` respectively, to keep receiving software and security updates. See more details in the [Good-bye AdoptOpenJDK post](https://blog.adoptopenjdk.net/2021/08/goodbye-adoptopenjdk-hello-adoptium/).
121122
> - For Azul Zulu OpenJDK architectures x64 and arm64 are mapped to x86 / arm with proper hw_bitness.
122123
> - To comply with the GraalVM Free Terms and Conditions (GFTC) license, it is recommended to use GraalVM JDK 17 version 17.0.12, as this is the only version of GraalVM JDK 17 available under the GFTC license. Additionally, it is encouraged to consider upgrading to GraalVM JDK 21, which offers the latest features and improvements.
124+
> - GraalVM Community is available as `distribution: 'graalvm-community'` for stable JDK 17 and later releases published on GitHub.
123125

124126
**NOTE:** Oracle JDK 17 licensing varies by patch level. As shown on the [JDK 17 Archive](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) (versions up to 17.0.12 are under the [NFTC](https://www.oracle.com/downloads/licenses/no-fee-license.html) license) and the [JDK 17.0.13+ Archive](https://www.oracle.com/java/technologies/javase/jdk17-0-13-later-archive-downloads.html) (versions 17.0.13 and later are under the [OTN](https://www.oracle.com/downloads/licenses/javase-license1.html) license). To stay on the free NFTC license, use `distribution: 'oracle'` with `java-version: '17.0.12'` (or earlier) instead of the floating `'17'`. Alternatively, upgrade to Oracle JDK 21+, which remains under the NFTC license.
125127

__tests__/distributors/graalvm-installer.test.ts

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import * as tc from '@actions/tool-cache';
33
import * as http from '@actions/http-client';
44
import fs from 'fs';
55
import path from 'path';
6-
import {GraalVMDistribution} from '../../src/distributions/graalvm/installer';
6+
import {
7+
GraalVMCommunityDistribution,
8+
GraalVMDistribution
9+
} from '../../src/distributions/graalvm/installer';
10+
import {getJavaDistribution} from '../../src/distributions/distribution-factory';
711
import {JavaInstallerOptions} from '../../src/distributions/base-models';
812
import * as util from '../../src/util';
913

@@ -41,6 +45,7 @@ beforeAll(() => {
4145

4246
describe('GraalVMDistribution', () => {
4347
let distribution: GraalVMDistribution;
48+
let communityDistribution: GraalVMCommunityDistribution;
4449
let mockHttpClient: jest.Mocked<http.HttpClient>;
4550
let spyCoreError: jest.SpyInstance;
4651

@@ -55,9 +60,11 @@ describe('GraalVMDistribution', () => {
5560
jest.clearAllMocks();
5661

5762
distribution = new GraalVMDistribution(defaultOptions);
63+
communityDistribution = new GraalVMCommunityDistribution(defaultOptions);
5864

5965
mockHttpClient = new http.HttpClient() as jest.Mocked<http.HttpClient>;
6066
(distribution as any).http = mockHttpClient;
67+
(communityDistribution as any).http = mockHttpClient;
6168

6269
(util.getDownloadArchiveExtension as jest.Mock).mockReturnValue('tar.gz');
6370

@@ -242,6 +249,23 @@ describe('GraalVMDistribution', () => {
242249
path: '/cached/java/path'
243250
});
244251
});
252+
253+
it('should use a dedicated toolcache folder for GraalVM Community', async () => {
254+
const result = await (communityDistribution as any).downloadTool(
255+
javaRelease
256+
);
257+
258+
expect(tc.cacheDir).toHaveBeenCalledWith(
259+
path.join('/tmp/extracted', 'graalvm-jdk-17.0.5'),
260+
'Java_GraalVM_Community_jdk',
261+
'17.0.5',
262+
'x64'
263+
);
264+
expect(result).toEqual({
265+
version: '17.0.5',
266+
path: '/cached/java/path'
267+
});
268+
});
245269
});
246270

247271
describe('findPackageForDownload', () => {
@@ -948,5 +972,107 @@ describe('GraalVMDistribution', () => {
948972
configurable: true
949973
});
950974
});
975+
976+
describe('GraalVMCommunityDistribution', () => {
977+
beforeEach(() => {
978+
jest
979+
.spyOn(communityDistribution, 'getPlatform')
980+
.mockReturnValue('linux');
981+
});
982+
983+
it('should resolve an exact GraalVM Community version from GitHub releases', async () => {
984+
mockHttpClient.getJson.mockResolvedValue({
985+
result: [
986+
{
987+
draft: false,
988+
prerelease: false,
989+
assets: [
990+
{
991+
name: 'graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz',
992+
browser_download_url:
993+
'https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz'
994+
}
995+
]
996+
}
997+
],
998+
statusCode: 200,
999+
headers: {}
1000+
});
1001+
1002+
const result = await (
1003+
communityDistribution as any
1004+
).findPackageForDownload('21.0.2');
1005+
1006+
expect(result).toEqual({
1007+
url: 'https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz',
1008+
version: '21.0.2'
1009+
});
1010+
});
1011+
1012+
it('should resolve the latest GraalVM Community release for a major version', async () => {
1013+
mockHttpClient.getJson.mockResolvedValue({
1014+
result: [
1015+
{
1016+
draft: false,
1017+
prerelease: false,
1018+
assets: [
1019+
{
1020+
name: 'graalvm-community-jdk-21.0.1_linux-x64_bin.tar.gz',
1021+
browser_download_url:
1022+
'https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.1/graalvm-community-jdk-21.0.1_linux-x64_bin.tar.gz'
1023+
}
1024+
]
1025+
},
1026+
{
1027+
draft: false,
1028+
prerelease: false,
1029+
assets: [
1030+
{
1031+
name: 'graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz',
1032+
browser_download_url:
1033+
'https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz'
1034+
}
1035+
]
1036+
}
1037+
],
1038+
statusCode: 200,
1039+
headers: {}
1040+
});
1041+
1042+
const result = await (
1043+
communityDistribution as any
1044+
).findPackageForDownload('21');
1045+
1046+
expect(result).toEqual({
1047+
url: 'https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz',
1048+
version: '21.0.2'
1049+
});
1050+
});
1051+
1052+
it('should reject GraalVM Community early access requests', async () => {
1053+
(communityDistribution as any).stable = false;
1054+
1055+
await expect(
1056+
(communityDistribution as any).findPackageForDownload('23')
1057+
).rejects.toThrow(
1058+
'GraalVM Community does not provide early access builds'
1059+
);
1060+
});
1061+
});
1062+
});
1063+
});
1064+
1065+
describe('distribution factory', () => {
1066+
const defaultOptions: JavaInstallerOptions = {
1067+
version: '17',
1068+
architecture: 'x64',
1069+
packageType: 'jdk',
1070+
checkLatest: false
1071+
};
1072+
1073+
it('should map graalvm-community to the community installer', () => {
1074+
const community = getJavaDistribution('graalvm-community', defaultOptions);
1075+
1076+
expect(community).toBeInstanceOf(GraalVMCommunityDistribution);
9511077
});
9521078
});

dist/setup/index.js

Lines changed: 132 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -78771,6 +78771,7 @@ var JavaDistribution;
7877178771
JavaDistribution["Dragonwell"] = "dragonwell";
7877278772
JavaDistribution["SapMachine"] = "sapmachine";
7877378773
JavaDistribution["GraalVM"] = "graalvm";
78774+
JavaDistribution["GraalVMCommunity"] = "graalvm-community";
7877478775
JavaDistribution["JetBrains"] = "jetbrains";
7877578776
})(JavaDistribution || (JavaDistribution = {}));
7877678777
function getJavaDistribution(distributionName, installerOptions, jdkFile) {
@@ -78802,6 +78803,8 @@ function getJavaDistribution(distributionName, installerOptions, jdkFile) {
7880278803
return new installer_11.SapMachineDistribution(installerOptions);
7880378804
case JavaDistribution.GraalVM:
7880478805
return new installer_12.GraalVMDistribution(installerOptions);
78806+
case JavaDistribution.GraalVMCommunity:
78807+
return new installer_12.GraalVMCommunityDistribution(installerOptions);
7880578808
case JavaDistribution.JetBrains:
7880678809
return new installer_13.JetBrainsDistribution(installerOptions);
7880778810
default:
@@ -79069,23 +79072,29 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
7906979072
return (mod && mod.__esModule) ? mod : { "default": mod };
7907079073
};
7907179074
Object.defineProperty(exports, "__esModule", ({ value: true }));
79072-
exports.GraalVMDistribution = void 0;
79075+
exports.GraalVMCommunityDistribution = exports.GraalVMDistribution = void 0;
7907379076
const core = __importStar(__nccwpck_require__(37484));
7907479077
const tc = __importStar(__nccwpck_require__(33472));
7907579078
const fs_1 = __importDefault(__nccwpck_require__(79896));
7907679079
const path_1 = __importDefault(__nccwpck_require__(16928));
79080+
const semver_1 = __importDefault(__nccwpck_require__(62088));
7907779081
const base_installer_1 = __nccwpck_require__(79935);
7907879082
const http_client_1 = __nccwpck_require__(54844);
7907979083
const util_1 = __nccwpck_require__(54527);
7908079084
const GRAALVM_DL_BASE = 'https://download.oracle.com/graalvm';
7908179085
const GRAALVM_DOWNLOAD_URL = 'https://www.graalvm.org/downloads/';
79086+
const GRAALVM_COMMUNITY_RELEASES_URL = 'https://api.github.com/repos/graalvm/graalvm-ce-builds/releases?per_page=100';
79087+
const GRAALVM_COMMUNITY_RELEASES_PAGE_ORIGIN = 'https://api.github.com';
79088+
const GRAALVM_COMMUNITY_DOWNLOAD_URL = 'https://github.com/graalvm/graalvm-ce-builds/releases';
79089+
const GRAALVM_COMMUNITY_ASSET_PREFIX = 'graalvm-community-jdk-';
79090+
const GRAALVM_COMMUNITY_VERSION_PATTERN = /^\d+(?:\.\d+)*$/;
7908279091
const IS_WINDOWS = process.platform === 'win32';
7908379092
const GRAALVM_PLATFORM = IS_WINDOWS ? 'windows' : process.platform;
7908479093
const GRAALVM_MIN_VERSION = 17;
7908579094
const SUPPORTED_ARCHITECTURES = ['x64', 'aarch64'];
7908679095
class GraalVMDistribution extends base_installer_1.JavaBase {
79087-
constructor(installerOptions) {
79088-
super('GraalVM', installerOptions);
79096+
constructor(installerOptions, distributionName = 'GraalVM') {
79097+
super(distributionName, installerOptions);
7908979098
}
7909079099
downloadTool(javaRelease) {
7909179100
return __awaiter(this, void 0, void 0, function* () {
@@ -79119,36 +79128,50 @@ class GraalVMDistribution extends base_installer_1.JavaBase {
7911979128
}
7912079129
findPackageForDownload(range) {
7912179130
return __awaiter(this, void 0, void 0, function* () {
79122-
// Add input validation
79123-
if (!range || typeof range !== 'string') {
79124-
throw new Error('Version range is required and must be a string');
79125-
}
79126-
const arch = this.distributionArchitecture();
79127-
if (!SUPPORTED_ARCHITECTURES.includes(arch)) {
79128-
throw new Error(`Unsupported architecture: ${this.architecture}. Supported architectures are: ${SUPPORTED_ARCHITECTURES.join(', ')}`);
79129-
}
79131+
this.validateVersionRange(range);
79132+
const arch = this.getSupportedArchitecture();
7913079133
if (!this.stable) {
7913179134
return this.findEABuildDownloadUrl(`${range}-ea`);
7913279135
}
79133-
if (this.packageType !== 'jdk') {
79134-
throw new Error('GraalVM provides only the `jdk` package type');
79135-
}
79136-
const platform = this.getPlatform();
79137-
const extension = (0, util_1.getDownloadArchiveExtension)();
79138-
const major = range.includes('.') ? range.split('.')[0] : range;
79139-
const majorVersion = parseInt(major);
79140-
if (isNaN(majorVersion)) {
79141-
throw new Error(`Invalid version format: ${range}`);
79142-
}
79143-
if (majorVersion < GRAALVM_MIN_VERSION) {
79144-
throw new Error(`GraalVM is only supported for JDK ${GRAALVM_MIN_VERSION} and later. Requested version: ${major}`);
79145-
}
79136+
const { platform, extension, major } = this.validateStableBuildRequest(range);
7914679137
const fileUrl = this.constructFileUrl(range, major, platform, arch, extension);
7914779138
const response = yield this.http.head(fileUrl);
7914879139
this.handleHttpResponse(response, range);
7914979140
return { url: fileUrl, version: range };
7915079141
});
7915179142
}
79143+
validateVersionRange(range) {
79144+
if (!range || typeof range !== 'string') {
79145+
throw new Error('Version range is required and must be a string');
79146+
}
79147+
}
79148+
getSupportedArchitecture() {
79149+
const arch = this.distributionArchitecture();
79150+
if (!SUPPORTED_ARCHITECTURES.includes(arch)) {
79151+
throw new Error(`Unsupported architecture: ${this.architecture}. Supported architectures are: ${SUPPORTED_ARCHITECTURES.join(', ')}`);
79152+
}
79153+
return arch;
79154+
}
79155+
validateStableBuildRequest(range) {
79156+
if (this.packageType !== 'jdk') {
79157+
throw new Error(`${this.distribution} provides only the \`jdk\` package type`);
79158+
}
79159+
const platform = this.getPlatform();
79160+
const extension = (0, util_1.getDownloadArchiveExtension)();
79161+
const major = range.includes('.') ? range.split('.')[0] : range;
79162+
const majorVersion = parseInt(major);
79163+
if (isNaN(majorVersion)) {
79164+
throw new Error(`Invalid version format: ${range}`);
79165+
}
79166+
if (majorVersion < GRAALVM_MIN_VERSION) {
79167+
throw new Error(`${this.distribution} is only supported for JDK ${GRAALVM_MIN_VERSION} and later. Requested version: ${major}`);
79168+
}
79169+
return {
79170+
platform,
79171+
major,
79172+
extension
79173+
};
79174+
}
7915279175
constructFileUrl(range, major, platform, arch, extension) {
7915379176
return range.includes('.')
7915479177
? `${GRAALVM_DL_BASE}/${major}/archive/graalvm-jdk-${range}_${platform}-${arch}_bin.${extension}`
@@ -79239,6 +79262,91 @@ class GraalVMDistribution extends base_installer_1.JavaBase {
7923979262
}
7924079263
}
7924179264
exports.GraalVMDistribution = GraalVMDistribution;
79265+
class GraalVMCommunityDistribution extends GraalVMDistribution {
79266+
constructor(installerOptions) {
79267+
super(installerOptions, 'GraalVM Community');
79268+
}
79269+
get toolcacheFolderName() {
79270+
return `Java_GraalVM_Community_${this.packageType}`;
79271+
}
79272+
findPackageForDownload(range) {
79273+
return __awaiter(this, void 0, void 0, function* () {
79274+
this.validateVersionRange(range);
79275+
if (!this.stable) {
79276+
throw new Error('GraalVM Community does not provide early access builds');
79277+
}
79278+
const arch = this.getSupportedArchitecture();
79279+
const { platform, extension } = this.validateStableBuildRequest(range);
79280+
// GraalVM Community asset names embed the platform, architecture and
79281+
// archive type, e.g. `graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz`.
79282+
const assetSuffix = `_${platform}-${arch}_bin.${extension}`;
79283+
const availableVersions = yield this.getAvailableVersions(assetSuffix);
79284+
const satisfiedVersion = availableVersions
79285+
.filter(item => (0, util_1.isVersionSatisfies)(range, item.version))
79286+
.sort((a, b) => -semver_1.default.compareBuild(a.version, b.version))[0];
79287+
if (!satisfiedVersion) {
79288+
const error = this.createVersionNotFoundError(range, availableVersions.map(item => item.version), `Platform: ${platform}`);
79289+
error.message += `\nPlease check if this version is available at ${GRAALVM_COMMUNITY_DOWNLOAD_URL}.`;
79290+
throw error;
79291+
}
79292+
return satisfiedVersion;
79293+
});
79294+
}
79295+
getAvailableVersions(assetSuffix) {
79296+
var _a;
79297+
return __awaiter(this, void 0, void 0, function* () {
79298+
const headers = (0, util_1.getGitHubHttpHeaders)();
79299+
const versions = new Map();
79300+
let releasesUrl = GRAALVM_COMMUNITY_RELEASES_URL;
79301+
for (let pageIndex = 0; releasesUrl && pageIndex < util_1.MAX_PAGINATION_PAGES; pageIndex++) {
79302+
const response = yield this.http.getJson(releasesUrl, headers);
79303+
const releases = Array.isArray(response.result) ? response.result : [];
79304+
if (releases.length === 0) {
79305+
break;
79306+
}
79307+
for (const release of releases) {
79308+
if (release.draft || release.prerelease) {
79309+
continue;
79310+
}
79311+
for (const asset of (_a = release.assets) !== null && _a !== void 0 ? _a : []) {
79312+
const version = this.extractAssetVersion(asset.name, assetSuffix);
79313+
if (version) {
79314+
versions.set(version, {
79315+
version,
79316+
url: asset.browser_download_url
79317+
});
79318+
}
79319+
}
79320+
}
79321+
releasesUrl = this.getNextReleasesUrl(response.headers);
79322+
}
79323+
return [...versions.values()];
79324+
});
79325+
}
79326+
// Returns the GraalVM JDK version encoded in a release asset name when it
79327+
// matches the requested platform/architecture/archive suffix, otherwise null.
79328+
extractAssetVersion(assetName, assetSuffix) {
79329+
if (!assetName.startsWith(GRAALVM_COMMUNITY_ASSET_PREFIX) ||
79330+
!assetName.endsWith(assetSuffix)) {
79331+
return null;
79332+
}
79333+
const rawVersion = assetName.slice(GRAALVM_COMMUNITY_ASSET_PREFIX.length, -assetSuffix.length);
79334+
if (!GRAALVM_COMMUNITY_VERSION_PATTERN.test(rawVersion)) {
79335+
return null;
79336+
}
79337+
return (0, util_1.convertVersionToSemver)(rawVersion);
79338+
}
79339+
getNextReleasesUrl(headers) {
79340+
const nextUrl = (0, util_1.getNextPageUrlFromLinkHeader)(headers);
79341+
if (nextUrl &&
79342+
!(0, util_1.validatePaginationUrl)(nextUrl, GRAALVM_COMMUNITY_RELEASES_PAGE_ORIGIN)) {
79343+
core.warning(`Ignoring pagination link with unexpected origin: ${nextUrl}`);
79344+
return null;
79345+
}
79346+
return nextUrl;
79347+
}
79348+
}
79349+
exports.GraalVMCommunityDistribution = GraalVMCommunityDistribution;
7924279350

7924379351

7924479352
/***/ }),

0 commit comments

Comments
 (0)