Skip to content

Commit 0d398f7

Browse files
authored
Adds low-level locators (#14655)
* Provide factory function for posix locator * Provide factory function for global virtual env * Provide factory function for windos store and reg * Refactor and add low-level locators * Tweak interfaces * clean up * Simplify disposing locators * Ignore dispose fails after logging * Rebase with main and fix conflicts
1 parent 7b275b4 commit 0d398f7

23 files changed

+343
-197
lines changed

src/client/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ async function activateUnsafe(
102102
const [serviceManager, serviceContainer] = initializeGlobals(context);
103103
activatedServiceContainer = serviceContainer;
104104
initializeCommon(context, serviceManager, serviceContainer);
105-
initializeComponents(context, serviceManager, serviceContainer);
105+
await initializeComponents(context, serviceManager, serviceContainer);
106106
const { activationPromise } = await activateComponents(context, serviceManager, serviceContainer);
107107

108108
//===============================================

src/client/extensionInit.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ export function initializeCommon(
4747
// We will be pulling other code over from activateLegacy().
4848
}
4949

50-
export function initializeComponents(
50+
export async function initializeComponents(
5151
_context: IExtensionContext,
5252
serviceManager: IServiceManager,
5353
serviceContainer: IServiceContainer
5454
) {
55-
activatePythonEnvironments(serviceManager, serviceContainer);
55+
await activatePythonEnvironments(serviceManager, serviceContainer);
5656
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { PythonEnvInfo } from './info';
5+
import {
6+
ILocator,
7+
IPythonEnvsIterator,
8+
NOOP_ITERATOR,
9+
PythonLocatorQuery,
10+
} from './locator';
11+
import { DisableableEnvsWatcher } from './watchers';
12+
13+
/**
14+
* A locator wrapper that can be disabled.
15+
*
16+
* If disabled, events emitted by the wrapped locator are discarded,
17+
* `iterEnvs()` yields nothing, and `resolveEnv()` already returns
18+
* `undefined`.
19+
*/
20+
export class DisableableLocator extends DisableableEnvsWatcher implements ILocator {
21+
constructor(
22+
// To wrapp more than one use `Locators`.
23+
private readonly locator: ILocator,
24+
) {
25+
super(locator);
26+
}
27+
28+
public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator {
29+
if (!this.enabled) {
30+
return NOOP_ITERATOR;
31+
}
32+
return this.locator.iterEnvs(query);
33+
}
34+
35+
public async resolveEnv(env: string | PythonEnvInfo): Promise<PythonEnvInfo | undefined> {
36+
if (!this.enabled) {
37+
return undefined;
38+
}
39+
return this.locator.resolveEnv(env);
40+
}
41+
}

src/client/pythonEnvironments/base/locator.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ export interface ILocator<E extends BasicPythonEnvsChangedEvent = PythonEnvsChan
167167
resolveEnv(env: string | PythonEnvInfo): Promise<PythonEnvInfo | undefined>;
168168
}
169169

170+
export interface IDisposableLocator<E extends BasicPythonEnvsChangedEvent = PythonEnvsChangedEvent>
171+
extends ILocator<E>, IDisposable{}
172+
170173
interface IEmitter<E extends BasicPythonEnvsChangedEvent> {
171174
fire(e: E): void;
172175
}
@@ -184,7 +187,7 @@ interface IEmitter<E extends BasicPythonEnvsChangedEvent> {
184187
* `BasicPythonEnvsChangedEvent`.
185188
*/
186189
abstract class LocatorBase<E extends BasicPythonEnvsChangedEvent = PythonEnvsChangedEvent>
187-
implements IDisposable, ILocator<E> {
190+
implements IDisposableLocator<E> {
188191
public readonly onChanged: Event<E>;
189192

190193
protected readonly emitter: IEmitter<E>;

src/client/pythonEnvironments/base/locators.ts

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

44
import { EventEmitter } from 'vscode';
5+
import { traceVerbose } from '../../common/logger';
56
import { chain } from '../../common/utils/async';
67
import { PythonEnvInfo } from './info';
78
import {
8-
ILocator,
9+
IDisposableLocator,
910
IPythonEnvsIterator,
10-
NOOP_ITERATOR,
1111
PythonEnvUpdatedEvent,
12-
PythonLocatorQuery
12+
PythonLocatorQuery,
1313
} from './locator';
14-
import { DisableableEnvsWatcher, PythonEnvsWatchers } from './watchers';
14+
import { PythonEnvsWatchers } from './watchers';
1515

1616
/**
1717
* Combine the `onUpdated` event of the given iterators into a single event.
@@ -48,10 +48,10 @@ export function combineIterators(iterators: IPythonEnvsIterator[]): IPythonEnvsI
4848
*
4949
* Events and iterator results are combined.
5050
*/
51-
export class Locators extends PythonEnvsWatchers implements ILocator {
51+
export class Locators extends PythonEnvsWatchers implements IDisposableLocator {
5252
constructor(
5353
// The locators will be watched as well as iterated.
54-
private readonly locators: ReadonlyArray<ILocator>
54+
private readonly locators: ReadonlyArray<IDisposableLocator>,
5555
) {
5656
super(locators);
5757
}
@@ -70,34 +70,14 @@ export class Locators extends PythonEnvsWatchers implements ILocator {
7070
}
7171
return undefined;
7272
}
73-
}
74-
75-
/**
76-
* A locator wrapper that can be disabled.
77-
*
78-
* If disabled, events emitted by the wrapped locator are discarded,
79-
* `iterEnvs()` yields nothing, and `resolveEnv()` already returns
80-
* `undefined`.
81-
*/
82-
export class DisableableLocator extends DisableableEnvsWatcher implements ILocator {
83-
constructor(
84-
// To wrapp more than one use `Locators`.
85-
private readonly locator: ILocator
86-
) {
87-
super(locator);
88-
}
8973

90-
public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator {
91-
if (!this.enabled) {
92-
return NOOP_ITERATOR;
93-
}
94-
return this.locator.iterEnvs(query);
95-
}
96-
97-
public async resolveEnv(env: string | PythonEnvInfo): Promise<PythonEnvInfo | undefined> {
98-
if (!this.enabled) {
99-
return undefined;
100-
}
101-
return this.locator.resolveEnv(env);
74+
public dispose(): void {
75+
this.locators.forEach((locator) => {
76+
try {
77+
locator.dispose();
78+
} catch (ex) {
79+
traceVerbose(`Dispose failed for ${typeof locator} locator:`, ex);
80+
}
81+
});
10282
}
10383
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { IEnvsCache } from '../../envsCache';
1010
import { PythonEnvInfo } from '../../info';
1111
import { getMinimalPartialInfo } from '../../info/env';
1212
import {
13-
ILocator,
13+
IDisposableLocator,
1414
IPythonEnvsIterator,
1515
PythonLocatorQuery,
1616
} from '../../locator';
@@ -21,7 +21,7 @@ import { pickBestEnv } from './reducingLocator';
2121
/**
2222
* A locator that stores the known environments in the given cache.
2323
*/
24-
export class CachingLocator implements ILocator {
24+
export class CachingLocator implements IDisposableLocator {
2525
public readonly onChanged: Event<PythonEnvsChangedEvent>;
2626

2727
private readonly watcher = new PythonEnvsWatcher();
@@ -34,7 +34,7 @@ export class CachingLocator implements ILocator {
3434

3535
constructor(
3636
private readonly cache: IEnvsCache,
37-
private readonly locator: ILocator,
37+
private readonly locator: IDisposableLocator,
3838
) {
3939
this.onChanged = this.watcher.onChanged;
4040
this.looper = new BackgroundRequestLooper({
@@ -76,6 +76,8 @@ export class CachingLocator implements ILocator {
7676
public dispose(): void {
7777
const waitUntilStopped = this.looper.stop();
7878
waitUntilStopped.ignoreErrors();
79+
80+
this.locator.dispose();
7981
}
8082

8183
public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator {

src/client/pythonEnvironments/discovery/locators/index.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,17 @@ import {
2525
WORKSPACE_VIRTUAL_ENV_SERVICE,
2626
} from '../../../interpreter/contracts';
2727
import { IServiceContainer } from '../../../ioc/types';
28+
import { DisableableLocator } from '../../base/disableableLocator';
2829
import { PythonEnvInfo } from '../../base/info';
2930
import {
30-
ILocator,
31+
IDisposableLocator,
3132
IPythonEnvsIterator,
3233
Locator,
3334
NOOP_ITERATOR,
3435
PythonLocatorQuery,
3536
} from '../../base/locator';
3637
import {
3738
combineIterators,
38-
DisableableLocator,
3939
Locators,
4040
} from '../../base/locators';
4141
import { PythonEnvironment } from '../../info';
@@ -48,16 +48,16 @@ import { GetInterpreterLocatorOptions } from './types';
4848
export class ExtensionLocators extends Locators {
4949
constructor(
5050
// These are expected to be low-level locators (e.g. system).
51-
nonWorkspace: ILocator[],
51+
nonWorkspace: IDisposableLocator[],
5252
// This is expected to be a locator wrapping any found in
5353
// the workspace (i.e. WorkspaceLocators).
54-
workspace: ILocator,
54+
workspace: IDisposableLocator,
5555
) {
5656
super([...nonWorkspace, workspace]);
5757
}
5858
}
5959

60-
type WorkspaceLocatorFactory = (root: Uri) => ILocator[];
60+
type WorkspaceLocatorFactory = (root: Uri) => IDisposableLocator[];
6161

6262
interface IWorkspaceFolders {
6363
readonly roots: ReadonlyArray<Uri>;
@@ -144,7 +144,7 @@ export class WorkspaceLocators extends Locator {
144144
// Drop the old one, if necessary.
145145
this.removeRoot(root);
146146
// Create the root's locator, wrapping each factory-generated locator.
147-
const locators: ILocator[] = [];
147+
const locators: IDisposableLocator[] = [];
148148
this.factories.forEach((create) => {
149149
locators.push(...create(root));
150150
});

src/client/pythonEnvironments/discovery/locators/services/globalVirtualEnvronmentLocator.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@ import * as path from 'path';
66
import { traceVerbose } from '../../../../common/logger';
77
import { chain, iterable } from '../../../../common/utils/async';
88
import {
9-
getEnvironmentVariable, getOSType, getUserHomeDir, OSType
9+
getEnvironmentVariable, getOSType, getUserHomeDir, OSType,
1010
} from '../../../../common/utils/platform';
1111
import { PythonEnvInfo, PythonEnvKind, UNKNOWN_PYTHON_VERSION } from '../../../base/info';
1212
import { buildEnvInfo } from '../../../base/info/env';
13-
import { IPythonEnvsIterator } from '../../../base/locator';
13+
import { IDisposableLocator, IPythonEnvsIterator } from '../../../base/locator';
1414
import { FSWatchingLocator } from '../../../base/locators/lowLevel/fsWatchingLocator';
1515
import { findInterpretersInDir } from '../../../common/commonUtils';
1616
import { getFileInfo, pathExists } from '../../../common/externalDependencies';
1717
import { isPipenvEnvironment } from './pipEnvHelper';
1818
import {
1919
isVenvEnvironment,
2020
isVirtualenvEnvironment,
21-
isVirtualenvwrapperEnvironment
21+
isVirtualenvwrapperEnvironment,
2222
} from './virtualEnvironmentIdentifier';
2323

2424
const DEFAULT_SEARCH_DEPTH = 2;
@@ -79,7 +79,7 @@ async function getVirtualEnvKind(interpreterPath: string): Promise<PythonEnvKind
7979
/**
8080
* Finds and resolves virtual environments created in known global locations.
8181
*/
82-
export class GlobalVirtualEnvironmentLocator extends FSWatchingLocator {
82+
class GlobalVirtualEnvironmentLocator extends FSWatchingLocator {
8383
private virtualEnvKinds = [
8484
PythonEnvKind.Venv,
8585
PythonEnvKind.VirtualEnv,
@@ -172,3 +172,9 @@ export class GlobalVirtualEnvironmentLocator extends FSWatchingLocator {
172172
return undefined;
173173
}
174174
}
175+
176+
export async function createGlobalVirtualEnvironmentLocator(searchDepth?: number): Promise<IDisposableLocator> {
177+
const locator = new GlobalVirtualEnvironmentLocator(searchDepth);
178+
await locator.initialize();
179+
return locator;
180+
}

src/client/pythonEnvironments/discovery/locators/services/posixKnownPathsLocator.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import {
1010
PythonEnvInfo, PythonEnvKind, PythonReleaseLevel, PythonVersion,
1111
} from '../../../base/info';
1212
import { parseVersion } from '../../../base/info/pythonVersion';
13-
import { ILocator, IPythonEnvsIterator } from '../../../base/locator';
14-
import { PythonEnvsWatcher } from '../../../base/watcher';
13+
import {
14+
IDisposableLocator, IPythonEnvsIterator, Locator,
15+
} from '../../../base/locator';
1516
import { getFileInfo, resolveSymbolicLink } from '../../../common/externalDependencies';
1617
import { commonPosixBinPaths, isPosixPythonBin } from '../../../common/posixUtils';
1718

@@ -39,7 +40,7 @@ async function getPythonBinFromKnownPaths(): Promise<string[]> {
3940
return Array.from(pythonBins);
4041
}
4142

42-
export class PosixKnownPathsLocator extends PythonEnvsWatcher implements ILocator {
43+
class PosixKnownPathsLocator extends Locator {
4344
private kind: PythonEnvKind = PythonEnvKind.OtherGlobal;
4445

4546
public iterEnvs(): IPythonEnvsIterator {
@@ -85,3 +86,8 @@ export class PosixKnownPathsLocator extends PythonEnvsWatcher implements ILocato
8586
};
8687
}
8788
}
89+
90+
export function createPosixKnownPathsLocator(): Promise<IDisposableLocator> {
91+
const locator = new PosixKnownPathsLocator();
92+
return Promise.resolve(locator);
93+
}

src/client/pythonEnvironments/discovery/locators/services/pyenvLocator.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import {
99
PythonEnvInfo, PythonEnvKind,
1010
} from '../../../base/info';
1111
import { buildEnvInfo } from '../../../base/info/env';
12-
import { ILocator, IPythonEnvsIterator } from '../../../base/locator';
13-
import { PythonEnvsWatcher } from '../../../base/watcher';
12+
import { IDisposableLocator, IPythonEnvsIterator, Locator } from '../../../base/locator';
1413
import {
1514
getEnvironmentDirFromPath, getInterpreterPathFromDir, getPythonVersionFromPath,
1615
} from '../../../common/commonUtils';
@@ -294,7 +293,7 @@ async function* getPyenvEnvironments(): AsyncIterableIterator<PythonEnvInfo> {
294293
}
295294
}
296295

297-
export class PyenvLocator extends PythonEnvsWatcher implements ILocator {
296+
class PyenvLocator extends Locator {
298297
// eslint-disable-next-line class-methods-use-this
299298
public iterEnvs(): IPythonEnvsIterator {
300299
return getPyenvEnvironments();
@@ -329,3 +328,8 @@ export class PyenvLocator extends PythonEnvsWatcher implements ILocator {
329328
return undefined;
330329
}
331330
}
331+
332+
export function createPyenvLocator(): Promise<IDisposableLocator> {
333+
const locator = new PyenvLocator();
334+
return Promise.resolve(locator);
335+
}

src/client/pythonEnvironments/discovery/locators/services/windowsRegistryLocator.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import {
99
PythonEnvInfo, PythonEnvKind, PythonVersion, UNKNOWN_PYTHON_VERSION,
1010
} from '../../../base/info';
1111
import { parseVersion } from '../../../base/info/pythonVersion';
12-
import { ILocator, IPythonEnvsIterator } from '../../../base/locator';
13-
import { PythonEnvsWatcher } from '../../../base/watcher';
12+
import {
13+
IDisposableLocator, IPythonEnvsIterator, Locator,
14+
} from '../../../base/locator';
1415
import { getFileInfo } from '../../../common/externalDependencies';
1516
import { getInterpreterDataFromRegistry, IRegistryInterpreterData, readRegistryKeys } from '../../../common/windowsUtils';
1617

@@ -44,7 +45,7 @@ function getArchitecture(data:IRegistryInterpreterData) {
4445
return arch;
4546
}
4647

47-
export class WindowsRegistryLocator extends PythonEnvsWatcher implements ILocator {
48+
class WindowsRegistryLocator extends Locator {
4849
private kind:PythonEnvKind = PythonEnvKind.OtherGlobal;
4950

5051
public iterEnvs(): IPythonEnvsIterator {
@@ -93,3 +94,8 @@ export class WindowsRegistryLocator extends PythonEnvsWatcher implements ILocato
9394
};
9495
}
9596
}
97+
98+
export function createWindowsRegistryLocator(): Promise<IDisposableLocator> {
99+
const locator = new WindowsRegistryLocator();
100+
return Promise.resolve(locator);
101+
}

0 commit comments

Comments
 (0)