-
Notifications
You must be signed in to change notification settings - Fork 613
/
Copy pathinstall-python.ts
169 lines (152 loc) · 5.03 KB
/
install-python.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import * as path from 'path';
import * as core from '@actions/core';
import * as tc from '@actions/tool-cache';
import * as exec from '@actions/exec';
import * as httpm from '@actions/http-client';
import {ExecOptions} from '@actions/exec/lib/interfaces';
import {IS_WINDOWS, IS_LINUX, getDownloadFileName} from './utils';
const TOKEN = core.getInput('token');
const AUTH = !TOKEN ? undefined : `token ${TOKEN}`;
const MANIFEST_REPO_OWNER = 'actions';
const MANIFEST_REPO_NAME = 'python-versions';
const MANIFEST_REPO_BRANCH = 'main';
export const MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`;
export async function findReleaseFromManifest(
semanticVersionSpec: string,
architecture: string,
manifest: tc.IToolRelease[] | null
): Promise<tc.IToolRelease | undefined> {
if (!manifest) {
manifest = await getManifest();
}
const foundRelease = await tc.findFromManifest(
semanticVersionSpec,
false,
manifest,
architecture
);
return foundRelease;
}
export async function getManifest(): Promise<tc.IToolRelease[]> {
try {
const manifestFromRepo = await getManifestFromRepo();
core.info('Successfully fetched the manifest from the repo.');
validateManifest(manifestFromRepo);
return manifestFromRepo;
} catch (err) {
logError('Fetching the manifest via the API failed.', err);
}
try {
const manifestFromURL = await getManifestFromURL();
core.info('Successfully fetched the manifest from the URL.');
return manifestFromURL;
} catch (err) {
logError('Fetching the manifest via the URL failed.', err);
// Rethrow the error or return a default value
throw new Error(
'Failed to fetch the manifest from both the repo and the URL.'
);
}
}
function validateManifest(manifest: any): void {
if (!Array.isArray(manifest) || !manifest.every(isValidManifestEntry)) {
throw new Error('Invalid manifest response');
}
}
function isValidManifestEntry(entry: any): boolean {
return (
typeof entry.version === 'string' &&
typeof entry.stable === 'boolean' &&
typeof entry.release_url === 'string' &&
Array.isArray(entry.files) &&
entry.files.every(isValidFileEntry)
);
}
function isValidFileEntry(file: any): boolean {
return (
typeof file.filename === 'string' &&
typeof file.arch === 'string' &&
typeof file.platform === 'string' &&
(typeof file.platform_version === 'string' ||
file.platform_version === undefined) &&
typeof file.download_url === 'string'
);
}
export function getManifestFromRepo(): Promise<tc.IToolRelease[]> {
core.info(
`Getting manifest from ${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}@${MANIFEST_REPO_BRANCH}`
);
return tc.getManifestFromRepo(
MANIFEST_REPO_OWNER,
MANIFEST_REPO_NAME,
AUTH,
MANIFEST_REPO_BRANCH
);
}
export async function getManifestFromURL(): Promise<tc.IToolRelease[]> {
core.debug('Falling back to fetching the manifest using raw URL.');
const http: httpm.HttpClient = new httpm.HttpClient('tool-cache');
const response = await http.getJson<tc.IToolRelease[]>(MANIFEST_URL);
if (!response.result) {
throw new Error(`Unable to get manifest from ${MANIFEST_URL}`);
}
return response.result;
}
async function installPython(workingDirectory: string) {
const options: ExecOptions = {
cwd: workingDirectory,
env: {
...process.env,
...(IS_LINUX && {LD_LIBRARY_PATH: path.join(workingDirectory, 'lib')})
},
silent: true,
listeners: {
stdout: (data: Buffer) => {
core.info(data.toString().trim());
},
stderr: (data: Buffer) => {
core.error(data.toString().trim());
}
}
};
const script = IS_WINDOWS ? 'powershell ./setup.ps1' : 'bash ./setup.sh';
await exec.exec(script, [], options);
}
export async function installCpythonFromRelease(release: tc.IToolRelease) {
const downloadUrl = release.files[0].download_url;
core.info(`Download from "${downloadUrl}"`);
try {
const fileName = getDownloadFileName(downloadUrl);
const pythonPath = await tc.downloadTool(downloadUrl, fileName, AUTH);
core.info('Extract downloaded archive');
const pythonExtractedFolder = IS_WINDOWS
? await tc.extractZip(pythonPath)
: await tc.extractTar(pythonPath);
core.info('Execute installation script');
await installPython(pythonExtractedFolder);
} catch (err) {
handleDownloadError(err);
throw err;
}
function handleDownloadError(err: any): void {
if (err instanceof tc.HTTPError) {
// Rate limit?
if (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) {
core.debug(err.stack);
}
}
}
}
function logError(message: string, err: any): void {
core.info(message);
if (err instanceof Error) {
core.info(err.message);
}
}