Skip to content

Commit c995d66

Browse files
Add WorkspaceLocators.
1 parent 0dfaf46 commit c995d66

File tree

4 files changed

+949
-12
lines changed

4 files changed

+949
-12
lines changed

src/client/common/utils/async.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ async function getNext<T>(it: AsyncIterator<T, T | void>, indexMaybe?: number):
137137
}
138138

139139
// tslint:disable-next-line:promise-must-complete no-empty
140-
const NEVER: Promise<unknown> = new Promise(() => {});
140+
export const NEVER: Promise<unknown> = new Promise(() => {});
141141

142142
/**
143143
* Yield everything produced by the given iterators as soon as each is ready.

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

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
import { traceDecorators } from '../../../common/logger';
66
import { IPlatformService } from '../../../common/platform/types';
77
import { IDisposableRegistry } from '../../../common/types';
8-
import { createDeferred, Deferred } from '../../../common/utils/async';
8+
import { chain, createDeferred, Deferred } from '../../../common/utils/async';
99
import { OSType } from '../../../common/utils/platform';
1010
import {
1111
CONDA_ENV_FILE_SERVICE,
@@ -20,13 +20,136 @@ import {
2020
WORKSPACE_VIRTUAL_ENV_SERVICE,
2121
} from '../../../interpreter/contracts';
2222
import { IServiceContainer } from '../../../ioc/types';
23+
import { PythonEnvInfo } from '../../base/info';
24+
import { Locator, ILocator, NOOP_ITERATOR, PythonEnvsIterator, PythonLocatorQuery } from '../../base/locator';
25+
import { DisableableLocator, Locators } from '../../base/locators';
2326
import { PythonEnvironment } from '../../info';
2427
import { isHiddenInterpreter } from './services/interpreterFilter';
2528
import { GetInterpreterLocatorOptions } from './types';
2629

2730
// tslint:disable-next-line:no-require-imports no-var-requires
2831
const flatten = require('lodash/flatten') as typeof import('lodash/flatten');
2932

33+
type WorkspaceLocatorFactory = (root: Uri) => ILocator[];
34+
35+
interface IWorkspaceFolders {
36+
readonly roots: ReadonlyArray<Uri>;
37+
readonly onAdded: Event<Uri>;
38+
readonly onRemoved: Event<Uri>;
39+
}
40+
41+
type RootURI = string;
42+
43+
/**
44+
* The collection of all workspace-specific locators used by the extension.
45+
*
46+
* The factories are used to produce the locators for each workspace folder.
47+
*/
48+
export class WorkspaceLocators extends Locator {
49+
private readonly locators: Record<RootURI, DisableableLocator> = {};
50+
private readonly roots: Record<RootURI, Uri> = {};
51+
constructor(
52+
// used to produce the per-root locators:
53+
private readonly factories: WorkspaceLocatorFactory[]
54+
) {
55+
super();
56+
}
57+
58+
/**
59+
* Activate the locator.
60+
*
61+
* @param folders - the info used to keep track of the workspace folders
62+
*/
63+
public activate(folders: IWorkspaceFolders) {
64+
for (const root of folders.roots) {
65+
this.addRoot(root);
66+
}
67+
folders.onAdded((root: Uri) => this.addRoot(root));
68+
folders.onRemoved((root: Uri) => this.removeRoot(root));
69+
}
70+
71+
public iterEnvs(query?: PythonLocatorQuery): PythonEnvsIterator {
72+
const iterators = Object.keys(this.locators).map((key) => {
73+
if (query?.searchLocations) {
74+
const root = this.roots[key];
75+
if (!matchURI(root, ...query.searchLocations)) {
76+
return NOOP_ITERATOR;
77+
}
78+
}
79+
// The query matches or was not location-specific.
80+
const locator = this.locators[key];
81+
return locator.iterEnvs(query);
82+
});
83+
return chain<PythonEnvInfo>(iterators);
84+
}
85+
86+
public async resolveEnv(env: PythonEnvInfo): Promise<PythonEnvInfo | undefined> {
87+
if (env.searchLocation) {
88+
const rootLocator = this.locators[env.searchLocation.toString()];
89+
if (rootLocator) {
90+
return rootLocator.resolveEnv(env);
91+
}
92+
}
93+
// Fall back to checking all the roots.
94+
for (const key of Object.keys(this.locators)) {
95+
const resolved = await this.locators[key].resolveEnv(env);
96+
if (resolved !== undefined) {
97+
return resolved;
98+
}
99+
}
100+
return undefined;
101+
}
102+
103+
private addRoot(root: Uri) {
104+
// Drop the old one, if necessary.
105+
this.removeRoot(root);
106+
// Create the root's locator, wrapping each factory-generated locator.
107+
const locators: ILocator[] = [];
108+
for (const create of this.factories) {
109+
locators.push(...create(root));
110+
}
111+
const locator = new DisableableLocator(new Locators(locators));
112+
// Cache it.
113+
const key = root.toString();
114+
this.locators[key] = locator;
115+
this.roots[key] = root;
116+
this.emitter.fire({ searchLocation: root });
117+
// Hook up the watchers.
118+
locator.onChanged((e) => {
119+
if (e.searchLocation === undefined) {
120+
e.searchLocation = root;
121+
}
122+
this.emitter.fire(e);
123+
});
124+
}
125+
126+
private removeRoot(root: Uri) {
127+
const key = root.toString();
128+
const locator = this.locators[key];
129+
if (locator === undefined) {
130+
return;
131+
}
132+
delete this.locators[key];
133+
delete this.roots[key];
134+
locator.disable();
135+
this.emitter.fire({ searchLocation: root });
136+
}
137+
}
138+
139+
function matchURI(uri: Uri, ...candidates: Uri[]): boolean {
140+
const uriPath = uri.path.endsWith('/') ? uri.path : `{uri.path}/`;
141+
for (const candidate of candidates) {
142+
if (candidate.scheme === uri.scheme) {
143+
if (candidate.path === uri.path) {
144+
return true;
145+
} else if (candidate.path.startsWith(uriPath)) {
146+
return true;
147+
}
148+
}
149+
}
150+
return false;
151+
}
152+
30153
/**
31154
* Facilitates locating Python interpreters.
32155
*/

src/test/pythonEnvironments/base/common.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
import { createDeferred, flattenIterator } from '../../../client/common/utils/async';
4+
import { createDeferred, flattenIterator, NEVER } from '../../../client/common/utils/async';
55
import { Architecture } from '../../../client/common/utils/platform';
66
import { EMPTY_VERSION, parseBasicVersionInfo } from '../../../client/common/utils/version';
77
import {
@@ -122,14 +122,29 @@ export class SimpleLocator extends Locator {
122122
if (callbacks?.before !== undefined) {
123123
await callbacks.before;
124124
}
125-
//yield* envs;
126-
for (const env of envs) {
127-
if (callbacks?.beforeEach !== undefined) {
128-
await callbacks.beforeEach(env);
125+
if (callbacks?.beforeEach !== undefined) {
126+
const pending: Promise<[PythonEnvInfo, number]>[] = [];
127+
for (const env of envs) {
128+
const closure = env;
129+
const index = pending.length;
130+
pending.push(
131+
callbacks.beforeEach(env).then(() => [closure, index])
132+
);
129133
}
130-
yield env;
131-
if (callbacks?.afterEach !== undefined) {
132-
await callbacks.afterEach(env);
134+
for(let i = 0; i < pending.length; i += 1) {
135+
const [env, index] = await Promise.race(pending);
136+
pending[index] = NEVER as Promise<[PythonEnvInfo, number]>;
137+
yield env
138+
if (callbacks?.afterEach !== undefined) {
139+
await callbacks.afterEach(env);
140+
}
141+
}
142+
} else {
143+
for (const env of envs) {
144+
yield env;
145+
if (callbacks?.afterEach !== undefined) {
146+
await callbacks.afterEach(env);
147+
}
133148
}
134149
}
135150
if (callbacks?.after!== undefined) {

0 commit comments

Comments
 (0)