Skip to content

Commit cd7f593

Browse files
committed
changes to how python utilities are located (setting path envionment variable, giving preference to python executable path) #178
1 parent 14ba1d8 commit cd7f593

File tree

1 file changed

+27
-42
lines changed

1 file changed

+27
-42
lines changed

src/client/common/utils.ts

Lines changed: 27 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ import * as child_process from "child_process";
66
import * as settings from "./configSettings";
77

88
let pythonInterpretterDirectory: string = null;
9+
let previouslyIdentifiedPythonPath: string = null;
10+
911
export function getPythonInterpreterDirectory(): Promise<string> {
10-
if (pythonInterpretterDirectory) {
12+
// If we already have it and the python path hasn't changed, yay
13+
if (pythonInterpretterDirectory && previouslyIdentifiedPythonPath === settings.PythonSettings.getInstance().pythonPath) {
1114
return Promise.resolve(pythonInterpretterDirectory);
1215
}
1316

@@ -22,74 +25,46 @@ export function getPythonInterpreterDirectory(): Promise<string> {
2225

2326
// If we can execute the python, then get the path from the fullyqualitified name
2427
child_process.execFile(pythonFileName, ["-c", "print(1234)"], (error, stdout, stderr) => {
28+
// Yes this is a valid python path
2529
if (stdout.startsWith("1234")) {
2630
return resolve(path.dirname(pythonFileName));
2731
}
32+
// No idea, didn't work, hence don't reject, but return empty path
2833
resolve("");
2934
});
3035
}).then(value => {
36+
// Cache and return
37+
previouslyIdentifiedPythonPath = settings.PythonSettings.getInstance().pythonPath;
3138
return pythonInterpretterDirectory = value;
3239
}).catch(() => {
3340
// Don't care what the error is, all we know is that this doesn't work
3441
return pythonInterpretterDirectory = "";
3542
});
3643
}
3744

38-
const IN_VALID_FILE_PATHS: Map<string, boolean> = new Map<string, boolean>();
39-
4045
export function execPythonFile(file: string, args: string[], cwd: string, includeErrorAsResponse: boolean = false): Promise<string> {
41-
// Whether to try executing the command without prefixing it with the python path
42-
let tryUsingCommandArg = false;
43-
let fullyQualifiedFilePromise = getPythonInterpreterDirectory().then(pyPath => {
44-
let pythonIntepreterPath = pyPath;
45-
let fullyQualifiedFile = file;
46+
return getPythonInterpreterDirectory().then(pyPath => {
47+
let options: any = { cwd: cwd };
4648

47-
// Qualify the command with the python path
48-
if (pythonIntepreterPath.length > 0 && !file.startsWith(pyPath)) {
49-
fullyQualifiedFile = pythonIntepreterPath + (pythonIntepreterPath.endsWith(path.sep) ? "" : path.sep) + file;
50-
51-
// Check if we know whether this trow ENONE errors
52-
if (IN_VALID_FILE_PATHS.has(fullyQualifiedFile)) {
53-
fullyQualifiedFile = file;
54-
}
55-
else {
56-
tryUsingCommandArg = true;
57-
}
49+
// If we have a fully qualitified path to the python executable directory, then we can use that
50+
// as the base path for all other executables
51+
if (pyPath.length > 0) {
52+
options.env = mergeEnvVariables({ PATH: pyPath + path.delimiter + process.env.PATH });
5853
}
5954

60-
return execFileInternal(fullyQualifiedFile, args, cwd, includeErrorAsResponse, true);
55+
return execFileInternal(file, args, options, includeErrorAsResponse);
6156
});
62-
63-
if (tryUsingCommandArg) {
64-
return fullyQualifiedFilePromise.catch(error => {
65-
if (error && (<any>error).code === "ENOENT") {
66-
// Re-execute the file, without the python path prefix
67-
// Only if we know that the previous one failed with ENOENT
68-
return execFileInternal(file, args, cwd, includeErrorAsResponse, true);
69-
}
70-
Promise.reject(error);
71-
});
72-
}
73-
else {
74-
return fullyQualifiedFilePromise;
75-
}
7657
}
7758

78-
function execFileInternal(file: string, args: string[], cwd: string, includeErrorAsResponse: boolean, rejectIfENOENTErrors: boolean = false): Promise<string> {
59+
function execFileInternal(file: string, args: string[], options: child_process.ExecFileOptions, includeErrorAsResponse: boolean): Promise<string> {
7960
return new Promise<string>((resolve, reject) => {
80-
child_process.execFile(file, args, { cwd: cwd }, (error, stdout, stderr) => {
61+
child_process.execFile(file, args, options, (error, stdout, stderr) => {
8162
// pylint:
8263
// In the case of pylint we have some messages (such as config file not found and using default etc...) being returned in stderr
8364
// These error messages are useless when using pylint
8465
if (includeErrorAsResponse && (stdout.length > 0 || stderr.length > 0)) {
8566
return resolve(stdout + "\n" + stderr);
8667
}
87-
if (error && (<any>error).code === "ENOENT" && rejectIfENOENTErrors) {
88-
if (!IN_VALID_FILE_PATHS.has(file)) {
89-
IN_VALID_FILE_PATHS.set(file, true);
90-
}
91-
return reject(error);
92-
}
9368

9469
let hasErrors = (error && error.message.length > 0) || (stderr && stderr.length > 0);
9570
if (hasErrors && (typeof stdout !== "string" || stdout.length === 0)) {
@@ -101,3 +76,13 @@ function execFileInternal(file: string, args: string[], cwd: string, includeErro
10176
});
10277
});
10378
}
79+
80+
export function mergeEnvVariables(newVariables: { [key: string]: string }): any {
81+
for (let setting in process.env) {
82+
if (!newVariables[setting]) {
83+
newVariables[setting] = process.env[setting];
84+
}
85+
}
86+
87+
return newVariables;
88+
}

0 commit comments

Comments
 (0)