Skip to content

Commit 16152fc

Browse files
Kartik Rajkarthiknadig
Kartik Raj
authored andcommitted
Ensure workspace interpreters are discovered and watched (#17144)
1 parent aacea59 commit 16152fc

File tree

8 files changed

+35
-13
lines changed

8 files changed

+35
-13
lines changed

news/2 Fixes/17144.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Ensure workspace interpreters are discovered and watched when in `pythonDiscoveryModuleWithoutWatcher` experiment.

src/client/common/platform/fs-paths.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,7 @@ export function normCasePath(filePath: string): string {
156156
export function isParentPath(filePath: string, parentPath: string): boolean {
157157
return normCasePath(filePath).startsWith(normCasePath(parentPath));
158158
}
159+
160+
export function arePathsSame(path1: string, path2: string): boolean {
161+
return normCasePath(path1) === normCasePath(path2);
162+
}

src/client/common/utils/misc.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import type { TextDocument, Uri } from 'vscode';
55
import { InteractiveInputScheme, NotebookCellScheme } from '../constants';
66
import { InterpreterUri } from '../installer/types';
7+
import { arePathsSame, isParentPath } from '../platform/fs-paths';
78
import { Resource } from '../types';
89
import { isPromise } from './async';
910
import { StopWatch } from './stopWatch';
@@ -126,15 +127,15 @@ export function getURIFilter(
126127
while (candidate.path.endsWith('/')) {
127128
candidatePath = candidatePath.slice(0, -1);
128129
}
129-
if (opts.checkExact && candidatePath === uriPath) {
130+
if (opts.checkExact && arePathsSame(candidatePath, uriPath)) {
130131
return true;
131132
}
132-
if (opts.checkParent && candidatePath.startsWith(uriRoot)) {
133+
if (opts.checkParent && isParentPath(candidatePath, uriRoot)) {
133134
return true;
134135
}
135136
if (opts.checkChild) {
136-
const candidateRoot = `{candidatePath}/`;
137-
if (uriPath.startsWith(candidateRoot)) {
137+
const candidateRoot = `${candidatePath}/`;
138+
if (isParentPath(uriPath, candidateRoot)) {
138139
return true;
139140
}
140141
}

src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
import { Event } from 'vscode';
5+
import { traceInfo } from '../../../../common/logger';
56
import { asyncFilter } from '../../../../common/utils/arrayUtils';
67
import { pathExists } from '../../../common/externalDependencies';
78
import { PythonEnvInfo } from '../../info';
@@ -112,6 +113,7 @@ export class PythonEnvInfoCache extends PythonEnvsWatcher<PythonEnvCollectionCha
112113

113114
public async flush(): Promise<void> {
114115
if (this.envs.length) {
116+
traceInfo('Environments added to cache', JSON.stringify(this.envs));
115117
await this.persistentStorage.store(this.envs);
116118
}
117119
}

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as fs from 'fs';
55
import * as path from 'path';
66
import { Uri } from 'vscode';
77
import { DiscoveryVariants } from '../../../../common/experiments/groups';
8+
import { traceError, traceVerbose } from '../../../../common/logger';
89
import { FileChangeType } from '../../../../common/platform/fileSystemWatcher';
910
import { sleep } from '../../../../common/utils/async';
1011
import { logError } from '../../../../logging';
@@ -33,6 +34,7 @@ function checkDirWatchable(dirname: string): DirUnwatchableReason {
3334
try {
3435
names = fs.readdirSync(dirname);
3536
} catch (err) {
37+
traceError('Reading directory to watch failed', err);
3638
if (err.code === 'ENOENT') {
3739
// We treat a missing directory as watchable since it should
3840
// be watchable if created later.
@@ -92,12 +94,15 @@ export abstract class FSWatchingLocator<I = PythonEnvInfo> extends LazyResourceB
9294
// Enable global watchers only if the experiment allows it.
9395
const enableGlobalWatchers = await inExperiment(DiscoveryVariants.discoverWithFileWatching);
9496
if (!enableGlobalWatchers) {
97+
traceVerbose('Watcher disabled');
9598
return;
9699
}
97100
}
98101

99102
// Start the FS watchers.
103+
traceVerbose('Getting roots');
100104
let roots = await this.getRoots();
105+
traceVerbose('Found roots');
101106
if (typeof roots === 'string') {
102107
roots = [roots];
103108
}
@@ -106,13 +111,12 @@ export abstract class FSWatchingLocator<I = PythonEnvInfo> extends LazyResourceB
106111
// that might be watched due to a glob are not checked.
107112
const unwatchable = await checkDirWatchable(root);
108113
if (unwatchable) {
109-
logError(`dir "${root}" is not watchable (${unwatchable})`);
114+
logError(`Dir "${root}" is not watchable (${unwatchable})`);
110115
return undefined;
111116
}
112117
return root;
113118
});
114119
const watchableRoots = (await Promise.all(promises)).filter((root) => !!root) as string[];
115-
116120
watchableRoots.forEach((root) => this.startWatchers(root));
117121
}
118122

@@ -147,6 +151,7 @@ export abstract class FSWatchingLocator<I = PythonEnvInfo> extends LazyResourceB
147151
// The structure determines which globs are returned.
148152
this.opts.envStructure,
149153
);
154+
traceVerbose('Start watching root', root, 'for globs', JSON.stringify(globs));
150155
const watchers = globs.map((g) => watchLocationForPythonBinaries(root, callback, g));
151156
this.disposables.push(...watchers);
152157
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { traceError, traceVerbose } from '../../../../common/logger';
88
import { chain, iterable } from '../../../../common/utils/async';
99
import { PythonEnvKind } from '../../info';
1010
import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator';
11-
import { FSWatchingLocator } from './fsWatchingLocator';
11+
import { FSWatcherKind, FSWatchingLocator } from './fsWatchingLocator';
1212
import { getInterpreterPathFromDir } from '../../../common/commonUtils';
1313
import { pathExists } from '../../../common/externalDependencies';
1414
import { isPoetryEnvironment, localPoetryEnvDirName, Poetry } from '../../../common/environmentManagers/poetry';
@@ -65,6 +65,8 @@ export class PoetryLocator extends FSWatchingLocator<BasicEnvInfo> {
6565
super(
6666
() => getRootVirtualEnvDir(root),
6767
async () => PythonEnvKind.Poetry,
68+
undefined,
69+
FSWatcherKind.Workspace,
6870
);
6971
}
7072

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { isPipenvEnvironment } from '../../../common/environmentManagers/pipenv'
1010
import { isVenvEnvironment, isVirtualenvEnvironment } from '../../../common/environmentManagers/simplevirtualenvs';
1111
import { PythonEnvKind } from '../../info';
1212
import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator';
13-
import { FSWatchingLocator } from './fsWatchingLocator';
13+
import { FSWatcherKind, FSWatchingLocator } from './fsWatchingLocator';
1414
import '../../../../common/extensions';
1515
import { asyncFilter } from '../../../../common/utils/arrayUtils';
1616

@@ -52,11 +52,16 @@ async function getVirtualEnvKind(interpreterPath: string): Promise<PythonEnvKind
5252
*/
5353
export class WorkspaceVirtualEnvironmentLocator extends FSWatchingLocator<BasicEnvInfo> {
5454
public constructor(private readonly root: string) {
55-
super(() => getWorkspaceVirtualEnvDirs(this.root), getVirtualEnvKind, {
56-
// Note detecting kind of virtual env depends on the file structure around the
57-
// executable, so we need to wait before attempting to detect it.
58-
delayOnCreated: 1000,
59-
});
55+
super(
56+
() => getWorkspaceVirtualEnvDirs(this.root),
57+
getVirtualEnvKind,
58+
{
59+
// Note detecting kind of virtual env depends on the file structure around the
60+
// executable, so we need to wait before attempting to detect it.
61+
delayOnCreated: 1000,
62+
},
63+
FSWatcherKind.Workspace,
64+
);
6065
}
6166

6267
protected doIterEnvs(): IPythonEnvsIterator<BasicEnvInfo> {

src/client/pythonEnvironments/common/pythonBinariesWatcher.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import * as minimatch from 'minimatch';
77
import * as path from 'path';
8+
import { traceVerbose } from '../../common/logger';
89
import { FileChangeType, watchLocationForPattern } from '../../common/platform/fileSystemWatcher';
910
import { getOSType, OSType } from '../../common/utils/platform';
1011
import { IDisposable } from '../../common/utils/resourceLifecycle';
@@ -26,6 +27,7 @@ export function watchLocationForPythonBinaries(
2627
const resolvedGlob = path.posix.normalize(executableGlob);
2728
const [baseGlob] = resolvedGlob.split('/').slice(-1);
2829
function callbackClosure(type: FileChangeType, e: string) {
30+
traceVerbose('Received event', JSON.stringify(e), 'for baseglob', baseGlob);
2931
const isMatch = minimatch(path.basename(e), baseGlob, { nocase: getOSType() === OSType.Windows });
3032
if (!isMatch) {
3133
// When deleting the file for some reason path to all directories leading up to python are reported

0 commit comments

Comments
 (0)