Skip to content

Commit

Permalink
Add a legacy DI adapter for the Python envs component. (#13858)
Browse files Browse the repository at this point in the history
This allows us to start using the new discovery code in the extension.
  • Loading branch information
ericsnowcurrently committed Sep 16, 2020
1 parent a9b5603 commit 016cce8
Show file tree
Hide file tree
Showing 2 changed files with 290 additions and 11 deletions.
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

0 comments on commit 016cce8

Please sign in to comment.