2020-12-17 18:03:54 +03:00
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 exec from '@actions/exec' ;
import fs from 'fs' ;
import {
IS_WINDOWS ,
2021-04-12 20:59:38 +03:00
WINDOWS_ARCHS ,
WINDOWS_PLATFORMS ,
2020-12-17 18:03:54 +03:00
IPyPyManifestRelease ,
createSymlinkInFolder ,
isNightlyKeyword ,
writeExactPyPyVersionFile
} from './utils' ;
export async function installPyPy (
pypyVersion : string ,
pythonVersion : string ,
2022-07-25 16:54:04 +02:00
architecture : string ,
releases : IPyPyManifestRelease [ ] | undefined
2020-12-17 18:03:54 +03:00
) {
let downloadDir ;
2022-07-25 16:54:04 +02:00
releases = releases ? ? ( await getAvailablePyPyVersions ( ) ) ;
2020-12-17 18:03:54 +03:00
if ( ! releases || releases . length === 0 ) {
throw new Error ( 'No release was found in PyPy version.json' ) ;
}
const releaseData = findRelease (
releases ,
pythonVersion ,
pypyVersion ,
architecture
) ;
if ( ! releaseData || ! releaseData . foundAsset ) {
throw new Error (
` PyPy version ${ pythonVersion } ( ${ pypyVersion } ) with arch ${ architecture } not found `
) ;
}
const { foundAsset , resolvedPythonVersion , resolvedPyPyVersion } = releaseData ;
let downloadUrl = ` ${ foundAsset . download_url } ` ;
core . info ( ` Downloading PyPy from " ${ downloadUrl } " ... ` ) ;
const pypyPath = await tc . downloadTool ( downloadUrl ) ;
core . info ( 'Extracting downloaded archive...' ) ;
if ( IS_WINDOWS ) {
downloadDir = await tc . extractZip ( pypyPath ) ;
} else {
downloadDir = await tc . extractTar ( pypyPath , undefined , 'x' ) ;
}
// 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 ( resolvedPyPyVersion ) ) {
installDir = await tc . cacheDir (
toolDir ,
'PyPy' ,
resolvedPythonVersion ,
architecture
) ;
}
writeExactPyPyVersionFile ( installDir , resolvedPyPyVersion ) ;
const binaryPath = getPyPyBinaryPath ( installDir ) ;
await createPyPySymlink ( binaryPath , resolvedPythonVersion ) ;
await installPip ( binaryPath ) ;
return { installDir , resolvedPythonVersion , resolvedPyPyVersion } ;
}
2022-07-25 16:54:04 +02:00
export async function getAvailablePyPyVersions() {
2020-12-17 18:03:54 +03:00
const url = 'https://downloads.python.org/pypy/versions.json' ;
const http : httpm.HttpClient = new httpm . HttpClient ( 'tool-cache' ) ;
const response = await http . getJson < IPyPyManifestRelease [ ] > ( url ) ;
if ( ! response . result ) {
throw new Error (
` Unable to retrieve the list of available PyPy versions from ' ${ url } ' `
) ;
}
return response . result ;
}
async function createPyPySymlink (
pypyBinaryPath : string ,
pythonVersion : string
) {
const version = semver . coerce ( pythonVersion ) ! ;
const pythonBinaryPostfix = semver . major ( version ) ;
2022-04-28 15:26:17 +02:00
const pythonMinor = semver . minor ( version ) ;
2020-12-17 18:03:54 +03:00
const pypyBinaryPostfix = pythonBinaryPostfix === 2 ? '' : '3' ;
2022-04-28 15:26:17 +02:00
const pypyMajorMinorBinaryPostfix = ` ${ pythonBinaryPostfix } . ${ pythonMinor } ` ;
2020-12-17 18:03:54 +03:00
let binaryExtension = IS_WINDOWS ? '.exe' : '' ;
core . info ( 'Creating symlinks...' ) ;
createSymlinkInFolder (
pypyBinaryPath ,
` pypy ${ pypyBinaryPostfix } ${ binaryExtension } ` ,
` python ${ pythonBinaryPostfix } ${ binaryExtension } ` ,
true
) ;
createSymlinkInFolder (
pypyBinaryPath ,
` pypy ${ pypyBinaryPostfix } ${ binaryExtension } ` ,
` python ${ binaryExtension } ` ,
true
) ;
2022-04-28 15:26:17 +02:00
createSymlinkInFolder (
pypyBinaryPath ,
` pypy ${ pypyBinaryPostfix } ${ binaryExtension } ` ,
` pypy ${ pypyMajorMinorBinaryPostfix } ${ binaryExtension } ` ,
true
) ;
2020-12-17 18:03:54 +03:00
}
async function installPip ( pythonLocation : string ) {
core . info ( 'Installing and updating pip' ) ;
const pythonBinary = path . join ( pythonLocation , 'python' ) ;
await exec . exec ( ` ${ pythonBinary } -m ensurepip ` ) ;
await exec . exec (
` ${ pythonLocation } /python -m pip install --ignore-installed pip `
) ;
}
export function findRelease (
releases : IPyPyManifestRelease [ ] ,
pythonVersion : string ,
pypyVersion : string ,
architecture : string
) {
const filterReleases = releases . filter ( item = > {
const isPythonVersionSatisfied = semver . satisfies (
semver . coerce ( item . python_version ) ! ,
pythonVersion
) ;
const isPyPyNightly =
isNightlyKeyword ( pypyVersion ) && isNightlyKeyword ( item . pypy_version ) ;
const isPyPyVersionSatisfied =
isPyPyNightly ||
semver . satisfies ( pypyVersionToSemantic ( item . pypy_version ) , pypyVersion ) ;
const isArchPresent =
item . files &&
2021-04-12 20:59:38 +03:00
( IS_WINDOWS
2022-10-12 16:18:54 +02:00
? isArchPresentForWindows ( item , architecture )
2021-04-12 20:59:38 +03:00
: isArchPresentForMacOrLinux ( item , architecture , process . platform ) ) ;
2020-12-17 18:03:54 +03:00
return isPythonVersionSatisfied && isPyPyVersionSatisfied && isArchPresent ;
} ) ;
if ( filterReleases . length === 0 ) {
return null ;
}
const sortedReleases = filterReleases . sort ( ( previous , current ) = > {
return (
semver . compare (
semver . coerce ( pypyVersionToSemantic ( current . pypy_version ) ) ! ,
semver . coerce ( pypyVersionToSemantic ( previous . pypy_version ) ) !
) ||
semver . compare (
semver . coerce ( current . python_version ) ! ,
semver . coerce ( previous . python_version ) !
)
) ;
} ) ;
const foundRelease = sortedReleases [ 0 ] ;
2021-04-12 20:59:38 +03:00
const foundAsset = IS_WINDOWS
? findAssetForWindows ( foundRelease )
: findAssetForMacOrLinux ( foundRelease , architecture , process . platform ) ;
2020-12-17 18:03:54 +03:00
return {
foundAsset ,
resolvedPythonVersion : foundRelease.python_version ,
resolvedPyPyVersion : foundRelease.pypy_version
} ;
}
/ * * G e t P y P y b i n a r y l o c a t i o n f r o m t h e t o o l o f i n s t a l l a t i o n d i r e c t o r y
* - 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' ) ;
}
2021-04-12 20:59:38 +03:00
2022-10-12 16:18:54 +02:00
export function isArchPresentForWindows ( item : any , architecture : string ) {
2021-04-12 20:59:38 +03:00
return item . files . some (
( file : any ) = >
2022-10-12 16:18:54 +02:00
file . arch === ( architecture === 'x32' ? 'x86' : architecture ) && // convert x32 to x86 cause os.arch() return x32 for 32-bit system but PyPy releases json has x86 arch value.
2021-04-12 20:59:38 +03:00
WINDOWS_PLATFORMS . includes ( file . platform )
) ;
}
export function isArchPresentForMacOrLinux (
item : any ,
architecture : string ,
platform : string
) {
return item . files . some (
( file : any ) = > file . arch === architecture && file . platform === platform
) ;
}
export function findAssetForWindows ( releases : any ) {
return releases . files . find (
( item : any ) = >
WINDOWS_ARCHS . includes ( item . arch ) &&
WINDOWS_PLATFORMS . includes ( item . platform )
) ;
}
export function findAssetForMacOrLinux (
releases : any ,
architecture : string ,
platform : string
) {
return releases . files . find (
( item : any ) = > item . arch === architecture && item . platform === platform
) ;
}