forked from DonJayamanne/pythonVSCode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathposixUtils.ts
More file actions
156 lines (144 loc) · 5.82 KB
/
posixUtils.ts
File metadata and controls
156 lines (144 loc) · 5.82 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
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
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as fs from 'fs';
import * as fsapi from 'fs-extra';
import * as path from 'path';
import { uniq } from 'lodash';
import { getSearchPathEntries } from '../../common/utils/exec';
import { resolveSymbolicLink } from './externalDependencies';
import { traceError, traceInfo, traceVerbose, traceWarn } from '../../logging';
/**
* Determine if the given filename looks like the simplest Python executable.
*/
export function matchBasicPythonBinFilename(filename: string): boolean {
return path.basename(filename) === 'python';
}
/**
* Checks if a given path matches pattern for standard non-windows python binary.
* @param {string} interpreterPath : Path to python interpreter.
* @returns {boolean} : Returns true if the path matches pattern for non-windows python binary.
*/
export function matchPythonBinFilename(filename: string): boolean {
/**
* This Reg-ex matches following file names:
* python
* python3
* python38
* python3.8
*/
const posixPythonBinPattern = /^python(\d+(\.\d+)?)?$/;
return posixPythonBinPattern.test(path.basename(filename));
}
export async function commonPosixBinPaths(): Promise<string[]> {
const searchPaths = getSearchPathEntries();
const paths: string[] = Array.from(
new Set(
[
'/bin',
'/etc',
'/lib',
'/lib/x86_64-linux-gnu',
'/lib64',
'/sbin',
'/snap/bin',
'/usr/bin',
'/usr/games',
'/usr/include',
'/usr/lib',
'/usr/lib/x86_64-linux-gnu',
'/usr/lib64',
'/usr/libexec',
'/usr/local',
'/usr/local/bin',
'/usr/local/etc',
'/usr/local/games',
'/usr/local/lib',
'/usr/local/sbin',
'/usr/sbin',
'/usr/share',
'~/.local/bin',
].concat(searchPaths),
),
);
const exists = await Promise.all(paths.map((p) => fsapi.pathExists(p)));
return paths.filter((_, index) => exists[index]);
}
/**
* Finds python interpreter binaries or symlinks in a given directory.
* @param searchDir : Directory to search in
* @returns : Paths to python binaries found in the search directory.
*/
async function findPythonBinariesInDir(searchDir: string) {
return (await fs.promises.readdir(searchDir, { withFileTypes: true }))
.filter((dirent: fs.Dirent) => !dirent.isDirectory())
.map((dirent: fs.Dirent) => path.join(searchDir, dirent.name))
.filter(matchPythonBinFilename);
}
/**
* Pick the shortest versions of the paths. The paths could be
* the binary itself or its symlink, whichever path is shorter.
*
* E.g:
* /usr/bin/python -> /System/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7
* /usr/bin/python3 -> /System/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7
* /usr/bin/python3.7 -> /System/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7
*
* Of the 4 possible paths to same binary (3 symlinks and 1 binary path),
* the code below will pick '/usr/bin/python'.
*/
function pickShortestPath(pythonPaths: string[]) {
let shortestLen = pythonPaths[0].length;
let shortestPath = pythonPaths[0];
for (const p of pythonPaths) {
if (p.length <= shortestLen) {
shortestLen = p.length;
shortestPath = p;
}
}
return shortestPath;
}
/**
* Finds python binaries in given directories. This function additionally reduces the
* found binaries to unique set be resolving symlinks, and returns the shortest paths
* to the said unique binaries.
* @param searchDirs : Directories to search for python binaries
* @returns : Unique paths to python interpreters found in the search dirs.
*/
export async function getPythonBinFromPosixPaths(searchDirs: string[]): Promise<string[]> {
const binToLinkMap = new Map<string, string[]>();
for (const searchDir of searchDirs) {
const paths = await findPythonBinariesInDir(searchDir).catch((ex) => {
traceWarn('Looking for python binaries within', searchDir, 'failed with', ex);
return [];
});
for (const filepath of paths) {
// Ensure that we have a collection of unique global binaries by
// resolving all symlinks to the target binaries.
try {
traceVerbose(`Attempting to resolve symbolic link: ${filepath}`);
const resolvedBin = await resolveSymbolicLink(filepath);
if (binToLinkMap.has(resolvedBin)) {
binToLinkMap.get(resolvedBin)?.push(filepath);
} else {
binToLinkMap.set(resolvedBin, [filepath]);
}
traceInfo(`Found: ${filepath} --> ${resolvedBin}`);
} catch (ex) {
traceError('Failed to resolve symbolic link: ', ex);
}
}
}
// Pick the shortest versions of the paths. The paths could be
// the binary itself or its symlink, whichever path is shorter.
//
// E.g:
// /usr/bin/python -> /System/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7
// /usr/bin/python3 -> /System/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7
// /usr/bin/python3.7 -> /System/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7
//
// Of the 4 possible paths to same binary (3 symlinks and 1 binary path),
// the code below will pick '/usr/bin/python'.
const keys = Array.from(binToLinkMap.keys());
const pythonPaths = keys.map((key) => pickShortestPath([key, ...(binToLinkMap.get(key) ?? [])]));
return uniq(pythonPaths);
}