setup-python/src/find-pypy.ts

194 lines
5.9 KiB
TypeScript

import * as path from 'path';
import * as pypyInstall from './install-pypy';
import {
IS_WINDOWS,
WINDOWS_ARCHS,
validateVersion,
getPyPyVersionFromPath,
readExactPyPyVersionFile,
validatePythonVersionFormatForPyPy,
IPyPyManifestRelease
} from './utils';
import * as semver from 'semver';
import * as core from '@actions/core';
import * as tc from '@actions/tool-cache';
interface IPyPyVersionSpec {
pypyVersion: string;
pythonVersion: string;
}
export async function findPyPyVersion(
versionSpec: string,
architecture: string,
updateEnvironment: boolean,
checkLatest: boolean,
allowPreReleases: boolean
): Promise<{resolvedPyPyVersion: string; resolvedPythonVersion: string}> {
let resolvedPyPyVersion = '';
let resolvedPythonVersion = '';
let installDir: string | null;
let releases: IPyPyManifestRelease[] | undefined;
const pypyVersionSpec = parsePyPyVersion(versionSpec);
if (checkLatest) {
releases = await pypyInstall.getAvailablePyPyVersions();
if (releases && releases.length > 0) {
const releaseData = pypyInstall.findRelease(
releases,
pypyVersionSpec.pythonVersion,
pypyVersionSpec.pypyVersion,
architecture,
false
);
if (releaseData) {
core.info(
`Resolved as PyPy ${releaseData.resolvedPyPyVersion} with Python (${releaseData.resolvedPythonVersion})`
);
pypyVersionSpec.pythonVersion = releaseData.resolvedPythonVersion;
pypyVersionSpec.pypyVersion = releaseData.resolvedPyPyVersion;
} else {
core.info(
`Failed to resolve PyPy ${pypyVersionSpec.pypyVersion} with Python (${pypyVersionSpec.pythonVersion}) from manifest`
);
}
}
}
({installDir, resolvedPythonVersion, resolvedPyPyVersion} = findPyPyToolCache(
pypyVersionSpec.pythonVersion,
pypyVersionSpec.pypyVersion,
architecture
));
if (!installDir) {
({
installDir,
resolvedPythonVersion,
resolvedPyPyVersion
} = await pypyInstall.installPyPy(
pypyVersionSpec.pypyVersion,
pypyVersionSpec.pythonVersion,
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 = pypyInstall.getPyPyBinaryPath(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', 'pypy' + resolvedPyPyVersion);
core.setOutput('python-path', pythonPath);
return {resolvedPyPyVersion, resolvedPythonVersion};
}
export function findPyPyToolCache(
pythonVersion: string,
pypyVersion: string,
architecture: string
) {
let resolvedPyPyVersion = '';
let resolvedPythonVersion = '';
let installDir: string | null = IS_WINDOWS
? findPyPyInstallDirForWindows(pythonVersion)
: tc.find('PyPy', pythonVersion, architecture);
if (installDir) {
// 'tc.find' finds tool based on Python version but we also need to check
// whether PyPy version satisfies requested version.
resolvedPythonVersion = getPyPyVersionFromPath(installDir);
resolvedPyPyVersion = readExactPyPyVersionFile(installDir);
const isPyPyVersionSatisfies = semver.satisfies(
resolvedPyPyVersion,
pypyVersion
);
if (!isPyPyVersionSatisfies) {
installDir = null;
resolvedPyPyVersion = '';
resolvedPythonVersion = '';
}
}
if (!installDir) {
core.info(
`PyPy version ${pythonVersion} (${pypyVersion}) was not found in the local cache`
);
}
return {installDir, resolvedPythonVersion, resolvedPyPyVersion};
}
export function parsePyPyVersion(versionSpec: string): IPyPyVersionSpec {
const versions = versionSpec.split('-').filter(item => !!item);
if (/^(pypy)(.+)/.test(versions[0])) {
let pythonVersion = versions[0].replace('pypy', '');
versions.splice(0, 1, 'pypy', pythonVersion);
}
if (versions.length < 2 || versions[0] != 'pypy') {
throw new Error(
"Invalid 'version' property for PyPy. PyPy version should be specified as 'pypy<python-version>' or 'pypy-<python-version>'. See README for examples and documentation."
);
}
const pythonVersion = versions[1];
let pypyVersion: string;
if (versions.length > 2) {
pypyVersion = pypyInstall.pypyVersionToSemantic(versions[2]);
} else {
pypyVersion = 'x';
}
if (!validateVersion(pythonVersion) || !validateVersion(pypyVersion)) {
throw new Error(
"Invalid 'version' property for PyPy. Both Python version and PyPy versions should satisfy SemVer notation. See README for examples and documentation."
);
}
if (!validatePythonVersionFormatForPyPy(pythonVersion)) {
throw new Error(
"Invalid format of Python version for PyPy. Python version should be specified in format 'x.y'. See README for examples and documentation."
);
}
return {
pypyVersion: pypyVersion,
pythonVersion: pythonVersion
};
}
export function findPyPyInstallDirForWindows(pythonVersion: string): string {
let installDir = '';
WINDOWS_ARCHS.forEach(
architecture =>
(installDir = installDir || tc.find('PyPy', pythonVersion, architecture))
);
return installDir;
}