mirror of
				https://gitea.com/actions/setup-python.git
				synced 2025-10-31 03:38:56 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			290 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			290 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 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/lib/interfaces';
 | |
| import * as exec from '@actions/exec';
 | |
| 
 | |
| import fs from 'fs';
 | |
| import * as http from 'http';
 | |
| 
 | |
| import {
 | |
|   IS_WINDOWS,
 | |
|   IGraalPyManifestRelease,
 | |
|   createSymlinkInFolder,
 | |
|   isNightlyKeyword,
 | |
|   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...');
 | |
|     if (IS_WINDOWS) {
 | |
|       downloadDir = await tc.extractZip(graalpyPath);
 | |
|     } else {
 | |
|       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 = path.join(installDir, 'bin');
 | |
|     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: http.OutgoingHttpHeaders = {};
 | |
|   if (AUTH) {
 | |
|     headers.authorization = AUTH;
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|   Get releases first.
 | |
|   */
 | |
|   let url: string | null =
 | |
|     'https://api.github.com/repos/oracle/graalpython/releases';
 | |
|   const result: IGraalPyManifestRelease[] = [];
 | |
|   do {
 | |
|     const response: ifm.TypedResponse<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);
 | |
| 
 | |
|   /*
 | |
|   Add pre-release builds.
 | |
|   */
 | |
|   url =
 | |
|     'https://api.github.com/repos/graalvm/graal-languages-ea-builds/releases';
 | |
|   do {
 | |
|     const response: ifm.TypedResponse<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+)?)(?:-((?:ea|a|b|rc))\.0*(\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 graalpyExt = platform == 'win32' ? 'zip' : 'tar.gz';
 | |
|   const found = item.assets.filter(
 | |
|     file =>
 | |
|       file.name.startsWith('graalpy') &&
 | |
|       file.name.endsWith(`-${graalpyPlatform}-${graalpyArch}.${graalpyExt}`)
 | |
|   );
 | |
|   /*
 | |
|   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];
 | |
| }
 | 
