Skip to content

Commit 9800de6

Browse files
karthiknadiganthonykim1
authored andcommitted
Tweaks to how native finder is spawned and logging (microsoft#23387)
1 parent a7d44e9 commit 9800de6

File tree

5 files changed

+184
-71
lines changed

5 files changed

+184
-71
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -550,9 +550,9 @@
550550
"experimental"
551551
]
552552
},
553-
"python.nativeLocator": {
553+
"python.locator": {
554554
"default": "js",
555-
"description": "%python.nativeLocator.description%",
555+
"description": "%python.locator.description%",
556556
"enum": [
557557
"js",
558558
"native"

package.nls.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
"python.logging.level.description": "The logging level the extension logs at, defaults to 'error'",
5858
"python.logging.level.deprecation": "This setting is deprecated. Please use command `Developer: Set Log Level...` to set logging level.",
5959
"python.missingPackage.severity.description": "Set severity of missing packages in requirements.txt or pyproject.toml",
60-
"python.nativeLocator.description": "[Experimental] Select implementation of environment locators. This is an experimental setting while we test native environment location.",
60+
"python.locator.description": "[Experimental] Select implementation of environment locators. This is an experimental setting while we test native environment location.",
6161
"python.pipenvPath.description": "Path to the pipenv executable to use for activation.",
6262
"python.poetryPath.description": "Path to the poetry executable.",
6363
"python.EnableREPLSmartSend.description": "Toggle Smart Send for the Python REPL. Smart Send enables sending the smallest runnable block of code to the REPL on Shift+Enter and moves the cursor accordingly.",
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { CancellationToken, Disposable, Event, EventEmitter } from 'vscode';
5+
import * as ch from 'child_process';
6+
import * as path from 'path';
7+
import * as rpc from 'vscode-jsonrpc/node';
8+
import { isWindows } from '../../../../common/platform/platformService';
9+
import { EXTENSION_ROOT_DIR } from '../../../../constants';
10+
import { traceError, traceInfo, traceLog, traceVerbose, traceWarn } from '../../../../logging';
11+
import { createDeferred } from '../../../../common/utils/async';
12+
13+
const NATIVE_LOCATOR = isWindows()
14+
? path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'python-finder.exe')
15+
: path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'python-finder');
16+
17+
export interface NativeEnvInfo {
18+
name: string;
19+
pythonExecutablePath?: string;
20+
category: string;
21+
version?: string;
22+
pythonRunCommand?: string[];
23+
envPath?: string;
24+
sysPrefixPath?: string;
25+
/**
26+
* Path to the project directory when dealing with pipenv virtual environments.
27+
*/
28+
projectPath?: string;
29+
}
30+
31+
export interface NativeEnvManagerInfo {
32+
tool: string;
33+
executablePath: string;
34+
version?: string;
35+
}
36+
37+
export interface NativeGlobalPythonFinder extends Disposable {
38+
startSearch(token?: CancellationToken): Promise<void>;
39+
onDidFindPythonEnvironment: Event<NativeEnvInfo>;
40+
onDidFindEnvironmentManager: Event<NativeEnvManagerInfo>;
41+
}
42+
43+
interface NativeLog {
44+
level: string;
45+
message: string;
46+
}
47+
48+
class NativeGlobalPythonFinderImpl implements NativeGlobalPythonFinder {
49+
private readonly _onDidFindPythonEnvironment = new EventEmitter<NativeEnvInfo>();
50+
51+
private readonly _onDidFindEnvironmentManager = new EventEmitter<NativeEnvManagerInfo>();
52+
53+
public readonly onDidFindPythonEnvironment = this._onDidFindPythonEnvironment.event;
54+
55+
public readonly onDidFindEnvironmentManager = this._onDidFindEnvironmentManager.event;
56+
57+
public startSearch(token?: CancellationToken): Promise<void> {
58+
const deferred = createDeferred<void>();
59+
const proc = ch.spawn(NATIVE_LOCATOR, [], { stdio: 'pipe' });
60+
const disposables: Disposable[] = [];
61+
62+
const connection = rpc.createMessageConnection(
63+
new rpc.StreamMessageReader(proc.stdout),
64+
new rpc.StreamMessageWriter(proc.stdin),
65+
);
66+
67+
disposables.push(
68+
connection,
69+
connection.onNotification('pythonEnvironment', (data: NativeEnvInfo) => {
70+
this._onDidFindPythonEnvironment.fire(data);
71+
}),
72+
connection.onNotification('envManager', (data: NativeEnvManagerInfo) => {
73+
this._onDidFindEnvironmentManager.fire(data);
74+
}),
75+
connection.onNotification('exit', () => {
76+
traceVerbose('Native Python Finder exited');
77+
}),
78+
connection.onNotification('log', (data: NativeLog) => {
79+
switch (data.level) {
80+
case 'info':
81+
traceInfo(`Native Python Finder: ${data.message}`);
82+
break;
83+
case 'warning':
84+
traceWarn(`Native Python Finder: ${data.message}`);
85+
break;
86+
case 'error':
87+
traceError(`Native Python Finder: ${data.message}`);
88+
break;
89+
case 'debug':
90+
traceVerbose(`Native Python Finder: ${data.message}`);
91+
break;
92+
default:
93+
traceLog(`Native Python Finder: ${data.message}`);
94+
}
95+
}),
96+
connection.onClose(() => {
97+
deferred.resolve();
98+
disposables.forEach((d) => d.dispose());
99+
}),
100+
{
101+
dispose: () => {
102+
try {
103+
if (proc.exitCode === null) {
104+
proc.kill();
105+
}
106+
} catch (err) {
107+
traceVerbose('Error while disposing Native Python Finder', err);
108+
}
109+
},
110+
},
111+
);
112+
113+
if (token) {
114+
disposables.push(
115+
token.onCancellationRequested(() => {
116+
deferred.resolve();
117+
try {
118+
proc.kill();
119+
} catch (err) {
120+
traceVerbose('Error while handling cancellation request for Native Python Finder', err);
121+
}
122+
}),
123+
);
124+
}
125+
126+
connection.listen();
127+
return deferred.promise;
128+
}
129+
130+
public dispose() {
131+
this._onDidFindPythonEnvironment.dispose();
132+
this._onDidFindEnvironmentManager.dispose();
133+
}
134+
}
135+
136+
export function createNativeGlobalPythonFinder(): NativeGlobalPythonFinder {
137+
return new NativeGlobalPythonFinderImpl();
138+
}

src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts

Lines changed: 42 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,21 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
14
import { Event, EventEmitter } from 'vscode';
2-
import * as ch from 'child_process';
3-
import * as path from 'path';
4-
import * as rpc from 'vscode-jsonrpc/node';
5-
import { EXTENSION_ROOT_DIR } from '../../../../constants';
6-
import { isWindows } from '../../../../common/platform/platformService';
75
import { IDisposable } from '../../../../common/types';
86
import { ILocator, BasicEnvInfo, IPythonEnvsIterator } from '../../locator';
97
import { PythonEnvsChangedEvent } from '../../watcher';
10-
import { createDeferred } from '../../../../common/utils/async';
118
import { PythonEnvKind, PythonVersion } from '../../info';
129
import { Conda } from '../../../common/environmentManagers/conda';
1310
import { traceError } from '../../../../logging';
1411
import type { KnownEnvironmentTools } from '../../../../api/types';
1512
import { setPyEnvBinary } from '../../../common/environmentManagers/pyenv';
16-
17-
const NATIVE_LOCATOR = isWindows()
18-
? path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'python-finder.exe')
19-
: path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'python-finder');
20-
21-
interface NativeEnvInfo {
22-
name: string;
23-
pythonExecutablePath?: string;
24-
category: string;
25-
version?: string;
26-
pythonRunCommand?: string[];
27-
envPath?: string;
28-
sysPrefixPath?: string;
29-
/**
30-
* Path to the project directory when dealing with pipenv virtual environments.
31-
*/
32-
projectPath?: string;
33-
}
34-
35-
interface EnvManager {
36-
tool: string;
37-
executablePath: string;
38-
version?: string;
39-
}
13+
import {
14+
NativeEnvInfo,
15+
NativeEnvManagerInfo,
16+
NativeGlobalPythonFinder,
17+
createNativeGlobalPythonFinder,
18+
} from '../common/nativePythonFinder';
4019

4120
function categoryToKind(category: string): PythonEnvKind {
4221
switch (category.toLowerCase()) {
@@ -61,6 +40,7 @@ function categoryToKind(category: string): PythonEnvKind {
6140
}
6241
}
6342
}
43+
6444
function toolToKnownEnvironmentTool(tool: string): KnownEnvironmentTools {
6545
switch (tool.toLowerCase()) {
6646
case 'conda':
@@ -99,9 +79,12 @@ export class NativeLocator implements ILocator<BasicEnvInfo>, IDisposable {
9979

10080
private readonly disposables: IDisposable[] = [];
10181

82+
private readonly finder: NativeGlobalPythonFinder;
83+
10284
constructor() {
10385
this.onChanged = this.onChangedEmitter.event;
104-
this.disposables.push(this.onChangedEmitter);
86+
this.finder = createNativeGlobalPythonFinder();
87+
this.disposables.push(this.onChangedEmitter, this.finder);
10588
}
10689

10790
public readonly onChanged: Event<PythonEnvsChangedEvent>;
@@ -112,46 +95,38 @@ export class NativeLocator implements ILocator<BasicEnvInfo>, IDisposable {
11295
}
11396

11497
public iterEnvs(): IPythonEnvsIterator<BasicEnvInfo> {
115-
const proc = ch.spawn(NATIVE_LOCATOR, [], { stdio: 'pipe' });
98+
const promise = this.finder.startSearch();
11699
const envs: BasicEnvInfo[] = [];
117-
const deferred = createDeferred<void>();
118-
const connection = rpc.createMessageConnection(
119-
new rpc.StreamMessageReader(proc.stdout),
120-
new rpc.StreamMessageWriter(proc.stdin),
121-
);
122-
this.disposables.push(connection);
123-
connection.onNotification('pythonEnvironment', (data: NativeEnvInfo) => {
124-
envs.push({
125-
kind: categoryToKind(data.category),
126-
// TODO: What if executable is undefined?
127-
executablePath: data.pythonExecutablePath!,
128-
envPath: data.envPath,
129-
version: parseVersion(data.version),
130-
name: data.name === '' ? undefined : data.name,
131-
});
132-
});
133-
connection.onNotification('envManager', (data: EnvManager) => {
134-
switch (toolToKnownEnvironmentTool(data.tool)) {
135-
case 'Conda': {
136-
Conda.setConda(data.executablePath);
137-
break;
100+
this.disposables.push(
101+
this.finder.onDidFindPythonEnvironment((data: NativeEnvInfo) => {
102+
envs.push({
103+
kind: categoryToKind(data.category),
104+
// TODO: What if executable is undefined?
105+
executablePath: data.pythonExecutablePath!,
106+
envPath: data.envPath,
107+
version: parseVersion(data.version),
108+
name: data.name === '' ? undefined : data.name,
109+
});
110+
}),
111+
this.finder.onDidFindEnvironmentManager((data: NativeEnvManagerInfo) => {
112+
switch (toolToKnownEnvironmentTool(data.tool)) {
113+
case 'Conda': {
114+
Conda.setConda(data.executablePath);
115+
break;
116+
}
117+
case 'Pyenv': {
118+
setPyEnvBinary(data.executablePath);
119+
break;
120+
}
121+
default: {
122+
break;
123+
}
138124
}
139-
case 'Pyenv': {
140-
setPyEnvBinary(data.executablePath);
141-
break;
142-
}
143-
default: {
144-
break;
145-
}
146-
}
147-
});
148-
connection.onNotification('exit', () => {
149-
deferred.resolve();
150-
});
151-
connection.listen();
125+
}),
126+
);
152127

153128
const iterator = async function* (): IPythonEnvsIterator<BasicEnvInfo> {
154-
await deferred.promise;
129+
await promise;
155130
yield* envs;
156131
};
157132
return iterator();

src/client/pythonEnvironments/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ async function createLocator(
140140

141141
function useNativeLocator(): boolean {
142142
const config = getConfiguration('python');
143-
return config.get<string>('nativeLocator', 'js') === 'native';
143+
return config.get<string>('locator', 'js') === 'native';
144144
}
145145

146146
function createNonWorkspaceLocators(ext: ExtensionState): ILocator<BasicEnvInfo>[] {

0 commit comments

Comments
 (0)