11// Copyright (c) Microsoft Corporation. All rights reserved.
22// Licensed under the MIT License.
33
4- import { cloneDeep , isEqual } from 'lodash' ;
4+ import { isEqual } from 'lodash' ;
55import { Event , EventEmitter } from 'vscode' ;
66import { traceVerbose } from '../../../../common/logger' ;
7- import { createDeferred } from '../../../../common/utils/async' ;
87import { PythonEnvInfo , PythonEnvKind } from '../../info' ;
9- import { areSameEnv } from '../../info/env' ;
8+ import { areSameEnv , mergeEnvironments } from '../../info/env' ;
109import {
1110 ILocator , IPythonEnvsIterator , PythonEnvUpdatedEvent , PythonLocatorQuery ,
1211} from '../../locator' ;
12+ import { getEnvs } from '../../locatorUtils' ;
1313import { PythonEnvsChangedEvent } from '../../watcher' ;
1414
1515/**
@@ -23,27 +23,11 @@ export class PythonEnvsReducer implements ILocator {
2323 constructor ( private readonly parentLocator : ILocator ) { }
2424
2525 public async resolveEnv ( env : string | PythonEnvInfo ) : Promise < PythonEnvInfo | undefined > {
26- let environment : PythonEnvInfo | undefined ;
27- const waitForUpdatesDeferred = createDeferred < void > ( ) ;
28- const iterator = this . iterEnvs ( ) ;
29- iterator . onUpdated ! ( ( event ) => {
30- if ( event === null ) {
31- waitForUpdatesDeferred . resolve ( ) ;
32- } else if ( environment && areSameEnv ( environment , event . update ) ) {
33- environment = event . update ;
34- }
35- } ) ;
36- let result = await iterator . next ( ) ;
37- while ( ! result . done ) {
38- if ( areSameEnv ( result . value , env ) ) {
39- environment = result . value ;
40- }
41- result = await iterator . next ( ) ;
42- }
26+ const environments = await getEnvs ( this . iterEnvs ( ) ) ;
27+ const environment = environments . find ( ( e ) => areSameEnv ( e , env ) ) ;
4328 if ( ! environment ) {
4429 return undefined ;
4530 }
46- await waitForUpdatesDeferred . promise ;
4731 return this . parentLocator . resolveEnv ( environment ) ;
4832 }
4933
@@ -73,8 +57,7 @@ async function* iterEnvsIterator(
7357 checkIfFinishedAndNotify ( state , didUpdate ) ;
7458 } else if ( seen [ event . index ] !== undefined ) {
7559 state . pending += 1 ;
76- resolveDifferencesInBackground ( event . index , event . update , state , didUpdate , seen )
77- . ignoreErrors ( ) ;
60+ resolveDifferencesInBackground ( event . index , event . update , state , didUpdate , seen ) . ignoreErrors ( ) ;
7861 } else {
7962 // This implies a problem in a downstream locator
8063 traceVerbose ( `Expected already iterated env, got ${ event . old } (#${ event . index } )` ) ;
@@ -110,7 +93,7 @@ async function resolveDifferencesInBackground(
11093 seen : PythonEnvInfo [ ] ,
11194) {
11295 const oldEnv = seen [ oldIndex ] ;
113- const merged = mergeEnvironments ( oldEnv , newEnv ) ;
96+ const merged = resolveEnvCollision ( oldEnv , newEnv ) ;
11497 if ( ! isEqual ( oldEnv , merged ) ) {
11598 seen [ oldIndex ] = merged ;
11699 didUpdate . fire ( { index : oldIndex , old : oldEnv , update : merged } ) ;
@@ -134,29 +117,63 @@ function checkIfFinishedAndNotify(
134117 }
135118}
136119
137- export function mergeEnvironments ( environment : PythonEnvInfo , other : PythonEnvInfo ) : PythonEnvInfo {
138- const result = cloneDeep ( environment ) ;
139- // Preserve type information.
140- // Possible we identified environment as unknown, but a later provider has identified env type.
141- if ( environment . kind === PythonEnvKind . Unknown && other . kind && other . kind !== PythonEnvKind . Unknown ) {
142- result . kind = other . kind ;
143- }
144- const props : ( keyof PythonEnvInfo ) [ ] = [
145- 'version' ,
146- 'kind' ,
147- 'executable' ,
148- 'name' ,
149- 'arch' ,
150- 'distro' ,
151- 'defaultDisplayName' ,
152- 'searchLocation' ,
120+ function resolveEnvCollision ( oldEnv : PythonEnvInfo , newEnv : PythonEnvInfo ) : PythonEnvInfo {
121+ const [ env , other ] = sortEnvInfoByPriority ( oldEnv , newEnv ) ;
122+ return mergeEnvironments ( env , other ) ;
123+ }
124+
125+ /**
126+ * Selects an environment based on the environment selection priority. This should
127+ * match the priority in the environment identifier.
128+ */
129+ function sortEnvInfoByPriority ( ...envs : PythonEnvInfo [ ] ) : PythonEnvInfo [ ] {
130+ // tslint:disable-next-line: no-suspicious-comment
131+ // TODO: When we consolidate the PythonEnvKind and EnvironmentType we should have
132+ // one location where we define priority.
133+ const envKindByPriority : PythonEnvKind [ ] = getPrioritizedEnvironmentKind ( ) ;
134+ return envs . sort (
135+ ( a : PythonEnvInfo , b : PythonEnvInfo ) => envKindByPriority . indexOf ( a . kind ) - envKindByPriority . indexOf ( b . kind ) ,
136+ ) ;
137+ }
138+
139+ /**
140+ * Gets a prioritized list of environment types for identification.
141+ * @returns {PythonEnvKind[] } : List of environments ordered by identification priority
142+ *
143+ * Remarks: This is the order of detection based on how the various distributions and tools
144+ * configure the environment, and the fall back for identification.
145+ * Top level we have the following environment types, since they leave a unique signature
146+ * in the environment or * use a unique path for the environments they create.
147+ * 1. Conda
148+ * 2. Windows Store
149+ * 3. PipEnv
150+ * 4. Pyenv
151+ * 5. Poetry
152+ *
153+ * Next level we have the following virtual environment tools. The are here because they
154+ * are consumed by the tools above, and can also be used independently.
155+ * 1. venv
156+ * 2. virtualenvwrapper
157+ * 3. virtualenv
158+ *
159+ * Last category is globally installed python, or system python.
160+ */
161+ function getPrioritizedEnvironmentKind ( ) : PythonEnvKind [ ] {
162+ return [
163+ PythonEnvKind . CondaBase ,
164+ PythonEnvKind . Conda ,
165+ PythonEnvKind . WindowsStore ,
166+ PythonEnvKind . Pipenv ,
167+ PythonEnvKind . Pyenv ,
168+ PythonEnvKind . Poetry ,
169+ PythonEnvKind . Venv ,
170+ PythonEnvKind . VirtualEnvWrapper ,
171+ PythonEnvKind . VirtualEnv ,
172+ PythonEnvKind . OtherVirtual ,
173+ PythonEnvKind . OtherGlobal ,
174+ PythonEnvKind . MacDefault ,
175+ PythonEnvKind . System ,
176+ PythonEnvKind . Custom ,
177+ PythonEnvKind . Unknown ,
153178 ] ;
154- props . forEach ( ( prop ) => {
155- if ( ! result [ prop ] && other [ prop ] ) {
156- // tslint:disable: no-any
157- // eslint-disable-next-line @typescript-eslint/no-explicit-any
158- ( result as any ) [ prop ] = other [ prop ] ;
159- }
160- } ) ;
161- return result ;
162179}
0 commit comments