Skip to content

Commit cfd55ca

Browse files
authored
graalpy: add graalpy early-access and windows builds (#880)
1 parent bba65e5 commit cfd55ca

File tree

8 files changed

+1560
-1607
lines changed

8 files changed

+1560
-1607
lines changed

.github/workflows/test-graalpy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ jobs:
106106
strategy:
107107
fail-fast: false
108108
matrix:
109-
os: [ubuntu-latest, macos-latest, macos-13]
109+
os: [ubuntu-latest, windows-latest, macos-latest, macos-13]
110110
steps:
111111
- uses: actions/checkout@v5
112112
- name: Setup GraalPy and check latest

__tests__/data/graalpy.json

Lines changed: 1470 additions & 1550 deletions
Large diffs are not rendered by default.

__tests__/find-graalpy.test.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import * as path from 'path';
1010
import * as semver from 'semver';
1111

1212
import * as finder from '../src/find-graalpy';
13-
import {IGraalPyManifestRelease, IS_WINDOWS} from '../src/utils';
13+
import {IGraalPyManifestRelease} from '../src/utils';
1414

1515
import manifestData from './data/graalpy.json';
1616

@@ -19,9 +19,6 @@ const architecture = 'x64';
1919
const toolDir = path.join(__dirname, 'runner', 'tools');
2020
const tempDir = path.join(__dirname, 'runner', 'temp');
2121

22-
/* GraalPy doesn't have a windows release yet */
23-
const describeSkipOnWindows = IS_WINDOWS ? describe.skip : describe;
24-
2522
describe('parseGraalPyVersion', () => {
2623
it.each([
2724
['graalpy-23', '23'],
@@ -108,7 +105,7 @@ describe('findGraalPyToolCache', () => {
108105
});
109106
});
110107

111-
describeSkipOnWindows('findGraalPyVersion', () => {
108+
describe('findGraalPyVersion', () => {
112109
let getBooleanInputSpy: jest.SpyInstance;
113110
let warningSpy: jest.SpyInstance;
114111
let debugSpy: jest.SpyInstance;
@@ -358,21 +355,21 @@ describeSkipOnWindows('findGraalPyVersion', () => {
358355
it('found and install successfully, pre-release fallback', async () => {
359356
spyCacheDir = jest.spyOn(tc, 'cacheDir');
360357
spyCacheDir.mockImplementation(() =>
361-
path.join(toolDir, 'GraalPy', '23.1', architecture)
358+
path.join(toolDir, 'GraalPy', '24.1', architecture)
362359
);
363360
spyChmodSync = jest.spyOn(fs, 'chmodSync');
364361
spyChmodSync.mockImplementation(() => undefined);
365362
await expect(
366363
finder.findGraalPyVersion(
367-
'graalpy23.1',
364+
'graalpy24.1',
368365
architecture,
369366
false,
370367
false,
371368
false
372369
)
373370
).rejects.toThrow();
374371
await expect(
375-
finder.findGraalPyVersion('graalpy23.1', architecture, false, false, true)
376-
).resolves.toEqual('23.1.0-a.1');
372+
finder.findGraalPyVersion('graalpy24.1', architecture, false, false, true)
373+
).resolves.toEqual('24.1.0-ea.9');
377374
});
378375
});

__tests__/install-graalpy.test.ts

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,21 @@ const architecture = 'x64';
2121
const toolDir = path.join(__dirname, 'runner', 'tools');
2222
const tempDir = path.join(__dirname, 'runner', 'temp');
2323

24-
/* GraalPy doesn't have a windows release yet */
25-
const describeSkipOnWindows = IS_WINDOWS ? describe.skip : describe;
26-
2724
describe('graalpyVersionToSemantic', () => {
2825
it.each([
29-
['23.0.0a1', '23.0.0a1'],
30-
['23.0.0', '23.0.0'],
31-
['23.0.x', '23.0.x'],
32-
['23.x', '23.x']
26+
['graalpy-24.1.0-ea.09', '24.1.0-ea.9'],
27+
['graal-23.0.0', '23.0.0'],
28+
['vm-23.0.x', '23.0.x'],
29+
['graal-23.x', '23.x']
3330
])('%s -> %s', (input, expected) => {
3431
expect(installer.graalPyTagToVersion(input)).toEqual(expected);
3532
});
3633
});
3734

38-
describeSkipOnWindows('findRelease', () => {
35+
describe('findRelease', () => {
3936
const result = JSON.stringify(manifestData);
4037
const releases = JSON.parse(result) as IGraalPyManifestRelease[];
41-
const extension = 'tar.gz';
38+
const extension = IS_WINDOWS ? 'zip' : 'tar.gz';
4239
const arch = installer.toGraalPyArchitecture(architecture);
4340
const platform = installer.toGraalPyPlatform(process.platform);
4441
const extensionName = `${platform}-${arch}.${extension}`;
@@ -47,8 +44,8 @@ describeSkipOnWindows('findRelease', () => {
4744
browser_download_url: `https://github.com/oracle/graalpython/releases/download/graal-23.0.0/graalpython-23.0.0-${extensionName}`
4845
};
4946
const filesRC1: IGraalPyManifestAsset = {
50-
name: `graalpython-23.1.0a1-${extensionName}`,
51-
browser_download_url: `https://github.com/oracle/graalpython/releases/download/graal-23.1.0a1/graalpython-23.1.0a1-${extensionName}`
47+
name: `graalpy-24.1.0-ea.09-${extensionName}`,
48+
browser_download_url: `https://github.com/graalvm/graal-languages-ea-builds/releases/download/graalpy-24.1.0-ea.09/graalpy-24.1.0-ea.09-${extensionName}`
5249
};
5350

5451
let warningSpy: jest.SpyInstance;
@@ -84,15 +81,15 @@ describeSkipOnWindows('findRelease', () => {
8481
});
8582

8683
it('Preview version of GraalPy is found', () => {
87-
const graalpyVersion = installer.graalPyTagToVersion('vm-23.1.0a1');
84+
const graalpyVersion = installer.graalPyTagToVersion('vm-24.1.0-ea.09');
8885
expect(
8986
installer.findRelease(releases, graalpyVersion, architecture, false)
9087
).toMatchObject({
9188
foundAsset: {
92-
name: `graalpython-23.1.0a1-${extensionName}`,
93-
browser_download_url: `https://github.com/oracle/graalpython/releases/download/graal-23.1.0a1/graalpython-23.1.0a1-${extensionName}`
89+
name: `graalpy-24.1.0-ea.09-${extensionName}`,
90+
browser_download_url: `https://github.com/graalvm/graal-languages-ea-builds/releases/download/graalpy-24.1.0-ea.09/graalpy-24.1.0-ea.09-${extensionName}`
9491
},
95-
resolvedGraalPyVersion: '23.1.0-a.1'
92+
resolvedGraalPyVersion: '24.1.0-ea.9'
9693
});
9794
});
9895

@@ -107,20 +104,20 @@ describeSkipOnWindows('findRelease', () => {
107104
});
108105

109106
it('GraalPy version matches semver (pre-release)', () => {
110-
const graalpyVersion = '23.1.x';
107+
const graalpyVersion = '24.1.x';
111108
expect(
112109
installer.findRelease(releases, graalpyVersion, architecture, false)
113110
).toBeNull();
114111
expect(
115112
installer.findRelease(releases, graalpyVersion, architecture, true)
116113
).toMatchObject({
117114
foundAsset: filesRC1,
118-
resolvedGraalPyVersion: '23.1.0-a.1'
115+
resolvedGraalPyVersion: '24.1.0-ea.9'
119116
});
120117
});
121118
});
122119

123-
describeSkipOnWindows('installGraalPy', () => {
120+
describe('installGraalPy', () => {
124121
let tcFind: jest.SpyInstance;
125122
let warningSpy: jest.SpyInstance;
126123
let debugSpy: jest.SpyInstance;
@@ -232,20 +229,20 @@ describeSkipOnWindows('installGraalPy', () => {
232229
it('found and install GraalPy, pre-release fallback', async () => {
233230
spyCacheDir = jest.spyOn(tc, 'cacheDir');
234231
spyCacheDir.mockImplementation(() =>
235-
path.join(toolDir, 'GraalPy', '23.1.0', architecture)
232+
path.join(toolDir, 'GraalPy', '24.1.0', architecture)
236233
);
237234

238235
spyChmodSync = jest.spyOn(fs, 'chmodSync');
239236
spyChmodSync.mockImplementation(() => undefined);
240237

241238
await expect(
242-
installer.installGraalPy('23.1.x', architecture, false, undefined)
239+
installer.installGraalPy('24.1.x', architecture, false, undefined)
243240
).rejects.toThrow();
244241
await expect(
245-
installer.installGraalPy('23.1.x', architecture, true, undefined)
242+
installer.installGraalPy('24.1.x', architecture, true, undefined)
246243
).resolves.toEqual({
247-
installDir: path.join(toolDir, 'GraalPy', '23.1.0', architecture),
248-
resolvedGraalPyVersion: '23.1.0-a.1'
244+
installDir: path.join(toolDir, 'GraalPy', '24.1.0', architecture),
245+
resolvedGraalPyVersion: '24.1.0-ea.9'
249246
});
250247

251248
expect(spyHttpClient).toHaveBeenCalled();

dist/setup/index.js

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96769,8 +96769,8 @@ async function findGraalPyVersion(versionSpec, architecture, updateEnvironment,
9676996769
const pipDir = utils_1.IS_WINDOWS ? 'Scripts' : 'bin';
9677096770
const _binDir = path.join(installDir, pipDir);
9677196771
const binaryExtension = utils_1.IS_WINDOWS ? '.exe' : '';
96772-
const pythonPath = path.join(utils_1.IS_WINDOWS ? installDir : _binDir, `python${binaryExtension}`);
96773-
const pythonLocation = (0, utils_1.getBinaryDirectory)(installDir);
96772+
const pythonPath = path.join(_binDir, `python${binaryExtension}`);
96773+
const pythonLocation = path.join(installDir, 'bin');
9677496774
if (updateEnvironment) {
9677596775
core.exportVariable('pythonLocation', installDir);
9677696776
// https://cmake.org/cmake/help/latest/module/FindPython.html#module:FindPython
@@ -97315,7 +97315,12 @@ async function installGraalPy(graalpyVersion, architecture, allowPreReleases, re
9731597315
try {
9731697316
const graalpyPath = await tc.downloadTool(downloadUrl, undefined, AUTH);
9731797317
core.info('Extracting downloaded archive...');
97318-
downloadDir = await tc.extractTar(graalpyPath);
97318+
if (utils_1.IS_WINDOWS) {
97319+
downloadDir = await tc.extractZip(graalpyPath);
97320+
}
97321+
else {
97322+
downloadDir = await tc.extractTar(graalpyPath);
97323+
}
9731997324
// root folder in archive can have unpredictable name so just take the first folder
9732097325
// downloadDir is unique folder under TEMP and can't contain any other folders
9732197326
const archiveName = fs_1.default.readdirSync(downloadDir)[0];
@@ -97324,7 +97329,7 @@ async function installGraalPy(graalpyVersion, architecture, allowPreReleases, re
9732497329
if (!(0, utils_1.isNightlyKeyword)(resolvedGraalPyVersion)) {
9732597330
installDir = await tc.cacheDir(toolDir, 'GraalPy', resolvedGraalPyVersion, architecture);
9732697331
}
97327-
const binaryPath = (0, utils_1.getBinaryDirectory)(installDir);
97332+
const binaryPath = path.join(installDir, 'bin');
9732897333
await createGraalPySymlink(binaryPath, resolvedGraalPyVersion);
9732997334
await installPip(binaryPath);
9733097335
return { installDir, resolvedGraalPyVersion };
@@ -97352,6 +97357,9 @@ async function getAvailableGraalPyVersions() {
9735297357
if (AUTH) {
9735397358
headers.authorization = AUTH;
9735497359
}
97360+
/*
97361+
Get releases first.
97362+
*/
9735597363
let url = 'https://api.github.com/repos/oracle/graalpython/releases';
9735697364
const result = [];
9735797365
do {
@@ -97362,6 +97370,19 @@ async function getAvailableGraalPyVersions() {
9736297370
result.push(...response.result);
9736397371
url = (0, utils_1.getNextPageUrl)(response);
9736497372
} while (url);
97373+
/*
97374+
Add pre-release builds.
97375+
*/
97376+
url =
97377+
'https://api.github.com/repos/graalvm/graal-languages-ea-builds/releases';
97378+
do {
97379+
const response = await http.getJson(url, headers);
97380+
if (!response.result) {
97381+
throw new Error(`Unable to retrieve the list of available GraalPy versions from '${url}'`);
97382+
}
97383+
result.push(...response.result);
97384+
url = (0, utils_1.getNextPageUrl)(response);
97385+
} while (url);
9736597386
return result;
9736697387
}
9736797388
async function createGraalPySymlink(graalpyBinaryPath, graalpyVersion) {
@@ -97381,7 +97402,7 @@ async function installPip(pythonLocation) {
9738197402
await exec.exec(`${pythonBinary} -m ensurepip --default-pip`);
9738297403
}
9738397404
function graalPyTagToVersion(tag) {
97384-
const versionPattern = /.*-(\d+\.\d+\.\d+(?:\.\d+)?)((?:a|b|rc))?(\d*)?/;
97405+
const versionPattern = /.*-(\d+\.\d+\.\d+(?:\.\d+)?)(?:-((?:ea|a|b|rc))\.0*(\d+))?/;
9738597406
const match = tag.match(versionPattern);
9738697407
if (match && match[2]) {
9738797408
return `${match[1]}-${match[2]}.${match[3]}`;
@@ -97431,8 +97452,9 @@ function toGraalPyArchitecture(architecture) {
9743197452
function findAsset(item, architecture, platform) {
9743297453
const graalpyArch = toGraalPyArchitecture(architecture);
9743397454
const graalpyPlatform = toGraalPyPlatform(platform);
97455+
const graalpyExt = platform == 'win32' ? 'zip' : 'tar.gz';
9743497456
const found = item.assets.filter(file => file.name.startsWith('graalpy') &&
97435-
file.name.endsWith(`-${graalpyPlatform}-${graalpyArch}.tar.gz`));
97457+
file.name.endsWith(`-${graalpyPlatform}-${graalpyArch}.${graalpyExt}`));
9743697458
/*
9743797459
In the future there could be more variants of GraalPy for a single release. Pick the shortest name, that one is the most likely to be the primary variant.
9743897460
*/
@@ -98363,7 +98385,7 @@ function getVersionInputFromFile(versionFile) {
9836398385
}
9836498386
}
9836598387
/**
98366-
* Get the directory containing interpreter binary from installation directory of PyPy or GraalPy
98388+
* Get the directory containing interpreter binary from installation directory of PyPy
9836798389
* - On Linux and macOS, the Python interpreter is in 'bin'.
9836898390
* - On Windows, it is in the installation root.
9836998391
*/

src/find-graalpy.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
import * as path from 'path';
22
import * as graalpyInstall from './install-graalpy';
3-
import {
4-
IS_WINDOWS,
5-
validateVersion,
6-
IGraalPyManifestRelease,
7-
getBinaryDirectory
8-
} from './utils';
3+
import {IS_WINDOWS, validateVersion, IGraalPyManifestRelease} from './utils';
94

105
import * as semver from 'semver';
116
import * as core from '@actions/core';
@@ -62,11 +57,8 @@ export async function findGraalPyVersion(
6257
const pipDir = IS_WINDOWS ? 'Scripts' : 'bin';
6358
const _binDir = path.join(installDir, pipDir);
6459
const binaryExtension = IS_WINDOWS ? '.exe' : '';
65-
const pythonPath = path.join(
66-
IS_WINDOWS ? installDir : _binDir,
67-
`python${binaryExtension}`
68-
);
69-
const pythonLocation = getBinaryDirectory(installDir);
60+
const pythonPath = path.join(_binDir, `python${binaryExtension}`);
61+
const pythonLocation = path.join(installDir, 'bin');
7062
if (updateEnvironment) {
7163
core.exportVariable('pythonLocation', installDir);
7264
// https://cmake.org/cmake/help/latest/module/FindPython.html#module:FindPython

src/install-graalpy.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
IGraalPyManifestRelease,
1616
createSymlinkInFolder,
1717
isNightlyKeyword,
18-
getBinaryDirectory,
1918
getNextPageUrl
2019
} from './utils';
2120

@@ -64,7 +63,11 @@ export async function installGraalPy(
6463
const graalpyPath = await tc.downloadTool(downloadUrl, undefined, AUTH);
6564

6665
core.info('Extracting downloaded archive...');
67-
downloadDir = await tc.extractTar(graalpyPath);
66+
if (IS_WINDOWS) {
67+
downloadDir = await tc.extractZip(graalpyPath);
68+
} else {
69+
downloadDir = await tc.extractTar(graalpyPath);
70+
}
6871

6972
// root folder in archive can have unpredictable name so just take the first folder
7073
// downloadDir is unique folder under TEMP and can't contain any other folders
@@ -81,7 +84,7 @@ export async function installGraalPy(
8184
);
8285
}
8386

84-
const binaryPath = getBinaryDirectory(installDir);
87+
const binaryPath = path.join(installDir, 'bin');
8588
await createGraalPySymlink(binaryPath, resolvedGraalPyVersion);
8689
await installPip(binaryPath);
8790

@@ -115,6 +118,9 @@ export async function getAvailableGraalPyVersions() {
115118
headers.authorization = AUTH;
116119
}
117120

121+
/*
122+
Get releases first.
123+
*/
118124
let url: string | null =
119125
'https://api.github.com/repos/oracle/graalpython/releases';
120126
const result: IGraalPyManifestRelease[] = [];
@@ -130,6 +136,23 @@ export async function getAvailableGraalPyVersions() {
130136
url = getNextPageUrl(response);
131137
} while (url);
132138

139+
/*
140+
Add pre-release builds.
141+
*/
142+
url =
143+
'https://api.github.com/repos/graalvm/graal-languages-ea-builds/releases';
144+
do {
145+
const response: ifm.TypedResponse<IGraalPyManifestRelease[]> =
146+
await http.getJson(url, headers);
147+
if (!response.result) {
148+
throw new Error(
149+
`Unable to retrieve the list of available GraalPy versions from '${url}'`
150+
);
151+
}
152+
result.push(...response.result);
153+
url = getNextPageUrl(response);
154+
} while (url);
155+
133156
return result;
134157
}
135158

@@ -175,7 +198,8 @@ async function installPip(pythonLocation: string) {
175198
}
176199

177200
export function graalPyTagToVersion(tag: string) {
178-
const versionPattern = /.*-(\d+\.\d+\.\d+(?:\.\d+)?)((?:a|b|rc))?(\d*)?/;
201+
const versionPattern =
202+
/.*-(\d+\.\d+\.\d+(?:\.\d+)?)(?:-((?:ea|a|b|rc))\.0*(\d+))?/;
179203
const match = tag.match(versionPattern);
180204
if (match && match[2]) {
181205
return `${match[1]}-${match[2]}.${match[3]}`;
@@ -251,10 +275,11 @@ export function findAsset(
251275
) {
252276
const graalpyArch = toGraalPyArchitecture(architecture);
253277
const graalpyPlatform = toGraalPyPlatform(platform);
278+
const graalpyExt = platform == 'win32' ? 'zip' : 'tar.gz';
254279
const found = item.assets.filter(
255280
file =>
256281
file.name.startsWith('graalpy') &&
257-
file.name.endsWith(`-${graalpyPlatform}-${graalpyArch}.tar.gz`)
282+
file.name.endsWith(`-${graalpyPlatform}-${graalpyArch}.${graalpyExt}`)
258283
);
259284
/*
260285
In the future there could be more variants of GraalPy for a single release. Pick the shortest name, that one is the most likely to be the primary variant.

src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ export function getVersionInputFromFile(versionFile: string): string[] {
379379
}
380380

381381
/**
382-
* Get the directory containing interpreter binary from installation directory of PyPy or GraalPy
382+
* Get the directory containing interpreter binary from installation directory of PyPy
383383
* - On Linux and macOS, the Python interpreter is in 'bin'.
384384
* - On Windows, it is in the installation root.
385385
*/

0 commit comments

Comments
 (0)