Skip to content

Commit

Permalink
mid-way broken
Browse files Browse the repository at this point in the history
  • Loading branch information
karthiknadig committed Jul 22, 2024
1 parent fd28299 commit de6d176
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 73 deletions.
133 changes: 104 additions & 29 deletions src/client/pythonEnvironments/base/locators/common/pythonWatcher.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,53 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import {
Disposable,
Event,
EventEmitter,
FileChangeType,
FileSystemWatcher,
RelativePattern,
Uri,
WorkspaceFolder,
WorkspaceFoldersChangeEvent,
} from 'vscode';
import { NativeEnvInfo } from './nativePythonFinder';
import { NativePythonEnvironmentKind } from './nativePythonUtils';
import {
createFileSystemWatcher,
getWorkspaceFolder,
getWorkspaceFolderPaths,
} from '../../../../common/vscodeApis/workspaceApis';
import { Disposable, Event, EventEmitter, GlobPattern, RelativePattern, Uri, WorkspaceFolder } from 'vscode';
import { createFileSystemWatcher, getWorkspaceFolder } from '../../../../common/vscodeApis/workspaceApis';
import { isWindows } from '../../../../common/platform/platformService';
import { arePathsSame } from '../../../common/externalDependencies';
import { FileChangeType } from '../../../../common/platform/fileSystemWatcher';

export interface PythonWorkspaceEnvEvent {
type: FileChangeType;
workspaceFolder: WorkspaceFolder;
envs?: NativeEnvInfo[];
}

export interface PythonGlobalEnvEvent {
kind?: NativePythonEnvironmentKind;
type?: FileChangeType;
type: FileChangeType;
uri: Uri;
}

export interface PythonWatcher extends Disposable {
watchPath(uri: Uri): Disposable;
watchWorkspace(wf: WorkspaceFolder): void;
unwatchWorkspace(wf: WorkspaceFolder): void;
onDidWorkspaceEnvChanged: Event<PythonWorkspaceEnvEvent>;

watchPath(uri: Uri, pattern?: string): void;
unwatchPath(uri: Uri): void;
onDidGlobalEnvChanged: Event<PythonGlobalEnvEvent>;
}

/*
* The pattern to search for python executables in the workspace.
* project
* ├── python or python.exe <--- This is what we are looking for.
* ├── .conda
* │ └── python or python.exe <--- This is what we are looking for.
* └── .venv
* │ └── Scripts or bin
* │ └── python or python.exe <--- This is what we are looking for.
*/
const WORKSPACE_PATTERN = isWindows() ? '{,*/,*/Scripts/,*/scripts/}python.exe' : '{,*/,*/bin/}python';

class PythonWatcherImpl implements PythonWatcher {
private disposables: Disposable[] = [];

private readonly _onDidWorkspaceEnvChanged = new EventEmitter<PythonWorkspaceEnvEvent>();

private readonly _onDidGlobalEnvChanged = new EventEmitter<PythonGlobalEnvEvent>();

private readonly _disposeMap: Map<string, Disposable> = new Map<string, Disposable>();

constructor() {
this.disposables.push(this._onDidWorkspaceEnvChanged, this._onDidGlobalEnvChanged);
}
Expand All @@ -52,16 +56,87 @@ class PythonWatcherImpl implements PythonWatcher {

onDidWorkspaceEnvChanged: Event<PythonWorkspaceEnvEvent> = this._onDidWorkspaceEnvChanged.event;

watchPath(uri: Uri): Disposable {
const wf = getWorkspaceFolder(uri);
if (wf) {
const watcher = this.watchWorkspaceFolder(wf);
return watcher;
watchWorkspace(wf: WorkspaceFolder): void {
if (this._disposeMap.has(wf.uri.fsPath)) {
const disposer = this._disposeMap.get(wf.uri.fsPath);
disposer?.dispose();
}

const disposables: Disposable[] = [];
const watcher = createFileSystemWatcher(new RelativePattern(wf, WORKSPACE_PATTERN));
disposables.push(
watcher,
watcher.onDidChange((uri) => {
this.fireWorkspaceEvent(FileChangeType.Changed, wf, uri);
}),
watcher.onDidCreate((uri) => {
this.fireWorkspaceEvent(FileChangeType.Created, wf, uri);
}),
watcher.onDidDelete((uri) => {
this.fireWorkspaceEvent(FileChangeType.Deleted, wf, uri);
}),
);

const disposable = {
dispose: () => {
disposables.forEach((d) => d.dispose());
this._disposeMap.delete(wf.uri.fsPath);
},
};
this._disposeMap.set(wf.uri.fsPath, disposable);
}

unwatchWorkspace(wf: WorkspaceFolder): void {
const disposable = this._disposeMap.get(wf.uri.fsPath);
disposable?.dispose();
}

private fireWorkspaceEvent(type: FileChangeType, wf: WorkspaceFolder, uri: Uri) {
const uriWorkspace = getWorkspaceFolder(uri);
if (uriWorkspace && arePathsSame(uriWorkspace.uri.fsPath, wf.uri.fsPath)) {
this._onDidWorkspaceEnvChanged.fire({ type, workspaceFolder: wf });
}
}

watchPath(uri: Uri, pattern?: string): void {
if (this._disposeMap.has(uri.fsPath)) {
const disposer = this._disposeMap.get(uri.fsPath);
disposer?.dispose();
}

const glob: GlobPattern = pattern ? new RelativePattern(uri, pattern) : uri.fsPath;
const disposables: Disposable[] = [];
const watcher = createFileSystemWatcher(glob);
disposables.push(
watcher,
watcher.onDidChange(() => {
this._onDidGlobalEnvChanged.fire({ type: FileChangeType.Changed, uri });
}),
watcher.onDidCreate(() => {
this._onDidGlobalEnvChanged.fire({ type: FileChangeType.Created, uri });
}),
watcher.onDidDelete(() => {
this._onDidGlobalEnvChanged.fire({ type: FileChangeType.Deleted, uri });
}),
);

const disposable = {
dispose: () => {
disposables.forEach((d) => d.dispose());
this._disposeMap.delete(uri.fsPath);
},
};
this._disposeMap.set(uri.fsPath, disposable);
}

unwatchPath(uri: Uri): void {
const disposable = this._disposeMap.get(uri.fsPath);
disposable?.dispose();
}

private watchWorkspaceFolder(workspaceFolder: WorkspaceFolder): Disposable {
const watcher = createFileSystemWatcher(new RelativePattern(workspaceFolder));
dispose() {
this.disposables.forEach((d) => d.dispose());
this._disposeMap.forEach((d) => d.dispose());
}
}

Expand Down
138 changes: 94 additions & 44 deletions src/client/pythonEnvironments/nativeAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT License.

import * as path from 'path';
import { Disposable, Event, EventEmitter } from 'vscode';
import { Disposable, Event, EventEmitter, Uri } from 'vscode';
import { PythonEnvInfo, PythonEnvKind, PythonEnvType, PythonVersion } from './base/info';
import {
GetRefreshEnvironmentsOptions,
Expand All @@ -13,15 +13,23 @@ import {
TriggerRefreshOptions,
} from './base/locator';
import { PythonEnvCollectionChangedEvent } from './base/watcher';
import { isNativeEnvInfo, NativeEnvInfo, NativePythonFinder } from './base/locators/common/nativePythonFinder';
import {
isNativeEnvInfo,
NativeEnvInfo,
NativeEnvManagerInfo,
NativePythonFinder,
} from './base/locators/common/nativePythonFinder';
import { createDeferred, Deferred } from '../common/utils/async';
import { Architecture } from '../common/utils/platform';
import { parseVersion } from './base/info/pythonVersion';
import { cache } from '../common/utils/decorators';
import { traceError, traceLog } from '../logging';
import { traceError, traceLog, traceWarn } from '../logging';
import { StopWatch } from '../common/utils/stopWatch';
import { FileChangeType } from '../common/platform/fileSystemWatcher';
import { categoryToKind } from './base/locators/common/nativePythonUtils';
import { createPythonWatcher } from './base/locators/common/pythonWatcher';
import { Conda } from './common/environmentManagers/conda';
import { setPyEnvBinary } from './common/environmentManagers/pyenv';

function makeExecutablePath(prefix?: string): string {
if (!prefix) {
Expand Down Expand Up @@ -200,12 +208,23 @@ class NativePythonEnvironments implements IDiscoveryAPI, Disposable {

private _envs: PythonEnvInfo[] = [];

private _disposables: Disposable[] = [];

constructor(private readonly finder: NativePythonFinder) {
this._onProgress = new EventEmitter<ProgressNotificationEvent>();
this._onChanged = new EventEmitter<PythonEnvCollectionChangedEvent>();

this.onProgress = this._onProgress.event;
this.onChanged = this._onChanged.event;

this.refreshState = ProgressReportStage.idle;
this._disposables.push(this._onProgress, this._onChanged);

this.initializeWatcher();
}

dispose(): void {
this._disposables.forEach((d) => d.dispose());
}

refreshState: ProgressReportStage;
Expand All @@ -232,44 +251,10 @@ class NativePythonEnvironments implements IDiscoveryAPI, Disposable {
setImmediate(async () => {
try {
for await (const native of this.finder.refresh()) {
if (!isNativeEnvInfo(native) || !validEnv(native)) {
// eslint-disable-next-line no-continue
continue;
}
try {
const envPath = native.executable ?? native.prefix;
const version = native.version ? parseVersion(native.version) : undefined;

if (categoryToKind(native.kind) === PythonEnvKind.Conda && !native.executable) {
// This is a conda env without python, no point trying to resolve this.
// There is nothing to resolve
this.addEnv(native);
} else if (
envPath &&
(!version || version.major < 0 || version.minor < 0 || version.micro < 0)
) {
// We have a path, but no version info, try to resolve the environment.
this.finder
.resolve(envPath)
.then((env) => {
if (env) {
this.addEnv(env);
}
})
.ignoreErrors();
} else if (
envPath &&
version &&
version.major >= 0 &&
version.minor >= 0 &&
version.micro >= 0
) {
this.addEnv(native);
} else {
traceError(`Failed to process environment: ${JSON.stringify(native)}`);
}
} catch (err) {
traceError(`Failed to process environment: ${err}`);
if (isNativeEnvInfo(native)) {
this.processEnv(native);
} else {
this.processEnvManager(native);
}
}
this._refreshPromise?.resolve();
Expand All @@ -286,6 +271,57 @@ class NativePythonEnvironments implements IDiscoveryAPI, Disposable {
return this._refreshPromise?.promise;
}

private processEnv(native: NativeEnvInfo): void {
if (!validEnv(native)) {
return;
}

try {
const envPath = native.executable ?? native.prefix;
const version = native.version ? parseVersion(native.version) : undefined;

if (categoryToKind(native.kind) === PythonEnvKind.Conda && !native.executable) {
// This is a conda env without python, no point trying to resolve this.
// There is nothing to resolve
this.addEnv(native);
} else if (envPath && (!version || version.major < 0 || version.minor < 0 || version.micro < 0)) {
// We have a path, but no version info, try to resolve the environment.
this.finder
.resolve(envPath)
.then((env) => {
if (env) {
this.addEnv(env);
}
})
.ignoreErrors();
} else if (envPath && version && version.major >= 0 && version.minor >= 0 && version.micro >= 0) {
this.addEnv(native);
} else {
traceError(`Failed to process environment: ${JSON.stringify(native)}`);
}
} catch (err) {
traceError(`Failed to process environment: ${err}`);
}
}

// eslint-disable-next-line class-methods-use-this
private processEnvManager(native: NativeEnvManagerInfo) {
const tool = native.tool.toLowerCase();
switch (tool) {
case 'conda':
traceLog(`Conda environment manager found at: ${native.executable}`);
Conda.setConda(native.executable);
break;
case 'pyenv':
traceLog(`Pyenv environment manager found at: ${native.executable}`);
setPyEnvBinary(native.executable);
break;
default:
traceWarn(`Unknown environment manager: ${native.tool}`);
break;
}
}

getEnvs(_query?: PythonLocatorQuery): PythonEnvInfo[] {
return this._envs;
}
Expand Down Expand Up @@ -328,9 +364,23 @@ class NativePythonEnvironments implements IDiscoveryAPI, Disposable {
return undefined;
}

dispose(): void {
this._onProgress.dispose();
this._onChanged.dispose();
private initializeWatcher(): void {
const watcher = createPythonWatcher();
this._disposables.push(
watcher,
watcher.onDidGlobalEnvChanged((e) => this.pathEventHandler(e.type, e.uri)),
);
}

private pathEventHandler(e: FileChangeType, uri: Uri): void {}

private workspaceEventHandler(e: FileChangeType, uri: string): void {
if (e === FileChangeType.Deleted) {
this._envs = this._envs.filter((item) => item.location !== uri);
this._onChanged.fire({ type: FileChangeType.Deleted, old: undefined, new: undefined });
} else {
this.triggerRefresh().ignoreErrors();
}
}
}

Expand Down

0 comments on commit de6d176

Please sign in to comment.