@@ -10,7 +10,10 @@ import { isWindows } from '../../../../common/platform/platformService';
10
10
import { EXTENSION_ROOT_DIR } from '../../../../constants' ;
11
11
import { traceError , traceInfo , traceLog , traceVerbose , traceWarn } from '../../../../logging' ;
12
12
import { createDeferred } from '../../../../common/utils/async' ;
13
- import { DisposableBase } from '../../../../common/utils/resourceLifecycle' ;
13
+ import { DisposableBase , DisposableStore } from '../../../../common/utils/resourceLifecycle' ;
14
+ import { getPythonSetting } from '../../../common/externalDependencies' ;
15
+ import { DEFAULT_INTERPRETER_PATH_SETTING_KEY } from '../lowLevel/customWorkspaceLocator' ;
16
+ import { noop } from '../../../../common/utils/misc' ;
14
17
15
18
const NATIVE_LOCATOR = isWindows ( )
16
19
? path . join ( EXTENSION_ROOT_DIR , 'native_locator' , 'bin' , 'pet.exe' )
@@ -39,6 +42,7 @@ export interface NativeEnvManagerInfo {
39
42
}
40
43
41
44
export interface NativeGlobalPythonFinder extends Disposable {
45
+ resolve ( executable : string ) : Promise < NativeEnvInfo > ;
42
46
refresh ( paths : Uri [ ] ) : AsyncIterable < NativeEnvInfo > ;
43
47
}
44
48
@@ -48,18 +52,41 @@ interface NativeLog {
48
52
}
49
53
50
54
class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGlobalPythonFinder {
55
+ private readonly connection : rpc . MessageConnection ;
56
+
57
+ constructor ( ) {
58
+ super ( ) ;
59
+ this . connection = this . start ( ) ;
60
+ }
61
+
62
+ public async resolve ( executable : string ) : Promise < NativeEnvInfo > {
63
+ const { environment, duration } = await this . connection . sendRequest < {
64
+ duration : number ;
65
+ environment : NativeEnvInfo ;
66
+ } > ( 'resolve' , {
67
+ executable,
68
+ } ) ;
69
+
70
+ traceInfo ( `Resolved Python Environment ${ environment . executable } in ${ duration } ms` ) ;
71
+ return environment ;
72
+ }
73
+
51
74
async * refresh ( _paths : Uri [ ] ) : AsyncIterable < NativeEnvInfo > {
52
- const result = this . start ( ) ;
75
+ const result = this . doRefresh ( ) ;
53
76
let completed = false ;
54
77
void result . completed . finally ( ( ) => {
55
78
completed = true ;
56
79
} ) ;
57
80
const envs : NativeEnvInfo [ ] = [ ] ;
58
81
let discovered = createDeferred ( ) ;
59
- const disposable = result . discovered ( ( data ) => envs . push ( data ) ) ;
60
-
82
+ const disposable = result . discovered ( ( data ) => {
83
+ envs . push ( data ) ;
84
+ discovered . resolve ( ) ;
85
+ } ) ;
61
86
do {
62
- await Promise . race ( [ result . completed , discovered . promise ] ) ;
87
+ if ( ! envs . length ) {
88
+ await Promise . race ( [ result . completed , discovered . promise ] ) ;
89
+ }
63
90
if ( envs . length ) {
64
91
const dataToSend = [ ...envs ] ;
65
92
envs . length = 0 ;
@@ -69,27 +96,22 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba
69
96
}
70
97
if ( ! completed ) {
71
98
discovered = createDeferred ( ) ;
72
- envs . length = 0 ;
73
99
}
74
100
} while ( ! completed ) ;
75
-
76
101
disposable . dispose ( ) ;
77
102
}
78
103
79
104
// eslint-disable-next-line class-methods-use-this
80
- private start ( ) : { completed : Promise < void > ; discovered : Event < NativeEnvInfo > } {
81
- const discovered = new EventEmitter < NativeEnvInfo > ( ) ;
82
- const completed = createDeferred < void > ( ) ;
105
+ private start ( ) : rpc . MessageConnection {
83
106
const proc = ch . spawn ( NATIVE_LOCATOR , [ 'server' ] , { env : process . env } ) ;
84
107
const disposables : Disposable [ ] = [ ] ;
85
108
// jsonrpc package cannot handle messages coming through too quicly.
86
109
// Lets handle the messages and close the stream only when
87
110
// we have got the exit event.
88
111
const readable = new PassThrough ( ) ;
89
112
proc . stdout . pipe ( readable , { end : false } ) ;
90
- let err = '' ;
91
113
proc . stderr . on ( 'data' , ( data ) => {
92
- err + = data . toString ( ) ;
114
+ const err = data . toString ( ) ;
93
115
traceError ( 'Native Python Finder' , err ) ;
94
116
} ) ;
95
117
const writable = new PassThrough ( ) ;
@@ -105,17 +127,10 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba
105
127
disposables . push (
106
128
connection ,
107
129
disposeStreams ,
108
- discovered ,
109
130
connection . onError ( ( ex ) => {
110
131
disposeStreams . dispose ( ) ;
111
132
traceError ( 'Error in Native Python Finder' , ex ) ;
112
133
} ) ,
113
- connection . onNotification ( 'environment' , ( data : NativeEnvInfo ) => {
114
- discovered . fire ( data ) ;
115
- } ) ,
116
- // connection.onNotification((method: string, data: any) => {
117
- // console.log(method, data);
118
- // }),
119
134
connection . onNotification ( 'log' , ( data : NativeLog ) => {
120
135
switch ( data . level ) {
121
136
case 'info' :
@@ -135,7 +150,6 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba
135
150
}
136
151
} ) ,
137
152
connection . onClose ( ( ) => {
138
- completed . resolve ( ) ;
139
153
disposables . forEach ( ( d ) => d . dispose ( ) ) ;
140
154
} ) ,
141
155
{
@@ -152,19 +166,86 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba
152
166
) ;
153
167
154
168
connection . listen ( ) ;
155
- connection
156
- . sendRequest < number > ( 'refresh' , {
157
- // Send configuration information to the Python finder.
158
- search_paths : ( workspace . workspaceFolders || [ ] ) . map ( ( w ) => w . uri . fsPath ) ,
159
- conda_executable : undefined ,
160
- } )
161
- . then ( ( durationInMilliSeconds : number ) => {
162
- completed . resolve ( ) ;
163
- traceInfo ( `Native Python Finder took ${ durationInMilliSeconds } ms to complete.` ) ;
164
- } )
165
- . catch ( ( ex ) => traceError ( 'Error in Native Python Finder' , ex ) ) ;
166
-
167
- return { completed : completed . promise , discovered : discovered . event } ;
169
+ this . _register ( Disposable . from ( ...disposables ) ) ;
170
+ return connection ;
171
+ }
172
+
173
+ private doRefresh ( ) : { completed : Promise < void > ; discovered : Event < NativeEnvInfo > } {
174
+ const disposable = this . _register ( new DisposableStore ( ) ) ;
175
+ const discovered = disposable . add ( new EventEmitter < NativeEnvInfo > ( ) ) ;
176
+ const completed = createDeferred < void > ( ) ;
177
+ const pendingPromises : Promise < void > [ ] = [ ] ;
178
+
179
+ const notifyUponCompletion = ( ) => {
180
+ const initialCount = pendingPromises . length ;
181
+ Promise . all ( pendingPromises )
182
+ . then ( ( ) => {
183
+ if ( initialCount === pendingPromises . length ) {
184
+ completed . resolve ( ) ;
185
+ } else {
186
+ setTimeout ( notifyUponCompletion , 0 ) ;
187
+ }
188
+ } )
189
+ . catch ( noop ) ;
190
+ } ;
191
+ const trackPromiseAndNotifyOnCompletion = ( promise : Promise < void > ) => {
192
+ pendingPromises . push ( promise ) ;
193
+ notifyUponCompletion ( ) ;
194
+ } ;
195
+
196
+ disposable . add (
197
+ this . connection . onNotification ( 'environment' , ( data : NativeEnvInfo ) => {
198
+ // We know that in the Python extension if either Version of Prefix is not provided by locator
199
+ // Then we end up resolving the information.
200
+ // Lets do that here,
201
+ // This is a hack, as the other part of the code that resolves the version information
202
+ // doesn't work as expected, as its still a WIP.
203
+ if ( data . executable && ( ! data . version || ! data . prefix ) ) {
204
+ // HACK = TEMPORARY WORK AROUND, TO GET STUFF WORKING
205
+ // HACK = TEMPORARY WORK AROUND, TO GET STUFF WORKING
206
+ // HACK = TEMPORARY WORK AROUND, TO GET STUFF WORKING
207
+ // HACK = TEMPORARY WORK AROUND, TO GET STUFF WORKING
208
+ const promise = this . connection
209
+ . sendRequest < { duration : number ; environment : NativeEnvInfo } > ( 'resolve' , {
210
+ executable : data . executable ,
211
+ } )
212
+ . then ( ( { environment, duration } ) => {
213
+ traceInfo ( `Resolved Python Environment ${ environment . executable } in ${ duration } ms` ) ;
214
+ discovered . fire ( environment ) ;
215
+ } )
216
+ . catch ( ( ex ) => traceError ( `Error in Resolving Python Environment ${ data } ` , ex ) ) ;
217
+ trackPromiseAndNotifyOnCompletion ( promise ) ;
218
+ } else {
219
+ discovered . fire ( data ) ;
220
+ }
221
+ } ) ,
222
+ ) ;
223
+
224
+ const pythonPathSettings = ( workspace . workspaceFolders || [ ] ) . map ( ( w ) =>
225
+ getPythonSetting < string > ( DEFAULT_INTERPRETER_PATH_SETTING_KEY , w . uri . fsPath ) ,
226
+ ) ;
227
+ pythonPathSettings . push ( getPythonSetting < string > ( DEFAULT_INTERPRETER_PATH_SETTING_KEY ) ) ;
228
+ const pythonSettings = Array . from ( new Set ( pythonPathSettings . filter ( ( item ) => ! ! item ) ) . values ( ) ) . map ( ( p ) =>
229
+ // We only want the parent directories.
230
+ path . dirname ( p ! ) ,
231
+ ) ;
232
+ trackPromiseAndNotifyOnCompletion (
233
+ this . connection
234
+ . sendRequest < { duration : number } > ( 'refresh' , {
235
+ // Send configuration information to the Python finder.
236
+ search_paths : ( workspace . workspaceFolders || [ ] ) . map ( ( w ) => w . uri . fsPath ) ,
237
+ // Also send the python paths that are configured in the settings.
238
+ python_path_settings : pythonSettings ,
239
+ conda_executable : undefined ,
240
+ } )
241
+ . then ( ( { duration } ) => traceInfo ( `Native Python Finder completed in ${ duration } ms` ) )
242
+ . catch ( ( ex ) => traceError ( 'Error in Native Python Finder' , ex ) ) ,
243
+ ) ;
244
+ completed . promise . finally ( ( ) => disposable . dispose ( ) ) ;
245
+ return {
246
+ completed : completed . promise ,
247
+ discovered : discovered . event ,
248
+ } ;
168
249
}
169
250
}
170
251
0 commit comments