mirror of
https://github.com/actions/setup-python.git
synced 2024-11-21 14:40:58 -05:00
Add GraalPy support (#694)
* Add support for graalpy * add graalpy test workflow * format, lint and build * symlink graalpy binaries names * fix macos names for graalpy * Don't attempt to update pip for graalpy * Remove test schedule * Extract common getBinaryDirectory function for PyPy and GraalPy * Clean up and format * Pass GitHub token to GraalPy queries * Utilize pagination when querying GraalPy GitHub releases * Build * Fix lint errors * Deal with possible multiple artifacts for a single releases * Skip few GraalPy tests on windows - we don't have a windows release yet * Fix GraalPy test on Mac OS * Build * Skip one more GraalPy test on windows --------- Co-authored-by: Michael Simacek <michael.simacek@oracle.com>
This commit is contained in:
parent
3467d92d48
commit
5f2af211d6
12 changed files with 7429 additions and 27 deletions
116
.github/workflows/test-graalpy.yml
vendored
Normal file
116
.github/workflows/test-graalpy.yml
vendored
Normal file
|
@ -0,0 +1,116 @@
|
|||
name: Validate GraalPy e2e
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
|
||||
jobs:
|
||||
setup-graalpy:
|
||||
name: Setup GraalPy ${{ matrix.graalpy }} ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-20.04, ubuntu-latest]
|
||||
graalpy:
|
||||
- 'graalpy-23.0'
|
||||
- 'graalpy-22.3'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: setup-python ${{ matrix.graalpy }}
|
||||
id: setup-python
|
||||
uses: ./
|
||||
with:
|
||||
python-version: ${{ matrix.graalpy }}
|
||||
|
||||
- name: Check python-path
|
||||
run: ./__tests__/check-python-path.sh '${{ steps.setup-python.outputs.python-path }}'
|
||||
shell: bash
|
||||
|
||||
- name: GraalPy and Python version
|
||||
run: python --version
|
||||
|
||||
- name: Run simple code
|
||||
run: python -c 'import math; print(math.factorial(5))'
|
||||
|
||||
- name: Assert GraalPy is running
|
||||
run: |
|
||||
import platform
|
||||
assert platform.python_implementation().lower() == "graalvm"
|
||||
shell: python
|
||||
|
||||
- name: Assert expected binaries (or symlinks) are present
|
||||
run: |
|
||||
EXECUTABLE=${{ matrix.graalpy }}
|
||||
EXECUTABLE=${EXECUTABLE/graalpy-/graalpy} # remove the first '-' in "graalpy-X.Y" -> "graalpyX.Y" to match executable name
|
||||
EXECUTABLE=${EXECUTABLE%%-*} # remove any -* suffixe
|
||||
${EXECUTABLE} --version
|
||||
shell: bash
|
||||
|
||||
setup-graalpy-noenv:
|
||||
name: Setup GraalPy ${{ matrix.graalpy }} ${{ matrix.os }} (noenv)
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-20.04, ubuntu-latest]
|
||||
graalpy: ['graalpy23.0', 'graalpy22.3']
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: setup-python ${{ matrix.graalpy }}
|
||||
id: setup-python
|
||||
uses: ./
|
||||
with:
|
||||
python-version: ${{ matrix.graalpy }}
|
||||
update-environment: false
|
||||
|
||||
- name: GraalPy and Python version
|
||||
run: ${{ steps.setup-python.outputs.python-path }} --version
|
||||
|
||||
- name: Run simple code
|
||||
run: ${{ steps.setup-python.outputs.python-path }} -c 'import math; print(math.factorial(5))'
|
||||
|
||||
check-latest:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup GraalPy and check latest
|
||||
uses: ./
|
||||
with:
|
||||
python-version: 'graalpy-23.x'
|
||||
check-latest: true
|
||||
- name: GraalPy and Python version
|
||||
run: python --version
|
||||
|
||||
- name: Run simple code
|
||||
run: python -c 'import math; print(math.factorial(5))'
|
||||
|
||||
- name: Assert GraalPy is running
|
||||
run: |
|
||||
import platform
|
||||
assert platform.python_implementation().lower() == "graalvm"
|
||||
shell: python
|
||||
|
||||
- name: Assert expected binaries (or symlinks) are present
|
||||
run: |
|
||||
EXECUTABLE="graalpy-23.0"
|
||||
EXECUTABLE=${EXECUTABLE/-/} # remove the first '-' in "graalpy-X.Y" -> "graalpyX.Y" to match executable name
|
||||
EXECUTABLE=${EXECUTABLE%%-*} # remove any -* suffixe
|
||||
${EXECUTABLE} --version
|
||||
shell: bash
|
5798
__tests__/data/graalpy.json
Normal file
5798
__tests__/data/graalpy.json
Normal file
File diff suppressed because it is too large
Load diff
378
__tests__/find-graalpy.test.ts
Normal file
378
__tests__/find-graalpy.test.ts
Normal file
|
@ -0,0 +1,378 @@
|
|||
import fs from 'fs';
|
||||
|
||||
import {HttpClient} from '@actions/http-client';
|
||||
import * as ifm from '@actions/http-client/interfaces';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as semver from 'semver';
|
||||
|
||||
import * as finder from '../src/find-graalpy';
|
||||
import {IGraalPyManifestRelease, IS_WINDOWS} from '../src/utils';
|
||||
|
||||
import manifestData from './data/graalpy.json';
|
||||
|
||||
const architecture = 'x64';
|
||||
|
||||
const toolDir = path.join(__dirname, 'runner', 'tools');
|
||||
const tempDir = path.join(__dirname, 'runner', 'temp');
|
||||
|
||||
/* GraalPy doesn't have a windows release yet */
|
||||
const describeSkipOnWindows = IS_WINDOWS ? describe.skip : describe;
|
||||
|
||||
describe('parseGraalPyVersion', () => {
|
||||
it.each([
|
||||
['graalpy-23', '23'],
|
||||
['graalpy-23.0', '23.0'],
|
||||
['graalpy23.0', '23.0']
|
||||
])('%s -> %s', (input, expected) => {
|
||||
expect(finder.parseGraalPyVersion(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
it.each(['', 'graalpy-', 'graalpy', 'p', 'notgraalpy-'])(
|
||||
'throw on invalid input "%s"',
|
||||
input => {
|
||||
expect(() => finder.parseGraalPyVersion(input)).toThrow(
|
||||
"Invalid 'version' property for GraalPy. GraalPy version should be specified as 'graalpy<python-version>' or 'graalpy-<python-version>'. See README for examples and documentation."
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('findGraalPyToolCache', () => {
|
||||
const actualGraalPyVersion = '23.0.0';
|
||||
const graalpyPath = path.join('GraalPy', actualGraalPyVersion, architecture);
|
||||
let tcFind: jest.SpyInstance;
|
||||
let infoSpy: jest.SpyInstance;
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let debugSpy: jest.SpyInstance;
|
||||
let addPathSpy: jest.SpyInstance;
|
||||
let exportVariableSpy: jest.SpyInstance;
|
||||
let setOutputSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
tcFind = jest.spyOn(tc, 'find');
|
||||
tcFind.mockImplementation((toolname: string, pythonVersion: string) => {
|
||||
const semverVersion = new semver.Range(pythonVersion);
|
||||
return semver.satisfies(actualGraalPyVersion, semverVersion)
|
||||
? graalpyPath
|
||||
: '';
|
||||
});
|
||||
|
||||
infoSpy = jest.spyOn(core, 'info');
|
||||
infoSpy.mockImplementation(() => null);
|
||||
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
warningSpy.mockImplementation(() => null);
|
||||
|
||||
debugSpy = jest.spyOn(core, 'debug');
|
||||
debugSpy.mockImplementation(() => null);
|
||||
|
||||
addPathSpy = jest.spyOn(core, 'addPath');
|
||||
addPathSpy.mockImplementation(() => null);
|
||||
|
||||
exportVariableSpy = jest.spyOn(core, 'exportVariable');
|
||||
exportVariableSpy.mockImplementation(() => null);
|
||||
|
||||
setOutputSpy = jest.spyOn(core, 'setOutput');
|
||||
setOutputSpy.mockImplementation(() => null);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('GraalPy exists on the path and versions are satisfied', () => {
|
||||
expect(finder.findGraalPyToolCache('23.0.0', architecture)).toEqual({
|
||||
installDir: graalpyPath,
|
||||
resolvedGraalPyVersion: actualGraalPyVersion
|
||||
});
|
||||
});
|
||||
|
||||
it('GraalPy exists on the path and versions are satisfied with semver', () => {
|
||||
expect(finder.findGraalPyToolCache('23.0', architecture)).toEqual({
|
||||
installDir: graalpyPath,
|
||||
resolvedGraalPyVersion: actualGraalPyVersion
|
||||
});
|
||||
});
|
||||
|
||||
it("GraalPy exists on the path, but version doesn't match", () => {
|
||||
expect(finder.findGraalPyToolCache('22.3', architecture)).toEqual({
|
||||
installDir: '',
|
||||
resolvedGraalPyVersion: ''
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describeSkipOnWindows('findGraalPyVersion', () => {
|
||||
let getBooleanInputSpy: jest.SpyInstance;
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let debugSpy: jest.SpyInstance;
|
||||
let infoSpy: jest.SpyInstance;
|
||||
let addPathSpy: jest.SpyInstance;
|
||||
let exportVariableSpy: jest.SpyInstance;
|
||||
let setOutputSpy: jest.SpyInstance;
|
||||
let tcFind: jest.SpyInstance;
|
||||
let spyExtractZip: jest.SpyInstance;
|
||||
let spyExtractTar: jest.SpyInstance;
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyExistsSync: jest.SpyInstance;
|
||||
let spyExec: jest.SpyInstance;
|
||||
let spySymlinkSync: jest.SpyInstance;
|
||||
let spyDownloadTool: jest.SpyInstance;
|
||||
let spyFsReadDir: jest.SpyInstance;
|
||||
let spyCacheDir: jest.SpyInstance;
|
||||
let spyChmodSync: jest.SpyInstance;
|
||||
let spyCoreAddPath: jest.SpyInstance;
|
||||
let spyCoreExportVariable: jest.SpyInstance;
|
||||
const env = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
|
||||
getBooleanInputSpy.mockImplementation(() => false);
|
||||
|
||||
infoSpy = jest.spyOn(core, 'info');
|
||||
infoSpy.mockImplementation(() => {});
|
||||
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
warningSpy.mockImplementation(() => null);
|
||||
|
||||
debugSpy = jest.spyOn(core, 'debug');
|
||||
debugSpy.mockImplementation(() => null);
|
||||
|
||||
addPathSpy = jest.spyOn(core, 'addPath');
|
||||
addPathSpy.mockImplementation(() => null);
|
||||
|
||||
exportVariableSpy = jest.spyOn(core, 'exportVariable');
|
||||
exportVariableSpy.mockImplementation(() => null);
|
||||
|
||||
setOutputSpy = jest.spyOn(core, 'setOutput');
|
||||
setOutputSpy.mockImplementation(() => null);
|
||||
|
||||
jest.resetModules();
|
||||
process.env = {...env};
|
||||
tcFind = jest.spyOn(tc, 'find');
|
||||
tcFind.mockImplementation((tool: string, version: string) => {
|
||||
const semverRange = new semver.Range(version);
|
||||
let graalpyPath = '';
|
||||
if (semver.satisfies('23.0.0', semverRange)) {
|
||||
graalpyPath = path.join(toolDir, 'GraalPy', '23.0.0', architecture);
|
||||
}
|
||||
return graalpyPath;
|
||||
});
|
||||
|
||||
spyDownloadTool = jest.spyOn(tc, 'downloadTool');
|
||||
spyDownloadTool.mockImplementation(() => path.join(tempDir, 'GraalPy'));
|
||||
|
||||
spyExtractZip = jest.spyOn(tc, 'extractZip');
|
||||
spyExtractZip.mockImplementation(() => tempDir);
|
||||
|
||||
spyExtractTar = jest.spyOn(tc, 'extractTar');
|
||||
spyExtractTar.mockImplementation(() => tempDir);
|
||||
|
||||
spyFsReadDir = jest.spyOn(fs, 'readdirSync');
|
||||
spyFsReadDir.mockImplementation((directory: string) => ['GraalPyTest']);
|
||||
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
spyHttpClient.mockImplementation(
|
||||
async (): Promise<ifm.ITypedResponse<IGraalPyManifestRelease[]>> => {
|
||||
const result = JSON.stringify(manifestData);
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
result: JSON.parse(result) as IGraalPyManifestRelease[]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
spyExec = jest.spyOn(exec, 'exec');
|
||||
spyExec.mockImplementation(() => undefined);
|
||||
|
||||
spySymlinkSync = jest.spyOn(fs, 'symlinkSync');
|
||||
spySymlinkSync.mockImplementation(() => undefined);
|
||||
|
||||
spyExistsSync = jest.spyOn(fs, 'existsSync');
|
||||
spyExistsSync.mockReturnValue(true);
|
||||
|
||||
spyCoreAddPath = jest.spyOn(core, 'addPath');
|
||||
|
||||
spyCoreExportVariable = jest.spyOn(core, 'exportVariable');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
process.env = env;
|
||||
});
|
||||
|
||||
it('found GraalPy in toolcache', async () => {
|
||||
await expect(
|
||||
finder.findGraalPyVersion(
|
||||
'graalpy-23.0',
|
||||
architecture,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
)
|
||||
).resolves.toEqual('23.0.0');
|
||||
expect(spyCoreAddPath).toHaveBeenCalled();
|
||||
expect(spyCoreExportVariable).toHaveBeenCalledWith(
|
||||
'pythonLocation',
|
||||
expect.anything()
|
||||
);
|
||||
expect(spyCoreExportVariable).toHaveBeenCalledWith(
|
||||
'PKG_CONFIG_PATH',
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it('throw on invalid input format', async () => {
|
||||
await expect(
|
||||
finder.findGraalPyVersion('graalpy-x23', architecture, true, false, false)
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('found and install successfully', async () => {
|
||||
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
||||
spyCacheDir.mockImplementation(() =>
|
||||
path.join(toolDir, 'GraalPy', '23.0.0', architecture)
|
||||
);
|
||||
spyChmodSync = jest.spyOn(fs, 'chmodSync');
|
||||
spyChmodSync.mockImplementation(() => undefined);
|
||||
await expect(
|
||||
finder.findGraalPyVersion(
|
||||
'graalpy-23.0.0',
|
||||
architecture,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
)
|
||||
).resolves.toEqual('23.0.0');
|
||||
expect(spyCoreAddPath).toHaveBeenCalled();
|
||||
expect(spyCoreExportVariable).toHaveBeenCalledWith(
|
||||
'pythonLocation',
|
||||
expect.anything()
|
||||
);
|
||||
expect(spyCoreExportVariable).toHaveBeenCalledWith(
|
||||
'PKG_CONFIG_PATH',
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it('found and install successfully without environment update', async () => {
|
||||
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
||||
spyCacheDir.mockImplementation(() =>
|
||||
path.join(toolDir, 'GraalPy', '23.0.0', architecture)
|
||||
);
|
||||
spyChmodSync = jest.spyOn(fs, 'chmodSync');
|
||||
spyChmodSync.mockImplementation(() => undefined);
|
||||
await expect(
|
||||
finder.findGraalPyVersion(
|
||||
'graalpy-23.0.0',
|
||||
architecture,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
)
|
||||
).resolves.toEqual('23.0.0');
|
||||
expect(spyCoreAddPath).not.toHaveBeenCalled();
|
||||
expect(spyCoreExportVariable).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('throw if release is not found', async () => {
|
||||
await expect(
|
||||
finder.findGraalPyVersion(
|
||||
'graalpy-19.0.0',
|
||||
architecture,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
)
|
||||
).rejects.toThrow(
|
||||
`GraalPy version 19.0.0 with arch ${architecture} not found`
|
||||
);
|
||||
});
|
||||
|
||||
it('check-latest enabled version found and used from toolcache', async () => {
|
||||
await expect(
|
||||
finder.findGraalPyVersion(
|
||||
'graalpy-23.0.0',
|
||||
architecture,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
)
|
||||
).resolves.toEqual('23.0.0');
|
||||
|
||||
expect(infoSpy).toHaveBeenCalledWith('Resolved as GraalPy 23.0.0');
|
||||
});
|
||||
|
||||
it('check-latest enabled version found and install successfully', async () => {
|
||||
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
||||
spyCacheDir.mockImplementation(() =>
|
||||
path.join(toolDir, 'GraalPy', '23.0.0', architecture)
|
||||
);
|
||||
spyChmodSync = jest.spyOn(fs, 'chmodSync');
|
||||
spyChmodSync.mockImplementation(() => undefined);
|
||||
await expect(
|
||||
finder.findGraalPyVersion(
|
||||
'graalpy-23.0.0',
|
||||
architecture,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
)
|
||||
).resolves.toEqual('23.0.0');
|
||||
expect(infoSpy).toHaveBeenCalledWith('Resolved as GraalPy 23.0.0');
|
||||
});
|
||||
|
||||
it('check-latest enabled version is not found and used from toolcache', async () => {
|
||||
tcFind.mockImplementationOnce((tool: string, version: string) => {
|
||||
const semverRange = new semver.Range(version);
|
||||
let graalpyPath = '';
|
||||
if (semver.satisfies('22.3.4', semverRange)) {
|
||||
graalpyPath = path.join(toolDir, 'GraalPy', '22.3.4', architecture);
|
||||
}
|
||||
return graalpyPath;
|
||||
});
|
||||
await expect(
|
||||
finder.findGraalPyVersion(
|
||||
'graalpy-22.3.4',
|
||||
architecture,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
)
|
||||
).resolves.toEqual('22.3.4');
|
||||
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
'Failed to resolve GraalPy 22.3.4 from manifest'
|
||||
);
|
||||
});
|
||||
|
||||
it('found and install successfully, pre-release fallback', async () => {
|
||||
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
||||
spyCacheDir.mockImplementation(() =>
|
||||
path.join(toolDir, 'GraalPy', '23.1', architecture)
|
||||
);
|
||||
spyChmodSync = jest.spyOn(fs, 'chmodSync');
|
||||
spyChmodSync.mockImplementation(() => undefined);
|
||||
await expect(
|
||||
finder.findGraalPyVersion(
|
||||
'graalpy23.1',
|
||||
architecture,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
)
|
||||
).rejects.toThrow();
|
||||
await expect(
|
||||
finder.findGraalPyVersion('graalpy23.1', architecture, false, false, true)
|
||||
).resolves.toEqual('23.1.0-a.1');
|
||||
});
|
||||
});
|
256
__tests__/install-graalpy.test.ts
Normal file
256
__tests__/install-graalpy.test.ts
Normal file
|
@ -0,0 +1,256 @@
|
|||
import fs from 'fs';
|
||||
|
||||
import {HttpClient} from '@actions/http-client';
|
||||
import * as ifm from '@actions/http-client/interfaces';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as core from '@actions/core';
|
||||
import * as path from 'path';
|
||||
|
||||
import * as installer from '../src/install-graalpy';
|
||||
import {
|
||||
IGraalPyManifestRelease,
|
||||
IGraalPyManifestAsset,
|
||||
IS_WINDOWS
|
||||
} from '../src/utils';
|
||||
|
||||
import manifestData from './data/graalpy.json';
|
||||
|
||||
const architecture = 'x64';
|
||||
|
||||
const toolDir = path.join(__dirname, 'runner', 'tools');
|
||||
const tempDir = path.join(__dirname, 'runner', 'temp');
|
||||
|
||||
/* GraalPy doesn't have a windows release yet */
|
||||
const describeSkipOnWindows = IS_WINDOWS ? describe.skip : describe;
|
||||
|
||||
describe('graalpyVersionToSemantic', () => {
|
||||
it.each([
|
||||
['23.0.0a1', '23.0.0a1'],
|
||||
['23.0.0', '23.0.0'],
|
||||
['23.0.x', '23.0.x'],
|
||||
['23.x', '23.x']
|
||||
])('%s -> %s', (input, expected) => {
|
||||
expect(installer.graalPyTagToVersion(input)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describeSkipOnWindows('findRelease', () => {
|
||||
const result = JSON.stringify(manifestData);
|
||||
const releases = JSON.parse(result) as IGraalPyManifestRelease[];
|
||||
const extension = 'tar.gz';
|
||||
const arch = installer.toGraalPyArchitecture(architecture);
|
||||
const platform = installer.toGraalPyPlatform(process.platform);
|
||||
const extensionName = `${platform}-${arch}.${extension}`;
|
||||
const files: IGraalPyManifestAsset = {
|
||||
name: `graalpython-23.0.0-${extensionName}`,
|
||||
browser_download_url: `https://github.com/oracle/graalpython/releases/download/graal-23.0.0/graalpython-23.0.0-${extensionName}`
|
||||
};
|
||||
const filesRC1: IGraalPyManifestAsset = {
|
||||
name: `graalpython-23.1.0a1-${extensionName}`,
|
||||
browser_download_url: `https://github.com/oracle/graalpython/releases/download/graal-23.1.0a1/graalpython-23.1.0a1-${extensionName}`
|
||||
};
|
||||
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let debugSpy: jest.SpyInstance;
|
||||
let infoSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
infoSpy = jest.spyOn(core, 'info');
|
||||
infoSpy.mockImplementation(() => {});
|
||||
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
warningSpy.mockImplementation(() => null);
|
||||
|
||||
debugSpy = jest.spyOn(core, 'debug');
|
||||
debugSpy.mockImplementation(() => null);
|
||||
});
|
||||
|
||||
it("GraalPy version doesn't match", () => {
|
||||
const graalpyVersion = '12.0.0';
|
||||
expect(
|
||||
installer.findRelease(releases, graalpyVersion, architecture, false)
|
||||
).toEqual(null);
|
||||
});
|
||||
|
||||
it('GraalPy version matches', () => {
|
||||
const graalpyVersion = '23.0.0';
|
||||
expect(
|
||||
installer.findRelease(releases, graalpyVersion, architecture, false)
|
||||
).toMatchObject({
|
||||
foundAsset: files,
|
||||
resolvedGraalPyVersion: graalpyVersion
|
||||
});
|
||||
});
|
||||
|
||||
it('Preview version of GraalPy is found', () => {
|
||||
const graalpyVersion = installer.graalPyTagToVersion('vm-23.1.0a1');
|
||||
expect(
|
||||
installer.findRelease(releases, graalpyVersion, architecture, false)
|
||||
).toMatchObject({
|
||||
foundAsset: {
|
||||
name: `graalpython-23.1.0a1-${extensionName}`,
|
||||
browser_download_url: `https://github.com/oracle/graalpython/releases/download/graal-23.1.0a1/graalpython-23.1.0a1-${extensionName}`
|
||||
},
|
||||
resolvedGraalPyVersion: '23.1.0-a.1'
|
||||
});
|
||||
});
|
||||
|
||||
it('Latest GraalPy is found', () => {
|
||||
const graalpyVersion = 'x';
|
||||
expect(
|
||||
installer.findRelease(releases, graalpyVersion, architecture, false)
|
||||
).toMatchObject({
|
||||
foundAsset: files,
|
||||
resolvedGraalPyVersion: '23.0.0'
|
||||
});
|
||||
});
|
||||
|
||||
it('GraalPy version matches semver (pre-release)', () => {
|
||||
const graalpyVersion = '23.1.x';
|
||||
expect(
|
||||
installer.findRelease(releases, graalpyVersion, architecture, false)
|
||||
).toBeNull();
|
||||
expect(
|
||||
installer.findRelease(releases, graalpyVersion, architecture, true)
|
||||
).toMatchObject({
|
||||
foundAsset: filesRC1,
|
||||
resolvedGraalPyVersion: '23.1.0-a.1'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describeSkipOnWindows('installGraalPy', () => {
|
||||
let tcFind: jest.SpyInstance;
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let debugSpy: jest.SpyInstance;
|
||||
let infoSpy: jest.SpyInstance;
|
||||
let spyExtractZip: jest.SpyInstance;
|
||||
let spyExtractTar: jest.SpyInstance;
|
||||
let spyFsReadDir: jest.SpyInstance;
|
||||
let spyFsWriteFile: jest.SpyInstance;
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyExistsSync: jest.SpyInstance;
|
||||
let spyExec: jest.SpyInstance;
|
||||
let spySymlinkSync: jest.SpyInstance;
|
||||
let spyDownloadTool: jest.SpyInstance;
|
||||
let spyCacheDir: jest.SpyInstance;
|
||||
let spyChmodSync: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
tcFind = jest.spyOn(tc, 'find');
|
||||
tcFind.mockImplementation(() =>
|
||||
path.join('GraalPy', '3.6.12', architecture)
|
||||
);
|
||||
|
||||
spyDownloadTool = jest.spyOn(tc, 'downloadTool');
|
||||
spyDownloadTool.mockImplementation(() => path.join(tempDir, 'GraalPy'));
|
||||
|
||||
spyExtractZip = jest.spyOn(tc, 'extractZip');
|
||||
spyExtractZip.mockImplementation(() => tempDir);
|
||||
|
||||
spyExtractTar = jest.spyOn(tc, 'extractTar');
|
||||
spyExtractTar.mockImplementation(() => tempDir);
|
||||
|
||||
infoSpy = jest.spyOn(core, 'info');
|
||||
infoSpy.mockImplementation(() => {});
|
||||
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
warningSpy.mockImplementation(() => null);
|
||||
|
||||
debugSpy = jest.spyOn(core, 'debug');
|
||||
debugSpy.mockImplementation(() => null);
|
||||
|
||||
spyFsReadDir = jest.spyOn(fs, 'readdirSync');
|
||||
spyFsReadDir.mockImplementation(() => ['GraalPyTest']);
|
||||
|
||||
spyFsWriteFile = jest.spyOn(fs, 'writeFileSync');
|
||||
spyFsWriteFile.mockImplementation(() => undefined);
|
||||
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
spyHttpClient.mockImplementation(
|
||||
async (): Promise<ifm.ITypedResponse<IGraalPyManifestRelease[]>> => {
|
||||
const result = JSON.stringify(manifestData);
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
result: JSON.parse(result) as IGraalPyManifestRelease[]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
spyExec = jest.spyOn(exec, 'exec');
|
||||
spyExec.mockImplementation(() => undefined);
|
||||
|
||||
spySymlinkSync = jest.spyOn(fs, 'symlinkSync');
|
||||
spySymlinkSync.mockImplementation(() => undefined);
|
||||
|
||||
spyExistsSync = jest.spyOn(fs, 'existsSync');
|
||||
spyExistsSync.mockImplementation(() => false);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('throw if release is not found', async () => {
|
||||
await expect(
|
||||
installer.installGraalPy('7.3.3', architecture, false, undefined)
|
||||
).rejects.toThrow(
|
||||
`GraalPy version 7.3.3 with arch ${architecture} not found`
|
||||
);
|
||||
|
||||
expect(spyHttpClient).toHaveBeenCalled();
|
||||
expect(spyDownloadTool).not.toHaveBeenCalled();
|
||||
expect(spyExec).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('found and install GraalPy', async () => {
|
||||
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
||||
spyCacheDir.mockImplementation(() =>
|
||||
path.join(toolDir, 'GraalPy', '21.3.0', architecture)
|
||||
);
|
||||
|
||||
spyChmodSync = jest.spyOn(fs, 'chmodSync');
|
||||
spyChmodSync.mockImplementation(() => undefined);
|
||||
|
||||
await expect(
|
||||
installer.installGraalPy('21.x', architecture, false, undefined)
|
||||
).resolves.toEqual({
|
||||
installDir: path.join(toolDir, 'GraalPy', '21.3.0', architecture),
|
||||
resolvedGraalPyVersion: '21.3.0'
|
||||
});
|
||||
|
||||
expect(spyHttpClient).toHaveBeenCalled();
|
||||
expect(spyDownloadTool).toHaveBeenCalled();
|
||||
expect(spyCacheDir).toHaveBeenCalled();
|
||||
expect(spyExec).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('found and install GraalPy, pre-release fallback', async () => {
|
||||
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
||||
spyCacheDir.mockImplementation(() =>
|
||||
path.join(toolDir, 'GraalPy', '23.1.0', architecture)
|
||||
);
|
||||
|
||||
spyChmodSync = jest.spyOn(fs, 'chmodSync');
|
||||
spyChmodSync.mockImplementation(() => undefined);
|
||||
|
||||
await expect(
|
||||
installer.installGraalPy('23.1.x', architecture, false, undefined)
|
||||
).rejects.toThrow();
|
||||
await expect(
|
||||
installer.installGraalPy('23.1.x', architecture, true, undefined)
|
||||
).resolves.toEqual({
|
||||
installDir: path.join(toolDir, 'GraalPy', '23.1.0', architecture),
|
||||
resolvedGraalPyVersion: '23.1.0-a.1'
|
||||
});
|
||||
|
||||
expect(spyHttpClient).toHaveBeenCalled();
|
||||
expect(spyDownloadTool).toHaveBeenCalled();
|
||||
expect(spyCacheDir).toHaveBeenCalled();
|
||||
expect(spyExec).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -11,7 +11,8 @@ import {
|
|||
isCacheFeatureAvailable,
|
||||
getVersionInputFromFile,
|
||||
getVersionInputFromPlainFile,
|
||||
getVersionInputFromTomlFile
|
||||
getVersionInputFromTomlFile,
|
||||
getNextPageUrl
|
||||
} from '../src/utils';
|
||||
|
||||
jest.mock('@actions/cache');
|
||||
|
@ -136,3 +137,25 @@ describe('Version from file test', () => {
|
|||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('getNextPageUrl', () => {
|
||||
it('GitHub API pagination next page is parsed correctly', () => {
|
||||
function generateResponse(link: string) {
|
||||
return {
|
||||
statusCode: 200,
|
||||
result: null,
|
||||
headers: {
|
||||
link: link
|
||||
}
|
||||
};
|
||||
}
|
||||
const page1Links =
|
||||
'<https://api.github.com/repositories/129883600/releases?page=2>; rel="next", <https://api.github.com/repositories/129883600/releases?page=3>; rel="last"';
|
||||
expect(getNextPageUrl(generateResponse(page1Links))).toStrictEqual(
|
||||
'https://api.github.com/repositories/129883600/releases?page=2'
|
||||
);
|
||||
const page2Links =
|
||||
'<https://api.github.com/repositories/129883600/releases?page=1>; rel="prev", <https://api.github.com/repositories/129883600/releases?page=1>; rel="first"';
|
||||
expect(getNextPageUrl(generateResponse(page2Links))).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
399
dist/setup/index.js
vendored
399
dist/setup/index.js
vendored
|
@ -69091,6 +69091,132 @@ class PoetryCache extends cache_distributor_1.default {
|
|||
exports["default"] = PoetryCache;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 8040:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.parseGraalPyVersion = exports.findGraalPyToolCache = exports.findGraalPyVersion = void 0;
|
||||
const path = __importStar(__nccwpck_require__(1017));
|
||||
const graalpyInstall = __importStar(__nccwpck_require__(8265));
|
||||
const utils_1 = __nccwpck_require__(1314);
|
||||
const semver = __importStar(__nccwpck_require__(1383));
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
const tc = __importStar(__nccwpck_require__(7784));
|
||||
function findGraalPyVersion(versionSpec, architecture, updateEnvironment, checkLatest, allowPreReleases) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let resolvedGraalPyVersion = '';
|
||||
let installDir;
|
||||
let releases;
|
||||
let graalpyVersionSpec = parseGraalPyVersion(versionSpec);
|
||||
if (checkLatest) {
|
||||
releases = yield graalpyInstall.getAvailableGraalPyVersions();
|
||||
if (releases && releases.length > 0) {
|
||||
const releaseData = graalpyInstall.findRelease(releases, graalpyVersionSpec, architecture, false);
|
||||
if (releaseData) {
|
||||
core.info(`Resolved as GraalPy ${releaseData.resolvedGraalPyVersion}`);
|
||||
graalpyVersionSpec = releaseData.resolvedGraalPyVersion;
|
||||
}
|
||||
else {
|
||||
core.info(`Failed to resolve GraalPy ${graalpyVersionSpec} from manifest`);
|
||||
}
|
||||
}
|
||||
}
|
||||
({ installDir, resolvedGraalPyVersion } = findGraalPyToolCache(graalpyVersionSpec, architecture));
|
||||
if (!installDir) {
|
||||
({ installDir, resolvedGraalPyVersion } = yield graalpyInstall.installGraalPy(graalpyVersionSpec, architecture, allowPreReleases, releases));
|
||||
}
|
||||
const pipDir = utils_1.IS_WINDOWS ? 'Scripts' : 'bin';
|
||||
const _binDir = path.join(installDir, pipDir);
|
||||
const binaryExtension = utils_1.IS_WINDOWS ? '.exe' : '';
|
||||
const pythonPath = path.join(utils_1.IS_WINDOWS ? installDir : _binDir, `python${binaryExtension}`);
|
||||
const pythonLocation = utils_1.getBinaryDirectory(installDir);
|
||||
if (updateEnvironment) {
|
||||
core.exportVariable('pythonLocation', installDir);
|
||||
// https://cmake.org/cmake/help/latest/module/FindPython.html#module:FindPython
|
||||
core.exportVariable('Python_ROOT_DIR', installDir);
|
||||
// https://cmake.org/cmake/help/latest/module/FindPython2.html#module:FindPython2
|
||||
core.exportVariable('Python2_ROOT_DIR', installDir);
|
||||
// https://cmake.org/cmake/help/latest/module/FindPython3.html#module:FindPython3
|
||||
core.exportVariable('Python3_ROOT_DIR', installDir);
|
||||
core.exportVariable('PKG_CONFIG_PATH', pythonLocation + '/lib/pkgconfig');
|
||||
core.addPath(pythonLocation);
|
||||
core.addPath(_binDir);
|
||||
}
|
||||
core.setOutput('python-version', 'graalpy' + resolvedGraalPyVersion);
|
||||
core.setOutput('python-path', pythonPath);
|
||||
return resolvedGraalPyVersion;
|
||||
});
|
||||
}
|
||||
exports.findGraalPyVersion = findGraalPyVersion;
|
||||
function findGraalPyToolCache(graalpyVersion, architecture) {
|
||||
let resolvedGraalPyVersion = '';
|
||||
let installDir = tc.find('GraalPy', graalpyVersion, architecture);
|
||||
if (installDir) {
|
||||
// 'tc.find' finds tool based on Python version but we also need to check
|
||||
// whether GraalPy version satisfies requested version.
|
||||
resolvedGraalPyVersion = path.basename(path.dirname(installDir));
|
||||
const isGraalPyVersionSatisfies = semver.satisfies(resolvedGraalPyVersion, graalpyVersion);
|
||||
if (!isGraalPyVersionSatisfies) {
|
||||
installDir = null;
|
||||
resolvedGraalPyVersion = '';
|
||||
}
|
||||
}
|
||||
if (!installDir) {
|
||||
core.info(`GraalPy version ${graalpyVersion} was not found in the local cache`);
|
||||
}
|
||||
return { installDir, resolvedGraalPyVersion };
|
||||
}
|
||||
exports.findGraalPyToolCache = findGraalPyToolCache;
|
||||
function parseGraalPyVersion(versionSpec) {
|
||||
const versions = versionSpec.split('-').filter(item => !!item);
|
||||
if (/^(graalpy)(.+)/.test(versions[0])) {
|
||||
const version = versions[0].replace('graalpy', '');
|
||||
versions.splice(0, 1, 'graalpy', version);
|
||||
}
|
||||
if (versions.length < 2 || versions[0] != 'graalpy') {
|
||||
throw new Error("Invalid 'version' property for GraalPy. GraalPy version should be specified as 'graalpy<python-version>' or 'graalpy-<python-version>'. See README for examples and documentation.");
|
||||
}
|
||||
const pythonVersion = versions[1];
|
||||
if (!utils_1.validateVersion(pythonVersion)) {
|
||||
throw new Error("Invalid 'version' property for GraalPy. GraalPy versions should satisfy SemVer notation. See README for examples and documentation.");
|
||||
}
|
||||
return pythonVersion;
|
||||
}
|
||||
exports.parseGraalPyVersion = parseGraalPyVersion;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 4003:
|
||||
|
@ -69164,7 +69290,7 @@ function findPyPyVersion(versionSpec, architecture, updateEnvironment, checkLate
|
|||
const _binDir = path.join(installDir, pipDir);
|
||||
const binaryExtension = utils_1.IS_WINDOWS ? '.exe' : '';
|
||||
const pythonPath = path.join(utils_1.IS_WINDOWS ? installDir : _binDir, `python${binaryExtension}`);
|
||||
const pythonLocation = pypyInstall.getPyPyBinaryPath(installDir);
|
||||
const pythonLocation = utils_1.getBinaryDirectory(installDir);
|
||||
if (updateEnvironment) {
|
||||
core.exportVariable('pythonLocation', installDir);
|
||||
// https://cmake.org/cmake/help/latest/module/FindPython.html#module:FindPython
|
||||
|
@ -69419,6 +69545,222 @@ function pythonVersionToSemantic(versionSpec, allowPreReleases) {
|
|||
exports.pythonVersionToSemantic = pythonVersionToSemantic;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 8265:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.findAsset = exports.toGraalPyArchitecture = exports.toGraalPyPlatform = exports.findRelease = exports.graalPyTagToVersion = exports.getAvailableGraalPyVersions = exports.installGraalPy = void 0;
|
||||
const os = __importStar(__nccwpck_require__(2037));
|
||||
const path = __importStar(__nccwpck_require__(1017));
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
const tc = __importStar(__nccwpck_require__(7784));
|
||||
const semver = __importStar(__nccwpck_require__(1383));
|
||||
const httpm = __importStar(__nccwpck_require__(9925));
|
||||
const exec = __importStar(__nccwpck_require__(1514));
|
||||
const fs_1 = __importDefault(__nccwpck_require__(7147));
|
||||
const utils_1 = __nccwpck_require__(1314);
|
||||
const TOKEN = core.getInput('token');
|
||||
const AUTH = !TOKEN ? undefined : `token ${TOKEN}`;
|
||||
function installGraalPy(graalpyVersion, architecture, allowPreReleases, releases) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let downloadDir;
|
||||
releases = releases !== null && releases !== void 0 ? releases : (yield getAvailableGraalPyVersions());
|
||||
if (!releases || !releases.length) {
|
||||
throw new Error('No release was found in GraalPy version.json');
|
||||
}
|
||||
let releaseData = findRelease(releases, graalpyVersion, architecture, false);
|
||||
if (allowPreReleases && (!releaseData || !releaseData.foundAsset)) {
|
||||
// check for pre-release
|
||||
core.info([
|
||||
`Stable GraalPy version ${graalpyVersion} with arch ${architecture} not found`,
|
||||
`Trying pre-release versions`
|
||||
].join(os.EOL));
|
||||
releaseData = findRelease(releases, graalpyVersion, architecture, true);
|
||||
}
|
||||
if (!releaseData || !releaseData.foundAsset) {
|
||||
throw new Error(`GraalPy version ${graalpyVersion} with arch ${architecture} not found`);
|
||||
}
|
||||
const { foundAsset, resolvedGraalPyVersion } = releaseData;
|
||||
const downloadUrl = `${foundAsset.browser_download_url}`;
|
||||
core.info(`Downloading GraalPy from "${downloadUrl}" ...`);
|
||||
try {
|
||||
const graalpyPath = yield tc.downloadTool(downloadUrl, undefined, AUTH);
|
||||
core.info('Extracting downloaded archive...');
|
||||
downloadDir = yield tc.extractTar(graalpyPath);
|
||||
// root folder in archive can have unpredictable name so just take the first folder
|
||||
// downloadDir is unique folder under TEMP and can't contain any other folders
|
||||
const archiveName = fs_1.default.readdirSync(downloadDir)[0];
|
||||
const toolDir = path.join(downloadDir, archiveName);
|
||||
let installDir = toolDir;
|
||||
if (!utils_1.isNightlyKeyword(resolvedGraalPyVersion)) {
|
||||
installDir = yield tc.cacheDir(toolDir, 'GraalPy', resolvedGraalPyVersion, architecture);
|
||||
}
|
||||
const binaryPath = utils_1.getBinaryDirectory(installDir);
|
||||
yield createGraalPySymlink(binaryPath, resolvedGraalPyVersion);
|
||||
yield installPip(binaryPath);
|
||||
return { installDir, resolvedGraalPyVersion };
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof Error) {
|
||||
// Rate limit?
|
||||
if (err instanceof tc.HTTPError &&
|
||||
(err.httpStatusCode === 403 || err.httpStatusCode === 429)) {
|
||||
core.info(`Received HTTP status code ${err.httpStatusCode}. This usually indicates the rate limit has been exceeded`);
|
||||
}
|
||||
else {
|
||||
core.info(err.message);
|
||||
}
|
||||
if (err.stack !== undefined) {
|
||||
core.debug(err.stack);
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.installGraalPy = installGraalPy;
|
||||
function getAvailableGraalPyVersions() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const http = new httpm.HttpClient('tool-cache');
|
||||
const headers = {};
|
||||
if (AUTH) {
|
||||
headers.authorization = AUTH;
|
||||
}
|
||||
let url = 'https://api.github.com/repos/oracle/graalpython/releases';
|
||||
const result = [];
|
||||
do {
|
||||
const response = yield http.getJson(url, headers);
|
||||
if (!response.result) {
|
||||
throw new Error(`Unable to retrieve the list of available GraalPy versions from '${url}'`);
|
||||
}
|
||||
result.push(...response.result);
|
||||
url = utils_1.getNextPageUrl(response);
|
||||
} while (url);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
exports.getAvailableGraalPyVersions = getAvailableGraalPyVersions;
|
||||
function createGraalPySymlink(graalpyBinaryPath, graalpyVersion) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const version = semver.coerce(graalpyVersion);
|
||||
const pythonBinaryPostfix = semver.major(version);
|
||||
const pythonMinor = semver.minor(version);
|
||||
const graalpyMajorMinorBinaryPostfix = `${pythonBinaryPostfix}.${pythonMinor}`;
|
||||
const binaryExtension = utils_1.IS_WINDOWS ? '.exe' : '';
|
||||
core.info('Creating symlinks...');
|
||||
utils_1.createSymlinkInFolder(graalpyBinaryPath, `graalpy${binaryExtension}`, `python${pythonBinaryPostfix}${binaryExtension}`, true);
|
||||
utils_1.createSymlinkInFolder(graalpyBinaryPath, `graalpy${binaryExtension}`, `python${binaryExtension}`, true);
|
||||
utils_1.createSymlinkInFolder(graalpyBinaryPath, `graalpy${binaryExtension}`, `graalpy${graalpyMajorMinorBinaryPostfix}${binaryExtension}`, true);
|
||||
});
|
||||
}
|
||||
function installPip(pythonLocation) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
core.info("Installing pip (GraalPy doesn't update pip because it uses a patched version of pip)");
|
||||
const pythonBinary = path.join(pythonLocation, 'python');
|
||||
yield exec.exec(`${pythonBinary} -m ensurepip --default-pip`);
|
||||
});
|
||||
}
|
||||
function graalPyTagToVersion(tag) {
|
||||
const versionPattern = /.*-(\d+\.\d+\.\d+(?:\.\d+)?)((?:a|b|rc))?(\d*)?/;
|
||||
const match = tag.match(versionPattern);
|
||||
if (match && match[2]) {
|
||||
return `${match[1]}-${match[2]}.${match[3]}`;
|
||||
}
|
||||
else if (match) {
|
||||
return match[1];
|
||||
}
|
||||
else {
|
||||
return tag.replace(/.*-/, '');
|
||||
}
|
||||
}
|
||||
exports.graalPyTagToVersion = graalPyTagToVersion;
|
||||
function findRelease(releases, graalpyVersion, architecture, includePrerelease) {
|
||||
const options = { includePrerelease: includePrerelease };
|
||||
const filterReleases = releases.filter(item => {
|
||||
const isVersionSatisfied = semver.satisfies(graalPyTagToVersion(item.tag_name), graalpyVersion, options);
|
||||
return (isVersionSatisfied && !!findAsset(item, architecture, process.platform));
|
||||
});
|
||||
if (!filterReleases.length) {
|
||||
return null;
|
||||
}
|
||||
const sortedReleases = filterReleases.sort((previous, current) => semver.compare(semver.coerce(graalPyTagToVersion(current.tag_name)), semver.coerce(graalPyTagToVersion(previous.tag_name))));
|
||||
const foundRelease = sortedReleases[0];
|
||||
const foundAsset = findAsset(foundRelease, architecture, process.platform);
|
||||
return {
|
||||
foundAsset,
|
||||
resolvedGraalPyVersion: graalPyTagToVersion(foundRelease.tag_name)
|
||||
};
|
||||
}
|
||||
exports.findRelease = findRelease;
|
||||
function toGraalPyPlatform(platform) {
|
||||
switch (platform) {
|
||||
case 'win32':
|
||||
return 'windows';
|
||||
case 'darwin':
|
||||
return 'macos';
|
||||
}
|
||||
return platform;
|
||||
}
|
||||
exports.toGraalPyPlatform = toGraalPyPlatform;
|
||||
function toGraalPyArchitecture(architecture) {
|
||||
switch (architecture) {
|
||||
case 'x64':
|
||||
return 'amd64';
|
||||
case 'arm64':
|
||||
return 'aarch64';
|
||||
}
|
||||
return architecture;
|
||||
}
|
||||
exports.toGraalPyArchitecture = toGraalPyArchitecture;
|
||||
function findAsset(item, architecture, platform) {
|
||||
const graalpyArch = toGraalPyArchitecture(architecture);
|
||||
const graalpyPlatform = toGraalPyPlatform(platform);
|
||||
const found = item.assets.filter(file => file.name.startsWith('graalpy') &&
|
||||
file.name.endsWith(`-${graalpyPlatform}-${graalpyArch}.tar.gz`));
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
found.sort((f1, f2) => f1.name.length - f2.name.length);
|
||||
return found[0];
|
||||
}
|
||||
exports.findAsset = findAsset;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 8168:
|
||||
|
@ -69458,7 +69800,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.findAssetForMacOrLinux = exports.findAssetForWindows = exports.isArchPresentForMacOrLinux = exports.isArchPresentForWindows = exports.pypyVersionToSemantic = exports.getPyPyBinaryPath = exports.findRelease = exports.getAvailablePyPyVersions = exports.installPyPy = void 0;
|
||||
exports.findAssetForMacOrLinux = exports.findAssetForWindows = exports.isArchPresentForMacOrLinux = exports.isArchPresentForWindows = exports.pypyVersionToSemantic = exports.findRelease = exports.getAvailablePyPyVersions = exports.installPyPy = void 0;
|
||||
const os = __importStar(__nccwpck_require__(2037));
|
||||
const path = __importStar(__nccwpck_require__(1017));
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
|
@ -69508,7 +69850,7 @@ function installPyPy(pypyVersion, pythonVersion, architecture, allowPreReleases,
|
|||
installDir = yield tc.cacheDir(toolDir, 'PyPy', resolvedPythonVersion, architecture);
|
||||
}
|
||||
utils_1.writeExactPyPyVersionFile(installDir, resolvedPyPyVersion);
|
||||
const binaryPath = getPyPyBinaryPath(installDir);
|
||||
const binaryPath = utils_1.getBinaryDirectory(installDir);
|
||||
yield createPyPySymlink(binaryPath, resolvedPythonVersion);
|
||||
yield installPip(binaryPath);
|
||||
return { installDir, resolvedPythonVersion, resolvedPyPyVersion };
|
||||
|
@ -69597,15 +69939,6 @@ function findRelease(releases, pythonVersion, pypyVersion, architecture, include
|
|||
};
|
||||
}
|
||||
exports.findRelease = findRelease;
|
||||
/** Get PyPy binary location from the tool of installation directory
|
||||
* - On Linux and macOS, the Python interpreter is in 'bin'.
|
||||
* - On Windows, it is in the installation root.
|
||||
*/
|
||||
function getPyPyBinaryPath(installDir) {
|
||||
const _binDir = path.join(installDir, 'bin');
|
||||
return utils_1.IS_WINDOWS ? installDir : _binDir;
|
||||
}
|
||||
exports.getPyPyBinaryPath = getPyPyBinaryPath;
|
||||
function pypyVersionToSemantic(versionSpec) {
|
||||
const prereleaseVersion = /(\d+\.\d+\.\d+)((?:a|b|rc))(\d*)/g;
|
||||
return versionSpec.replace(prereleaseVersion, '$1-$2.$3');
|
||||
|
@ -69804,6 +70137,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|||
const core = __importStar(__nccwpck_require__(2186));
|
||||
const finder = __importStar(__nccwpck_require__(9996));
|
||||
const finderPyPy = __importStar(__nccwpck_require__(4003));
|
||||
const finderGraalPy = __importStar(__nccwpck_require__(8040));
|
||||
const path = __importStar(__nccwpck_require__(1017));
|
||||
const os = __importStar(__nccwpck_require__(2037));
|
||||
const fs_1 = __importDefault(__nccwpck_require__(7147));
|
||||
|
@ -69812,6 +70146,9 @@ const utils_1 = __nccwpck_require__(1314);
|
|||
function isPyPyVersion(versionSpec) {
|
||||
return versionSpec.startsWith('pypy');
|
||||
}
|
||||
function isGraalPyVersion(versionSpec) {
|
||||
return versionSpec.startsWith('graalpy');
|
||||
}
|
||||
function cacheDependencies(cache, pythonVersion) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const cacheDependencyPath = core.getInput('cache-dependency-path') || undefined;
|
||||
|
@ -69880,6 +70217,11 @@ function run() {
|
|||
pythonVersion = `${installed.resolvedPyPyVersion}-${installed.resolvedPythonVersion}`;
|
||||
core.info(`Successfully set up PyPy ${installed.resolvedPyPyVersion} with Python (${installed.resolvedPythonVersion})`);
|
||||
}
|
||||
else if (isGraalPyVersion(version)) {
|
||||
const installed = yield finderGraalPy.findGraalPyVersion(version, arch, updateEnvironment, checkLatest, allowPreReleases);
|
||||
pythonVersion = `${installed}`;
|
||||
core.info(`Successfully set up GraalPy ${installed}`);
|
||||
}
|
||||
else {
|
||||
if (version.startsWith('2')) {
|
||||
core.warning('The support for python 2.7 will be removed on June 19. Related issue: https://github.com/actions/setup-python/issues/672');
|
||||
|
@ -69948,7 +70290,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.getVersionInputFromFile = exports.getVersionInputFromPlainFile = exports.getVersionInputFromTomlFile = exports.getOSInfo = exports.getLinuxInfo = exports.logWarning = exports.isCacheFeatureAvailable = exports.isGhes = exports.validatePythonVersionFormatForPyPy = exports.writeExactPyPyVersionFile = exports.readExactPyPyVersionFile = exports.getPyPyVersionFromPath = exports.isNightlyKeyword = exports.validateVersion = exports.createSymlinkInFolder = exports.WINDOWS_PLATFORMS = exports.WINDOWS_ARCHS = exports.IS_MAC = exports.IS_LINUX = exports.IS_WINDOWS = void 0;
|
||||
exports.getNextPageUrl = exports.getBinaryDirectory = exports.getVersionInputFromFile = exports.getVersionInputFromPlainFile = exports.getVersionInputFromTomlFile = exports.getOSInfo = exports.getLinuxInfo = exports.logWarning = exports.isCacheFeatureAvailable = exports.isGhes = exports.validatePythonVersionFormatForPyPy = exports.writeExactPyPyVersionFile = exports.readExactPyPyVersionFile = exports.getPyPyVersionFromPath = exports.isNightlyKeyword = exports.validateVersion = exports.createSymlinkInFolder = exports.WINDOWS_PLATFORMS = exports.WINDOWS_ARCHS = exports.IS_MAC = exports.IS_LINUX = exports.IS_WINDOWS = void 0;
|
||||
/* eslint no-unsafe-finally: "off" */
|
||||
const cache = __importStar(__nccwpck_require__(7799));
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
|
@ -70177,6 +70519,37 @@ function getVersionInputFromFile(versionFile) {
|
|||
}
|
||||
}
|
||||
exports.getVersionInputFromFile = getVersionInputFromFile;
|
||||
/**
|
||||
* Get the directory containing interpreter binary from installation directory of PyPy or GraalPy
|
||||
* - On Linux and macOS, the Python interpreter is in 'bin'.
|
||||
* - On Windows, it is in the installation root.
|
||||
*/
|
||||
function getBinaryDirectory(installDir) {
|
||||
return exports.IS_WINDOWS ? installDir : path.join(installDir, 'bin');
|
||||
}
|
||||
exports.getBinaryDirectory = getBinaryDirectory;
|
||||
/**
|
||||
* Extract next page URL from a HTTP response "link" header. Such headers are used in GitHub APIs.
|
||||
*/
|
||||
function getNextPageUrl(response) {
|
||||
const responseHeaders = response.headers;
|
||||
const linkHeader = responseHeaders.link;
|
||||
if (typeof linkHeader === 'string') {
|
||||
for (const link of linkHeader.split(/\s*,\s*/)) {
|
||||
const match = link.match(/<([^>]+)>(.*)/);
|
||||
if (match) {
|
||||
const url = match[1];
|
||||
for (const param of match[2].split(/\s*;\s*/)) {
|
||||
if (param.match(/rel="?next"?/)) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
exports.getNextPageUrl = getNextPageUrl;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
|
146
src/find-graalpy.ts
Normal file
146
src/find-graalpy.ts
Normal file
|
@ -0,0 +1,146 @@
|
|||
import * as path from 'path';
|
||||
import * as graalpyInstall from './install-graalpy';
|
||||
import {
|
||||
IS_WINDOWS,
|
||||
validateVersion,
|
||||
IGraalPyManifestRelease,
|
||||
getBinaryDirectory
|
||||
} from './utils';
|
||||
|
||||
import * as semver from 'semver';
|
||||
import * as core from '@actions/core';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
|
||||
export async function findGraalPyVersion(
|
||||
versionSpec: string,
|
||||
architecture: string,
|
||||
updateEnvironment: boolean,
|
||||
checkLatest: boolean,
|
||||
allowPreReleases: boolean
|
||||
): Promise<string> {
|
||||
let resolvedGraalPyVersion = '';
|
||||
let installDir: string | null;
|
||||
let releases: IGraalPyManifestRelease[] | undefined;
|
||||
|
||||
let graalpyVersionSpec = parseGraalPyVersion(versionSpec);
|
||||
|
||||
if (checkLatest) {
|
||||
releases = await graalpyInstall.getAvailableGraalPyVersions();
|
||||
if (releases && releases.length > 0) {
|
||||
const releaseData = graalpyInstall.findRelease(
|
||||
releases,
|
||||
graalpyVersionSpec,
|
||||
architecture,
|
||||
false
|
||||
);
|
||||
|
||||
if (releaseData) {
|
||||
core.info(`Resolved as GraalPy ${releaseData.resolvedGraalPyVersion}`);
|
||||
graalpyVersionSpec = releaseData.resolvedGraalPyVersion;
|
||||
} else {
|
||||
core.info(
|
||||
`Failed to resolve GraalPy ${graalpyVersionSpec} from manifest`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
({installDir, resolvedGraalPyVersion} = findGraalPyToolCache(
|
||||
graalpyVersionSpec,
|
||||
architecture
|
||||
));
|
||||
|
||||
if (!installDir) {
|
||||
({installDir, resolvedGraalPyVersion} = await graalpyInstall.installGraalPy(
|
||||
graalpyVersionSpec,
|
||||
architecture,
|
||||
allowPreReleases,
|
||||
releases
|
||||
));
|
||||
}
|
||||
|
||||
const pipDir = IS_WINDOWS ? 'Scripts' : 'bin';
|
||||
const _binDir = path.join(installDir, pipDir);
|
||||
const binaryExtension = IS_WINDOWS ? '.exe' : '';
|
||||
const pythonPath = path.join(
|
||||
IS_WINDOWS ? installDir : _binDir,
|
||||
`python${binaryExtension}`
|
||||
);
|
||||
const pythonLocation = getBinaryDirectory(installDir);
|
||||
if (updateEnvironment) {
|
||||
core.exportVariable('pythonLocation', installDir);
|
||||
// https://cmake.org/cmake/help/latest/module/FindPython.html#module:FindPython
|
||||
core.exportVariable('Python_ROOT_DIR', installDir);
|
||||
// https://cmake.org/cmake/help/latest/module/FindPython2.html#module:FindPython2
|
||||
core.exportVariable('Python2_ROOT_DIR', installDir);
|
||||
// https://cmake.org/cmake/help/latest/module/FindPython3.html#module:FindPython3
|
||||
core.exportVariable('Python3_ROOT_DIR', installDir);
|
||||
core.exportVariable('PKG_CONFIG_PATH', pythonLocation + '/lib/pkgconfig');
|
||||
core.addPath(pythonLocation);
|
||||
core.addPath(_binDir);
|
||||
}
|
||||
core.setOutput('python-version', 'graalpy' + resolvedGraalPyVersion);
|
||||
core.setOutput('python-path', pythonPath);
|
||||
|
||||
return resolvedGraalPyVersion;
|
||||
}
|
||||
|
||||
export function findGraalPyToolCache(
|
||||
graalpyVersion: string,
|
||||
architecture: string
|
||||
) {
|
||||
let resolvedGraalPyVersion = '';
|
||||
let installDir: string | null = tc.find(
|
||||
'GraalPy',
|
||||
graalpyVersion,
|
||||
architecture
|
||||
);
|
||||
|
||||
if (installDir) {
|
||||
// 'tc.find' finds tool based on Python version but we also need to check
|
||||
// whether GraalPy version satisfies requested version.
|
||||
resolvedGraalPyVersion = path.basename(path.dirname(installDir));
|
||||
|
||||
const isGraalPyVersionSatisfies = semver.satisfies(
|
||||
resolvedGraalPyVersion,
|
||||
graalpyVersion
|
||||
);
|
||||
if (!isGraalPyVersionSatisfies) {
|
||||
installDir = null;
|
||||
resolvedGraalPyVersion = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!installDir) {
|
||||
core.info(
|
||||
`GraalPy version ${graalpyVersion} was not found in the local cache`
|
||||
);
|
||||
}
|
||||
|
||||
return {installDir, resolvedGraalPyVersion};
|
||||
}
|
||||
|
||||
export function parseGraalPyVersion(versionSpec: string): string {
|
||||
const versions = versionSpec.split('-').filter(item => !!item);
|
||||
|
||||
if (/^(graalpy)(.+)/.test(versions[0])) {
|
||||
const version = versions[0].replace('graalpy', '');
|
||||
versions.splice(0, 1, 'graalpy', version);
|
||||
}
|
||||
|
||||
if (versions.length < 2 || versions[0] != 'graalpy') {
|
||||
throw new Error(
|
||||
"Invalid 'version' property for GraalPy. GraalPy version should be specified as 'graalpy<python-version>' or 'graalpy-<python-version>'. See README for examples and documentation."
|
||||
);
|
||||
}
|
||||
|
||||
const pythonVersion = versions[1];
|
||||
|
||||
if (!validateVersion(pythonVersion)) {
|
||||
throw new Error(
|
||||
"Invalid 'version' property for GraalPy. GraalPy versions should satisfy SemVer notation. See README for examples and documentation."
|
||||
);
|
||||
}
|
||||
|
||||
return pythonVersion;
|
||||
}
|
|
@ -7,7 +7,8 @@ import {
|
|||
getPyPyVersionFromPath,
|
||||
readExactPyPyVersionFile,
|
||||
validatePythonVersionFormatForPyPy,
|
||||
IPyPyManifestRelease
|
||||
IPyPyManifestRelease,
|
||||
getBinaryDirectory
|
||||
} from './utils';
|
||||
|
||||
import * as semver from 'semver';
|
||||
|
@ -82,7 +83,7 @@ export async function findPyPyVersion(
|
|||
IS_WINDOWS ? installDir : _binDir,
|
||||
`python${binaryExtension}`
|
||||
);
|
||||
const pythonLocation = pypyInstall.getPyPyBinaryPath(installDir);
|
||||
const pythonLocation = getBinaryDirectory(installDir);
|
||||
if (updateEnvironment) {
|
||||
core.exportVariable('pythonLocation', installDir);
|
||||
// https://cmake.org/cmake/help/latest/module/FindPython.html#module:FindPython
|
||||
|
|
262
src/install-graalpy.ts
Normal file
262
src/install-graalpy.ts
Normal file
|
@ -0,0 +1,262 @@
|
|||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as core from '@actions/core';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import * as semver from 'semver';
|
||||
import * as httpm from '@actions/http-client';
|
||||
import * as ifm from '@actions/http-client/interfaces';
|
||||
import * as exec from '@actions/exec';
|
||||
import fs from 'fs';
|
||||
|
||||
import {
|
||||
IS_WINDOWS,
|
||||
IGraalPyManifestRelease,
|
||||
createSymlinkInFolder,
|
||||
isNightlyKeyword,
|
||||
getBinaryDirectory,
|
||||
getNextPageUrl
|
||||
} from './utils';
|
||||
|
||||
const TOKEN = core.getInput('token');
|
||||
const AUTH = !TOKEN ? undefined : `token ${TOKEN}`;
|
||||
|
||||
export async function installGraalPy(
|
||||
graalpyVersion: string,
|
||||
architecture: string,
|
||||
allowPreReleases: boolean,
|
||||
releases: IGraalPyManifestRelease[] | undefined
|
||||
) {
|
||||
let downloadDir;
|
||||
|
||||
releases = releases ?? (await getAvailableGraalPyVersions());
|
||||
|
||||
if (!releases || !releases.length) {
|
||||
throw new Error('No release was found in GraalPy version.json');
|
||||
}
|
||||
|
||||
let releaseData = findRelease(releases, graalpyVersion, architecture, false);
|
||||
|
||||
if (allowPreReleases && (!releaseData || !releaseData.foundAsset)) {
|
||||
// check for pre-release
|
||||
core.info(
|
||||
[
|
||||
`Stable GraalPy version ${graalpyVersion} with arch ${architecture} not found`,
|
||||
`Trying pre-release versions`
|
||||
].join(os.EOL)
|
||||
);
|
||||
releaseData = findRelease(releases, graalpyVersion, architecture, true);
|
||||
}
|
||||
|
||||
if (!releaseData || !releaseData.foundAsset) {
|
||||
throw new Error(
|
||||
`GraalPy version ${graalpyVersion} with arch ${architecture} not found`
|
||||
);
|
||||
}
|
||||
|
||||
const {foundAsset, resolvedGraalPyVersion} = releaseData;
|
||||
const downloadUrl = `${foundAsset.browser_download_url}`;
|
||||
|
||||
core.info(`Downloading GraalPy from "${downloadUrl}" ...`);
|
||||
|
||||
try {
|
||||
const graalpyPath = await tc.downloadTool(downloadUrl, undefined, AUTH);
|
||||
|
||||
core.info('Extracting downloaded archive...');
|
||||
downloadDir = await tc.extractTar(graalpyPath);
|
||||
|
||||
// root folder in archive can have unpredictable name so just take the first folder
|
||||
// downloadDir is unique folder under TEMP and can't contain any other folders
|
||||
const archiveName = fs.readdirSync(downloadDir)[0];
|
||||
|
||||
const toolDir = path.join(downloadDir, archiveName);
|
||||
let installDir = toolDir;
|
||||
if (!isNightlyKeyword(resolvedGraalPyVersion)) {
|
||||
installDir = await tc.cacheDir(
|
||||
toolDir,
|
||||
'GraalPy',
|
||||
resolvedGraalPyVersion,
|
||||
architecture
|
||||
);
|
||||
}
|
||||
|
||||
const binaryPath = getBinaryDirectory(installDir);
|
||||
await createGraalPySymlink(binaryPath, resolvedGraalPyVersion);
|
||||
await installPip(binaryPath);
|
||||
|
||||
return {installDir, resolvedGraalPyVersion};
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
// Rate limit?
|
||||
if (
|
||||
err instanceof tc.HTTPError &&
|
||||
(err.httpStatusCode === 403 || err.httpStatusCode === 429)
|
||||
) {
|
||||
core.info(
|
||||
`Received HTTP status code ${err.httpStatusCode}. This usually indicates the rate limit has been exceeded`
|
||||
);
|
||||
} else {
|
||||
core.info(err.message);
|
||||
}
|
||||
if (err.stack !== undefined) {
|
||||
core.debug(err.stack);
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAvailableGraalPyVersions() {
|
||||
const http: httpm.HttpClient = new httpm.HttpClient('tool-cache');
|
||||
|
||||
const headers: ifm.IHeaders = {};
|
||||
if (AUTH) {
|
||||
headers.authorization = AUTH;
|
||||
}
|
||||
|
||||
let url: string | null =
|
||||
'https://api.github.com/repos/oracle/graalpython/releases';
|
||||
const result: IGraalPyManifestRelease[] = [];
|
||||
do {
|
||||
const response: ifm.ITypedResponse<IGraalPyManifestRelease[]> =
|
||||
await http.getJson(url, headers);
|
||||
if (!response.result) {
|
||||
throw new Error(
|
||||
`Unable to retrieve the list of available GraalPy versions from '${url}'`
|
||||
);
|
||||
}
|
||||
result.push(...response.result);
|
||||
url = getNextPageUrl(response);
|
||||
} while (url);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function createGraalPySymlink(
|
||||
graalpyBinaryPath: string,
|
||||
graalpyVersion: string
|
||||
) {
|
||||
const version = semver.coerce(graalpyVersion)!;
|
||||
const pythonBinaryPostfix = semver.major(version);
|
||||
const pythonMinor = semver.minor(version);
|
||||
const graalpyMajorMinorBinaryPostfix = `${pythonBinaryPostfix}.${pythonMinor}`;
|
||||
const binaryExtension = IS_WINDOWS ? '.exe' : '';
|
||||
|
||||
core.info('Creating symlinks...');
|
||||
createSymlinkInFolder(
|
||||
graalpyBinaryPath,
|
||||
`graalpy${binaryExtension}`,
|
||||
`python${pythonBinaryPostfix}${binaryExtension}`,
|
||||
true
|
||||
);
|
||||
|
||||
createSymlinkInFolder(
|
||||
graalpyBinaryPath,
|
||||
`graalpy${binaryExtension}`,
|
||||
`python${binaryExtension}`,
|
||||
true
|
||||
);
|
||||
|
||||
createSymlinkInFolder(
|
||||
graalpyBinaryPath,
|
||||
`graalpy${binaryExtension}`,
|
||||
`graalpy${graalpyMajorMinorBinaryPostfix}${binaryExtension}`,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
async function installPip(pythonLocation: string) {
|
||||
core.info(
|
||||
"Installing pip (GraalPy doesn't update pip because it uses a patched version of pip)"
|
||||
);
|
||||
const pythonBinary = path.join(pythonLocation, 'python');
|
||||
await exec.exec(`${pythonBinary} -m ensurepip --default-pip`);
|
||||
}
|
||||
|
||||
export function graalPyTagToVersion(tag: string) {
|
||||
const versionPattern = /.*-(\d+\.\d+\.\d+(?:\.\d+)?)((?:a|b|rc))?(\d*)?/;
|
||||
const match = tag.match(versionPattern);
|
||||
if (match && match[2]) {
|
||||
return `${match[1]}-${match[2]}.${match[3]}`;
|
||||
} else if (match) {
|
||||
return match[1];
|
||||
} else {
|
||||
return tag.replace(/.*-/, '');
|
||||
}
|
||||
}
|
||||
|
||||
export function findRelease(
|
||||
releases: IGraalPyManifestRelease[],
|
||||
graalpyVersion: string,
|
||||
architecture: string,
|
||||
includePrerelease: boolean
|
||||
) {
|
||||
const options = {includePrerelease: includePrerelease};
|
||||
const filterReleases = releases.filter(item => {
|
||||
const isVersionSatisfied = semver.satisfies(
|
||||
graalPyTagToVersion(item.tag_name),
|
||||
graalpyVersion,
|
||||
options
|
||||
);
|
||||
return (
|
||||
isVersionSatisfied && !!findAsset(item, architecture, process.platform)
|
||||
);
|
||||
});
|
||||
|
||||
if (!filterReleases.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sortedReleases = filterReleases.sort((previous, current) =>
|
||||
semver.compare(
|
||||
semver.coerce(graalPyTagToVersion(current.tag_name))!,
|
||||
semver.coerce(graalPyTagToVersion(previous.tag_name))!
|
||||
)
|
||||
);
|
||||
|
||||
const foundRelease = sortedReleases[0];
|
||||
const foundAsset = findAsset(foundRelease, architecture, process.platform);
|
||||
|
||||
return {
|
||||
foundAsset,
|
||||
resolvedGraalPyVersion: graalPyTagToVersion(foundRelease.tag_name)
|
||||
};
|
||||
}
|
||||
|
||||
export function toGraalPyPlatform(platform: string) {
|
||||
switch (platform) {
|
||||
case 'win32':
|
||||
return 'windows';
|
||||
case 'darwin':
|
||||
return 'macos';
|
||||
}
|
||||
return platform;
|
||||
}
|
||||
|
||||
export function toGraalPyArchitecture(architecture: string) {
|
||||
switch (architecture) {
|
||||
case 'x64':
|
||||
return 'amd64';
|
||||
case 'arm64':
|
||||
return 'aarch64';
|
||||
}
|
||||
return architecture;
|
||||
}
|
||||
|
||||
export function findAsset(
|
||||
item: IGraalPyManifestRelease,
|
||||
architecture: string,
|
||||
platform: string
|
||||
) {
|
||||
const graalpyArch = toGraalPyArchitecture(architecture);
|
||||
const graalpyPlatform = toGraalPyPlatform(platform);
|
||||
const found = item.assets.filter(
|
||||
file =>
|
||||
file.name.startsWith('graalpy') &&
|
||||
file.name.endsWith(`-${graalpyPlatform}-${graalpyArch}.tar.gz`)
|
||||
);
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
found.sort((f1, f2) => f1.name.length - f2.name.length);
|
||||
return found[0];
|
||||
}
|
|
@ -13,7 +13,8 @@ import {
|
|||
IPyPyManifestRelease,
|
||||
createSymlinkInFolder,
|
||||
isNightlyKeyword,
|
||||
writeExactPyPyVersionFile
|
||||
writeExactPyPyVersionFile,
|
||||
getBinaryDirectory
|
||||
} from './utils';
|
||||
|
||||
export async function installPyPy(
|
||||
|
@ -94,7 +95,7 @@ export async function installPyPy(
|
|||
|
||||
writeExactPyPyVersionFile(installDir, resolvedPyPyVersion);
|
||||
|
||||
const binaryPath = getPyPyBinaryPath(installDir);
|
||||
const binaryPath = getBinaryDirectory(installDir);
|
||||
await createPyPySymlink(binaryPath, resolvedPythonVersion);
|
||||
await installPip(binaryPath);
|
||||
|
||||
|
@ -237,15 +238,6 @@ export function findRelease(
|
|||
};
|
||||
}
|
||||
|
||||
/** Get PyPy binary location from the tool of installation directory
|
||||
* - On Linux and macOS, the Python interpreter is in 'bin'.
|
||||
* - On Windows, it is in the installation root.
|
||||
*/
|
||||
export function getPyPyBinaryPath(installDir: string) {
|
||||
const _binDir = path.join(installDir, 'bin');
|
||||
return IS_WINDOWS ? installDir : _binDir;
|
||||
}
|
||||
|
||||
export function pypyVersionToSemantic(versionSpec: string) {
|
||||
const prereleaseVersion = /(\d+\.\d+\.\d+)((?:a|b|rc))(\d*)/g;
|
||||
return versionSpec.replace(prereleaseVersion, '$1-$2.$3');
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as core from '@actions/core';
|
||||
import * as finder from './find-python';
|
||||
import * as finderPyPy from './find-pypy';
|
||||
import * as finderGraalPy from './find-graalpy';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import fs from 'fs';
|
||||
|
@ -17,6 +18,10 @@ function isPyPyVersion(versionSpec: string) {
|
|||
return versionSpec.startsWith('pypy');
|
||||
}
|
||||
|
||||
function isGraalPyVersion(versionSpec: string) {
|
||||
return versionSpec.startsWith('graalpy');
|
||||
}
|
||||
|
||||
async function cacheDependencies(cache: string, pythonVersion: string) {
|
||||
const cacheDependencyPath =
|
||||
core.getInput('cache-dependency-path') || undefined;
|
||||
|
@ -106,6 +111,16 @@ async function run() {
|
|||
core.info(
|
||||
`Successfully set up PyPy ${installed.resolvedPyPyVersion} with Python (${installed.resolvedPythonVersion})`
|
||||
);
|
||||
} else if (isGraalPyVersion(version)) {
|
||||
const installed = await finderGraalPy.findGraalPyVersion(
|
||||
version,
|
||||
arch,
|
||||
updateEnvironment,
|
||||
checkLatest,
|
||||
allowPreReleases
|
||||
);
|
||||
pythonVersion = `${installed}`;
|
||||
core.info(`Successfully set up GraalPy ${installed}`);
|
||||
} else {
|
||||
if (version.startsWith('2')) {
|
||||
core.warning(
|
||||
|
|
42
src/utils.ts
42
src/utils.ts
|
@ -6,6 +6,7 @@ import * as path from 'path';
|
|||
import * as semver from 'semver';
|
||||
import * as toml from '@iarna/toml';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as ifm from '@actions/http-client/interfaces';
|
||||
|
||||
export const IS_WINDOWS = process.platform === 'win32';
|
||||
export const IS_LINUX = process.platform === 'linux';
|
||||
|
@ -29,6 +30,16 @@ export interface IPyPyManifestRelease {
|
|||
files: IPyPyManifestAsset[];
|
||||
}
|
||||
|
||||
export interface IGraalPyManifestAsset {
|
||||
name: string;
|
||||
browser_download_url: string;
|
||||
}
|
||||
|
||||
export interface IGraalPyManifestRelease {
|
||||
tag_name: string;
|
||||
assets: IGraalPyManifestAsset[];
|
||||
}
|
||||
|
||||
/** create Symlinks for downloaded PyPy
|
||||
* It should be executed only for downloaded versions in runtime, because
|
||||
* toolcache versions have this setup.
|
||||
|
@ -266,3 +277,34 @@ export function getVersionInputFromFile(versionFile: string): string[] {
|
|||
return getVersionInputFromPlainFile(versionFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the directory containing interpreter binary from installation directory of PyPy or GraalPy
|
||||
* - On Linux and macOS, the Python interpreter is in 'bin'.
|
||||
* - On Windows, it is in the installation root.
|
||||
*/
|
||||
export function getBinaryDirectory(installDir: string) {
|
||||
return IS_WINDOWS ? installDir : path.join(installDir, 'bin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract next page URL from a HTTP response "link" header. Such headers are used in GitHub APIs.
|
||||
*/
|
||||
export function getNextPageUrl<T>(response: ifm.ITypedResponse<T>) {
|
||||
const responseHeaders = <ifm.IHeaders>response.headers;
|
||||
const linkHeader = responseHeaders.link;
|
||||
if (typeof linkHeader === 'string') {
|
||||
for (const link of linkHeader.split(/\s*,\s*/)) {
|
||||
const match = link.match(/<([^>]+)>(.*)/);
|
||||
if (match) {
|
||||
const url = match[1];
|
||||
for (const param of match[2].split(/\s*;\s*/)) {
|
||||
if (param.match(/rel="?next"?/)) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue