1
1
// Copyright (c) Microsoft Corporation. All rights reserved.
2
2
// Licensed under the MIT License.
3
3
4
+ import * as fsPath from 'path' ;
4
5
import { Event , EventEmitter , workspace } from 'vscode' ;
5
6
import '../../../../common/extensions' ;
6
7
import { createDeferred , Deferred } from '../../../../common/utils/async' ;
@@ -28,7 +29,13 @@ import { createNativeGlobalPythonFinder, NativeEnvInfo } from '../common/nativeP
28
29
import { pathExists } from '../../../../common/platform/fs-paths' ;
29
30
import { noop } from '../../../../common/utils/misc' ;
30
31
import { parseVersion } from '../../info/pythonVersion' ;
31
- import { Conda , isCondaEnvironment } from '../../../common/environmentManagers/conda' ;
32
+ import {
33
+ Conda ,
34
+ CONDAPATH_SETTING_KEY ,
35
+ getCondaEnvironmentsTxt ,
36
+ isCondaEnvironment ,
37
+ } from '../../../common/environmentManagers/conda' ;
38
+ import { getConfiguration } from '../../../../common/vscodeApis/workspaceApis' ;
32
39
33
40
/**
34
41
* A service which maintains the collection of known environments.
@@ -307,12 +314,8 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
307
314
const missingEnvironments = {
308
315
envsWithDuplicatePrefixes : 0 ,
309
316
envsNotFound : 0 ,
310
- condaEnvsInEnvDir : 0 ,
311
- invalidCondaEnvs : 0 ,
312
- prefixNotExistsCondaEnvs : 0 ,
313
- condaEnvsWithoutPrefix : 0 ,
314
- nativeCondaEnvsInEnvDir : 0 ,
315
317
missingNativeCondaEnvs : 0 ,
318
+ missingNativeCondaEnvsFromTxt : 0 ,
316
319
missingNativeCustomEnvs : 0 ,
317
320
missingNativeMicrosoftStoreEnvs : 0 ,
318
321
missingNativeGlobalEnvs : 0 ,
@@ -328,99 +331,220 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
328
331
missingNativeOtherGlobalEnvs : 0 ,
329
332
} ;
330
333
331
- let canSpawnConda : boolean | undefined ;
332
- let condaInfoEnvs : undefined | number ;
333
- let condaInfoEnvsInvalid = 0 ;
334
- let condaInfoEnvsDuplicate = 0 ;
335
- let condaInfoEnvsInvalidPrefix = 0 ;
336
- let condaInfoEnvsDirs = 0 ;
337
334
let envsDirs : string [ ] = [ ] ;
338
- let condaRcs : number | undefined ;
339
- let condaRootPrefixFoundInInfoNotInNative : undefined | boolean ;
340
- let condaDefaultPrefixFoundInInfoNotInNative : undefined | boolean ;
341
- try {
342
- const conda = await Conda . getConda ( ) ;
343
- const info = await conda ?. getInfo ( ) ;
344
- canSpawnConda = ! ! info ;
345
- condaInfoEnvs = info ?. envs ?. length ;
346
- // eslint-disable-next-line camelcase
347
- envsDirs = info ?. envs_dirs || [ ] ;
335
+
336
+ type CondaTelemetry = {
337
+ condaInfoEnvs : number ;
338
+ condaEnvsInEnvDir : number ;
339
+ prefixNotExistsCondaEnvs : number ;
340
+ condaEnvsWithoutPrefix : number ;
341
+ condaRootPrefixFoundInInfoNotInNative ?: boolean ;
342
+ condaRootPrefixInCondaExePath ?: boolean ;
343
+ condaDefaultPrefixFoundInInfoNotInNative ?: boolean ;
344
+ condaDefaultPrefixInCondaExePath ?: boolean ;
345
+ canSpawnConda ?: boolean ;
346
+ nativeCanSpawnConda ?: boolean ;
347
+ userProvidedCondaExe ?: boolean ;
348
+ condaInfoEnvsInvalid : number ;
349
+ invalidCondaEnvs : number ;
350
+ condaInfoEnvsDuplicate : number ;
351
+ condaInfoEnvsInvalidPrefix : number ;
352
+ condaInfoEnvsDirs : number ;
353
+ nativeCondaEnvsInEnvDir : number ;
354
+ nativeCondaInfoEnvsDirs ?: number ;
355
+ condaRcs ?: number ;
356
+ nativeCondaRcs ?: number ;
357
+ condaEnvsInTxt ?: number ;
358
+ nativeCondaRcsNotFound : number ;
359
+ nativeCondaEnvDirsNotFound : number ;
360
+ nativeCondaEnvDirsNotFoundHasEnvs : number ;
361
+ nativeCondaEnvDirsNotFoundHasEnvsInTxt : number ;
362
+ } ;
363
+
364
+ const userProvidedCondaExe = fsPath
365
+ . normalize ( ( getConfiguration ( 'python' ) . get < string > ( CONDAPATH_SETTING_KEY ) || '' ) . trim ( ) )
366
+ . toLowerCase ( ) ;
367
+ const condaTelemetry : CondaTelemetry = {
368
+ condaEnvsInEnvDir : 0 ,
369
+ condaInfoEnvs : 0 ,
370
+ prefixNotExistsCondaEnvs : 0 ,
371
+ condaEnvsWithoutPrefix : 0 ,
372
+ nativeCondaEnvsInEnvDir : 0 ,
373
+ userProvidedCondaExe : userProvidedCondaExe . length > 0 ,
374
+ condaInfoEnvsInvalid : 0 ,
375
+ invalidCondaEnvs : 0 ,
376
+ condaInfoEnvsDuplicate : 0 ,
377
+ condaInfoEnvsInvalidPrefix : 0 ,
378
+ condaInfoEnvsDirs : 0 ,
379
+ nativeCondaRcsNotFound : 0 ,
380
+ nativeCondaEnvDirsNotFound : 0 ,
381
+ nativeCondaEnvDirsNotFoundHasEnvs : 0 ,
382
+ nativeCondaEnvDirsNotFoundHasEnvsInTxt : 0 ,
383
+ } ;
384
+
385
+ // Get conda telemetry
386
+ {
387
+ const [ info , nativeCondaInfo , condaEnvsInEnvironmentsTxt ] = await Promise . all ( [
388
+ Conda . getConda ( )
389
+ . catch ( ( ex ) => traceError ( 'Failed to get conda info' , ex ) )
390
+ . then ( ( conda ) => conda ?. getInfo ( ) ) ,
391
+ this . nativeFinder
392
+ . getCondaInfo ( )
393
+ . catch ( ( ex ) => traceError ( `Failed to get conda info from native locator` , ex ) ) ,
394
+ getCondaEnvironmentsTxt ( )
395
+ . then ( async ( items ) => {
396
+ const validEnvs = new Set < string > ( ) ;
397
+ await Promise . all (
398
+ items . map ( async ( e ) => {
399
+ if ( ( await pathExists ( e ) ) && ( await isCondaEnvironment ( e ) ) ) {
400
+ validEnvs . add ( fsPath . normalize ( e ) . toLowerCase ( ) ) ;
401
+ }
402
+ } ) ,
403
+ ) ;
404
+ return Array . from ( validEnvs ) ;
405
+ } )
406
+ . catch ( ( ex ) => traceError ( `Failed to get conda envs from environments.txt` , ex ) )
407
+ . then ( ( items ) => items || [ ] ) ,
408
+ ] ) ;
409
+
410
+ if ( nativeCondaInfo ) {
411
+ condaTelemetry . nativeCanSpawnConda = nativeCondaInfo . canSpawnConda ;
412
+ condaTelemetry . nativeCondaInfoEnvsDirs = new Set ( nativeCondaInfo . envDirs ) . size ;
413
+ condaTelemetry . nativeCondaRcs = new Set ( nativeCondaInfo . condaRcs ) . size ;
414
+ }
415
+ condaTelemetry . condaEnvsInTxt = condaEnvsInEnvironmentsTxt . length ;
416
+ condaTelemetry . canSpawnConda = ! ! info ;
417
+
418
+ // Conda info rcs
348
419
const condaRcFiles = new Set < string > ( ) ;
349
420
await Promise . all (
350
421
// eslint-disable-next-line camelcase
351
422
[ info ?. rc_path , info ?. user_rc_path , info ?. sys_rc_path , ...( info ?. config_files || [ ] ) ] . map (
352
423
async ( rc ) => {
353
424
if ( rc && ( await pathExists ( rc ) ) ) {
354
- condaRcFiles . add ( rc ) ;
425
+ condaRcFiles . add ( fsPath . normalize ( rc ) . toLowerCase ( ) ) ;
355
426
}
356
427
} ,
357
428
) ,
358
429
) . catch ( noop ) ;
359
- condaRcs = condaRcFiles . size ;
430
+ const condaRcs = Array . from ( condaRcFiles ) ;
431
+ condaTelemetry . condaRcs = condaRcs . length ;
432
+
433
+ // Find the condarcs that were not found by native finder.
434
+ const nativeCondaRcs = ( nativeCondaInfo ?. condaRcs || [ ] ) . map ( ( rc ) => fsPath . normalize ( rc ) . toLowerCase ( ) ) ;
435
+ condaTelemetry . nativeCondaRcsNotFound = condaRcs . filter ( ( rc ) => ! nativeCondaRcs . includes ( rc ) ) . length ;
436
+
437
+ // Conda info envs
438
+ const validCondaInfoEnvs = new Set < string > ( ) ;
360
439
const duplicate = new Set < string > ( ) ;
440
+ // Duplicate, invalid conda environments.
361
441
Promise . all (
362
442
( info ?. envs || [ ] ) . map ( async ( e ) => {
363
443
if ( duplicate . has ( e ) ) {
364
- condaInfoEnvsDuplicate += 1 ;
444
+ condaTelemetry . condaInfoEnvsDuplicate += 1 ;
365
445
return ;
366
446
}
367
447
duplicate . add ( e ) ;
368
448
if ( ! ( await pathExists ( e ) ) ) {
369
- condaInfoEnvsInvalidPrefix += 1 ;
449
+ condaTelemetry . condaInfoEnvsInvalidPrefix += 1 ;
450
+ return ;
370
451
}
371
452
if ( ! ( await isCondaEnvironment ( e ) ) ) {
372
- condaInfoEnvsInvalid += 1 ;
453
+ condaTelemetry . condaInfoEnvsInvalid += 1 ;
454
+ return ;
373
455
}
456
+ validCondaInfoEnvs . add ( fsPath . normalize ( e ) . toLowerCase ( ) ) ;
374
457
} ) ,
375
458
) ;
459
+ const condaInfoEnvs = Array . from ( validCondaInfoEnvs ) ;
460
+ condaTelemetry . condaInfoEnvs = validCondaInfoEnvs . size ;
461
+
462
+ // Conda env_dirs
463
+ const validEnvDirs = new Set < string > ( ) ;
376
464
Promise . all (
377
- envsDirs . map ( async ( e ) => {
465
+ // eslint-disable-next-line camelcase
466
+ ( info ?. envs_dirs || [ ] ) . map ( async ( e ) => {
378
467
if ( await pathExists ( e ) ) {
379
- condaInfoEnvsDirs += 1 ;
468
+ validEnvDirs . add ( e ) ;
380
469
}
381
470
} ) ,
382
471
) ;
383
- nativeEnvs
384
- . filter ( ( e ) => this . nativeFinder . categoryToKind ( e . kind ) === PythonEnvKind . Conda )
385
- . forEach ( ( e ) => {
386
- if ( e . prefix && envsDirs . some ( ( d ) => e . prefix && e . prefix . startsWith ( d ) ) ) {
387
- missingEnvironments . nativeCondaEnvsInEnvDir += 1 ;
388
- }
389
- } ) ;
472
+ condaTelemetry . condaInfoEnvsDirs = validEnvDirs . size ;
473
+ envsDirs = Array . from ( validEnvDirs ) . map ( ( e ) => fsPath . normalize ( e ) . toLowerCase ( ) ) ;
390
474
391
- // Check if we have found the conda env that matches the `root_prefix` in the conda info.
392
- // eslint-disable-next-line camelcase
393
- const rootPrefix = ( info ?. root_prefix || '' ) . toLowerCase ( ) ;
394
- if ( rootPrefix ) {
395
- // Check if we have a conda env that matches this prefix.
475
+ const nativeCondaEnvs = nativeEnvs . filter (
476
+ ( e ) => this . nativeFinder . categoryToKind ( e . kind ) === PythonEnvKind . Conda ,
477
+ ) ;
478
+
479
+ // Find the env_dirs that were not found by native finder.
480
+ const nativeCondaEnvDirs = ( nativeCondaInfo ?. envDirs || [ ] ) . map ( ( envDir ) =>
481
+ fsPath . normalize ( envDir ) . toLowerCase ( ) ,
482
+ ) ;
483
+ const nativeCondaEnvPrefix = nativeCondaEnvs
484
+ . filter ( ( e ) => e . prefix )
485
+ . map ( ( e ) => fsPath . normalize ( e . prefix || '' ) . toLowerCase ( ) ) ;
486
+
487
+ envsDirs . forEach ( ( envDir ) => {
396
488
if (
397
- envs . some (
398
- ( e ) => e . executable . sysPrefix . toLowerCase ( ) === rootPrefix && e . kind === PythonEnvKind . Conda ,
399
- )
489
+ ! nativeCondaEnvDirs . includes ( envDir ) &&
490
+ ! nativeCondaEnvDirs . includes ( fsPath . join ( envDir , 'envs' ) ) &&
491
+ // If we have a native conda env from this env dir, then we're good.
492
+ ! nativeCondaEnvPrefix . some ( ( prefix ) => prefix . startsWith ( envDir ) )
400
493
) {
401
- condaRootPrefixFoundInInfoNotInNative = nativeEnvs . some (
402
- ( e ) => e . prefix ?. toLowerCase ( ) === rootPrefix . toLowerCase ( ) ,
403
- ) ;
494
+ condaTelemetry . nativeCondaEnvDirsNotFound += 1 ;
495
+
496
+ // Find what conda envs returned by `conda info` belong to this envdir folder.
497
+ // And find which of those envs do not exist in native conda envs
498
+ condaInfoEnvs . forEach ( ( env ) => {
499
+ if ( env . startsWith ( envDir ) ) {
500
+ condaTelemetry . nativeCondaEnvDirsNotFoundHasEnvs += 1 ;
501
+
502
+ // Check if this env was in the environments.txt file.
503
+ if ( condaEnvsInEnvironmentsTxt . includes ( env ) ) {
504
+ condaTelemetry . nativeCondaEnvDirsNotFoundHasEnvsInTxt += 1 ;
505
+ }
506
+ }
507
+ } ) ;
404
508
}
509
+ } ) ;
510
+
511
+ // How many envs are in environments.txt that were not found by native locator.
512
+ missingEnvironments . missingNativeCondaEnvsFromTxt = condaEnvsInEnvironmentsTxt . filter (
513
+ ( env ) => ! nativeCondaEnvPrefix . some ( ( prefix ) => prefix === env ) ,
514
+ ) . length ;
515
+
516
+ // How many envs found by native locator & conda info are in the env dirs.
517
+ condaTelemetry . condaEnvsInEnvDir = condaInfoEnvs . filter ( ( e ) =>
518
+ envsDirs . some ( ( d ) => e . startsWith ( d ) ) ,
519
+ ) . length ;
520
+ condaTelemetry . nativeCondaEnvsInEnvDir = nativeCondaEnvs . filter ( ( e ) =>
521
+ nativeCondaEnvDirs . some ( ( d ) => ( e . prefix || '' ) . startsWith ( d ) ) ,
522
+ ) . length ;
523
+
524
+ // Check if we have found the conda env that matches the `root_prefix` in the conda info.
525
+ // eslint-disable-next-line camelcase
526
+ let rootPrefix = info ?. root_prefix || '' ;
527
+ if ( rootPrefix && ( await pathExists ( rootPrefix ) ) && ( await isCondaEnvironment ( rootPrefix ) ) ) {
528
+ rootPrefix = fsPath . normalize ( rootPrefix ) . toLowerCase ( ) ;
529
+ condaTelemetry . condaRootPrefixInCondaExePath = userProvidedCondaExe . startsWith ( rootPrefix ) ;
530
+ // Check if we have a conda env that matches this prefix but not found in native envs.
531
+ condaTelemetry . condaRootPrefixFoundInInfoNotInNative =
532
+ condaInfoEnvs . some ( ( env ) => env === rootPrefix ) &&
533
+ ! nativeCondaEnvs . some ( ( e ) => fsPath . normalize ( e . prefix || '' ) . toLowerCase ( ) === rootPrefix ) ;
405
534
}
535
+
406
536
// eslint-disable-next-line camelcase
407
- const defaultPrefix = ( info ?. default_prefix || '' ) . toLowerCase ( ) ;
408
- if ( rootPrefix ) {
409
- // Check if we have a conda env that matches this prefix.
410
- if (
411
- envs . some (
412
- ( e ) => e . executable . sysPrefix . toLowerCase ( ) === defaultPrefix && e . kind === PythonEnvKind . Conda ,
413
- )
414
- ) {
415
- condaDefaultPrefixFoundInInfoNotInNative = nativeEnvs . some (
416
- ( e ) => e . prefix ?. toLowerCase ( ) === defaultPrefix . toLowerCase ( ) ,
417
- ) ;
418
- }
537
+ let defaultPrefix = info ?. default_prefix || '' ;
538
+ if ( defaultPrefix && ( await pathExists ( defaultPrefix ) ) && ( await isCondaEnvironment ( defaultPrefix ) ) ) {
539
+ defaultPrefix = fsPath . normalize ( defaultPrefix ) . toLowerCase ( ) ;
540
+ condaTelemetry . condaDefaultPrefixInCondaExePath = userProvidedCondaExe . startsWith ( defaultPrefix ) ;
541
+ // Check if we have a conda env that matches this prefix but not found in native envs.
542
+ condaTelemetry . condaDefaultPrefixFoundInInfoNotInNative =
543
+ condaInfoEnvs . some ( ( env ) => env === defaultPrefix ) &&
544
+ ! nativeCondaEnvs . some ( ( e ) => fsPath . normalize ( e . prefix || '' ) . toLowerCase ( ) === defaultPrefix ) ;
419
545
}
420
- } catch ( ex ) {
421
- canSpawnConda = false ;
422
546
}
423
- const nativeCondaInfoPromise = this . nativeFinder . getCondaInfo ( ) ;
547
+
424
548
const prefixesSeenAlready = new Set < string > ( ) ;
425
549
await Promise . all (
426
550
envs . map ( async ( env ) => {
@@ -466,12 +590,6 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
466
590
traceError ( `Environment ${ exe } is missing from native locator` ) ;
467
591
switch ( env . kind ) {
468
592
case PythonEnvKind . Conda :
469
- if (
470
- env . executable . sysPrefix &&
471
- envsDirs . some ( ( d ) => env . executable . sysPrefix . startsWith ( d ) )
472
- ) {
473
- missingEnvironments . condaEnvsInEnvDir += 1 ;
474
- }
475
593
missingEnvironments . missingNativeCondaEnvs += 1 ;
476
594
break ;
477
595
case PythonEnvKind . Custom :
@@ -530,22 +648,6 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
530
648
} ) ,
531
649
) . catch ( ( ex ) => traceError ( 'Failed to send telemetry for missing environments' , ex ) ) ;
532
650
533
- const nativeCondaInfo = await nativeCondaInfoPromise . catch ( ( ex ) =>
534
- traceError ( `Failed to get conda info from native locator` , ex ) ,
535
- ) ;
536
-
537
- type CondaTelemetry = {
538
- nativeCanSpawnConda ?: boolean ;
539
- nativeCondaInfoEnvsDirs ?: number ;
540
- nativeCondaRcs ?: number ;
541
- } ;
542
-
543
- const condaTelemetry : CondaTelemetry = { } ;
544
- if ( nativeCondaInfo ) {
545
- condaTelemetry . nativeCanSpawnConda = nativeCondaInfo . canSpawnConda ;
546
- condaTelemetry . nativeCondaInfoEnvsDirs = new Set ( nativeCondaInfo . envDirs ) . size ;
547
- condaTelemetry . nativeCondaRcs = new Set ( nativeCondaInfo . condaRcs ) . size ;
548
- }
549
651
const environmentsWithoutPython = envs . filter (
550
652
( e ) => getEnvPath ( e . executable . filename , e . location ) . pathType === 'envFolderPath' ,
551
653
) . length ;
@@ -572,15 +674,15 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
572
674
e . kind === PythonEnvKind . OtherVirtual ,
573
675
) . length ;
574
676
575
- missingEnvironments . condaEnvsWithoutPrefix = condaEnvs . filter ( ( e ) => ! e . executable . sysPrefix ) . length ;
677
+ condaTelemetry . condaEnvsWithoutPrefix = condaEnvs . filter ( ( e ) => ! e . executable . sysPrefix ) . length ;
576
678
577
679
await Promise . all (
578
680
condaEnvs . map ( async ( e ) => {
579
681
if ( e . executable . sysPrefix && ! ( await pathExists ( e . executable . sysPrefix ) ) ) {
580
- missingEnvironments . prefixNotExistsCondaEnvs += 1 ;
682
+ condaTelemetry . prefixNotExistsCondaEnvs += 1 ;
581
683
}
582
684
if ( e . executable . filename && ! ( await isCondaEnvironment ( e . executable . filename ) ) ) {
583
- missingEnvironments . invalidCondaEnvs += 1 ;
685
+ condaTelemetry . invalidCondaEnvs += 1 ;
584
686
}
585
687
} ) ,
586
688
) ;
@@ -634,19 +736,10 @@ export class EnvsCollectionService extends PythonEnvsWatcher<PythonEnvCollection
634
736
635
737
// Intent is to capture time taken for discovery of all envs to complete the first time.
636
738
sendTelemetryEvent ( EventName . PYTHON_INTERPRETER_DISCOVERY , elapsedTime , {
637
- telVer : 3 ,
638
- condaRcs,
639
- condaInfoEnvsInvalid,
640
- condaInfoEnvsDuplicate,
641
- condaInfoEnvsInvalidPrefix,
642
- condaRootPrefixFoundInInfoNotInNative,
643
- condaDefaultPrefixFoundInInfoNotInNative,
739
+ telVer : 4 ,
644
740
nativeDuration,
645
741
workspaceFolderCount : ( workspace . workspaceFolders || [ ] ) . length ,
646
742
interpreters : this . cache . getAllEnvs ( ) . length ,
647
- condaInfoEnvs,
648
- condaInfoEnvsDirs,
649
- canSpawnConda,
650
743
environmentsWithoutPython,
651
744
activeStateEnvs,
652
745
condaEnvs : condaEnvs . length ,
0 commit comments