Skip to content

Update reducer to use new implementation of merge environments #14152

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 5 commits into from
Oct 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
83 changes: 16 additions & 67 deletions src/client/pythonEnvironments/base/info/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export function buildEnvInfo(init?: {
},
name: '',
location: '',
searchLocation: undefined,
defaultDisplayName: undefined,
version: {
major: -1,
minor: -1,
Expand Down Expand Up @@ -109,7 +111,9 @@ export function getMinimalPartialInfo(env: string | Partial<PythonEnvInfo>): Par
return undefined;
}
return {
executable: { filename: env, sysPrefix: '', ctime: -1, mtime: -1 },
executable: {
filename: env, sysPrefix: '', ctime: -1, mtime: -1,
},
};
}
if (env.executable === undefined) {
Expand All @@ -136,7 +140,7 @@ export function getMinimalPartialInfo(env: string | Partial<PythonEnvInfo>): Par
export function areSameEnv(
left: string | Partial<PythonEnvInfo>,
right: string | Partial<PythonEnvInfo>,
allowPartialMatch?: boolean,
allowPartialMatch = true,
): boolean | undefined {
const leftInfo = getMinimalPartialInfo(left);
const rightInfo = getMinimalPartialInfo(right);
Expand Down Expand Up @@ -173,7 +177,7 @@ export function areSameEnv(
* weighted by most important to least important fields.
* Wn > Wn-1 + Wn-2 + ... W0
*/
function getPythonVersionInfoHeuristic(version:PythonVersion): number {
function getPythonVersionInfoHeuristic(version: PythonVersion): number {
let infoLevel = 0;
if (version.major > 0) {
infoLevel += 20; // W4
Expand Down Expand Up @@ -205,7 +209,7 @@ function getPythonVersionInfoHeuristic(version:PythonVersion): number {
* weighted by most important to least important fields.
* Wn > Wn-1 + Wn-2 + ... W0
*/
function getFileInfoHeuristic(file:FileInfo): number {
function getFileInfoHeuristic(file: FileInfo): number {
let infoLevel = 0;
if (file.filename.length > 0) {
infoLevel += 5; // W2
Expand All @@ -229,7 +233,7 @@ function getFileInfoHeuristic(file:FileInfo): number {
* weighted by most important to least important fields.
* Wn > Wn-1 + Wn-2 + ... W0
*/
function getDistroInfoHeuristic(distro:PythonDistroInfo):number {
function getDistroInfoHeuristic(distro: PythonDistroInfo): number {
let infoLevel = 0;
if (distro.org.length > 0) {
infoLevel += 20; // W3
Expand All @@ -250,62 +254,6 @@ function getDistroInfoHeuristic(distro:PythonDistroInfo):number {
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 based on the environment selection priority. This should
* match the priority in the environment identifier.
*/
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 envs.sort(
(a:PythonEnvInfo, b:PythonEnvInfo) => envKindByPriority.indexOf(a.kind) - envKindByPriority.indexOf(b.kind),
);
}

/**
* 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
Expand All @@ -318,18 +266,19 @@ export function mergeEnvironments(target: PythonEnvInfo, other: PythonEnvInfo):

const version = cloneDeep(
getPythonVersionInfoHeuristic(target.version) > getPythonVersionInfoHeuristic(other.version)
? target.version : other.version,
? target.version
: other.version,
);

const executable = cloneDeep(
getFileInfoHeuristic(target.executable) > getFileInfoHeuristic(other.executable)
? target.executable : other.executable,
? target.executable
: other.executable,
);
executable.sysPrefix = target.executable.sysPrefix ?? other.executable.sysPrefix;

const distro = cloneDeep(
getDistroInfoHeuristic(target.distro) > getDistroInfoHeuristic(other.distro)
? target.distro : other.distro,
getDistroInfoHeuristic(target.distro) > getDistroInfoHeuristic(other.distro) ? target.distro : other.distro,
);

merged.arch = merged.arch === Architecture.Unknown ? other.arch : target.arch;
Expand All @@ -341,8 +290,8 @@ export function mergeEnvironments(target: PythonEnvInfo, other: PythonEnvInfo):
// preferred env based on kind.
merged.kind = target.kind;

merged.location = merged.location ?? other.location;
merged.name = merged.name ?? other.name;
merged.location = merged.location.length ? merged.location : other.location;
merged.name = merged.name.length ? merged.name : other.name;
merged.searchLocation = merged.searchLocation ?? other.searchLocation;
merged.version = version;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { cloneDeep, isEqual } from 'lodash';
import { isEqual } from 'lodash';
import { Event, EventEmitter } from 'vscode';
import { traceVerbose } from '../../../../common/logger';
import { createDeferred } from '../../../../common/utils/async';
import { PythonEnvInfo, PythonEnvKind } from '../../info';
import { areSameEnv } from '../../info/env';
import { areSameEnv, mergeEnvironments } from '../../info/env';
import {
ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, PythonLocatorQuery,
} from '../../locator';
import { getEnvs } from '../../locatorUtils';
import { PythonEnvsChangedEvent } from '../../watcher';

/**
Expand All @@ -23,27 +23,11 @@ export class PythonEnvsReducer implements ILocator {
constructor(private readonly parentLocator: ILocator) {}

public async resolveEnv(env: string | PythonEnvInfo): Promise<PythonEnvInfo | undefined> {
let environment: PythonEnvInfo | undefined;
const waitForUpdatesDeferred = createDeferred<void>();
const iterator = this.iterEnvs();
iterator.onUpdated!((event) => {
if (event === null) {
waitForUpdatesDeferred.resolve();
} else if (environment && areSameEnv(environment, event.update)) {
environment = event.update;
}
});
let result = await iterator.next();
while (!result.done) {
if (areSameEnv(result.value, env)) {
environment = result.value;
}
result = await iterator.next();
}
const environments = await getEnvs(this.iterEnvs());
const environment = environments.find((e) => areSameEnv(e, env));
if (!environment) {
return undefined;
}
await waitForUpdatesDeferred.promise;
return this.parentLocator.resolveEnv(environment);
}

Expand Down Expand Up @@ -73,8 +57,7 @@ async function* iterEnvsIterator(
checkIfFinishedAndNotify(state, didUpdate);
} else if (seen[event.index] !== undefined) {
state.pending += 1;
resolveDifferencesInBackground(event.index, event.update, state, didUpdate, seen)
.ignoreErrors();
resolveDifferencesInBackground(event.index, event.update, state, didUpdate, seen).ignoreErrors();
} else {
// This implies a problem in a downstream locator
traceVerbose(`Expected already iterated env, got ${event.old} (#${event.index})`);
Expand Down Expand Up @@ -110,7 +93,7 @@ async function resolveDifferencesInBackground(
seen: PythonEnvInfo[],
) {
const oldEnv = seen[oldIndex];
const merged = mergeEnvironments(oldEnv, newEnv);
const merged = resolveEnvCollision(oldEnv, newEnv);
if (!isEqual(oldEnv, merged)) {
seen[oldIndex] = merged;
didUpdate.fire({ index: oldIndex, old: oldEnv, update: merged });
Expand All @@ -134,29 +117,63 @@ function checkIfFinishedAndNotify(
}
}

export function mergeEnvironments(environment: PythonEnvInfo, other: PythonEnvInfo): PythonEnvInfo {
const result = cloneDeep(environment);
// Preserve type information.
// Possible we identified environment as unknown, but a later provider has identified env type.
if (environment.kind === PythonEnvKind.Unknown && other.kind && other.kind !== PythonEnvKind.Unknown) {
result.kind = other.kind;
}
const props: (keyof PythonEnvInfo)[] = [
'version',
'kind',
'executable',
'name',
'arch',
'distro',
'defaultDisplayName',
'searchLocation',
function resolveEnvCollision(oldEnv: PythonEnvInfo, newEnv: PythonEnvInfo): PythonEnvInfo {
const [env, other] = sortEnvInfoByPriority(oldEnv, newEnv);
return mergeEnvironments(env, other);
}

/**
* Selects an environment based on the environment selection priority. This should
* match the priority in the environment identifier.
*/
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.
const envKindByPriority: PythonEnvKind[] = getPrioritizedEnvironmentKind();
return envs.sort(
(a: PythonEnvInfo, b: PythonEnvInfo) => envKindByPriority.indexOf(a.kind) - envKindByPriority.indexOf(b.kind),
);
}

/**
* 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.
*/
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,
];
props.forEach((prop) => {
if (!result[prop] && other[prop]) {
// tslint:disable: no-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(result as any)[prop] = other[prop];
}
});
return result;
}
Loading