Skip to content

Add a legacy DI adapter for the Python envs component. #13858

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6769b76
Stub out ComponentAdapter.
ericsnowcurrently Sep 3, 2020
192c4b0
Implement ComponentAdapter.isMacDefaultPythonPath().
ericsnowcurrently Sep 8, 2020
0009f46
Implement ComponentAdapter.isWindowsStoreInterpreter().
ericsnowcurrently Sep 8, 2020
8034272
Implement ComponentAdapter.isCondaEnvironment().
ericsnowcurrently Sep 8, 2020
26e3736
Implement ComponentAdapter.hasInterpreters().
ericsnowcurrently Sep 8, 2020
2889a20
Add a convertEnvInfo() helper.
ericsnowcurrently Sep 8, 2020
2394160
Implement ComponentAdapter.getInterpreterInformation().
ericsnowcurrently Sep 8, 2020
200595d
Implement ComponentAdapter.getInterpreterDetails().
ericsnowcurrently Sep 8, 2020
4837898
Implement ComponentAdapter.getCondaEnvironment().
ericsnowcurrently Sep 9, 2020
cfd1796
Implement ComponentAdapter.getInterpreters().
ericsnowcurrently Sep 9, 2020
424aa09
Drop a dead line of code.
ericsnowcurrently Sep 9, 2020
63b5e83
Move IComponentAdapter to interpreter/contracts.ts.
ericsnowcurrently Sep 10, 2020
e9589d2
Drop the ComponentAdapter constructor placeholder code.
ericsnowcurrently Sep 10, 2020
6972c7c
Start out with the adapter disabled.
ericsnowcurrently Sep 10, 2020
941f8c0
lint
ericsnowcurrently Sep 15, 2020
3f79e87
Avoid an import cycle.
ericsnowcurrently Sep 15, 2020
7769f09
Use object destructuring for convertEnvInfo().
ericsnowcurrently Sep 15, 2020
99add1f
Use the "resource" arg in getInterpreterDetails().
ericsnowcurrently Sep 16, 2020
ed94edf
Drop "options" from the adapter getInterpreters().
ericsnowcurrently Sep 16, 2020
5e380ff
Fix a typo.
ericsnowcurrently Sep 16, 2020
6667d60
Clarify about when `undefined` is returned from the adapter methods.
ericsnowcurrently Sep 16, 2020
4271fd4
Use a map for converting the env kinds.
ericsnowcurrently Sep 16, 2020
c04c6f5
Handle iterator.onUpdated in ComponentAdapter.getInterpreters().
ericsnowcurrently Sep 16, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/client/interpreter/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ export interface IVirtualEnvironmentsSearchPathProvider {
getSearchPaths(resource?: Uri): Promise<string[]>;
}

export const IComponentAdapter = Symbol('IComponentAdapter');
export interface IComponentAdapter {
// IInterpreterLocatorService
hasInterpreters: Promise<boolean | undefined>;
getInterpreters(resource?: Uri): Promise<PythonEnvironment[] | undefined>;
// IInterpreterService
getInterpreterDetails(pythonPath: string, _resource?: Uri): Promise<undefined | PythonEnvironment>;
// IInterpreterHelper
getInterpreterInformation(pythonPath: string): Promise<undefined | Partial<PythonEnvironment>>;
isMacDefaultPythonPath(pythonPath: string): Promise<boolean | undefined>;
// ICondaService
isCondaEnvironment(interpreterPath: string): Promise<boolean | undefined>;
getCondaEnvironment(interpreterPath: string): Promise<CondaEnvironmentInfo | undefined>;
// IWindowsStoreInterpreter
isWindowsStoreInterpreter(pythonPath: string): Promise<boolean | undefined>;
}

export const IInterpreterLocatorService = Symbol('IInterpreterLocatorService');

export interface IInterpreterLocatorService extends Disposable {
Expand Down
284 changes: 273 additions & 11 deletions src/client/pythonEnvironments/legacyIOC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
// Licensed under the MIT License.

import { injectable } from 'inversify';
import * as vscode from 'vscode';
import { createDeferred } from '../common/utils/async';
import { Architecture } from '../common/utils/platform';
import { getVersionString, parseVersion } from '../common/utils/version';
import {
CONDA_ENV_FILE_SERVICE,
CONDA_ENV_SERVICE,
CURRENT_PATH_SERVICE,
GLOBAL_VIRTUAL_ENV_SERVICE,
IComponentAdapter,
ICondaService,
IInterpreterLocatorHelper,
IInterpreterLocatorProgressService,
Expand All @@ -23,10 +28,13 @@ import {
} from '../interpreter/contracts';
import { IPipEnvServiceHelper, IPythonInPathCommandProvider } from '../interpreter/locators/types';
import { IServiceContainer, IServiceManager } from '../ioc/types';
import { PythonEnvInfo, PythonEnvKind, PythonReleaseLevel } from './base/info';
import { ILocator, PythonLocatorQuery } from './base/locator';
import { initializeExternalDependencies } from './common/externalDependencies';
import { PythonInterpreterLocatorService } from './discovery/locators';
import { InterpreterLocatorHelper } from './discovery/locators/helpers';
import { InterpreterLocatorProgressService } from './discovery/locators/progressService';
import { CondaEnvironmentInfo } from './discovery/locators/services/conda';
import { CondaEnvFileService } from './discovery/locators/services/condaEnvFileService';
import { CondaEnvService } from './discovery/locators/services/condaEnvService';
import { CondaService } from './discovery/locators/services/condaService';
Expand All @@ -48,31 +56,285 @@ import {
WorkspaceVirtualEnvService,
} from './discovery/locators/services/workspaceVirtualEnvService';
import { WorkspaceVirtualEnvWatcherService } from './discovery/locators/services/workspaceVirtualEnvWatcherService';
import { EnvironmentType, PythonEnvironment } from './info';
import { EnvironmentInfoService, IEnvironmentInfoService } from './info/environmentInfoService';

import { PythonEnvironments } from '.';
const convertedKinds = new Map(Object.entries({
[PythonEnvKind.System]: EnvironmentType.System,
[PythonEnvKind.MacDefault]: EnvironmentType.System,
[PythonEnvKind.WindowsStore]: EnvironmentType.WindowsStore,
[PythonEnvKind.Pyenv]: EnvironmentType.Pyenv,
[PythonEnvKind.Conda]: EnvironmentType.Conda,
[PythonEnvKind.CondaBase]: EnvironmentType.Conda,
[PythonEnvKind.VirtualEnv]: EnvironmentType.VirtualEnv,
[PythonEnvKind.Pipenv]: EnvironmentType.Pipenv,
[PythonEnvKind.Venv]: EnvironmentType.Venv,
}));

export const IComponentAdapter = Symbol('IComponentAdapter');
export interface IComponentAdapter {
// We will fill in the API separately.
function convertEnvInfo(info: PythonEnvInfo): PythonEnvironment {
const {
name,
location,
executable,
arch,
kind,
searchLocation,
version,
distro,
} = info;
const { filename, sysPrefix } = executable;
const env: PythonEnvironment = {
sysPrefix,
envType: EnvironmentType.Unknown,
envName: name,
envPath: location,
path: filename,
architecture: arch,
};

const envType = convertedKinds.get(kind);
if (envType !== undefined) {
env.envType = envType;
}
// Otherwise it stays Unknown.

if (searchLocation !== undefined) {
if (kind === PythonEnvKind.Pipenv) {
env.pipEnvWorkspaceFolder = searchLocation.fsPath;
}
}

if (version !== undefined) {
const { release, sysVersion } = version;
const { level, serial } = release;
const releaseStr = level === PythonReleaseLevel.Final
? 'final'
: `${level}${serial}`;
const versionStr = `${getVersionString(version)}-${releaseStr}`;
env.version = parseVersion(versionStr);
env.sysVersion = sysVersion;
}

if (distro !== undefined && distro.org !== '') {
env.companyDisplayName = distro.org;
}
// We do not worry about using distro.defaultDisplayName
// or info.defaultDisplayName.

return env;
}

function buildEmptyEnvInfo(): PythonEnvInfo {
return {
id: '',
kind: PythonEnvKind.Unknown,
executable: {
filename: '',
sysPrefix: '',
ctime: -1,
mtime: -1,
},
name: '',
location: '',
version: {
major: -1,
minor: -1,
micro: -1,
release: {
level: PythonReleaseLevel.Final,
serial: 0,
},
},
arch: Architecture.Unknown,
distro: {
org: '',
},
};
}

interface IPythonEnvironments extends ILocator {}

@injectable()
class ComponentAdapter implements IComponentAdapter {
constructor(
// The adapter only wraps one thing: the component API.
private readonly api: PythonEnvironments
) {
// For the moment we use this placeholder to exercise the property.
if (this.api.onChanged) {
this.api.onChanged((_event) => {
// do nothing
private readonly api: IPythonEnvironments,
// For now we effectively disable the component.
private readonly enabled = false,
) {}

// IInterpreterHelper

// A result of `undefined` means "Fall back to the old code!"
public async getInterpreterInformation(pythonPath: string): Promise<undefined | Partial<PythonEnvironment>> {
if (!this.enabled) {
return undefined;
}
const env = await this.api.resolveEnv(pythonPath);
if (env === undefined) {
return undefined;
}
return convertEnvInfo(env);
}

// A result of `undefined` means "Fall back to the old code!"
public async isMacDefaultPythonPath(pythonPath: string): Promise<boolean | undefined> {
if (!this.enabled) {
return undefined;
}
const env = await this.api.resolveEnv(pythonPath);
if (env === undefined) {
return undefined;
}
return env.kind === PythonEnvKind.MacDefault;
}

// IInterpreterService

// A result of `undefined` means "Fall back to the old code!"
public get hasInterpreters(): Promise<boolean | undefined> {
if (!this.enabled) {
return Promise.resolve(undefined);
}
const iterator = this.api.iterEnvs();
return iterator.next().then((res) => !res.done);
}

// We use the same getInterpreters() here as for IInterpreterLocatorService.

// A result of `undefined` means "Fall back to the old code!"
public async getInterpreterDetails(
pythonPath: string,
resource?: vscode.Uri,
): Promise<undefined | PythonEnvironment> {
if (!this.enabled) {
return undefined;
}
const info = buildEmptyEnvInfo();
info.executable.filename = pythonPath;
if (resource !== undefined) {
const wsFolder = vscode.workspace.getWorkspaceFolder(resource);
if (wsFolder !== undefined) {
info.searchLocation = wsFolder.uri;
}
}
const env = await this.api.resolveEnv(info);
if (env === undefined) {
return undefined;
}
return convertEnvInfo(env);
}

// ICondaService

// A result of `undefined` means "Fall back to the old code!"
public async isCondaEnvironment(interpreterPath: string): Promise<boolean | undefined> {
if (!this.enabled) {
return undefined;
}
const env = await this.api.resolveEnv(interpreterPath);
if (env === undefined) {
return undefined;
}
return env.kind === PythonEnvKind.Conda;
}

// A result of `undefined` means "Fall back to the old code!"
public async getCondaEnvironment(interpreterPath: string): Promise<CondaEnvironmentInfo | undefined> {
if (!this.enabled) {
return undefined;
}
const env = await this.api.resolveEnv(interpreterPath);
if (env === undefined) {
return undefined;
}
if (env.kind !== PythonEnvKind.Conda) {
return undefined;
}
if (env.name !== '') {
return { name: env.name, path: '' };
}
// else
return { name: '', path: env.location };
}

// IWindowsStoreInterpreter

// A result of `undefined` means "Fall back to the old code!"
public async isWindowsStoreInterpreter(pythonPath: string): Promise<boolean | undefined> {
if (!this.enabled) {
return undefined;
}
const env = await this.api.resolveEnv(pythonPath);
if (env === undefined) {
return undefined;
}
return env.kind === PythonEnvKind.WindowsStore;
}

// IInterpreterLocatorService

// A result of `undefined` means "Fall back to the old code!"
public async getInterpreters(
resource?: vscode.Uri,
// Currently we have no plans to support GetInterpreterLocatorOptions:
// {
// ignoreCache?: boolean
// onSuggestion?: boolean;
// }
): Promise<PythonEnvironment[] | undefined> {
if (!this.enabled) {
return undefined;
}
const query: PythonLocatorQuery = {};
if (resource !== undefined) {
const wsFolder = vscode.workspace.getWorkspaceFolder(resource);
if (wsFolder !== undefined) {
query.searchLocations = [wsFolder.uri];
}
}

const deferred = createDeferred<PythonEnvironment[]>();
const envs: PythonEnvironment[] = [];
const executableToLegacy: Record<string, PythonEnvironment> = {};
const iterator = this.api.iterEnvs(query);

if (iterator.onUpdated !== undefined) {
iterator.onUpdated((event) => {
if (event === null) {
deferred.resolve(envs);
} else {
// Replace the old one.
const old = executableToLegacy[event.old.executable.filename];
if (old !== undefined) {
const index = envs.indexOf(old);
if (index !== -1) {
envs[index] = convertEnvInfo(event.new);
}
}
}
});
} else {
deferred.resolve(envs);
}

let res = await iterator.next();
while (!res.done) {
const env = convertEnvInfo(res.value);
envs.push(env);
executableToLegacy[env.path] = env;
res = await iterator.next(); // eslint-disable-line no-await-in-loop
}

return deferred.promise;
}
}

export function registerForIOC(serviceManager: IServiceManager, serviceContainer: IServiceContainer, api: PythonEnvironments): void {
export function registerForIOC(
serviceManager: IServiceManager,
serviceContainer: IServiceContainer,
api: IPythonEnvironments,
): void {
const adapter = new ComponentAdapter(api);
serviceManager.addSingletonInstance<IComponentAdapter>(IComponentAdapter, adapter);

Expand Down