forked from DonJayamanne/pythonVSCode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpythonVersion.ts
More file actions
100 lines (94 loc) · 3.75 KB
/
Copy pathpythonVersion.ts
File metadata and controls
100 lines (94 loc) · 3.75 KB
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
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { SemVer } from 'semver';
import '../../common/extensions'; // For string.splitLines()
import { getVersion as getPythonVersionCommand } from '../../common/process/internal/python';
/**
* A representation of a Python runtime's version.
*
* @prop raw - the original version string
* @prop major - the "major" version
* @prop minor - the "minor" version
* @prop patch - the "patch" (or "micro") version
* @prop build - the build ID of the executable
* @prop prerelease - identifies a tag in the release process (e.g. beta 1)
*/
// Note that this is currently compatible with SemVer objects,
// but we may change it to match the format of sys.version_info.
export type PythonVersion = {
raw: string;
major: number;
minor: number;
patch: number;
// Eventually it may be useful to match what sys.version_info
// provides for the remainder here:
// * releaseLevel: 'alpha' | 'beta' | 'candidate' | 'final';
// * serial: number;
build: string[];
prerelease: string[];
};
/**
* Convert a Python version string.
*
* The supported formats are:
*
* * MAJOR.MINOR.MICRO-RELEASE_LEVEL
*
* (where RELEASE_LEVEL is one of {alpha,beta,candidate,final})
*
* Everything else, including an empty string, results in `undefined`.
*/
// Eventually we will want to also support the release serial
// (e.g. beta1, candidate3) and maybe even release abbreviations
// (e.g. 3.9.2b1, 3.8.10rc3).
export function parsePythonVersion(raw: string): PythonVersion | undefined {
if (!raw || raw.trim().length === 0) {
return undefined;
}
const versionParts = (raw || '')
.split('.')
.map((item) => item.trim())
.filter((item) => item.length > 0)
.filter((_, index) => index < 4);
if (versionParts.length > 0 && versionParts[versionParts.length - 1].indexOf('-') > 0) {
const lastPart = versionParts[versionParts.length - 1];
versionParts[versionParts.length - 1] = lastPart.split('-')[0].trim();
versionParts.push(lastPart.split('-')[1].trim());
}
while (versionParts.length < 4) {
versionParts.push('');
}
// Exclude PII from `version_info` to ensure we don't send this up via telemetry.
for (let index = 0; index < 3; index += 1) {
versionParts[index] = /^\d+$/.test(versionParts[index]) ? versionParts[index] : '0';
}
if (['alpha', 'beta', 'candidate', 'final'].indexOf(versionParts[3]) === -1) {
versionParts.pop();
}
const numberParts = `${versionParts[0]}.${versionParts[1]}.${versionParts[2]}`;
const rawVersion = versionParts.length === 4 ? `${numberParts}-${versionParts[3]}` : numberParts;
return new SemVer(rawVersion);
}
type ExecResult = {
stdout: string;
};
type ExecFunc = (command: string, args: string[]) => Promise<ExecResult>;
/**
* Get the version string of the given Python executable by running it.
*
* Effectively, we look up `sys.version`.
*
* @param pythonPath - the Python executable to exec
* @param defaultValue - the value to return if anything goes wrong
* @param exec - the function to call to run the Python executable
*/
export async function getPythonVersion(pythonPath: string, defaultValue: string, exec: ExecFunc): Promise<string> {
const [args, parse] = getPythonVersionCommand();
// It may make sense eventually to use buildPythonExecInfo() here
// instead of using pythonPath and args directly. That would allow
// buildPythonExecInfo() to assume any burden of flexibility.
return exec(pythonPath, args)
.then((result) => parse(result.stdout).splitLines()[0])
.then((version) => (version.length === 0 ? defaultValue : version))
.catch(() => defaultValue);
}