setup-python/src/install-graalpy.ts

258 lines
7.0 KiB
TypeScript
Raw Normal View History

2023-06-27 18:40:17 +00:00
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';
2023-08-29 07:37:32 +00:00
import * as ifm from '@actions/http-client/interfaces';
2023-06-27 18:40:17 +00:00
import * as exec from '@actions/exec';
import fs from 'fs';
import {
IS_WINDOWS,
IGraalPyManifestAsset,
IGraalPyManifestRelease,
2023-06-30 16:43:51 +00:00
createSymlinkInFolder,
isNightlyKeyword,
getBinaryDirectory,
getNextPageUrl
2023-06-27 18:40:17 +00:00
} from './utils';
2023-08-29 07:37:32 +00:00
const TOKEN = core.getInput('token');
const AUTH = !TOKEN ? undefined : `token ${TOKEN}`;
2023-06-27 18:40:17 +00:00
export async function installGraalPy(
graalpyVersion: string,
architecture: string,
allowPreReleases: boolean,
releases: IGraalPyManifestRelease[] | undefined
) {
let downloadDir;
releases = releases ?? (await getAvailableGraalPyVersions());
if (!releases || !releases.length) {
2023-06-27 18:40:17 +00:00
throw new Error('No release was found in GraalPy version.json');
}
2023-06-30 16:12:19 +00:00
let releaseData = findRelease(releases, graalpyVersion, architecture, false);
2023-06-27 18:40:17 +00:00
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)
);
2023-06-30 16:12:19 +00:00
releaseData = findRelease(releases, graalpyVersion, architecture, true);
2023-06-27 18:40:17 +00:00
}
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 {
2023-08-29 07:37:32 +00:00
const graalpyPath = await tc.downloadTool(downloadUrl, undefined, AUTH);
2023-06-27 18:40:17 +00:00
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);
2023-06-30 16:43:51 +00:00
await createGraalPySymlink(binaryPath, resolvedGraalPyVersion);
2023-06-27 18:40:17 +00:00
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');
2023-08-29 07:37:32 +00:00
let 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);
2023-06-27 18:40:17 +00:00
return result;
2023-06-27 18:40:17 +00:00
}
2023-06-30 16:43:51 +00:00
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
);
}
2023-06-27 18:40:17 +00:00
async function installPip(pythonLocation: string) {
2023-08-29 07:14:12 +00:00
core.info(
"Installing pip (GraalPy doesn't update pip because it uses a patched version of pip)"
);
2023-06-27 18:40:17 +00:00
const pythonBinary = path.join(pythonLocation, 'python');
await exec.exec(`${pythonBinary} -m ensurepip --default-pip`);
2023-06-27 18:40:17 +00:00
}
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
);
2023-06-30 16:12:19 +00:00
return (
isVersionSatisfied && !!findAsset(item, architecture, process.platform)
);
2023-06-27 18:40:17 +00:00
});
if (!filterReleases.length) {
2023-06-27 18:40:17 +00:00
return null;
}
2023-08-29 07:14:12 +00:00
const sortedReleases = filterReleases.sort((previous, current) =>
semver.compare(
semver.coerce(graalPyTagToVersion(current.tag_name))!,
semver.coerce(graalPyTagToVersion(previous.tag_name))!
)
);
2023-06-27 18:40:17 +00:00
const foundRelease = sortedReleases[0];
const foundAsset = findAsset(foundRelease, architecture, process.platform);
return {
foundAsset,
resolvedGraalPyVersion: graalPyTagToVersion(foundRelease.tag_name)
};
}
export function findAsset(
item: IGraalPyManifestRelease,
architecture: string,
platform: string
) {
2023-06-30 16:12:19 +00:00
const graalpyArch =
architecture === 'x64'
? 'amd64'
: architecture === 'arm64'
? 'aarch64'
: architecture;
2023-08-21 08:58:30 +00:00
const graalpyPlatform =
platform === 'win32'
? 'windows'
: platform === 'darwin'
? 'macos'
: platform;
2023-08-29 07:14:12 +00:00
if (item.assets.length) {
2023-06-30 16:12:19 +00:00
return item.assets.find((file: IGraalPyManifestAsset) => {
const match_data = file.name.match(
2023-08-21 08:58:30 +00:00
'.*(macos|linux|windows)-(amd64|aarch64).tar.gz$'
2023-06-30 16:12:19 +00:00
);
return (
match_data &&
match_data[1] === graalpyPlatform &&
match_data[2] === graalpyArch
);
});
2023-06-27 18:40:17 +00:00
} else {
return undefined;
}
}