Skip to content

Commit 2070c68

Browse files
authored
Activate the conda environment before running jupyter (microsoft#3506)
Run the conda activate script and scrape all of the environment from it prior to launching jupyter (or any other command with the same interpreter) I'm hoping this will finally fix #3341.
1 parent 27ae0be commit 2070c68

File tree

9 files changed

+381
-143
lines changed

9 files changed

+381
-143
lines changed

news/2 Fixes/3341.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Activate conda prior to running jupyter for the python interactive window.

src/client/common/process/proc.ts

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
3-
import { spawn } from 'child_process';
3+
import { exec, spawn } from 'child_process';
44
import { Observable } from 'rxjs/Observable';
55
import * as tk from 'tree-kill';
66
import { Disposable } from 'vscode';
@@ -14,6 +14,7 @@ import {
1414
IProcessService,
1515
ObservableExecutionResult,
1616
Output,
17+
ShellOptions,
1718
SpawnOptions,
1819
StdErrError
1920
} from './types';
@@ -37,23 +38,11 @@ export class ProcessService implements IProcessService {
3738
} catch {
3839
// Ignore.
3940
}
40-
4141
}
42-
public execObservable(file: string, args: string[], options: SpawnOptions = {}): ObservableExecutionResult<string> {
43-
const encoding = options.encoding = typeof options.encoding === 'string' && options.encoding.length > 0 ? options.encoding : DEFAULT_ENCODING;
44-
delete options.encoding;
45-
const spawnOptions = { ...options };
46-
if (!spawnOptions.env || Object.keys(spawnOptions).length === 0) {
47-
const env = this.env ? this.env : process.env;
48-
spawnOptions.env = { ...env };
49-
}
50-
51-
// Always ensure we have unbuffered output.
52-
spawnOptions.env.PYTHONUNBUFFERED = '1';
53-
if (!spawnOptions.env.PYTHONIOENCODING) {
54-
spawnOptions.env.PYTHONIOENCODING = 'utf-8';
55-
}
5642

43+
public execObservable(file: string, args: string[], options: SpawnOptions = {}): ObservableExecutionResult<string> {
44+
const spawnOptions = this.getDefaultOptions(options);
45+
const encoding = spawnOptions.encoding ? spawnOptions.encoding : 'utf8';
5746
const proc = spawn(file, args, spawnOptions);
5847
let procExited = false;
5948

@@ -109,19 +98,8 @@ export class ProcessService implements IProcessService {
10998
};
11099
}
111100
public exec(file: string, args: string[], options: SpawnOptions = {}): Promise<ExecutionResult<string>> {
112-
const encoding = options.encoding = typeof options.encoding === 'string' && options.encoding.length > 0 ? options.encoding : DEFAULT_ENCODING;
113-
delete options.encoding;
114-
const spawnOptions = { ...options };
115-
if (!spawnOptions.env || Object.keys(spawnOptions).length === 0) {
116-
const env = this.env ? this.env : process.env;
117-
spawnOptions.env = { ...env };
118-
}
119-
120-
// Always ensure we have unbuffered output.
121-
spawnOptions.env.PYTHONUNBUFFERED = '1';
122-
if (!spawnOptions.env.PYTHONIOENCODING) {
123-
spawnOptions.env.PYTHONIOENCODING = 'utf-8';
124-
}
101+
const spawnOptions = this.getDefaultOptions(options);
102+
const encoding = spawnOptions.encoding ? spawnOptions.encoding : 'utf8';
125103
const proc = spawn(file, args, spawnOptions);
126104
const deferred = createDeferred<ExecutionResult<string>>();
127105
const disposables: Disposable[] = [];
@@ -171,4 +149,45 @@ export class ProcessService implements IProcessService {
171149

172150
return deferred.promise;
173151
}
152+
153+
public shellExec(command: string, options: ShellOptions = {}): Promise<ExecutionResult<string>> {
154+
const shellOptions = this.getDefaultOptions(options);
155+
return new Promise((resolve, reject) => {
156+
exec(command, shellOptions, (e, stdout, stderr) => {
157+
if (e && e !== null) {
158+
reject(e);
159+
} else if (shellOptions.throwOnStdErr && stderr && stderr.length) {
160+
reject(new Error(stderr));
161+
} else {
162+
// Make sure stderr is undefined if we actually had none. This is checked
163+
// elsewhere because that's how exec behaves.
164+
resolve({ stderr: stderr && stderr.length > 0 ? stderr : undefined, stdout: stdout });
165+
}
166+
});
167+
});
168+
}
169+
170+
private getDefaultOptions<T extends (ShellOptions | SpawnOptions)>(options: T) : T {
171+
const execOptions = options as SpawnOptions;
172+
const defaultOptions = JSON.parse(JSON.stringify(options));
173+
if (execOptions)
174+
{
175+
const encoding = execOptions.encoding = typeof execOptions.encoding === 'string' && execOptions.encoding.length > 0 ? execOptions.encoding : DEFAULT_ENCODING;
176+
delete execOptions.encoding;
177+
defaultOptions.encoding = encoding;
178+
}
179+
if (!defaultOptions.env || Object.keys(defaultOptions).length === 0) {
180+
const env = this.env ? this.env : process.env;
181+
defaultOptions.env = { ...env };
182+
}
183+
184+
// Always ensure we have unbuffered output.
185+
defaultOptions.env.PYTHONUNBUFFERED = '1';
186+
if (!defaultOptions.env.PYTHONIOENCODING) {
187+
defaultOptions.env.PYTHONIOENCODING = 'utf-8';
188+
}
189+
190+
return defaultOptions;
191+
}
192+
174193
}

src/client/common/process/types.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
3-
4-
import { ChildProcess, SpawnOptions as ChildProcessSpawnOptions } from 'child_process';
3+
import { ChildProcess, ExecOptions, SpawnOptions as ChildProcessSpawnOptions } from 'child_process';
54
import { Observable } from 'rxjs/Observable';
65
import { CancellationToken, Uri } from 'vscode';
6+
77
import { ExecutionInfo } from '../types';
88
import { Architecture } from '../utils/platform';
99
import { EnvironmentVariables } from '../variables/types';
@@ -31,6 +31,9 @@ export type SpawnOptions = ChildProcessSpawnOptions & {
3131
throwOnStdErr?: boolean;
3232
};
3333

34+
// tslint:disable-next-line:interface-name
35+
export type ShellOptions = ExecOptions & { throwOnStdErr? : boolean };
36+
3437
export type ExecutionResult<T extends string | Buffer> = {
3538
stdout: T;
3639
stderr?: T;
@@ -39,6 +42,7 @@ export type ExecutionResult<T extends string | Buffer> = {
3942
export interface IProcessService {
4043
execObservable(file: string, args: string[], options?: SpawnOptions): ObservableExecutionResult<string>;
4144
exec(file: string, args: string[], options?: SpawnOptions): Promise<ExecutionResult<string>>;
45+
shellExec(command: string, options?: ShellOptions): Promise<ExecutionResult<string>>;
4246
}
4347

4448
export const IProcessServiceFactory = Symbol('IProcessServiceFactory');

src/client/datascience/jupyterExecution.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ class JupyterCommand {
106106
*/
107107
// Base Node.js SpawnOptions uses any for environment, so use that here as well
108108
// tslint:disable-next-line:no-any
109-
private fixupCondaEnv = async (inputEnv: any | undefined): Promise<any> => {
109+
private fixupCondaEnv = async (inputEnv?: NodeJS.ProcessEnv): Promise<any> => {
110110
if (!inputEnv) {
111111
inputEnv = process.env;
112112
}
@@ -324,6 +324,7 @@ export class JupyterExecution implements IJupyterExecution, Disposable {
324324

325325
return undefined;
326326
}
327+
327328
private getJupyterServerInfo = async () : Promise<JupyterServerInfo[] | undefined> => {
328329
// We have a small python file here that we will execute to get the server info from all running Jupyter instances
329330
const bestInterpreter = await this.getUsableJupyterPython();
@@ -469,7 +470,7 @@ export class JupyterExecution implements IJupyterExecution, Disposable {
469470
*/
470471
// Base Node.js SpawnOptions uses any for environment, so use that here as well
471472
// tslint:disable-next-line:no-any
472-
private fixupCondaEnv = async (inputEnv: any | undefined, interpreter: PythonInterpreter): Promise<any> => {
473+
private fixupCondaEnv = async (inputEnv: NodeJS.ProcessEnv, interpreter: PythonInterpreter): Promise<any> => {
473474
if (!inputEnv) {
474475
inputEnv = process.env;
475476
}

src/client/interpreter/contracts.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,7 @@ export interface ICondaService {
5353
getInterpreterPath(condaEnvironmentPath: string): string;
5454
isCondaEnvironment(interpreterPath: string): Promise<boolean>;
5555
getCondaEnvironment(interpreterPath: string): Promise<{ name: string; path: string } | undefined>;
56-
// Base Node.js SpawnOptions uses any for environment, so use that here as well
57-
// tslint:disable-next-line:no-any
58-
getActivatedCondaEnvironment(interpreter: PythonInterpreter, inputEnvironment: any): Promise<any>;
56+
getActivatedCondaEnvironment(interpreter: PythonInterpreter, inputEnvironment?: NodeJS.ProcessEnv): Promise<NodeJS.ProcessEnv>;
5957
}
6058

6159
export enum InterpreterType {

0 commit comments

Comments
 (0)