From 0047176fa623bbd1b451c0662f541f84eb1e5ce7 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Mon, 21 Sep 2020 11:15:51 -0700 Subject: [PATCH 01/10] Add some heuristic functions --- src/client/pythonEnvironments/base/info/index.ts | 1 + src/client/pythonEnvironments/info/index.ts | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/client/pythonEnvironments/base/info/index.ts b/src/client/pythonEnvironments/base/info/index.ts index c24cf8dc2aa3..1177f7435c6a 100644 --- a/src/client/pythonEnvironments/base/info/index.ts +++ b/src/client/pythonEnvironments/base/info/index.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import * as path from 'path'; import { Uri } from 'vscode'; import { Architecture } from '../../../common/utils/platform'; import { BasicVersionInfo, VersionInfo } from '../../../common/utils/version'; diff --git a/src/client/pythonEnvironments/info/index.ts b/src/client/pythonEnvironments/info/index.ts index ba0c90b5ab68..51fc84285638 100644 --- a/src/client/pythonEnvironments/info/index.ts +++ b/src/client/pythonEnvironments/info/index.ts @@ -92,7 +92,7 @@ export function normalizeEnvironment(environment: PartialPythonEnvironment): voi /** * Convert the Python environment type to a user-facing name. */ -export function getEnvironmentTypeName(environmentType: EnvironmentType) { +export function getEnvironmentTypeName(environmentType: EnvironmentType): string { switch (environmentType) { case EnvironmentType.Conda: { return 'conda'; @@ -109,6 +109,12 @@ export function getEnvironmentTypeName(environmentType: EnvironmentType) { case EnvironmentType.VirtualEnv: { return 'virtualenv'; } + case EnvironmentType.WindowsStore: { + return 'windows store'; + } + case EnvironmentType.Poetry: { + return 'poetry'; + } default: { return ''; } @@ -121,7 +127,7 @@ export function getEnvironmentTypeName(environmentType: EnvironmentType) { * @param environment1 - one of the two envs to compare * @param environment2 - one of the two envs to compare */ -export function areSameEnvironment( +export function areSamePartialEnvironment( environment1: PartialPythonEnvironment | undefined, environment2: PartialPythonEnvironment | undefined, fs: IFileSystem, @@ -185,7 +191,7 @@ export function mergeEnvironments( fs: IFileSystem, ): PartialPythonEnvironment[] { return environments.reduce((accumulator, current) => { - const existingItem = accumulator.find((item) => areSameEnvironment(current, item, fs)); + const existingItem = accumulator.find((item) => areSamePartialEnvironment(current, item, fs)); if (!existingItem) { const copied: PartialPythonEnvironment = { ...current }; normalizeEnvironment(copied); From 89d7b820f2b9d456192b952badb849ad6043162d Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Mon, 21 Sep 2020 15:37:42 -0700 Subject: [PATCH 02/10] Support merging. --- .../pythonEnvironments/base/info/index.ts | 175 +++++++++++++++--- .../common/environmentIdentifier.ts | 2 + 2 files changed, 156 insertions(+), 21 deletions(-) diff --git a/src/client/pythonEnvironments/base/info/index.ts b/src/client/pythonEnvironments/base/info/index.ts index 1177f7435c6a..7ccd0a9576ed 100644 --- a/src/client/pythonEnvironments/base/info/index.ts +++ b/src/client/pythonEnvironments/base/info/index.ts @@ -5,7 +5,8 @@ import * as path from 'path'; import { Uri } from 'vscode'; import { Architecture } from '../../../common/utils/platform'; import { BasicVersionInfo, VersionInfo } from '../../../common/utils/version'; -import { arePathsSame } from '../../common/externalDependencies'; +import { arePathsSame, pathExists } from '../../common/externalDependencies'; +import { EnvironmentType } from '../../info'; /** * IDs for the various supported Python environments. @@ -18,11 +19,13 @@ export enum PythonEnvKind { WindowsStore = 'global-windows-store', Pyenv = 'global-pyenv', CondaBase = 'global-conda-base', + Poetry = 'global-poetry', Custom = 'global-custom', OtherGlobal = 'global-other', // "virtual" Venv = 'virt-venv', VirtualEnv = 'virt-virtualenv', + VirtualEnvWrapper = 'virt-virtualenvwrapper', Pipenv = 'virt-pipenv', Conda = 'virt-conda', OtherVirtual = 'virt-other' @@ -146,30 +149,160 @@ export type PythonEnvInfo = _PythonEnvInfo & { searchLocation?: Uri; }; +export function areSameVersion(left: PythonVersion, right:PythonVersion): boolean { + return ( + left.major === right.major + && left.minor === right.minor + && left.micro === right.micro + && left.release.level === right.release.level + ); +} + +export function getPrioritizedEnvironmentKind(): PythonEnvKind[] { + return [ + PythonEnvKind.CondaBase, + PythonEnvKind.Conda, + PythonEnvKind.WindowsStore, + PythonEnvKind.Pipenv, + PythonEnvKind.Pyenv, + PythonEnvKind.Poetry, + PythonEnvKind.Venv, + PythonEnvKind.VirtualEnvWrapper, + PythonEnvKind.VirtualEnv, + PythonEnvKind.MacDefault, + PythonEnvKind.System, + PythonEnvKind.Custom, + PythonEnvKind.OtherVirtual, + PythonEnvKind.OtherGlobal, + PythonEnvKind.Unknown, + ]; +} + /** - * Determine if the given infos correspond to the same env. - * - * @param environment1 - one of the two envs to compare - * @param environment2 - one of the two envs to compare + * @deprecated */ -export function areSameEnvironment( - environment1: PythonEnvInfo | string, - environment2: PythonEnvInfo | string, -): boolean { - let path1: string; - let path2: string; - if (typeof environment1 === 'string') { - path1 = environment1; - } else { - path1 = environment1.executable.filename; +export function getPrioritizedEnvironmentType():EnvironmentType[] { + return [ + EnvironmentType.Conda, + EnvironmentType.WindowsStore, + EnvironmentType.Pipenv, + EnvironmentType.Pyenv, + EnvironmentType.Poetry, + EnvironmentType.Venv, + EnvironmentType.VirtualEnvWrapper, + EnvironmentType.VirtualEnv, + EnvironmentType.Global, + EnvironmentType.System, + EnvironmentType.Unknown, + ]; +} + +export function areSameEnvironment(left: PythonEnvInfo, right: PythonEnvInfo): boolean { + if (arePathsSame(left.executable.filename, right.executable.filename)) { + return true; } - if (typeof environment2 === 'string') { - path2 = environment2; - } else { - path2 = environment2.executable.filename; + if (!areSameVersion(left.version, right.version)) { + return false; } - if (arePathsSame(path1, path2)) { + if (arePathsSame(path.dirname(left.executable.filename), path.dirname(right.executable.filename))) { return true; } - return false; + return true; +} + +/** + * Returns a heuristic value on how much information is available in the given version object. + * @param {PythonVersion} version version object to generate heuristic from. + * @returns A heuristic value indicating the amount of info available in the object + * weighted by most important to least important fields. + * Wn > Wn-1 + Wn-2 + ... W0 + */ +function getVersionInfoHeuristic(version:PythonVersion): number { + let infoLevel = 0; + if (version.major > 0) { + infoLevel += 20; // W4 + } + + if (version.minor >= 0) { + infoLevel += 10; // W3 + } + + if (version.micro >= 0) { + infoLevel += 5; // W2 + } + + if (version.release.level) { + infoLevel += 3; // W1 + } + + if (version.release.serial || version.sysVersion) { + infoLevel += 1; // W0 + } + + return infoLevel; +} + +/** + * Returns a heuristic value on how much information is available in the given executable object. + * @param {PythonExecutableInfo} executable executable object to generate heuristic from. + * @returns A heuristic value indicating the amount of info available in the object + * weighted by most important to least important fields. + * Wn > Wn-1 + Wn-2 + ... W0 + */ +function getExecutableInfoHeuristic(executable:PythonExecutableInfo): number { + let infoLevel = 0; + if (executable.filename.length > 0) { + infoLevel += 10; // W3 + } + + if (executable.sysPrefix.length > 0) { + infoLevel += 5; // W2 + } + + if (executable.mtime) { + infoLevel += 2; // W1 + } + + if (executable.ctime) { + infoLevel += 1; // W0 + } + + return infoLevel; +} + +/** + * Selects an environment kind based on the environment selection priority. This should + * match the priority in the environment identifier. + * @param left + * @param right + */ +function pickEnvironmentKind(left: PythonEnvInfo, right: PythonEnvInfo): PythonEnvKind { + // tslint:disable-next-line: no-suspicious-comment + // TODO: When we consolidate the PythonEnvKind and EnvironmentType we should have + // one location where we define priority and + const envKindByPriority:PythonEnvKind[] = getPrioritizedEnvironmentKind(); + + return envKindByPriority.find((env) => left.kind === env || right.kind === env) ?? PythonEnvKind.Unknown; +} + +export function mergeEnvironments(left: PythonEnvInfo, right: PythonEnvInfo): PythonEnvInfo { + const kind = pickEnvironmentKind(left, right); + const version = (getVersionInfoHeuristic(left.version) > getVersionInfoHeuristic(right.version) + ? left.version : right.version + ); + const executable = (getExecutableInfoHeuristic(left.executable) > getExecutableInfoHeuristic(right.executable) + ? left.executable : right.executable + ); + const preferredEnv:PythonEnvInfo = left.kind === kind ? left : right; + + return { + ...preferredEnv, + id: '', // should we copy id from the preferred env? + executable: { ...executable }, + version: { + ...version, + release: { ...version.release }, + }, + distro: { ...preferredEnv.distro }, + }; } diff --git a/src/client/pythonEnvironments/common/environmentIdentifier.ts b/src/client/pythonEnvironments/common/environmentIdentifier.ts index b56c84e2b73e..4c56023c4ed6 100644 --- a/src/client/pythonEnvironments/common/environmentIdentifier.ts +++ b/src/client/pythonEnvironments/common/environmentIdentifier.ts @@ -34,6 +34,8 @@ import { EnvironmentType } from '../info'; * Last category is globally installed python, or system python. */ export async function identifyEnvironment(interpreterPath: string): Promise { + const notImplemented = () => false; + if (await isCondaEnvironment(interpreterPath)) { return EnvironmentType.Conda; } From 7d60921ffd66d2cab6d6ed75e4fbd4666753b1fd Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Mon, 21 Sep 2020 20:32:22 -0700 Subject: [PATCH 03/10] Clean up identifier code --- .../pythonEnvironments/base/info/index.ts | 62 +++++--------- .../common/environmentIdentifier.ts | 81 +++++++++++-------- 2 files changed, 70 insertions(+), 73 deletions(-) diff --git a/src/client/pythonEnvironments/base/info/index.ts b/src/client/pythonEnvironments/base/info/index.ts index 7ccd0a9576ed..03fa2101b98a 100644 --- a/src/client/pythonEnvironments/base/info/index.ts +++ b/src/client/pythonEnvironments/base/info/index.ts @@ -5,8 +5,7 @@ import * as path from 'path'; import { Uri } from 'vscode'; import { Architecture } from '../../../common/utils/platform'; import { BasicVersionInfo, VersionInfo } from '../../../common/utils/version'; -import { arePathsSame, pathExists } from '../../common/externalDependencies'; -import { EnvironmentType } from '../../info'; +import { arePathsSame } from '../../common/externalDependencies'; /** * IDs for the various supported Python environments. @@ -158,45 +157,6 @@ export function areSameVersion(left: PythonVersion, right:PythonVersion): boolea ); } -export function getPrioritizedEnvironmentKind(): PythonEnvKind[] { - return [ - PythonEnvKind.CondaBase, - PythonEnvKind.Conda, - PythonEnvKind.WindowsStore, - PythonEnvKind.Pipenv, - PythonEnvKind.Pyenv, - PythonEnvKind.Poetry, - PythonEnvKind.Venv, - PythonEnvKind.VirtualEnvWrapper, - PythonEnvKind.VirtualEnv, - PythonEnvKind.MacDefault, - PythonEnvKind.System, - PythonEnvKind.Custom, - PythonEnvKind.OtherVirtual, - PythonEnvKind.OtherGlobal, - PythonEnvKind.Unknown, - ]; -} - -/** - * @deprecated - */ -export function getPrioritizedEnvironmentType():EnvironmentType[] { - return [ - EnvironmentType.Conda, - EnvironmentType.WindowsStore, - EnvironmentType.Pipenv, - EnvironmentType.Pyenv, - EnvironmentType.Poetry, - EnvironmentType.Venv, - EnvironmentType.VirtualEnvWrapper, - EnvironmentType.VirtualEnv, - EnvironmentType.Global, - EnvironmentType.System, - EnvironmentType.Unknown, - ]; -} - export function areSameEnvironment(left: PythonEnvInfo, right: PythonEnvInfo): boolean { if (arePathsSame(left.executable.filename, right.executable.filename)) { return true; @@ -270,6 +230,26 @@ function getExecutableInfoHeuristic(executable:PythonExecutableInfo): number { return infoLevel; } +export function getPrioritizedEnvironmentKind(): PythonEnvKind[] { + return [ + PythonEnvKind.CondaBase, + PythonEnvKind.Conda, + PythonEnvKind.WindowsStore, + PythonEnvKind.Pipenv, + PythonEnvKind.Pyenv, + PythonEnvKind.Poetry, + PythonEnvKind.Venv, + PythonEnvKind.VirtualEnvWrapper, + PythonEnvKind.VirtualEnv, + PythonEnvKind.MacDefault, + PythonEnvKind.System, + PythonEnvKind.Custom, + PythonEnvKind.OtherVirtual, + PythonEnvKind.OtherGlobal, + PythonEnvKind.Unknown, + ]; +} + /** * Selects an environment kind based on the environment selection priority. This should * match the priority in the environment identifier. diff --git a/src/client/pythonEnvironments/common/environmentIdentifier.ts b/src/client/pythonEnvironments/common/environmentIdentifier.ts index 4c56023c4ed6..50ec65b0b25d 100644 --- a/src/client/pythonEnvironments/common/environmentIdentifier.ts +++ b/src/client/pythonEnvironments/common/environmentIdentifier.ts @@ -11,9 +11,8 @@ import { isWindowsStoreEnvironment } from '../discovery/locators/services/window import { EnvironmentType } from '../info'; /** - * Returns environment type. - * @param {string} interpreterPath : Absolute path to the python interpreter binary. - * @returns {EnvironmentType} + * Gets a prioritizes list of environment types for identification. + * @deprecated * * Remarks: This is the order of detection based on how the various distributions and tools * configure the environment, and the fall back for identification. @@ -33,38 +32,56 @@ import { EnvironmentType } from '../info'; * * Last category is globally installed python, or system python. */ -export async function identifyEnvironment(interpreterPath: string): Promise { - const notImplemented = () => false; - - if (await isCondaEnvironment(interpreterPath)) { - return EnvironmentType.Conda; - } - - if (await isWindowsStoreEnvironment(interpreterPath)) { - return EnvironmentType.WindowsStore; - } - - if (await isPipenvEnvironment(interpreterPath)) { - return EnvironmentType.Pipenv; - } - - if (await isPyenvEnvironment(interpreterPath)) { - return EnvironmentType.Pyenv; - } +export function getPrioritizedEnvironmentType():EnvironmentType[] { + return [ + EnvironmentType.Conda, + EnvironmentType.WindowsStore, + EnvironmentType.Pipenv, + EnvironmentType.Pyenv, + EnvironmentType.Poetry, + EnvironmentType.Venv, + EnvironmentType.VirtualEnvWrapper, + EnvironmentType.VirtualEnv, + EnvironmentType.Global, + EnvironmentType.System, + EnvironmentType.Unknown, + ]; +} - if (await isVenvEnvironment(interpreterPath)) { - return EnvironmentType.Venv; - } +function getIdentifiers(): Map Promise> { + const notImplemented = () => Promise.resolve(false); + const defaultTrue = () => Promise.resolve(true); + const identifier: Map Promise> = new Map(); + Object.keys(EnvironmentType).forEach((k:string) => { + identifier.set(k as EnvironmentType, notImplemented); + }); - if (await isVirtualenvwrapperEnvironment(interpreterPath)) { - return EnvironmentType.VirtualEnvWrapper; - } + identifier.set(EnvironmentType.Conda, isCondaEnvironment); + identifier.set(EnvironmentType.WindowsStore, isWindowsStoreEnvironment); + identifier.set(EnvironmentType.Pipenv, isPipenvEnvironment); + identifier.set(EnvironmentType.Pyenv, isPyenvEnvironment); + identifier.set(EnvironmentType.Venv, isVenvEnvironment); + identifier.set(EnvironmentType.VirtualEnvWrapper, isVirtualenvwrapperEnvironment); + identifier.set(EnvironmentType.VirtualEnv, isVirtualenvEnvironment); + identifier.set(EnvironmentType.Unknown, defaultTrue); + return identifier; +} - if (await isVirtualenvEnvironment(interpreterPath)) { - return EnvironmentType.VirtualEnv; +/** + * Returns environment type. + * @param {string} interpreterPath : Absolute path to the python interpreter binary. + * @returns {EnvironmentType} + */ +export async function identifyEnvironment(interpreterPath: string): Promise { + const identifiers = getIdentifiers(); + const prioritizedEnvTypes = getPrioritizedEnvironmentType(); + // eslint-disable-next-line no-restricted-syntax + for (const e of prioritizedEnvTypes) { + const identifier = identifiers.get(e); + // eslint-disable-next-line no-await-in-loop + if (identifier && await identifier(interpreterPath)) { + return e; + } } - - // additional identifiers go here - return EnvironmentType.Unknown; } From e1f5d6502b4140f5bb2a02527cb792dbfaaf8082 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Tue, 22 Sep 2020 11:58:04 -0700 Subject: [PATCH 04/10] Tweaks and fixes --- .../pythonEnvironments/base/info/index.ts | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/client/pythonEnvironments/base/info/index.ts b/src/client/pythonEnvironments/base/info/index.ts index 03fa2101b98a..5e0023f4c246 100644 --- a/src/client/pythonEnvironments/base/info/index.ts +++ b/src/client/pythonEnvironments/base/info/index.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { cloneDeep } from 'lodash'; import * as path from 'path'; import { Uri } from 'vscode'; import { Architecture } from '../../../common/utils/platform'; @@ -167,7 +168,7 @@ export function areSameEnvironment(left: PythonEnvInfo, right: PythonEnvInfo): b if (arePathsSame(path.dirname(left.executable.filename), path.dirname(right.executable.filename))) { return true; } - return true; + return false; } /** @@ -274,15 +275,12 @@ export function mergeEnvironments(left: PythonEnvInfo, right: PythonEnvInfo): Py ? left.executable : right.executable ); const preferredEnv:PythonEnvInfo = left.kind === kind ? left : right; + const merged = cloneDeep(preferredEnv); + merged.version = cloneDeep(version); + merged.executable = cloneDeep(executable); - return { - ...preferredEnv, - id: '', // should we copy id from the preferred env? - executable: { ...executable }, - version: { - ...version, - release: { ...version.release }, - }, - distro: { ...preferredEnv.distro }, - }; + // tslint:disable-next-line: no-suspicious-comment + // TODO: compute id for the merged environment + merged.id = ''; + return merged; } From a4f8a9db7f4f5b8fff597b77586749574ca5b9d6 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Tue, 22 Sep 2020 14:06:14 -0700 Subject: [PATCH 05/10] More clean up. --- .../base/info/envInfoHelpers.ts | 178 ++++++++++++++++++ .../pythonEnvironments/base/info/index.ts | 139 -------------- .../base/info/versionHelpers.ts | 13 ++ .../common/environmentIdentifier.ts | 2 +- 4 files changed, 192 insertions(+), 140 deletions(-) create mode 100644 src/client/pythonEnvironments/base/info/envInfoHelpers.ts create mode 100644 src/client/pythonEnvironments/base/info/versionHelpers.ts diff --git a/src/client/pythonEnvironments/base/info/envInfoHelpers.ts b/src/client/pythonEnvironments/base/info/envInfoHelpers.ts new file mode 100644 index 000000000000..4062cf7a82b5 --- /dev/null +++ b/src/client/pythonEnvironments/base/info/envInfoHelpers.ts @@ -0,0 +1,178 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { cloneDeep } from 'lodash'; +import * as path from 'path'; +import { + PythonEnvInfo, PythonEnvKind, PythonExecutableInfo, PythonVersion, +} from '.'; +import { arePathsSame } from '../../common/externalDependencies'; +import { areSameVersion } from './versionHelpers'; + +export function areSameEnvironment( + left: string | PythonEnvInfo, + right: string | PythonEnvInfo, + allowPartialMatch?:boolean, +): boolean { + const leftFilename = typeof left === 'string' ? left : left.executable.filename; + const rightFilename = typeof right === 'string' ? right : right.executable.filename; + + if (arePathsSame(leftFilename, rightFilename)) { + return true; + } + + if (arePathsSame(path.dirname(leftFilename), path.dirname(rightFilename))) { + const leftVersion = typeof left === 'string' ? undefined : left.version; + const rightVersion = typeof right === 'string' ? undefined : right.version; + if (leftVersion && rightVersion) { + if (areSameVersion(leftVersion, rightVersion)) { + return true; + } + + if ( + allowPartialMatch + && leftVersion.major === rightVersion.major + && leftVersion.minor === rightVersion.minor + ) { + return true; + } + } + } + return false; +} + +/** + * Returns a heuristic value on how much information is available in the given version object. + * @param {PythonVersion} version version object to generate heuristic from. + * @returns A heuristic value indicating the amount of info available in the object + * weighted by most important to least important fields. + * Wn > Wn-1 + Wn-2 + ... W0 + */ +function getVersionInfoHeuristic(version:PythonVersion): number { + let infoLevel = 0; + if (version.major > 0) { + infoLevel += 20; // W4 + } + + if (version.minor >= 0) { + infoLevel += 10; // W3 + } + + if (version.micro >= 0) { + infoLevel += 5; // W2 + } + + if (version.release.level) { + infoLevel += 3; // W1 + } + + if (version.release.serial || version.sysVersion) { + infoLevel += 1; // W0 + } + + return infoLevel; +} + +/** + * Returns a heuristic value on how much information is available in the given executable object. + * @param {PythonExecutableInfo} executable executable object to generate heuristic from. + * @returns A heuristic value indicating the amount of info available in the object + * weighted by most important to least important fields. + * Wn > Wn-1 + Wn-2 + ... W0 + */ +function getExecutableInfoHeuristic(executable:PythonExecutableInfo): number { + let infoLevel = 0; + if (executable.filename.length > 0) { + infoLevel += 10; // W3 + } + + if (executable.sysPrefix.length > 0) { + infoLevel += 5; // W2 + } + + if (executable.mtime) { + infoLevel += 2; // W1 + } + + if (executable.ctime) { + infoLevel += 1; // W0 + } + + return infoLevel; +} + +/** + * Gets a prioritized list of environment types for identification. + * @returns {PythonEnvKind[]} : List of environments ordered by identification priority + * + * Remarks: This is the order of detection based on how the various distributions and tools + * configure the environment, and the fall back for identification. + * Top level we have the following environment types, since they leave a unique signature + * in the environment or * use a unique path for the environments they create. + * 1. Conda + * 2. Windows Store + * 3. PipEnv + * 4. Pyenv + * 5. Poetry + * + * Next level we have the following virtual environment tools. The are here because they + * are consumed by the tools above, and can also be used independently. + * 1. venv + * 2. virtualenvwrapper + * 3. virtualenv + * + * Last category is globally installed python, or system python. + */ +export function getPrioritizedEnvironmentKind(): PythonEnvKind[] { + return [ + PythonEnvKind.CondaBase, + PythonEnvKind.Conda, + PythonEnvKind.WindowsStore, + PythonEnvKind.Pipenv, + PythonEnvKind.Pyenv, + PythonEnvKind.Poetry, + PythonEnvKind.Venv, + PythonEnvKind.VirtualEnvWrapper, + PythonEnvKind.VirtualEnv, + PythonEnvKind.OtherVirtual, + PythonEnvKind.OtherGlobal, + PythonEnvKind.MacDefault, + PythonEnvKind.System, + PythonEnvKind.Custom, + PythonEnvKind.Unknown, + ]; +} + +/** + * Selects an environment kind based on the environment selection priority. This should + * match the priority in the environment identifier. + * @param left + * @param right + */ +function pickEnvironmentKind(left: PythonEnvInfo, right: PythonEnvInfo): PythonEnvKind { + // tslint:disable-next-line: no-suspicious-comment + // TODO: When we consolidate the PythonEnvKind and EnvironmentType we should have + // one location where we define priority and + const envKindByPriority:PythonEnvKind[] = getPrioritizedEnvironmentKind(); + + return envKindByPriority.find((env) => left.kind === env || right.kind === env) ?? PythonEnvKind.Unknown; +} + +export function mergeEnvironments(left: PythonEnvInfo, right: PythonEnvInfo): PythonEnvInfo { + const kind = pickEnvironmentKind(left, right); + const version = (getVersionInfoHeuristic(left.version) > getVersionInfoHeuristic(right.version) + ? left.version : right.version + ); + const executable = (getExecutableInfoHeuristic(left.executable) > getExecutableInfoHeuristic(right.executable) + ? left.executable : right.executable + ); + const preferredEnv:PythonEnvInfo = left.kind === kind ? left : right; + const merged = cloneDeep(preferredEnv); + merged.version = cloneDeep(version); + merged.executable = cloneDeep(executable); + + // tslint:disable-next-line: no-suspicious-comment + // TODO: compute id for the merged environment + merged.id = ''; + return merged; +} diff --git a/src/client/pythonEnvironments/base/info/index.ts b/src/client/pythonEnvironments/base/info/index.ts index 5e0023f4c246..656d4bdf498e 100644 --- a/src/client/pythonEnvironments/base/info/index.ts +++ b/src/client/pythonEnvironments/base/info/index.ts @@ -1,12 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { cloneDeep } from 'lodash'; -import * as path from 'path'; import { Uri } from 'vscode'; import { Architecture } from '../../../common/utils/platform'; import { BasicVersionInfo, VersionInfo } from '../../../common/utils/version'; -import { arePathsSame } from '../../common/externalDependencies'; /** * IDs for the various supported Python environments. @@ -148,139 +145,3 @@ export type PythonEnvInfo = _PythonEnvInfo & { defaultDisplayName?: string; searchLocation?: Uri; }; - -export function areSameVersion(left: PythonVersion, right:PythonVersion): boolean { - return ( - left.major === right.major - && left.minor === right.minor - && left.micro === right.micro - && left.release.level === right.release.level - ); -} - -export function areSameEnvironment(left: PythonEnvInfo, right: PythonEnvInfo): boolean { - if (arePathsSame(left.executable.filename, right.executable.filename)) { - return true; - } - if (!areSameVersion(left.version, right.version)) { - return false; - } - if (arePathsSame(path.dirname(left.executable.filename), path.dirname(right.executable.filename))) { - return true; - } - return false; -} - -/** - * Returns a heuristic value on how much information is available in the given version object. - * @param {PythonVersion} version version object to generate heuristic from. - * @returns A heuristic value indicating the amount of info available in the object - * weighted by most important to least important fields. - * Wn > Wn-1 + Wn-2 + ... W0 - */ -function getVersionInfoHeuristic(version:PythonVersion): number { - let infoLevel = 0; - if (version.major > 0) { - infoLevel += 20; // W4 - } - - if (version.minor >= 0) { - infoLevel += 10; // W3 - } - - if (version.micro >= 0) { - infoLevel += 5; // W2 - } - - if (version.release.level) { - infoLevel += 3; // W1 - } - - if (version.release.serial || version.sysVersion) { - infoLevel += 1; // W0 - } - - return infoLevel; -} - -/** - * Returns a heuristic value on how much information is available in the given executable object. - * @param {PythonExecutableInfo} executable executable object to generate heuristic from. - * @returns A heuristic value indicating the amount of info available in the object - * weighted by most important to least important fields. - * Wn > Wn-1 + Wn-2 + ... W0 - */ -function getExecutableInfoHeuristic(executable:PythonExecutableInfo): number { - let infoLevel = 0; - if (executable.filename.length > 0) { - infoLevel += 10; // W3 - } - - if (executable.sysPrefix.length > 0) { - infoLevel += 5; // W2 - } - - if (executable.mtime) { - infoLevel += 2; // W1 - } - - if (executable.ctime) { - infoLevel += 1; // W0 - } - - return infoLevel; -} - -export function getPrioritizedEnvironmentKind(): PythonEnvKind[] { - return [ - PythonEnvKind.CondaBase, - PythonEnvKind.Conda, - PythonEnvKind.WindowsStore, - PythonEnvKind.Pipenv, - PythonEnvKind.Pyenv, - PythonEnvKind.Poetry, - PythonEnvKind.Venv, - PythonEnvKind.VirtualEnvWrapper, - PythonEnvKind.VirtualEnv, - PythonEnvKind.MacDefault, - PythonEnvKind.System, - PythonEnvKind.Custom, - PythonEnvKind.OtherVirtual, - PythonEnvKind.OtherGlobal, - PythonEnvKind.Unknown, - ]; -} - -/** - * Selects an environment kind based on the environment selection priority. This should - * match the priority in the environment identifier. - * @param left - * @param right - */ -function pickEnvironmentKind(left: PythonEnvInfo, right: PythonEnvInfo): PythonEnvKind { - // tslint:disable-next-line: no-suspicious-comment - // TODO: When we consolidate the PythonEnvKind and EnvironmentType we should have - // one location where we define priority and - const envKindByPriority:PythonEnvKind[] = getPrioritizedEnvironmentKind(); - - return envKindByPriority.find((env) => left.kind === env || right.kind === env) ?? PythonEnvKind.Unknown; -} - -export function mergeEnvironments(left: PythonEnvInfo, right: PythonEnvInfo): PythonEnvInfo { - const kind = pickEnvironmentKind(left, right); - const version = (getVersionInfoHeuristic(left.version) > getVersionInfoHeuristic(right.version) - ? left.version : right.version - ); - const executable = (getExecutableInfoHeuristic(left.executable) > getExecutableInfoHeuristic(right.executable) - ? left.executable : right.executable - ); - const preferredEnv:PythonEnvInfo = left.kind === kind ? left : right; - const merged = cloneDeep(preferredEnv); - merged.version = cloneDeep(version); - merged.executable = cloneDeep(executable); - - // tslint:disable-next-line: no-suspicious-comment - // TODO: compute id for the merged environment - merged.id = ''; - return merged; -} diff --git a/src/client/pythonEnvironments/base/info/versionHelpers.ts b/src/client/pythonEnvironments/base/info/versionHelpers.ts new file mode 100644 index 000000000000..ed1da491b2fb --- /dev/null +++ b/src/client/pythonEnvironments/base/info/versionHelpers.ts @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +import { PythonVersion } from '.'; + +// Licensed under the MIT License. +export function areSameVersion(left: PythonVersion, right:PythonVersion): boolean { + return ( + left.major === right.major + && left.minor === right.minor + && left.micro === right.micro + && left.release.level === right.release.level + ); +} diff --git a/src/client/pythonEnvironments/common/environmentIdentifier.ts b/src/client/pythonEnvironments/common/environmentIdentifier.ts index 50ec65b0b25d..3340cb01bdaf 100644 --- a/src/client/pythonEnvironments/common/environmentIdentifier.ts +++ b/src/client/pythonEnvironments/common/environmentIdentifier.ts @@ -11,7 +11,7 @@ import { isWindowsStoreEnvironment } from '../discovery/locators/services/window import { EnvironmentType } from '../info'; /** - * Gets a prioritizes list of environment types for identification. + * Gets a prioritized list of environment types for identification. * @deprecated * * Remarks: This is the order of detection based on how the various distributions and tools From a4392e5d856c2937459139d93cb15df20541c8f8 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Thu, 24 Sep 2020 16:02:32 -0700 Subject: [PATCH 06/10] versions tweak --- .../base/info/envInfoHelpers.ts | 4 +-- .../base/info/versionHelpers.ts | 32 +++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/client/pythonEnvironments/base/info/envInfoHelpers.ts b/src/client/pythonEnvironments/base/info/envInfoHelpers.ts index 4062cf7a82b5..19d944f9e9a1 100644 --- a/src/client/pythonEnvironments/base/info/envInfoHelpers.ts +++ b/src/client/pythonEnvironments/base/info/envInfoHelpers.ts @@ -7,7 +7,7 @@ import { PythonEnvInfo, PythonEnvKind, PythonExecutableInfo, PythonVersion, } from '.'; import { arePathsSame } from '../../common/externalDependencies'; -import { areSameVersion } from './versionHelpers'; +import { areEqualVersions } from './versionHelpers'; export function areSameEnvironment( left: string | PythonEnvInfo, @@ -25,7 +25,7 @@ export function areSameEnvironment( const leftVersion = typeof left === 'string' ? undefined : left.version; const rightVersion = typeof right === 'string' ? undefined : right.version; if (leftVersion && rightVersion) { - if (areSameVersion(leftVersion, rightVersion)) { + if (areEqualVersions(leftVersion, rightVersion)) { return true; } diff --git a/src/client/pythonEnvironments/base/info/versionHelpers.ts b/src/client/pythonEnvironments/base/info/versionHelpers.ts index ed1da491b2fb..d0943be10e1a 100644 --- a/src/client/pythonEnvironments/base/info/versionHelpers.ts +++ b/src/client/pythonEnvironments/base/info/versionHelpers.ts @@ -1,13 +1,41 @@ // Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. import { PythonVersion } from '.'; -// Licensed under the MIT License. -export function areSameVersion(left: PythonVersion, right:PythonVersion): boolean { +/** + * Checks if all the fields in the version object match. + * @param {PythonVersion} left + * @param {PythonVersion} right + * @returns {boolean} + */ +export function areEqualVersions(left: PythonVersion, right:PythonVersion): boolean { return ( left.major === right.major && left.minor === right.minor && left.micro === right.micro && left.release.level === right.release.level + && left.release.serial === right.release.serial + ); +} + +/** + * Checks if major and minor version fields match. True here means that the python ABI is the + * same, but the micro version could be different. But for the purpose this is being used + * it does not matter. + * @param {PythonVersion} left + * @param {PythonVersion} right + * @returns {boolean} + */ +export function areEquivalentVersions(left: PythonVersion, right:PythonVersion): boolean { + if (left.major === 2 && right.major === 2) { + // We are going to assume that if the major version is 2 then the version is 2.7 + return true; + } + + // In the case of 3.* if major and minor match we assume that they are equivalent versions + return ( + left.major === right.major + && left.minor === right.minor ); } From 6a3838fe2c2a786cf41e49a66e614d5f86482c9b Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Fri, 25 Sep 2020 00:17:31 -0700 Subject: [PATCH 07/10] Address comments --- .../base/info/{envInfoHelpers.ts => env.ts} | 121 ++++++++++++------ .../pythonEnvironments/base/info/index.ts | 12 +- .../base/info/pythonVersion.ts | 37 ++++++ .../base/info/versionHelpers.ts | 41 ------ .../locators/composite/environmentsReducer.ts | 7 +- 5 files changed, 133 insertions(+), 85 deletions(-) rename src/client/pythonEnvironments/base/info/{envInfoHelpers.ts => env.ts} (53%) delete mode 100644 src/client/pythonEnvironments/base/info/versionHelpers.ts diff --git a/src/client/pythonEnvironments/base/info/envInfoHelpers.ts b/src/client/pythonEnvironments/base/info/env.ts similarity index 53% rename from src/client/pythonEnvironments/base/info/envInfoHelpers.ts rename to src/client/pythonEnvironments/base/info/env.ts index 19d944f9e9a1..d056b42545a6 100644 --- a/src/client/pythonEnvironments/base/info/envInfoHelpers.ts +++ b/src/client/pythonEnvironments/base/info/env.ts @@ -4,10 +4,13 @@ import { cloneDeep } from 'lodash'; import * as path from 'path'; import { - PythonEnvInfo, PythonEnvKind, PythonExecutableInfo, PythonVersion, + FileInfo, + PythonDistroInfo, + PythonEnvInfo, PythonEnvKind, PythonVersion, } from '.'; +import { Architecture } from '../../../common/utils/platform'; import { arePathsSame } from '../../common/externalDependencies'; -import { areEqualVersions } from './versionHelpers'; +import { areEqualVersions, areEquivalentVersions } from './pythonVersion'; export function areSameEnvironment( left: string | PythonEnvInfo, @@ -25,14 +28,9 @@ export function areSameEnvironment( const leftVersion = typeof left === 'string' ? undefined : left.version; const rightVersion = typeof right === 'string' ? undefined : right.version; if (leftVersion && rightVersion) { - if (areEqualVersions(leftVersion, rightVersion)) { - return true; - } - if ( - allowPartialMatch - && leftVersion.major === rightVersion.major - && leftVersion.minor === rightVersion.minor + areEqualVersions(leftVersion, rightVersion) + || (allowPartialMatch && areEquivalentVersions(leftVersion, rightVersion)) ) { return true; } @@ -48,7 +46,7 @@ export function areSameEnvironment( * weighted by most important to least important fields. * Wn > Wn-1 + Wn-2 + ... W0 */ -function getVersionInfoHeuristic(version:PythonVersion): number { +function getPythonVersionInfoHeuristic(version:PythonVersion): number { let infoLevel = 0; if (version.major > 0) { infoLevel += 20; // W4 @@ -75,32 +73,56 @@ function getVersionInfoHeuristic(version:PythonVersion): number { /** * Returns a heuristic value on how much information is available in the given executable object. - * @param {PythonExecutableInfo} executable executable object to generate heuristic from. + * @param {FileInfo} executable executable object to generate heuristic from. * @returns A heuristic value indicating the amount of info available in the object * weighted by most important to least important fields. * Wn > Wn-1 + Wn-2 + ... W0 */ -function getExecutableInfoHeuristic(executable:PythonExecutableInfo): number { +function getFileInfoHeuristic(file:FileInfo): number { let infoLevel = 0; - if (executable.filename.length > 0) { - infoLevel += 10; // W3 - } - - if (executable.sysPrefix.length > 0) { + if (file.filename.length > 0) { infoLevel += 5; // W2 } - if (executable.mtime) { + if (file.mtime) { infoLevel += 2; // W1 } - if (executable.ctime) { + if (file.ctime || file.mtime) { infoLevel += 1; // W0 } return infoLevel; } +/** + * Returns a heuristic value on how much information is available in the given distro object. + * @param {PythonDistroInfo} distro distro object to generate heuristic from. + * @returns A heuristic value indicating the amount of info available in the object + * weighted by most important to least important fields. + * Wn > Wn-1 + Wn-2 + ... W0 + */ +function getDistroInfoHeuristic(distro:PythonDistroInfo):number { + let infoLevel = 0; + if (distro.org.length > 0) { + infoLevel += 20; // W3 + } + + if (distro.defaultDisplayName) { + infoLevel += 10; // W2 + } + + if (distro.binDir) { + infoLevel += 5; // W1 + } + + if (distro.version) { + infoLevel += 2; + } + + return infoLevel; +} + /** * Gets a prioritized list of environment types for identification. * @returns {PythonEnvKind[]} : List of environments ordered by identification priority @@ -144,35 +166,58 @@ export function getPrioritizedEnvironmentKind(): PythonEnvKind[] { } /** - * Selects an environment kind based on the environment selection priority. This should + * Selects an environment based on the environment selection priority. This should * match the priority in the environment identifier. - * @param left - * @param right */ -function pickEnvironmentKind(left: PythonEnvInfo, right: PythonEnvInfo): PythonEnvKind { +export function sortEnvInfoByPriority(...envs: PythonEnvInfo[]): PythonEnvInfo[] { // tslint:disable-next-line: no-suspicious-comment // TODO: When we consolidate the PythonEnvKind and EnvironmentType we should have // one location where we define priority and const envKindByPriority:PythonEnvKind[] = getPrioritizedEnvironmentKind(); - - return envKindByPriority.find((env) => left.kind === env || right.kind === env) ?? PythonEnvKind.Unknown; + return envs.sort( + (a:PythonEnvInfo, b:PythonEnvInfo) => envKindByPriority.indexOf(a.kind) - envKindByPriority.indexOf(b.kind), + ); } -export function mergeEnvironments(left: PythonEnvInfo, right: PythonEnvInfo): PythonEnvInfo { - const kind = pickEnvironmentKind(left, right); - const version = (getVersionInfoHeuristic(left.version) > getVersionInfoHeuristic(right.version) - ? left.version : right.version +/** + * Merges properties of the `target` environment and `other` environment and returns the merged environment. + * if the value in the `target` environment is not defined or has less information. This does not mutate + * the `target` instead it returns a new object that contains the merged results. + * @param {PythonEnvInfo} target : Properties of this object are favored. + * @param {PythonEnvInfo} other : Properties of this object are used to fill the gaps in the merged result. + */ +export function mergeEnvironments(target: PythonEnvInfo, other: PythonEnvInfo): PythonEnvInfo { + const merged = cloneDeep(target); + + const version = cloneDeep( + getPythonVersionInfoHeuristic(target.version) > getPythonVersionInfoHeuristic(other.version) + ? target.version : other.version, ); - const executable = (getExecutableInfoHeuristic(left.executable) > getExecutableInfoHeuristic(right.executable) - ? left.executable : right.executable + + const executable = cloneDeep( + getFileInfoHeuristic(target.executable) > getFileInfoHeuristic(other.executable) + ? target.executable : other.executable, ); - const preferredEnv:PythonEnvInfo = left.kind === kind ? left : right; - const merged = cloneDeep(preferredEnv); - merged.version = cloneDeep(version); - merged.executable = cloneDeep(executable); + executable.sysPrefix = target.executable.sysPrefix ?? other.executable.sysPrefix; + + const distro = cloneDeep( + getDistroInfoHeuristic(target.distro) > getDistroInfoHeuristic(other.distro) + ? target.distro : other.distro, + ); + + merged.arch = merged.arch === Architecture.Unknown ? other.arch : target.arch; + merged.defaultDisplayName = merged.defaultDisplayName ?? other.defaultDisplayName; + merged.distro = distro; + merged.executable = executable; + + // No need to check this just use preferred kind. Since the first thing we do is figure out the + // preferred env based on kind. + merged.kind = target.kind; + + merged.location = merged.location ?? other.location; + merged.name = merged.name ?? other.name; + merged.searchLocation = merged.searchLocation ?? other.searchLocation; + merged.version = version; - // tslint:disable-next-line: no-suspicious-comment - // TODO: compute id for the merged environment - merged.id = ''; return merged; } diff --git a/src/client/pythonEnvironments/base/info/index.ts b/src/client/pythonEnvironments/base/info/index.ts index 656d4bdf498e..5298a3d73f9e 100644 --- a/src/client/pythonEnvironments/base/info/index.ts +++ b/src/client/pythonEnvironments/base/info/index.ts @@ -29,15 +29,21 @@ export enum PythonEnvKind { } /** - * Information about a Python binary/executable. + * Information about a file. */ -export type PythonExecutableInfo = { +export type FileInfo = { filename: string; - sysPrefix: string; ctime: number; mtime: number; }; +/** + * Information about a Python binary/executable. + */ +export type PythonExecutableInfo = FileInfo & { + sysPrefix: string; +}; + /** * A (system-global) unique ID for a single Python environment. */ diff --git a/src/client/pythonEnvironments/base/info/pythonVersion.ts b/src/client/pythonEnvironments/base/info/pythonVersion.ts index 58248f2c1789..6ecaa24afe1c 100644 --- a/src/client/pythonEnvironments/base/info/pythonVersion.ts +++ b/src/client/pythonEnvironments/base/info/pythonVersion.ts @@ -33,3 +33,40 @@ export function parseVersion(versionStr: string): PythonVersion { } return version; } + +/** + * Checks if all the fields in the version object match. + * @param {PythonVersion} left + * @param {PythonVersion} right + * @returns {boolean} + */ +export function areEqualVersions(left: PythonVersion, right:PythonVersion): boolean { + return ( + left.major === right.major + && left.minor === right.minor + && left.micro === right.micro + && left.release.level === right.release.level + && left.release.serial === right.release.serial + ); +} + +/** + * Checks if major and minor version fields match. True here means that the python ABI is the + * same, but the micro version could be different. But for the purpose this is being used + * it does not matter. + * @param {PythonVersion} left + * @param {PythonVersion} right + * @returns {boolean} + */ +export function areEquivalentVersions(left: PythonVersion, right:PythonVersion): boolean { + if (left.major === 2 && right.major === 2) { + // We are going to assume that if the major version is 2 then the version is 2.7 + return true; + } + + // In the case of 3.* if major and minor match we assume that they are equivalent versions + return ( + left.major === right.major + && left.minor === right.minor + ); +} diff --git a/src/client/pythonEnvironments/base/info/versionHelpers.ts b/src/client/pythonEnvironments/base/info/versionHelpers.ts deleted file mode 100644 index d0943be10e1a..000000000000 --- a/src/client/pythonEnvironments/base/info/versionHelpers.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { PythonVersion } from '.'; - -/** - * Checks if all the fields in the version object match. - * @param {PythonVersion} left - * @param {PythonVersion} right - * @returns {boolean} - */ -export function areEqualVersions(left: PythonVersion, right:PythonVersion): boolean { - return ( - left.major === right.major - && left.minor === right.minor - && left.micro === right.micro - && left.release.level === right.release.level - && left.release.serial === right.release.serial - ); -} - -/** - * Checks if major and minor version fields match. True here means that the python ABI is the - * same, but the micro version could be different. But for the purpose this is being used - * it does not matter. - * @param {PythonVersion} left - * @param {PythonVersion} right - * @returns {boolean} - */ -export function areEquivalentVersions(left: PythonVersion, right:PythonVersion): boolean { - if (left.major === 2 && right.major === 2) { - // We are going to assume that if the major version is 2 then the version is 2.7 - return true; - } - - // In the case of 3.* if major and minor match we assume that they are equivalent versions - return ( - left.major === right.major - && left.minor === right.minor - ); -} diff --git a/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts b/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts index ba00eac2a546..a37fcb018ae4 100644 --- a/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts +++ b/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts @@ -3,9 +3,10 @@ import { cloneDeep, isEqual } from 'lodash'; import { Event, EventEmitter } from 'vscode'; -import { traceVerbose } from '../../../../common/logger'; -import { createDeferred } from '../../../../common/utils/async'; -import { areSameEnvironment, PythonEnvInfo, PythonEnvKind } from '../../info'; +import { traceVerbose } from '../../common/logger'; +import { createDeferred } from '../../common/utils/async'; +import { PythonEnvInfo, PythonEnvKind } from '../base/info'; +import { areSameEnvironment } from '../base/info/env'; import { ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, PythonLocatorQuery, } from '../../locator'; From 4cdfcb01a598f6f8f4afa5791de71d178ae6fa62 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Mon, 28 Sep 2020 11:01:35 -0700 Subject: [PATCH 08/10] more comments --- src/client/pythonEnvironments/base/info/env.ts | 16 ++++++++++++++-- .../base/info/pythonVersion.ts | 8 +------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/client/pythonEnvironments/base/info/env.ts b/src/client/pythonEnvironments/base/info/env.ts index d056b42545a6..5d88a27c0710 100644 --- a/src/client/pythonEnvironments/base/info/env.ts +++ b/src/client/pythonEnvironments/base/info/env.ts @@ -12,10 +12,22 @@ import { Architecture } from '../../../common/utils/platform'; import { arePathsSame } from '../../common/externalDependencies'; import { areEqualVersions, areEquivalentVersions } from './pythonVersion'; +/** + * Checks if two environments are same. + * @param {string | PythonEnvInfo} left: environment to compare. + * @param {string | PythonEnvInfo} right: environment to compare. + * @param {boolean} allowPartialMatch: allow partial matches of properties when comparing. + * + * Remarks: The current comparison assumes that if the path to the executables are the same + * then it is the same environment. Additionally, if the paths are not same but executables + * are in the same directory and the version of python is the same than we can assume it + * to be same environment. This later case is needed for comparing windows store python, + * where multiple versions of python executables are all put in the same directory. + */ export function areSameEnvironment( left: string | PythonEnvInfo, right: string | PythonEnvInfo, - allowPartialMatch?:boolean, + allowPartialMatch?: boolean, ): boolean { const leftFilename = typeof left === 'string' ? left : left.executable.filename; const rightFilename = typeof right === 'string' ? right : right.executable.filename; @@ -88,7 +100,7 @@ function getFileInfoHeuristic(file:FileInfo): number { infoLevel += 2; // W1 } - if (file.ctime || file.mtime) { + if (file.ctime) { infoLevel += 1; // W0 } diff --git a/src/client/pythonEnvironments/base/info/pythonVersion.ts b/src/client/pythonEnvironments/base/info/pythonVersion.ts index 6ecaa24afe1c..40e663c6231e 100644 --- a/src/client/pythonEnvironments/base/info/pythonVersion.ts +++ b/src/client/pythonEnvironments/base/info/pythonVersion.ts @@ -41,13 +41,7 @@ export function parseVersion(versionStr: string): PythonVersion { * @returns {boolean} */ export function areEqualVersions(left: PythonVersion, right:PythonVersion): boolean { - return ( - left.major === right.major - && left.minor === right.minor - && left.micro === right.micro - && left.release.level === right.release.level - && left.release.serial === right.release.serial - ); + return left === right; } /** From e40f8de2eda6da8e54f385ba3ca1d2df787b2dc6 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Mon, 28 Sep 2020 11:08:05 -0700 Subject: [PATCH 09/10] Fix merge issues. --- .../base/locators/composite/environmentsReducer.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts b/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts index a37fcb018ae4..617160c05b11 100644 --- a/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts +++ b/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts @@ -3,10 +3,10 @@ import { cloneDeep, isEqual } from 'lodash'; import { Event, EventEmitter } from 'vscode'; -import { traceVerbose } from '../../common/logger'; -import { createDeferred } from '../../common/utils/async'; -import { PythonEnvInfo, PythonEnvKind } from '../base/info'; -import { areSameEnvironment } from '../base/info/env'; +import { traceVerbose } from '../../../../common/logger'; +import { createDeferred } from '../../../../common/utils/async'; +import { PythonEnvInfo, PythonEnvKind } from '../../info'; +import { areSameEnvironment } from '../../info/env'; import { ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, PythonLocatorQuery, } from '../../locator'; From ecae3243d3b201b8277cac552a705373c3a4b89f Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Mon, 28 Sep 2020 11:09:22 -0700 Subject: [PATCH 10/10] Fix more merge issues. --- .../base/locators/composite/environmentsResolver.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts index 347785f73660..83ea21e726e5 100644 --- a/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts +++ b/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts @@ -5,7 +5,8 @@ import { cloneDeep } from 'lodash'; import { Event, EventEmitter } from 'vscode'; import { traceVerbose } from '../../../../common/logger'; import { IEnvironmentInfoService } from '../../../info/environmentInfoService'; -import { areSameEnvironment, PythonEnvInfo } from '../../info'; +import { PythonEnvInfo } from '../../info'; +import { areSameEnvironment } from '../../info/env'; import { InterpreterInformation } from '../../info/interpreter'; import { ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, PythonLocatorQuery,