Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions docs/startup-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ python environments extension begins activation
1. sets up a JSON-RPC connection to it over stdin/stdout
2. register all built-in managers in parallel (Promise.all):
- system: create SysPythonManager + VenvManager + PipPackageManager, register immediately (✅ NO PET call, sets up file watcher)
- conda: `getConda(nativeFinder)` checks settings → cache → persistent state → PATH
- pyenv & pipenv & poetry: create PyEnvManager, register immediately
- conda & pyenv & pipenv & poetry: create manager, register immediately
- ✅ NO PET call — always registers unconditionally (lazy discovery)
- shellStartupVars: initialize
- all managers fire `onDidChangeEnvironmentManager` → ManagerReady resolves
Expand Down
72 changes: 70 additions & 2 deletions src/managers/conda/condaEnvManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,29 @@ import {
SetEnvironmentScope,
} from '../../api';
import { CondaStrings } from '../../common/localize';
import { traceError } from '../../common/logging';
import { traceError, traceInfo } from '../../common/logging';
import { StopWatch } from '../../common/stopWatch';
import { EventNames } from '../../common/telemetry/constants';
import { classifyError } from '../../common/telemetry/errorClassifier';
import { sendTelemetryEvent } from '../../common/telemetry/sender';
import { createDeferred, Deferred } from '../../common/utils/deferred';
import { normalizePath } from '../../common/utils/pathUtils';
import { showErrorMessage, showInformationMessage, withProgress } from '../../common/window.apis';
import { PythonProjectManager } from '../../internal.api';
import { getProjectFsPathForScope, tryFastPathGet } from '../common/fastPath';
import { NativePythonFinder } from '../common/nativePythonFinder';
import { CondaSourcingStatus } from './condaSourcingUtils';
import { notifyMissingManagerIfDefault } from '../common/utils';
import { constructCondaSourcingStatus, CondaSourcingStatus } from './condaSourcingUtils';
import {
checkForNoPythonCondaEnvironment,
clearCondaCache,
createCondaEnvironment,
deleteCondaEnvironment,
generateName,
getConda,
getCondaForGlobal,
getCondaForWorkspace,
getCondaPathSetting,
getDefaultCondaPrefix,
quickCreateConda,
refreshCondaEnvs,
Expand All @@ -61,6 +69,7 @@ export class CondaEnvManager implements EnvironmentManager, Disposable {
private readonly nativeFinder: NativePythonFinder,
private readonly api: PythonEnvironmentApi,
public readonly log: LogOutputChannel,
private readonly projectManager?: PythonProjectManager,
) {
this.name = 'conda';
this.displayName = 'Conda';
Expand All @@ -87,8 +96,27 @@ export class CondaEnvManager implements EnvironmentManager, Disposable {
}

this._initialized = createDeferred();
const stopWatch = new StopWatch();
let result: 'success' | 'tool_not_found' | 'error' = 'success';
let envCount = 0;
let toolSource = 'none';
let errorType: string | undefined;

try {
// Check if tool is findable before PET refresh (settings/cache/persistent state/PATH only, no PET).
// Calling getConda() without a native finder skips the PET refresh path inside getCondaExecutable.
const hasExplicitSetting = !!getCondaPathSetting();
let condaTool: string | undefined;
try {
condaTool = await getConda();
} catch {
condaTool = undefined;
}
const preRefreshTool = condaTool;
if (preRefreshTool) {
toolSource = hasExplicitSetting ? 'settings' : 'local';
}

await withProgress(
{
location: ProgressLocation.Window,
Expand All @@ -104,7 +132,47 @@ export class CondaEnvManager implements EnvironmentManager, Disposable {
);
},
);

envCount = this.collection.length;

// If tool wasn't found via local lookup, check if refresh discovered it via PET
// (refreshCondaEnvs persists the conda path it found from native data).
if (!preRefreshTool) {
try {
condaTool = await getConda();
} catch {
condaTool = undefined;
}
toolSource = condaTool ? 'pet' : 'none';
}

if (toolSource === 'none') {
result = 'tool_not_found';
traceInfo('Conda not found, conda features will be inactive.');
if (this.projectManager) {
await notifyMissingManagerIfDefault('ms-python.python:conda', this.projectManager, this.api);
}
} else if (condaTool) {
// Conda was found — populate sourcing information for activation/shell support.
try {
this.sourcingInformation = await constructCondaSourcingStatus(condaTool);
traceInfo(this.sourcingInformation.toString());
} catch (ex) {
traceError('Failed to construct conda sourcing status', ex);
}
}
} catch (ex) {
result = 'error';
errorType = classifyError(ex);
traceError('Conda lazy initialization failed', ex);
} finally {
sendTelemetryEvent(EventNames.MANAGER_LAZY_INIT, stopWatch.elapsedTime, {
managerName: 'conda',
result,
envCount,
toolSource,
errorType,
});
this._initialized.resolve();
}
}
Expand Down
55 changes: 9 additions & 46 deletions src/managers/conda/main.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,28 @@
import { Disposable, LogOutputChannel } from 'vscode';
import { PythonEnvironmentApi } from '../../api';
import { traceInfo } from '../../common/logging';
import { EventNames } from '../../common/telemetry/constants';
import { sendTelemetryEvent } from '../../common/telemetry/sender';
import { getPythonApi } from '../../features/pythonApi';
import { PythonProjectManager } from '../../internal.api';
import { NativePythonFinder } from '../common/nativePythonFinder';
import { notifyMissingManagerIfDefault } from '../common/utils';
import { CondaEnvManager } from './condaEnvManager';
import { CondaPackageManager } from './condaPackageManager';
import { CondaSourcingStatus, constructCondaSourcingStatus } from './condaSourcingUtils';
import { getConda } from './condaUtils';

export async function registerCondaFeatures(
nativeFinder: NativePythonFinder,
disposables: Disposable[],
log: LogOutputChannel,
projectManager: PythonProjectManager,
): Promise<void> {
let stage = 'getPythonApi';
const api: PythonEnvironmentApi = await getPythonApi();

let condaPath: string | undefined;
try {
// get Conda will return only ONE conda manager, that correlates to a single conda install
stage = 'getConda';
condaPath = await getConda(nativeFinder);
} catch (ex) {
traceInfo('Conda not found, turning off conda features.', ex);
sendTelemetryEvent(EventNames.MANAGER_REGISTRATION_SKIPPED, undefined, {
managerName: 'conda',
reason: 'tool_not_found',
});
await notifyMissingManagerIfDefault('ms-python.python:conda', projectManager, api);
return;
}
traceInfo('Registering conda manager (environments will be discovered lazily)');
const envManager = new CondaEnvManager(nativeFinder, api, log, projectManager);
const packageManager = new CondaPackageManager(api, log);

// Conda was found — errors below are real registration failures (let safeRegister handle telemetry)
try {
stage = 'constructCondaSourcingStatus';
const sourcingStatus: CondaSourcingStatus = await constructCondaSourcingStatus(condaPath);
traceInfo(sourcingStatus.toString());

stage = 'createEnvManager';
const envManager = new CondaEnvManager(nativeFinder, api, log);
stage = 'createPkgManager';
const packageManager = new CondaPackageManager(api, log);

envManager.sourcingInformation = sourcingStatus;

stage = 'registerManagers';
disposables.push(
envManager,
packageManager,
api.registerEnvironmentManager(envManager),
api.registerPackageManager(packageManager),
);
} catch (ex) {
await notifyMissingManagerIfDefault('ms-python.python:conda', projectManager, api);
const err = ex instanceof Error ? ex : new Error(String(ex));
(err as Error & { failureStage?: string }).failureStage = stage;
throw err;
}
disposables.push(
envManager,
packageManager,
api.registerEnvironmentManager(envManager),
api.registerPackageManager(packageManager),
);
}
Loading