diff --git a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts index 1392886da7cc..0905540fd50f 100644 --- a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts +++ b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts @@ -62,6 +62,7 @@ export interface NativeGlobalPythonFinder extends Disposable { refresh(): AsyncIterable; categoryToKind(category?: string): PythonEnvKind; getCondaInfo(): Promise; + find(searchPath: string): Promise; } interface NativeLog { @@ -376,6 +377,10 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativeGloba async getCondaInfo(): Promise { return this.connection.sendRequest('condaInfo'); } + + public async find(searchPath: string): Promise { + return this.connection.sendRequest('find', { searchPath }); + } } type ConfigurationOptions = { @@ -387,6 +392,7 @@ type ConfigurationOptions = { environmentDirectories: string[]; condaExecutable: string | undefined; poetryExecutable: string | undefined; + cacheDirectory?: string; }; /** * Gets all custom virtual environment locations to look for environments. diff --git a/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts b/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts index 85b852cc32d0..4d6805a4609e 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts @@ -9,7 +9,7 @@ import { StopWatch } from '../../../../common/utils/stopWatch'; import { traceError, traceInfo, traceVerbose } from '../../../../logging'; import { sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; -import { normalizePath } from '../../../common/externalDependencies'; +import { normalizePath, readFile } from '../../../common/externalDependencies'; import { PythonEnvInfo, PythonEnvKind } from '../../info'; import { getEnvPath } from '../../info/env'; import { @@ -25,17 +25,17 @@ import { import { getQueryFilter } from '../../locatorUtils'; import { PythonEnvCollectionChangedEvent, PythonEnvsWatcher } from '../../watcher'; import { IEnvsCollectionCache } from './envsCollectionCache'; -import { createNativeGlobalPythonFinder, NativeEnvInfo } from '../common/nativePythonFinder'; +import { + createNativeGlobalPythonFinder, + NativeEnvInfo, + NativeGlobalPythonFinder as NativePythonFinder, +} from '../common/nativePythonFinder'; import { pathExists } from '../../../../common/platform/fs-paths'; import { noop } from '../../../../common/utils/misc'; import { parseVersion } from '../../info/pythonVersion'; -import { - Conda, - CONDAPATH_SETTING_KEY, - getCondaEnvironmentsTxt, - isCondaEnvironment, -} from '../../../common/environmentManagers/conda'; +import { Conda, CONDAPATH_SETTING_KEY, isCondaEnvironment } from '../../../common/environmentManagers/conda'; import { getConfiguration } from '../../../../common/vscodeApis/workspaceApis'; +import { getUserHomeDir } from '../../../../common/utils/platform'; /** * A service which maintains the collection of known environments. @@ -294,7 +294,7 @@ export class EnvsCollectionService extends PythonEnvsWatcher(); const nativeStopWatch = new StopWatch(); for await (const data of this.nativeFinder.refresh()) { @@ -315,7 +315,6 @@ export class EnvsCollectionService extends PythonEnvsWatcher(CONDAPATH_SETTING_KEY) || '').trim()) - .toLowerCase(); - const condaTelemetry: CondaTelemetry = { - condaEnvsInEnvDir: 0, - condaInfoEnvs: 0, - prefixNotExistsCondaEnvs: 0, - condaEnvsWithoutPrefix: 0, - nativeCondaEnvsInEnvDir: 0, - userProvidedCondaExe: userProvidedCondaExe.length > 0, - condaInfoEnvsInvalid: 0, - invalidCondaEnvs: 0, - condaInfoEnvsDuplicate: 0, - condaInfoEnvsInvalidPrefix: 0, - condaInfoEnvsDirs: 0, - nativeCondaRcsNotFound: 0, - nativeCondaEnvDirsNotFound: 0, - nativeCondaEnvDirsNotFoundHasEnvs: 0, - nativeCondaEnvDirsNotFoundHasEnvsInTxt: 0, - nativeCondaEnvsFromTxt: 0, - }; - - // Get conda telemetry - { - const [info, nativeCondaInfo, condaEnvsInEnvironmentsTxt, envTxt] = await Promise.all([ - Conda.getConda() - .catch((ex) => traceError('Failed to get conda info', ex)) - .then((conda) => conda?.getInfo()), - this.nativeFinder - .getCondaInfo() - .catch((ex) => traceError(`Failed to get conda info from native locator`, ex)), - getCondaEnvironmentsTxt() - .then(async (items) => { - const validEnvs = new Set(); - await Promise.all( - items.map(async (e) => { - if ((await pathExists(e)) && (await isCondaEnvironment(e))) { - validEnvs.add(fsPath.normalize(e).toLowerCase()); - } - }), - ); - return Array.from(validEnvs); - }) - .catch((ex) => traceError(`Failed to get conda envs from environments.txt`, ex)) - .then((items) => items || []), - getCondaEnvironmentsTxt().catch(noop), - ]); - - const environmentsTxt = - Array.isArray(envTxt) && envTxt.length ? fsPath.normalize(envTxt[0]).toLowerCase() : undefined; - if (nativeCondaInfo) { - condaTelemetry.nativeCanSpawnConda = nativeCondaInfo.canSpawnConda; - condaTelemetry.nativeCondaInfoEnvsDirs = new Set(nativeCondaInfo.envDirs).size; - condaTelemetry.nativeCondaRcs = new Set(nativeCondaInfo.condaRcs).size; - condaTelemetry.userProvidedEnvFound = nativeCondaInfo.userProvidedEnvFound; - - const nativeEnvTxt = fsPath.normalize(nativeCondaInfo.environmentsTxt || '').toLowerCase(); - condaTelemetry.nativeCondaEnvTxtExists = nativeCondaInfo.environmentsTxtExists === true; - condaTelemetry.nativeCondaEnvsFromTxt = (nativeCondaInfo.environmentsFromTxt || []).length; - condaTelemetry.nativeCondaEnvTxtSame = nativeEnvTxt === environmentsTxt; - } - condaTelemetry.condaEnvsInTxt = condaEnvsInEnvironmentsTxt.length; - condaTelemetry.canSpawnConda = !!info; - - // Conda info rcs - const condaRcFiles = new Set(); - await Promise.all( - // eslint-disable-next-line camelcase - [info?.rc_path, info?.user_rc_path, info?.sys_rc_path, ...(info?.config_files || [])].map( - async (rc) => { - if (rc && (await pathExists(rc))) { - condaRcFiles.add(fsPath.normalize(rc).toLowerCase()); - } - }, - ), - ).catch(noop); - const condaRcs = Array.from(condaRcFiles); - condaTelemetry.condaRcs = condaRcs.length; - - // Find the condarcs that were not found by native finder. - const nativeCondaRcs = (nativeCondaInfo?.condaRcs || []).map((rc) => fsPath.normalize(rc).toLowerCase()); - condaTelemetry.nativeCondaRcsNotFound = condaRcs.filter((rc) => !nativeCondaRcs.includes(rc)).length; - - // Conda info envs - const validCondaInfoEnvs = new Set(); - const duplicate = new Set(); - // Duplicate, invalid conda environments. - await Promise.all( - (info?.envs || []).map(async (e) => { - if (duplicate.has(e)) { - condaTelemetry.condaInfoEnvsDuplicate += 1; - return; - } - duplicate.add(e); - if (!(await pathExists(e))) { - condaTelemetry.condaInfoEnvsInvalidPrefix += 1; - return; - } - if (!(await isCondaEnvironment(e))) { - condaTelemetry.condaInfoEnvsInvalid += 1; - return; - } - validCondaInfoEnvs.add(fsPath.normalize(e).toLowerCase()); - }), - ); - const condaInfoEnvs = Array.from(validCondaInfoEnvs); - condaTelemetry.condaInfoEnvs = validCondaInfoEnvs.size; - - // Conda env_dirs - const validEnvDirs = new Set(); - await Promise.all( - // eslint-disable-next-line camelcase - (info?.envs_dirs || []).map(async (e) => { - if (await pathExists(e)) { - validEnvDirs.add(e); - } - }), - ); - condaTelemetry.condaInfoEnvsDirs = validEnvDirs.size; - envsDirs = Array.from(validEnvDirs).map((e) => fsPath.normalize(e).toLowerCase()); - - const nativeCondaEnvs = nativeEnvs.filter( - (e) => this.nativeFinder.categoryToKind(e.kind) === PythonEnvKind.Conda, - ); - - // Find the env_dirs that were not found by native finder. - const nativeCondaEnvDirs = (nativeCondaInfo?.envDirs || []).map((envDir) => - fsPath.normalize(envDir).toLowerCase(), - ); - const nativeCondaEnvPrefix = nativeCondaEnvs - .filter((e) => e.prefix) - .map((e) => fsPath.normalize(e.prefix || '').toLowerCase()); - - envsDirs.forEach((envDir) => { - if ( - !nativeCondaEnvDirs.includes(envDir) && - !nativeCondaEnvDirs.includes(fsPath.join(envDir, 'envs')) && - // If we have a native conda env from this env dir, then we're good. - !nativeCondaEnvPrefix.some((prefix) => prefix.startsWith(envDir)) - ) { - condaTelemetry.nativeCondaEnvDirsNotFound += 1; - - // Find what conda envs returned by `conda info` belong to this envdir folder. - // And find which of those envs do not exist in native conda envs - condaInfoEnvs.forEach((env) => { - if (env.startsWith(envDir)) { - condaTelemetry.nativeCondaEnvDirsNotFoundHasEnvs += 1; - - // Check if this env was in the environments.txt file. - if (condaEnvsInEnvironmentsTxt.includes(env)) { - condaTelemetry.nativeCondaEnvDirsNotFoundHasEnvsInTxt += 1; - } - } - }); - } - }); - - // How many envs are in environments.txt that were not found by native locator. - missingEnvironments.missingNativeCondaEnvsFromTxt = condaEnvsInEnvironmentsTxt.filter( - (env) => !nativeCondaEnvPrefix.some((prefix) => prefix === env), - ).length; - - // How many envs found by native locator & conda info are in the env dirs. - condaTelemetry.condaEnvsInEnvDir = condaInfoEnvs.filter((e) => - envsDirs.some((d) => e.startsWith(d)), - ).length; - condaTelemetry.nativeCondaEnvsInEnvDir = nativeCondaEnvs.filter((e) => - nativeCondaEnvDirs.some((d) => (e.prefix || '').startsWith(d)), - ).length; - - // Check if we have found the conda env that matches the `root_prefix` in the conda info. - // eslint-disable-next-line camelcase - let rootPrefix = info?.root_prefix || ''; - if (rootPrefix && (await pathExists(rootPrefix)) && (await isCondaEnvironment(rootPrefix))) { - rootPrefix = fsPath.normalize(rootPrefix).toLowerCase(); - condaTelemetry.condaRootPrefixInCondaExePath = userProvidedCondaExe.startsWith(rootPrefix); - // Check if we have a conda env that matches this prefix but not found in native envs. - condaTelemetry.condaRootPrefixFoundInInfoNotInNative = - condaInfoEnvs.some((env) => env === rootPrefix) && - !nativeCondaEnvs.some((e) => fsPath.normalize(e.prefix || '').toLowerCase() === rootPrefix); - } - - // eslint-disable-next-line camelcase - let defaultPrefix = info?.default_prefix || ''; - if (defaultPrefix && (await pathExists(defaultPrefix)) && (await isCondaEnvironment(defaultPrefix))) { - defaultPrefix = fsPath.normalize(defaultPrefix).toLowerCase(); - condaTelemetry.condaDefaultPrefixInCondaExePath = userProvidedCondaExe.startsWith(defaultPrefix); - // Check if we have a conda env that matches this prefix but not found in native envs. - condaTelemetry.condaDefaultPrefixFoundInInfoNotInNative = - condaInfoEnvs.some((env) => env === defaultPrefix) && - !nativeCondaEnvs.some((e) => fsPath.normalize(e.prefix || '').toLowerCase() === defaultPrefix); - } - } - + const nativeCondaEnvs = nativeEnvs.filter( + (e) => this.nativeFinder.categoryToKind(e.kind) === PythonEnvKind.Conda, + ); + const condaTelemetry = await getCondaTelemetry(this.nativeFinder, nativeCondaEnvs, nativeEnvs); const prefixesSeenAlready = new Set(); await Promise.all( envs.map(async (env) => { @@ -702,9 +477,6 @@ export class EnvsCollectionService extends PythonEnvsWatcher e.executable === undefined).length; - const nativeCondaEnvs = nativeEnvs.filter( - (e) => this.nativeFinder.categoryToKind(e.kind) === PythonEnvKind.Conda, - ).length; const nativeCustomEnvs = nativeEnvs.filter( (e) => this.nativeFinder.categoryToKind(e.kind) === PythonEnvKind.Custom, ).length; @@ -750,7 +522,7 @@ export class EnvsCollectionService extends PythonEnvsWatcher { + const homeDir = getUserHomeDir(); + if (!homeDir) { + return undefined; + } + return fsPath.join(homeDir, '.conda', 'environments.txt'); +} + +async function getCondaTelemetry( + nativeFinder: NativePythonFinder, + nativeCondaEnvs: NativeEnvInfo[], + nativeEnvs: NativeEnvInfo[], +): Promise { + let envsDirs: string[] = []; + const userProvidedCondaExe = fsPath.normalize( + (getConfiguration('python').get(CONDAPATH_SETTING_KEY) || '').trim(), + ); + + const condaTelemetry: CondaTelemetry = { + condaEnvsInEnvDir: 0, + condaInfoEnvs: 0, + prefixNotExistsCondaEnvs: 0, + condaEnvsWithoutPrefix: 0, + nativeCondaEnvsInEnvDir: 0, + userProvidedCondaExe: userProvidedCondaExe.length > 0, + condaInfoEnvsInvalid: 0, + invalidCondaEnvs: 0, + condaInfoEnvsDuplicate: 0, + condaInfoEnvsInvalidPrefix: 0, + condaInfoEnvsDirs: 0, + nativeCondaRcsNotFound: 0, + nativeCondaEnvDirsNotFound: 0, + nativeCondaEnvDirsNotFoundHasEnvs: 0, + nativeCondaEnvDirsNotFoundHasEnvsInTxt: 0, + nativeCondaEnvsFromTxt: 0, + missingNativeCondaEnvsFromTxt: 0, + }; + + const [info, nativeCondaInfo, condaEnvsInEnvironmentsTxt, environmentsTxt] = await Promise.all([ + Conda.getConda() + .catch((ex) => traceError('Failed to get conda info', ex)) + .then((conda) => conda?.getInfo()), + nativeFinder.getCondaInfo().catch((ex) => traceError(`Failed to get conda info from native locator`, ex)), + getCondaEnvironmentsTxt() + .then(async (txtFile) => { + if (!txtFile) { + return []; + } + const envs: string[] = []; + const lines = await readFile(txtFile) + .catch(() => '') + .then((c) => c.splitLines({ trim: true, removeEmptyEntries: true })); + + await Promise.all( + lines.map(async (line) => { + if ((await pathExists(line)) && (await isCondaEnvironment(line))) { + envs.push(line); + } + }), + ); + return envs; + }) + .catch((ex) => traceError(`Failed to get conda envs from environments.txt`, ex)) + .then((items) => items || []), + getCondaEnvironmentsTxt().catch(noop), + ]); + + if (nativeCondaInfo) { + condaTelemetry.nativeCanSpawnConda = nativeCondaInfo.canSpawnConda; + condaTelemetry.nativeCondaInfoEnvsDirs = new Set(nativeCondaInfo.envDirs).size; + condaTelemetry.nativeCondaRcs = new Set(nativeCondaInfo.condaRcs).size; + condaTelemetry.userProvidedEnvFound = nativeCondaInfo.userProvidedEnvFound; + + const nativeEnvTxt = fsPath.normalize(nativeCondaInfo.environmentsTxt || ''); + condaTelemetry.nativeCondaEnvTxtExists = nativeCondaInfo.environmentsTxtExists === true; + condaTelemetry.nativeCondaEnvsFromTxt = (nativeCondaInfo.environmentsFromTxt || []).length; + condaTelemetry.nativeCondaEnvTxtSame = nativeEnvTxt === environmentsTxt; + } + condaTelemetry.condaEnvsInTxt = condaEnvsInEnvironmentsTxt.length; + condaTelemetry.canSpawnConda = !!info; + + // Conda info rcs + const condaRcFiles = new Set(); + await Promise.all( + // eslint-disable-next-line camelcase + [info?.rc_path, info?.user_rc_path, info?.sys_rc_path, ...(info?.config_files || [])].map(async (rc) => { + if (rc && (await pathExists(rc))) { + condaRcFiles.add(fsPath.normalize(rc)); + } + }), + ).catch(noop); + const condaRcs = Array.from(condaRcFiles); + condaTelemetry.condaRcs = condaRcs.length; + + // Find the condarcs that were not found by native finder. + const nativeCondaRcs = (nativeCondaInfo?.condaRcs || []).map((rc) => fsPath.normalize(rc)); + condaTelemetry.nativeCondaRcsNotFound = condaRcs.filter((rc) => !nativeCondaRcs.includes(rc)).length; + + // Conda info envs + const validCondaInfoEnvs = new Set(); + const duplicate = new Set(); + // Duplicate, invalid conda environments. + await Promise.all( + (info?.envs || []).map(async (e) => { + if (duplicate.has(e)) { + condaTelemetry.condaInfoEnvsDuplicate += 1; + return; + } + duplicate.add(e); + if (!(await pathExists(e))) { + condaTelemetry.condaInfoEnvsInvalidPrefix += 1; + return; + } + if (!(await isCondaEnvironment(e))) { + condaTelemetry.condaInfoEnvsInvalid += 1; + return; + } + validCondaInfoEnvs.add(fsPath.normalize(e)); + }), + ); + const condaInfoEnvs = Array.from(validCondaInfoEnvs); + condaTelemetry.condaInfoEnvs = validCondaInfoEnvs.size; + + // Conda env_dirs + const validEnvDirs = new Set(); + await Promise.all( + // eslint-disable-next-line camelcase + (info?.envs_dirs || []).map(async (e) => { + if (await pathExists(e)) { + validEnvDirs.add(fsPath.normalize(e)); + } + }), + ); + condaTelemetry.condaInfoEnvsDirs = validEnvDirs.size; + envsDirs = Array.from(validEnvDirs); + + // Find the env_dirs that were not found by native finder. + const nativeCondaEnvDirs = (nativeCondaInfo?.envDirs || []).map((envDir) => fsPath.normalize(envDir)); + const nativeCondaEnvPrefix = nativeCondaEnvs.filter((e) => e.prefix).map((e) => fsPath.normalize(e.prefix || '')); + + envsDirs.forEach((envDir) => { + if ( + !nativeCondaEnvDirs.includes(envDir) && + !nativeCondaEnvDirs.includes(fsPath.join(envDir, 'envs')) && + // If we have a native conda env from this env dir, then we're good. + !nativeCondaEnvPrefix.some((prefix) => prefix.startsWith(envDir)) + ) { + condaTelemetry.nativeCondaEnvDirsNotFound += 1; + + // Find what conda envs returned by `conda info` belong to this envdir folder. + // And find which of those envs do not exist in native conda envs + condaInfoEnvs.forEach((env) => { + if (env.startsWith(envDir)) { + condaTelemetry.nativeCondaEnvDirsNotFoundHasEnvs += 1; + + // Check if this env was in the environments.txt file. + if (condaEnvsInEnvironmentsTxt.includes(env)) { + condaTelemetry.nativeCondaEnvDirsNotFoundHasEnvsInTxt += 1; + } + } + }); + } + }); + + // How many envs are in environments.txt that were not found by native locator. + condaTelemetry.missingNativeCondaEnvsFromTxt = condaEnvsInEnvironmentsTxt.filter( + (env) => !nativeCondaEnvPrefix.some((prefix) => prefix === env), + ).length; + + // How many envs found by native locator & conda info are in the env dirs. + condaTelemetry.condaEnvsInEnvDir = condaInfoEnvs.filter((e) => envsDirs.some((d) => e.startsWith(d))).length; + condaTelemetry.nativeCondaEnvsInEnvDir = nativeCondaEnvs.filter((e) => + nativeCondaEnvDirs.some((d) => (e.prefix || '').startsWith(d)), + ).length; + + // Check if we have found the conda env that matches the `root_prefix` in the conda info. + // eslint-disable-next-line camelcase + let rootPrefix = info?.root_prefix || ''; + if (rootPrefix && (await pathExists(rootPrefix)) && (await isCondaEnvironment(rootPrefix))) { + rootPrefix = fsPath.normalize(rootPrefix); + condaTelemetry.condaRootPrefixInCondaExePath = userProvidedCondaExe + .toLowerCase() + .startsWith(rootPrefix.toLowerCase()); + // Check if we have a conda env that matches this prefix but not found in native envs. + condaTelemetry.condaRootPrefixFoundInInfoNotInNative = + condaInfoEnvs.some((env) => env.toLowerCase() === rootPrefix.toLowerCase()) && + !nativeCondaEnvs.some((e) => fsPath.normalize(e.prefix || '').toLowerCase() === rootPrefix.toLowerCase()); + condaTelemetry.condaRootPrefixFoundInTxt = condaEnvsInEnvironmentsTxt.some( + (e) => e.toLowerCase() === rootPrefix.toLowerCase(), + ); + + if (condaTelemetry.condaRootPrefixFoundInInfoNotInNative) { + // Verify we are able to discover this environment as a conda env using native finder. + const rootPrefixEnvs = await nativeFinder.find(rootPrefix); + // Did we find an env with the same prefix? + const rootPrefixEnv = rootPrefixEnvs.find( + (e) => fsPath.normalize(e.prefix || '').toLowerCase() === rootPrefix.toLowerCase(), + ); + condaTelemetry.condaRootPrefixEnvsAfterFind = rootPrefixEnvs.length; + condaTelemetry.condaRootPrefixFoundInInfoAfterFind = !!rootPrefixEnv; + condaTelemetry.condaRootPrefixFoundInInfoAfterFindKind = rootPrefixEnv?.kind; + condaTelemetry.condaRootPrefixFoundAsAnotherKind = nativeEnvs.find( + (e) => fsPath.normalize(e.prefix || '').toLowerCase() === rootPrefix.toLowerCase(), + )?.kind; + condaTelemetry.condaRootPrefixFoundAsPrefixOfAnother = nativeEnvs.find((e) => + fsPath + .normalize(e.prefix || '') + .toLowerCase() + .startsWith(rootPrefix.toLowerCase()), + )?.kind; + } + } + + // eslint-disable-next-line camelcase + let defaultPrefix = info?.default_prefix || ''; + if (defaultPrefix && (await pathExists(defaultPrefix)) && (await isCondaEnvironment(defaultPrefix))) { + defaultPrefix = fsPath.normalize(defaultPrefix); + condaTelemetry.condaDefaultPrefixInCondaExePath = userProvidedCondaExe + .toLowerCase() + .startsWith(defaultPrefix.toLowerCase()); + // Check if we have a conda env that matches this prefix but not found in native envs. + condaTelemetry.condaDefaultPrefixFoundInInfoNotInNative = + condaInfoEnvs.some((env) => env.toLowerCase() === defaultPrefix.toLowerCase()) && + !nativeCondaEnvs.some( + (e) => fsPath.normalize(e.prefix || '').toLowerCase() === defaultPrefix.toLowerCase(), + ); + condaTelemetry.condaDefaultPrefixFoundInTxt = condaEnvsInEnvironmentsTxt.some( + (e) => e.toLowerCase() === rootPrefix.toLowerCase(), + ); + + if (condaTelemetry.condaDefaultPrefixFoundInInfoNotInNative) { + // Verify we are able to discover this environment as a conda env using native finder. + const defaultPrefixEnvs = await nativeFinder.find(defaultPrefix); + // Did we find an env with the same prefix? + const defaultPrefixEnv = defaultPrefixEnvs.find( + (e) => fsPath.normalize(e.prefix || '').toLowerCase() === defaultPrefix.toLowerCase(), + ); + condaTelemetry.condaDefaultPrefixEnvsAfterFind = defaultPrefixEnvs.length; + condaTelemetry.condaDefaultPrefixFoundInInfoAfterFind = !!defaultPrefixEnv; + condaTelemetry.condaDefaultPrefixFoundInInfoAfterFindKind = defaultPrefixEnv?.kind; + condaTelemetry.condaDefaultPrefixFoundAsAnotherKind = nativeEnvs.find( + (e) => fsPath.normalize(e.prefix || '').toLowerCase() === defaultPrefix.toLowerCase(), + )?.kind; + condaTelemetry.condaDefaultPrefixFoundAsPrefixOfAnother = nativeEnvs.find((e) => + fsPath + .normalize(e.prefix || '') + .toLowerCase() + .startsWith(defaultPrefix.toLowerCase()), + )?.kind; + } + } + + return condaTelemetry; +} diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index f4c108d7385d..f7283597b688 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -1168,10 +1168,22 @@ export interface IEventNamePropertyMapping { "nativeCanSpawnConda" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne"}, "userProvidedEnvFound" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, "condaRootPrefixFoundInInfoNotInNative" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaDefaultPrefixFoundAsAnotherKind" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaRootPrefixFoundAsPrefixOfAnother" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaDefaultPrefixFoundAsPrefixOfAnother" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaRootPrefixFoundInTxt" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaDefaultPrefixFoundInTxt" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaRootPrefixFoundInInfoAfterFind" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaRootPrefixFoundInInfoAfterFindKind" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaRootPrefixFoundAsAnotherKind" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, "condaRootPrefixInCondaExePath" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, "condaDefaultPrefixFoundInInfoNotInNative" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaDefaultPrefixFoundInInfoAfterFind" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaDefaultPrefixFoundInInfoAfterFindKind" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, "condaDefaultPrefixInCondaExePath" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, "userProvidedCondaExe" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaRootPrefixEnvsAfterFind" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "condaDefaultPrefixEnvsAfterFind" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, "activeStateEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, "condaEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, "customEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, @@ -1309,11 +1321,28 @@ export interface IEventNamePropertyMapping { * Global conda envs locations are returned by `conda info` in the `envs_dirs` setting. */ nativeCondaEnvsInEnvDir?: number; + condaRootPrefixEnvsAfterFind?: number; + condaDefaultPrefixEnvsAfterFind?: number; /** * A conda env found that matches the root_prefix returned by `conda info` * However a corresponding conda env not found by native locator. */ - condaRootPrefixFoundInInfoNotInNative?: boolean; + condaDefaultPrefixFoundInInfoAfterFind?: boolean; + condaRootPrefixFoundInTxt?: boolean; + condaDefaultPrefixFoundInTxt?: boolean; + condaDefaultPrefixFoundInInfoAfterFindKind?: string; + condaRootPrefixFoundAsAnotherKind?: string; + condaRootPrefixFoundAsPrefixOfAnother?: string; + condaDefaultPrefixFoundAsAnotherKind?: string; + condaDefaultPrefixFoundAsPrefixOfAnother?: string; + /** + * Whether we were able to identify the conda root prefix in the conda exe path as a conda env using `find` in native finder API. + */ + condaRootPrefixFoundInInfoAfterFind?: boolean; + /** + * Type of python env detected for the conda root prefix. + */ + condaRootPrefixFoundInInfoAfterFindKind?: string; /** * The conda root prefix is found in the conda exe path. */ diff --git a/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts index 9acd76a6913f..92978738373d 100644 --- a/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts @@ -29,6 +29,10 @@ import { OSType, getOSType } from '../../../../common'; import * as nativeFinder from '../../../../../client/pythonEnvironments/base/locators/common/nativePythonFinder'; class MockNativePythonFinder implements nativeFinder.NativeGlobalPythonFinder { + find(_searchPath: string): Promise { + throw new Error('Method not implemented.'); + } + getCondaInfo(): Promise { throw new Error('Method not implemented.'); }