1
1
// Copyright (c) Microsoft Corporation. All rights reserved.
2
2
// Licensed under the MIT License.
3
3
4
- import { cloneDeep , isEqual } from 'lodash' ;
4
+ import { isEqual } from 'lodash' ;
5
5
import { Event , EventEmitter } from 'vscode' ;
6
6
import { traceVerbose } from '../../../../common/logger' ;
7
- import { createDeferred } from '../../../../common/utils/async' ;
8
7
import { PythonEnvInfo , PythonEnvKind } from '../../info' ;
9
- import { areSameEnv } from '../../info/env' ;
8
+ import { areSameEnv , mergeEnvironments } from '../../info/env' ;
10
9
import {
11
10
ILocator , IPythonEnvsIterator , PythonEnvUpdatedEvent , PythonLocatorQuery ,
12
11
} from '../../locator' ;
12
+ import { getEnvs } from '../../locatorUtils' ;
13
13
import { PythonEnvsChangedEvent } from '../../watcher' ;
14
14
15
15
/**
@@ -23,27 +23,11 @@ export class PythonEnvsReducer implements ILocator {
23
23
constructor ( private readonly parentLocator : ILocator ) { }
24
24
25
25
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 ) ) ;
43
28
if ( ! environment ) {
44
29
return undefined ;
45
30
}
46
- await waitForUpdatesDeferred . promise ;
47
31
return this . parentLocator . resolveEnv ( environment ) ;
48
32
}
49
33
@@ -73,8 +57,7 @@ async function* iterEnvsIterator(
73
57
checkIfFinishedAndNotify ( state , didUpdate ) ;
74
58
} else if ( seen [ event . index ] !== undefined ) {
75
59
state . pending += 1 ;
76
- resolveDifferencesInBackground ( event . index , event . update , state , didUpdate , seen )
77
- . ignoreErrors ( ) ;
60
+ resolveDifferencesInBackground ( event . index , event . update , state , didUpdate , seen ) . ignoreErrors ( ) ;
78
61
} else {
79
62
// This implies a problem in a downstream locator
80
63
traceVerbose ( `Expected already iterated env, got ${ event . old } (#${ event . index } )` ) ;
@@ -110,7 +93,7 @@ async function resolveDifferencesInBackground(
110
93
seen : PythonEnvInfo [ ] ,
111
94
) {
112
95
const oldEnv = seen [ oldIndex ] ;
113
- const merged = mergeEnvironments ( oldEnv , newEnv ) ;
96
+ const merged = resolveEnvCollision ( oldEnv , newEnv ) ;
114
97
if ( ! isEqual ( oldEnv , merged ) ) {
115
98
seen [ oldIndex ] = merged ;
116
99
didUpdate . fire ( { index : oldIndex , old : oldEnv , update : merged } ) ;
@@ -134,29 +117,63 @@ function checkIfFinishedAndNotify(
134
117
}
135
118
}
136
119
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 ,
153
178
] ;
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 ;
162
179
}
0 commit comments