diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index ded0c0ffa00e..c1ad838a9e10 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -31,6 +31,23 @@ export interface IVirtualEnvironmentsSearchPathProvider { getSearchPaths(resource?: Uri): Promise; } +export const IComponentAdapter = Symbol('IComponentAdapter'); +export interface IComponentAdapter { + // IInterpreterLocatorService + hasInterpreters: Promise; + getInterpreters(resource?: Uri): Promise; + // IInterpreterService + getInterpreterDetails(pythonPath: string, _resource?: Uri): Promise; + // IInterpreterHelper + getInterpreterInformation(pythonPath: string): Promise>; + isMacDefaultPythonPath(pythonPath: string): Promise; + // ICondaService + isCondaEnvironment(interpreterPath: string): Promise; + getCondaEnvironment(interpreterPath: string): Promise; + // IWindowsStoreInterpreter + isWindowsStoreInterpreter(pythonPath: string): Promise; +} + export const IInterpreterLocatorService = Symbol('IInterpreterLocatorService'); export interface IInterpreterLocatorService extends Disposable { diff --git a/src/client/pythonEnvironments/legacyIOC.ts b/src/client/pythonEnvironments/legacyIOC.ts index 5a3aee2741a1..49abb879ccbf 100644 --- a/src/client/pythonEnvironments/legacyIOC.ts +++ b/src/client/pythonEnvironments/legacyIOC.ts @@ -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, @@ -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'; @@ -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> { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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(); + const envs: PythonEnvironment[] = []; + const executableToLegacy: Record = {}; + 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, adapter);