Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -550,9 +550,9 @@
"experimental"
]
},
"python.nativeLocator": {
"python.locator": {
"default": "js",
"description": "%python.nativeLocator.description%",
"description": "%python.locator.description%",
"enum": [
"js",
"native"
Expand Down
2 changes: 1 addition & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"python.logging.level.description": "The logging level the extension logs at, defaults to 'error'",
"python.logging.level.deprecation": "This setting is deprecated. Please use command `Developer: Set Log Level...` to set logging level.",
"python.missingPackage.severity.description": "Set severity of missing packages in requirements.txt or pyproject.toml",
"python.nativeLocator.description": "[Experimental] Select implementation of environment locators. This is an experimental setting while we test native environment location.",
"python.locator.description": "[Experimental] Select implementation of environment locators. This is an experimental setting while we test native environment location.",
"python.pipenvPath.description": "Path to the pipenv executable to use for activation.",
"python.poetryPath.description": "Path to the poetry executable.",
"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.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { CancellationToken, Disposable, Event, EventEmitter } from 'vscode';
import * as ch from 'child_process';
import * as path from 'path';
import * as rpc from 'vscode-jsonrpc/node';
import { isWindows } from '../../../../common/platform/platformService';
import { EXTENSION_ROOT_DIR } from '../../../../constants';
import { traceError, traceInfo, traceLog, traceVerbose, traceWarn } from '../../../../logging';
import { createDeferred } from '../../../../common/utils/async';

const NATIVE_LOCATOR = isWindows()
? path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'python-finder.exe')
: path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'python-finder');

export interface NativeEnvInfo {
name: string;
pythonExecutablePath?: string;
category: string;
version?: string;
pythonRunCommand?: string[];
envPath?: string;
sysPrefixPath?: string;
/**
* Path to the project directory when dealing with pipenv virtual environments.
*/
projectPath?: string;
}

export interface NativeEnvManagerInfo {
tool: string;
executablePath: string;
version?: string;
}

export interface NativeGlobalPythonFinder extends Disposable {
startSearch(token?: CancellationToken): Promise<void>;
onDidFindPythonEnvironment: Event<NativeEnvInfo>;
onDidFindEnvironmentManager: Event<NativeEnvManagerInfo>;
}

interface NativeLog {
level: string;
message: string;
}

class NativeGlobalPythonFinderImpl implements NativeGlobalPythonFinder {
private readonly _onDidFindPythonEnvironment = new EventEmitter<NativeEnvInfo>();

private readonly _onDidFindEnvironmentManager = new EventEmitter<NativeEnvManagerInfo>();

public readonly onDidFindPythonEnvironment = this._onDidFindPythonEnvironment.event;

public readonly onDidFindEnvironmentManager = this._onDidFindEnvironmentManager.event;

public startSearch(token?: CancellationToken): Promise<void> {
const deferred = createDeferred<void>();
const proc = ch.spawn(NATIVE_LOCATOR, [], { stdio: 'pipe' });
const disposables: Disposable[] = [];

const connection = rpc.createMessageConnection(
new rpc.StreamMessageReader(proc.stdout),
new rpc.StreamMessageWriter(proc.stdin),
);

disposables.push(
connection,
connection.onNotification('pythonEnvironment', (data: NativeEnvInfo) => {
this._onDidFindPythonEnvironment.fire(data);
}),
connection.onNotification('envManager', (data: NativeEnvManagerInfo) => {
this._onDidFindEnvironmentManager.fire(data);
}),
connection.onNotification('exit', () => {
traceVerbose('Native Python Finder exited');
}),
connection.onNotification('log', (data: NativeLog) => {
switch (data.level) {
case 'info':
traceInfo(`Native Python Finder: ${data.message}`);
break;
case 'warning':
traceWarn(`Native Python Finder: ${data.message}`);
break;
case 'error':
traceError(`Native Python Finder: ${data.message}`);
break;
case 'debug':
traceVerbose(`Native Python Finder: ${data.message}`);
break;
default:
traceLog(`Native Python Finder: ${data.message}`);
}
}),
connection.onClose(() => {
deferred.resolve();
disposables.forEach((d) => d.dispose());
}),
{
dispose: () => {
try {
if (proc.exitCode === null) {
proc.kill();
}
} catch (err) {
traceVerbose('Error while disposing Native Python Finder', err);
}
},
},
);

if (token) {
disposables.push(
token.onCancellationRequested(() => {
deferred.resolve();
try {
proc.kill();
} catch (err) {
traceVerbose('Error while handling cancellation request for Native Python Finder', err);
}
}),
);
}

connection.listen();
return deferred.promise;
}

public dispose() {
this._onDidFindPythonEnvironment.dispose();
this._onDidFindEnvironmentManager.dispose();
}
}

export function createNativeGlobalPythonFinder(): NativeGlobalPythonFinder {
return new NativeGlobalPythonFinderImpl();
}
109 changes: 42 additions & 67 deletions src/client/pythonEnvironments/base/locators/lowLevel/nativeLocator.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { Event, EventEmitter } from 'vscode';
import * as ch from 'child_process';
import * as path from 'path';
import * as rpc from 'vscode-jsonrpc/node';
import { EXTENSION_ROOT_DIR } from '../../../../constants';
import { isWindows } from '../../../../common/platform/platformService';
import { IDisposable } from '../../../../common/types';
import { ILocator, BasicEnvInfo, IPythonEnvsIterator } from '../../locator';
import { PythonEnvsChangedEvent } from '../../watcher';
import { createDeferred } from '../../../../common/utils/async';
import { PythonEnvKind, PythonVersion } from '../../info';
import { Conda } from '../../../common/environmentManagers/conda';
import { traceError } from '../../../../logging';
import type { KnownEnvironmentTools } from '../../../../api/types';
import { setPyEnvBinary } from '../../../common/environmentManagers/pyenv';

const NATIVE_LOCATOR = isWindows()
? path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'python-finder.exe')
: path.join(EXTENSION_ROOT_DIR, 'native_locator', 'bin', 'python-finder');

interface NativeEnvInfo {
name: string;
pythonExecutablePath?: string;
category: string;
version?: string;
pythonRunCommand?: string[];
envPath?: string;
sysPrefixPath?: string;
/**
* Path to the project directory when dealing with pipenv virtual environments.
*/
projectPath?: string;
}

interface EnvManager {
tool: string;
executablePath: string;
version?: string;
}
import {
NativeEnvInfo,
NativeEnvManagerInfo,
NativeGlobalPythonFinder,
createNativeGlobalPythonFinder,
} from '../common/nativePythonFinder';

function categoryToKind(category: string): PythonEnvKind {
switch (category.toLowerCase()) {
Expand All @@ -59,6 +38,7 @@ function categoryToKind(category: string): PythonEnvKind {
}
}
}

function toolToKnownEnvironmentTool(tool: string): KnownEnvironmentTools {
switch (tool.toLowerCase()) {
case 'conda':
Expand Down Expand Up @@ -97,9 +77,12 @@ export class NativeLocator implements ILocator<BasicEnvInfo>, IDisposable {

private readonly disposables: IDisposable[] = [];

private readonly finder: NativeGlobalPythonFinder;

constructor() {
this.onChanged = this.onChangedEmitter.event;
this.disposables.push(this.onChangedEmitter);
this.finder = createNativeGlobalPythonFinder();
this.disposables.push(this.onChangedEmitter, this.finder);
}

public readonly onChanged: Event<PythonEnvsChangedEvent>;
Expand All @@ -110,46 +93,38 @@ export class NativeLocator implements ILocator<BasicEnvInfo>, IDisposable {
}

public iterEnvs(): IPythonEnvsIterator<BasicEnvInfo> {
const proc = ch.spawn(NATIVE_LOCATOR, [], { stdio: 'pipe' });
const promise = this.finder.startSearch();
const envs: BasicEnvInfo[] = [];
const deferred = createDeferred<void>();
const connection = rpc.createMessageConnection(
new rpc.StreamMessageReader(proc.stdout),
new rpc.StreamMessageWriter(proc.stdin),
);
this.disposables.push(connection);
connection.onNotification('pythonEnvironment', (data: NativeEnvInfo) => {
envs.push({
kind: categoryToKind(data.category),
// TODO: What if executable is undefined?
executablePath: data.pythonExecutablePath!,
envPath: data.envPath,
version: parseVersion(data.version),
name: data.name === '' ? undefined : data.name,
});
});
connection.onNotification('envManager', (data: EnvManager) => {
switch (toolToKnownEnvironmentTool(data.tool)) {
case 'Conda': {
Conda.setConda(data.executablePath);
break;
this.disposables.push(
this.finder.onDidFindPythonEnvironment((data: NativeEnvInfo) => {
envs.push({
kind: categoryToKind(data.category),
// TODO: What if executable is undefined?
executablePath: data.pythonExecutablePath!,
envPath: data.envPath,
version: parseVersion(data.version),
name: data.name === '' ? undefined : data.name,
});
}),
this.finder.onDidFindEnvironmentManager((data: NativeEnvManagerInfo) => {
switch (toolToKnownEnvironmentTool(data.tool)) {
case 'Conda': {
Conda.setConda(data.executablePath);
break;
}
case 'Pyenv': {
setPyEnvBinary(data.executablePath);
break;
}
default: {
break;
}
}
case 'Pyenv': {
setPyEnvBinary(data.executablePath);
break;
}
default: {
break;
}
}
});
connection.onNotification('exit', () => {
deferred.resolve();
});
connection.listen();
}),
);

const iterator = async function* (): IPythonEnvsIterator<BasicEnvInfo> {
await deferred.promise;
await promise;
yield* envs;
};
return iterator();
Expand Down
2 changes: 1 addition & 1 deletion src/client/pythonEnvironments/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ async function createLocator(

function useNativeLocator(): boolean {
const config = getConfiguration('python');
return config.get<string>('nativeLocator', 'js') === 'native';
return config.get<string>('locator', 'js') === 'native';
}

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