Skip to content

Commit 016cce8

Browse files
Add a legacy DI adapter for the Python envs component. (#13858)
This allows us to start using the new discovery code in the extension.
1 parent a9b5603 commit 016cce8

File tree

2 files changed

+290
-11
lines changed

2 files changed

+290
-11
lines changed

src/client/interpreter/contracts.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,23 @@ export interface IVirtualEnvironmentsSearchPathProvider {
3131
getSearchPaths(resource?: Uri): Promise<string[]>;
3232
}
3333

34+
export const IComponentAdapter = Symbol('IComponentAdapter');
35+
export interface IComponentAdapter {
36+
// IInterpreterLocatorService
37+
hasInterpreters: Promise<boolean | undefined>;
38+
getInterpreters(resource?: Uri): Promise<PythonEnvironment[] | undefined>;
39+
// IInterpreterService
40+
getInterpreterDetails(pythonPath: string, _resource?: Uri): Promise<undefined | PythonEnvironment>;
41+
// IInterpreterHelper
42+
getInterpreterInformation(pythonPath: string): Promise<undefined | Partial<PythonEnvironment>>;
43+
isMacDefaultPythonPath(pythonPath: string): Promise<boolean | undefined>;
44+
// ICondaService
45+
isCondaEnvironment(interpreterPath: string): Promise<boolean | undefined>;
46+
getCondaEnvironment(interpreterPath: string): Promise<CondaEnvironmentInfo | undefined>;
47+
// IWindowsStoreInterpreter
48+
isWindowsStoreInterpreter(pythonPath: string): Promise<boolean | undefined>;
49+
}
50+
3451
export const IInterpreterLocatorService = Symbol('IInterpreterLocatorService');
3552

3653
export interface IInterpreterLocatorService extends Disposable {

src/client/pythonEnvironments/legacyIOC.ts

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

44
import { injectable } from 'inversify';
5+
import * as vscode from 'vscode';
6+
import { createDeferred } from '../common/utils/async';
7+
import { Architecture } from '../common/utils/platform';
8+
import { getVersionString, parseVersion } from '../common/utils/version';
59
import {
610
CONDA_ENV_FILE_SERVICE,
711
CONDA_ENV_SERVICE,
812
CURRENT_PATH_SERVICE,
913
GLOBAL_VIRTUAL_ENV_SERVICE,
14+
IComponentAdapter,
1015
ICondaService,
1116
IInterpreterLocatorHelper,
1217
IInterpreterLocatorProgressService,
@@ -23,10 +28,13 @@ import {
2328
} from '../interpreter/contracts';
2429
import { IPipEnvServiceHelper, IPythonInPathCommandProvider } from '../interpreter/locators/types';
2530
import { IServiceContainer, IServiceManager } from '../ioc/types';
31+
import { PythonEnvInfo, PythonEnvKind, PythonReleaseLevel } from './base/info';
32+
import { ILocator, PythonLocatorQuery } from './base/locator';
2633
import { initializeExternalDependencies } from './common/externalDependencies';
2734
import { PythonInterpreterLocatorService } from './discovery/locators';
2835
import { InterpreterLocatorHelper } from './discovery/locators/helpers';
2936
import { InterpreterLocatorProgressService } from './discovery/locators/progressService';
37+
import { CondaEnvironmentInfo } from './discovery/locators/services/conda';
3038
import { CondaEnvFileService } from './discovery/locators/services/condaEnvFileService';
3139
import { CondaEnvService } from './discovery/locators/services/condaEnvService';
3240
import { CondaService } from './discovery/locators/services/condaService';
@@ -48,31 +56,285 @@ import {
4856
WorkspaceVirtualEnvService,
4957
} from './discovery/locators/services/workspaceVirtualEnvService';
5058
import { WorkspaceVirtualEnvWatcherService } from './discovery/locators/services/workspaceVirtualEnvWatcherService';
59+
import { EnvironmentType, PythonEnvironment } from './info';
5160
import { EnvironmentInfoService, IEnvironmentInfoService } from './info/environmentInfoService';
5261

53-
import { PythonEnvironments } from '.';
62+
const convertedKinds = new Map(Object.entries({
63+
[PythonEnvKind.System]: EnvironmentType.System,
64+
[PythonEnvKind.MacDefault]: EnvironmentType.System,
65+
[PythonEnvKind.WindowsStore]: EnvironmentType.WindowsStore,
66+
[PythonEnvKind.Pyenv]: EnvironmentType.Pyenv,
67+
[PythonEnvKind.Conda]: EnvironmentType.Conda,
68+
[PythonEnvKind.CondaBase]: EnvironmentType.Conda,
69+
[PythonEnvKind.VirtualEnv]: EnvironmentType.VirtualEnv,
70+
[PythonEnvKind.Pipenv]: EnvironmentType.Pipenv,
71+
[PythonEnvKind.Venv]: EnvironmentType.Venv,
72+
}));
5473

55-
export const IComponentAdapter = Symbol('IComponentAdapter');
56-
export interface IComponentAdapter {
57-
// We will fill in the API separately.
74+
function convertEnvInfo(info: PythonEnvInfo): PythonEnvironment {
75+
const {
76+
name,
77+
location,
78+
executable,
79+
arch,
80+
kind,
81+
searchLocation,
82+
version,
83+
distro,
84+
} = info;
85+
const { filename, sysPrefix } = executable;
86+
const env: PythonEnvironment = {
87+
sysPrefix,
88+
envType: EnvironmentType.Unknown,
89+
envName: name,
90+
envPath: location,
91+
path: filename,
92+
architecture: arch,
93+
};
94+
95+
const envType = convertedKinds.get(kind);
96+
if (envType !== undefined) {
97+
env.envType = envType;
98+
}
99+
// Otherwise it stays Unknown.
100+
101+
if (searchLocation !== undefined) {
102+
if (kind === PythonEnvKind.Pipenv) {
103+
env.pipEnvWorkspaceFolder = searchLocation.fsPath;
104+
}
105+
}
106+
107+
if (version !== undefined) {
108+
const { release, sysVersion } = version;
109+
const { level, serial } = release;
110+
const releaseStr = level === PythonReleaseLevel.Final
111+
? 'final'
112+
: `${level}${serial}`;
113+
const versionStr = `${getVersionString(version)}-${releaseStr}`;
114+
env.version = parseVersion(versionStr);
115+
env.sysVersion = sysVersion;
116+
}
117+
118+
if (distro !== undefined && distro.org !== '') {
119+
env.companyDisplayName = distro.org;
120+
}
121+
// We do not worry about using distro.defaultDisplayName
122+
// or info.defaultDisplayName.
123+
124+
return env;
125+
}
126+
127+
function buildEmptyEnvInfo(): PythonEnvInfo {
128+
return {
129+
id: '',
130+
kind: PythonEnvKind.Unknown,
131+
executable: {
132+
filename: '',
133+
sysPrefix: '',
134+
ctime: -1,
135+
mtime: -1,
136+
},
137+
name: '',
138+
location: '',
139+
version: {
140+
major: -1,
141+
minor: -1,
142+
micro: -1,
143+
release: {
144+
level: PythonReleaseLevel.Final,
145+
serial: 0,
146+
},
147+
},
148+
arch: Architecture.Unknown,
149+
distro: {
150+
org: '',
151+
},
152+
};
58153
}
59154

155+
interface IPythonEnvironments extends ILocator {}
156+
60157
@injectable()
61158
class ComponentAdapter implements IComponentAdapter {
62159
constructor(
63160
// The adapter only wraps one thing: the component API.
64-
private readonly api: PythonEnvironments
65-
) {
66-
// For the moment we use this placeholder to exercise the property.
67-
if (this.api.onChanged) {
68-
this.api.onChanged((_event) => {
69-
// do nothing
161+
private readonly api: IPythonEnvironments,
162+
// For now we effectively disable the component.
163+
private readonly enabled = false,
164+
) {}
165+
166+
// IInterpreterHelper
167+
168+
// A result of `undefined` means "Fall back to the old code!"
169+
public async getInterpreterInformation(pythonPath: string): Promise<undefined | Partial<PythonEnvironment>> {
170+
if (!this.enabled) {
171+
return undefined;
172+
}
173+
const env = await this.api.resolveEnv(pythonPath);
174+
if (env === undefined) {
175+
return undefined;
176+
}
177+
return convertEnvInfo(env);
178+
}
179+
180+
// A result of `undefined` means "Fall back to the old code!"
181+
public async isMacDefaultPythonPath(pythonPath: string): Promise<boolean | undefined> {
182+
if (!this.enabled) {
183+
return undefined;
184+
}
185+
const env = await this.api.resolveEnv(pythonPath);
186+
if (env === undefined) {
187+
return undefined;
188+
}
189+
return env.kind === PythonEnvKind.MacDefault;
190+
}
191+
192+
// IInterpreterService
193+
194+
// A result of `undefined` means "Fall back to the old code!"
195+
public get hasInterpreters(): Promise<boolean | undefined> {
196+
if (!this.enabled) {
197+
return Promise.resolve(undefined);
198+
}
199+
const iterator = this.api.iterEnvs();
200+
return iterator.next().then((res) => !res.done);
201+
}
202+
203+
// We use the same getInterpreters() here as for IInterpreterLocatorService.
204+
205+
// A result of `undefined` means "Fall back to the old code!"
206+
public async getInterpreterDetails(
207+
pythonPath: string,
208+
resource?: vscode.Uri,
209+
): Promise<undefined | PythonEnvironment> {
210+
if (!this.enabled) {
211+
return undefined;
212+
}
213+
const info = buildEmptyEnvInfo();
214+
info.executable.filename = pythonPath;
215+
if (resource !== undefined) {
216+
const wsFolder = vscode.workspace.getWorkspaceFolder(resource);
217+
if (wsFolder !== undefined) {
218+
info.searchLocation = wsFolder.uri;
219+
}
220+
}
221+
const env = await this.api.resolveEnv(info);
222+
if (env === undefined) {
223+
return undefined;
224+
}
225+
return convertEnvInfo(env);
226+
}
227+
228+
// ICondaService
229+
230+
// A result of `undefined` means "Fall back to the old code!"
231+
public async isCondaEnvironment(interpreterPath: string): Promise<boolean | undefined> {
232+
if (!this.enabled) {
233+
return undefined;
234+
}
235+
const env = await this.api.resolveEnv(interpreterPath);
236+
if (env === undefined) {
237+
return undefined;
238+
}
239+
return env.kind === PythonEnvKind.Conda;
240+
}
241+
242+
// A result of `undefined` means "Fall back to the old code!"
243+
public async getCondaEnvironment(interpreterPath: string): Promise<CondaEnvironmentInfo | undefined> {
244+
if (!this.enabled) {
245+
return undefined;
246+
}
247+
const env = await this.api.resolveEnv(interpreterPath);
248+
if (env === undefined) {
249+
return undefined;
250+
}
251+
if (env.kind !== PythonEnvKind.Conda) {
252+
return undefined;
253+
}
254+
if (env.name !== '') {
255+
return { name: env.name, path: '' };
256+
}
257+
// else
258+
return { name: '', path: env.location };
259+
}
260+
261+
// IWindowsStoreInterpreter
262+
263+
// A result of `undefined` means "Fall back to the old code!"
264+
public async isWindowsStoreInterpreter(pythonPath: string): Promise<boolean | undefined> {
265+
if (!this.enabled) {
266+
return undefined;
267+
}
268+
const env = await this.api.resolveEnv(pythonPath);
269+
if (env === undefined) {
270+
return undefined;
271+
}
272+
return env.kind === PythonEnvKind.WindowsStore;
273+
}
274+
275+
// IInterpreterLocatorService
276+
277+
// A result of `undefined` means "Fall back to the old code!"
278+
public async getInterpreters(
279+
resource?: vscode.Uri,
280+
// Currently we have no plans to support GetInterpreterLocatorOptions:
281+
// {
282+
// ignoreCache?: boolean
283+
// onSuggestion?: boolean;
284+
// }
285+
): Promise<PythonEnvironment[] | undefined> {
286+
if (!this.enabled) {
287+
return undefined;
288+
}
289+
const query: PythonLocatorQuery = {};
290+
if (resource !== undefined) {
291+
const wsFolder = vscode.workspace.getWorkspaceFolder(resource);
292+
if (wsFolder !== undefined) {
293+
query.searchLocations = [wsFolder.uri];
294+
}
295+
}
296+
297+
const deferred = createDeferred<PythonEnvironment[]>();
298+
const envs: PythonEnvironment[] = [];
299+
const executableToLegacy: Record<string, PythonEnvironment> = {};
300+
const iterator = this.api.iterEnvs(query);
301+
302+
if (iterator.onUpdated !== undefined) {
303+
iterator.onUpdated((event) => {
304+
if (event === null) {
305+
deferred.resolve(envs);
306+
} else {
307+
// Replace the old one.
308+
const old = executableToLegacy[event.old.executable.filename];
309+
if (old !== undefined) {
310+
const index = envs.indexOf(old);
311+
if (index !== -1) {
312+
envs[index] = convertEnvInfo(event.new);
313+
}
314+
}
315+
}
70316
});
317+
} else {
318+
deferred.resolve(envs);
71319
}
320+
321+
let res = await iterator.next();
322+
while (!res.done) {
323+
const env = convertEnvInfo(res.value);
324+
envs.push(env);
325+
executableToLegacy[env.path] = env;
326+
res = await iterator.next(); // eslint-disable-line no-await-in-loop
327+
}
328+
329+
return deferred.promise;
72330
}
73331
}
74332

75-
export function registerForIOC(serviceManager: IServiceManager, serviceContainer: IServiceContainer, api: PythonEnvironments): void {
333+
export function registerForIOC(
334+
serviceManager: IServiceManager,
335+
serviceContainer: IServiceContainer,
336+
api: IPythonEnvironments,
337+
): void {
76338
const adapter = new ComponentAdapter(api);
77339
serviceManager.addSingletonInstance<IComponentAdapter>(IComponentAdapter, adapter);
78340

0 commit comments

Comments
 (0)