Skip to content

Commit 09fdfba

Browse files
authored
bug: surface warning for broken defaultEnvManager and resolve (#787)
fixes microsoft/vscode-python#25430 <img width="585" height="174" alt="image" src="https://github.com/user-attachments/assets/e09f1d28-5e36-4ccd-a7b2-0c42adacf54f" />
1 parent 1816c28 commit 09fdfba

File tree

8 files changed

+186
-11
lines changed

8 files changed

+186
-11
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
applyTo: '**'
3+
---
4+
5+
Provide project context and coding guidelines that AI should follow when generating code, answering questions, or reviewing changes.# Coding Instructions for vscode-python-environments
6+
7+
## Localization
8+
9+
- Localize all user-facing messages using VS Code’s `l10n` API.
10+
- Internal log messages do not require localization.
11+
12+
## Logging
13+
14+
- Use the extension’s logging utilities (`traceLog`, `traceVerbose`) for internal logs.
15+
- Do not use `console.log` or `console.warn` for logging.
16+
17+
## Settings Precedence
18+
19+
- Always consider VS Code settings precedence:
20+
1. Workspace folder
21+
2. Workspace
22+
3. User/global
23+
- Remove or update settings from the highest precedence scope first.
24+
25+
## Error Handling & User Notifications
26+
27+
- Avoid showing the same error message multiple times in a session; track state with a module-level variable.
28+
- Use clear, actionable error messages and offer relevant buttons (e.g., "Open settings", "Close").
29+
30+
## Documentation
31+
32+
- Add clear docstrings to public functions, describing their purpose, parameters, and behavior.

src/extension.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -563,10 +563,10 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
563563
sysPythonManager.resolve(sysMgr);
564564
await Promise.all([
565565
registerSystemPythonFeatures(nativeFinder, context.subscriptions, outputChannel, sysMgr),
566-
registerCondaFeatures(nativeFinder, context.subscriptions, outputChannel),
567-
registerPyenvFeatures(nativeFinder, context.subscriptions),
568-
registerPipenvFeatures(nativeFinder, context.subscriptions),
569-
registerPoetryFeatures(nativeFinder, context.subscriptions, outputChannel),
566+
registerCondaFeatures(nativeFinder, context.subscriptions, outputChannel, projectManager),
567+
registerPyenvFeatures(nativeFinder, context.subscriptions, projectManager),
568+
registerPipenvFeatures(nativeFinder, context.subscriptions, projectManager),
569+
registerPoetryFeatures(nativeFinder, context.subscriptions, outputChannel, projectManager),
570570
shellStartupVarsMgr.initialize(),
571571
]);
572572

@@ -616,7 +616,7 @@ async function resolveDefaultInterpreter(
616616

617617
if (defaultInterpreterPath) {
618618
const defaultManager = getConfiguration('python-envs').get<string>('defaultEnvManager', 'undefined');
619-
traceInfo(`resolveDefaultInterpreter setting exists; found defaultEnvManager: ${defaultManager}`);
619+
traceInfo(`resolveDefaultInterpreter setting exists; found defaultEnvManager: ${defaultManager}. `);
620620
if (!defaultManager || defaultManager === 'ms-python.python:venv') {
621621
try {
622622
const resolved: NativeEnvInfo = await nativeFinder.resolve(defaultInterpreterPath);

src/features/settings/settingHelpers.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from 'vscode';
1010
import { PythonProject } from '../../api';
1111
import { DEFAULT_ENV_MANAGER_ID, DEFAULT_PACKAGE_MANAGER_ID } from '../../common/constants';
12-
import { traceError, traceInfo } from '../../common/logging';
12+
import { traceError, traceInfo, traceWarn } from '../../common/logging';
1313
import { getWorkspaceFile, getWorkspaceFolders } from '../../common/workspace.apis';
1414
import { PythonProjectManager, PythonProjectSettings } from '../../internal.api';
1515

@@ -31,17 +31,34 @@ function getSettings(
3131
return undefined;
3232
}
3333

34+
let DEFAULT_ENV_MANAGER_BROKEN = false;
35+
let hasShownDefaultEnvManagerBrokenWarn = false;
36+
37+
export function setDefaultEnvManagerBroken(broken: boolean) {
38+
DEFAULT_ENV_MANAGER_BROKEN = broken;
39+
}
40+
export function isDefaultEnvManagerBroken(): boolean {
41+
return DEFAULT_ENV_MANAGER_BROKEN;
42+
}
43+
3444
export function getDefaultEnvManagerSetting(wm: PythonProjectManager, scope?: Uri): string {
3545
const config = workspace.getConfiguration('python-envs', scope);
3646
const settings = getSettings(wm, config, scope);
3747
if (settings && settings.envManager.length > 0) {
3848
return settings.envManager;
3949
}
40-
50+
// Only show the warning once per session
51+
if (isDefaultEnvManagerBroken()) {
52+
if (!hasShownDefaultEnvManagerBrokenWarn) {
53+
traceWarn(`Default environment manager is broken, using system default: ${DEFAULT_ENV_MANAGER_ID}`);
54+
hasShownDefaultEnvManagerBrokenWarn = true;
55+
}
56+
return DEFAULT_ENV_MANAGER_ID;
57+
}
4158
const defaultManager = config.get<string>('defaultEnvManager');
4259
if (defaultManager === undefined || defaultManager === null || defaultManager === '') {
4360
traceError('No default environment manager set. Check setting python-envs.defaultEnvManager');
44-
traceInfo(`Using system default package manager: ${DEFAULT_ENV_MANAGER_ID}`);
61+
traceWarn(`Using system default package manager: ${DEFAULT_ENV_MANAGER_ID}`);
4562
return DEFAULT_ENV_MANAGER_ID;
4663
}
4764
return defaultManager;

src/managers/common/utils.ts

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import * as fs from 'fs-extra';
22
import path from 'path';
3-
import { PythonCommandRunConfiguration, PythonEnvironment } from '../../api';
3+
import { commands, ConfigurationTarget, l10n, window, workspace } from 'vscode';
4+
import { PythonCommandRunConfiguration, PythonEnvironment, PythonEnvironmentApi } from '../../api';
5+
import { traceLog, traceVerbose } from '../../common/logging';
46
import { isWindows } from '../../common/utils/platformUtils';
57
import { ShellConstants } from '../../features/common/shellConstants';
8+
import { getDefaultEnvManagerSetting, setDefaultEnvManagerBroken } from '../../features/settings/settingHelpers';
9+
import { PythonProjectManager } from '../../internal.api';
610
import { Installable } from './types';
711

812
export function noop() {
@@ -194,3 +198,104 @@ export async function getShellActivationCommands(binDir: string): Promise<{
194198
shellDeactivation,
195199
};
196200
}
201+
202+
// Tracks if the broken defaultEnvManager error message has been shown this session
203+
let hasShownBrokenDefaultEnvManagerError = false;
204+
205+
/**
206+
* Checks if the given managerId is set as the default environment manager for the project.
207+
* If so, marks the default manager as broken, refreshes environments, and shows an error message to the user.
208+
* The error message offers to reset the setting, view the setting, or close.
209+
* The error message is only shown once per session.
210+
*
211+
* @param managerId The environment manager id to check.
212+
* @param projectManager The Python project manager instance.
213+
* @param api The Python environment API instance.
214+
*/
215+
export async function notifyMissingManagerIfDefault(
216+
managerId: string,
217+
projectManager: PythonProjectManager,
218+
api: PythonEnvironmentApi,
219+
) {
220+
const defaultEnvManager = getDefaultEnvManagerSetting(projectManager);
221+
if (defaultEnvManager === managerId) {
222+
if (hasShownBrokenDefaultEnvManagerError) {
223+
return;
224+
}
225+
hasShownBrokenDefaultEnvManagerError = true;
226+
setDefaultEnvManagerBroken(true);
227+
await api.refreshEnvironments(undefined);
228+
window
229+
.showErrorMessage(
230+
l10n.t(
231+
"The default environment manager is set to '{0}', but the {1} executable could not be found.",
232+
defaultEnvManager,
233+
managerId.split(':')[1],
234+
),
235+
l10n.t('Reset setting'),
236+
l10n.t('View setting'),
237+
l10n.t('Close'),
238+
)
239+
.then(async (selection) => {
240+
if (selection === 'Reset setting') {
241+
const result = await removeFirstDefaultEnvManagerSettingDetailed(managerId);
242+
if (!result.found) {
243+
window
244+
.showErrorMessage(
245+
l10n.t(
246+
"Could not find a setting for 'defaultEnvManager' set to '{0}' to reset.",
247+
managerId,
248+
),
249+
l10n.t('Open settings'),
250+
l10n.t('Close'),
251+
)
252+
.then((sel) => {
253+
if (sel === 'Open settings') {
254+
commands.executeCommand(
255+
'workbench.action.openSettings',
256+
'python-envs.defaultEnvManager',
257+
);
258+
}
259+
});
260+
}
261+
}
262+
if (selection === 'View setting') {
263+
commands.executeCommand('workbench.action.openSettings', 'python-envs.defaultEnvManager');
264+
}
265+
});
266+
}
267+
}
268+
269+
/**
270+
* Removes the first occurrence of 'defaultEnvManager' set to managerId, returns where it was removed, and logs the action.
271+
* @param managerId The manager id to match and remove.
272+
* @returns { found: boolean, scope?: string }
273+
*/
274+
export async function removeFirstDefaultEnvManagerSettingDetailed(
275+
managerId: string,
276+
): Promise<{ found: boolean; scope?: string }> {
277+
const config = workspace.getConfiguration('python-envs');
278+
const inspect = config.inspect('defaultEnvManager');
279+
280+
// Workspace folder settings (multi-root)
281+
if (inspect?.workspaceFolderValue !== undefined && inspect.workspaceFolderValue === managerId) {
282+
await config.update('defaultEnvManager', undefined, ConfigurationTarget.WorkspaceFolder);
283+
traceLog("[python-envs] Removed 'defaultEnvManager' from Workspace Folder settings.");
284+
return { found: true, scope: 'Workspace Folder' };
285+
}
286+
// Workspace settings
287+
if (inspect?.workspaceValue !== undefined && inspect.workspaceValue === managerId) {
288+
await config.update('defaultEnvManager', undefined, ConfigurationTarget.Workspace);
289+
traceLog("[python-envs] Removed 'defaultEnvManager' from Workspace settings.");
290+
return { found: true, scope: 'Workspace' };
291+
}
292+
// User/global settings
293+
if (inspect?.globalValue !== undefined && inspect.globalValue === managerId) {
294+
await config.update('defaultEnvManager', undefined, ConfigurationTarget.Global);
295+
traceLog("[python-envs] Removed 'defaultEnvManager' from User/Global settings.");
296+
return { found: true, scope: 'User/Global' };
297+
}
298+
// No matching setting found
299+
traceVerbose(`[python-envs] Could not find 'defaultEnvManager' set to '${managerId}' in any scope.`);
300+
return { found: false };
301+
}

src/managers/conda/main.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { Disposable, LogOutputChannel } from 'vscode';
22
import { PythonEnvironmentApi } from '../../api';
33
import { traceInfo } from '../../common/logging';
44
import { getPythonApi } from '../../features/pythonApi';
5+
import { PythonProjectManager } from '../../internal.api';
56
import { NativePythonFinder } from '../common/nativePythonFinder';
7+
import { notifyMissingManagerIfDefault } from '../common/utils';
68
import { CondaEnvManager } from './condaEnvManager';
79
import { CondaPackageManager } from './condaPackageManager';
810
import { CondaSourcingStatus, constructCondaSourcingStatus } from './condaSourcingUtils';
@@ -12,6 +14,7 @@ export async function registerCondaFeatures(
1214
nativeFinder: NativePythonFinder,
1315
disposables: Disposable[],
1416
log: LogOutputChannel,
17+
projectManager: PythonProjectManager,
1518
): Promise<void> {
1619
const api: PythonEnvironmentApi = await getPythonApi();
1720

@@ -34,5 +37,6 @@ export async function registerCondaFeatures(
3437
);
3538
} catch (ex) {
3639
traceInfo('Conda not found, turning off conda features.', ex);
40+
await notifyMissingManagerIfDefault('ms-python.python:conda', projectManager, api);
3741
}
3842
}

src/managers/pipenv/main.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ import { Disposable } from 'vscode';
22
import { PythonEnvironmentApi } from '../../api';
33
import { traceInfo } from '../../common/logging';
44
import { getPythonApi } from '../../features/pythonApi';
5+
import { PythonProjectManager } from '../../internal.api';
56
import { NativePythonFinder } from '../common/nativePythonFinder';
67
import { PipenvManager } from './pipenvManager';
78
import { getPipenv } from './pipenvUtils';
89

10+
import { notifyMissingManagerIfDefault } from '../common/utils';
11+
912
export async function registerPipenvFeatures(
1013
nativeFinder: NativePythonFinder,
1114
disposables: Disposable[],
15+
projectManager: PythonProjectManager,
1216
): Promise<void> {
1317
const api: PythonEnvironmentApi = await getPythonApi();
1418

@@ -17,12 +21,13 @@ export async function registerPipenvFeatures(
1721

1822
if (pipenv) {
1923
const mgr = new PipenvManager(nativeFinder, api);
20-
2124
disposables.push(mgr, api.registerEnvironmentManager(mgr));
2225
} else {
2326
traceInfo('Pipenv not found, turning off pipenv features.');
27+
await notifyMissingManagerIfDefault('ms-python.python:pipenv', projectManager, api);
2428
}
2529
} catch (ex) {
2630
traceInfo('Pipenv not found, turning off pipenv features.', ex);
31+
await notifyMissingManagerIfDefault('ms-python.python:pipenv', projectManager, api);
2732
}
2833
}

src/managers/poetry/main.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { Disposable, LogOutputChannel } from 'vscode';
22
import { PythonEnvironmentApi } from '../../api';
33
import { traceInfo } from '../../common/logging';
44
import { getPythonApi } from '../../features/pythonApi';
5+
import { PythonProjectManager } from '../../internal.api';
56
import { NativePythonFinder } from '../common/nativePythonFinder';
7+
import { notifyMissingManagerIfDefault } from '../common/utils';
68
import { PoetryManager } from './poetryManager';
79
import { PoetryPackageManager } from './poetryPackageManager';
810
import { getPoetry, getPoetryVersion } from './poetryUtils';
@@ -11,6 +13,7 @@ export async function registerPoetryFeatures(
1113
nativeFinder: NativePythonFinder,
1214
disposables: Disposable[],
1315
outputChannel: LogOutputChannel,
16+
projectManager: PythonProjectManager,
1417
): Promise<void> {
1518
const api: PythonEnvironmentApi = await getPythonApi();
1619

@@ -31,8 +34,12 @@ export async function registerPoetryFeatures(
3134
api.registerEnvironmentManager(envManager),
3235
api.registerPackageManager(pkgManager),
3336
);
37+
} else {
38+
traceInfo('Poetry not found, turning off poetry features.');
39+
await notifyMissingManagerIfDefault('ms-python.python:poetry', projectManager, api);
3440
}
3541
} catch (ex) {
3642
traceInfo('Poetry not found, turning off poetry features.', ex);
43+
await notifyMissingManagerIfDefault('ms-python.python:poetry', projectManager, api);
3744
}
3845
}

src/managers/pyenv/main.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,31 @@ import { Disposable } from 'vscode';
22
import { PythonEnvironmentApi } from '../../api';
33
import { traceInfo } from '../../common/logging';
44
import { getPythonApi } from '../../features/pythonApi';
5+
import { PythonProjectManager } from '../../internal.api';
56
import { NativePythonFinder } from '../common/nativePythonFinder';
7+
import { notifyMissingManagerIfDefault } from '../common/utils';
68
import { PyEnvManager } from './pyenvManager';
79
import { getPyenv } from './pyenvUtils';
810

911
export async function registerPyenvFeatures(
1012
nativeFinder: NativePythonFinder,
1113
disposables: Disposable[],
14+
projectManager: PythonProjectManager,
1215
): Promise<void> {
1316
const api: PythonEnvironmentApi = await getPythonApi();
1417

1518
try {
1619
const pyenv = await getPyenv(nativeFinder);
17-
20+
1821
if (pyenv) {
1922
const mgr = new PyEnvManager(nativeFinder, api);
2023
disposables.push(mgr, api.registerEnvironmentManager(mgr));
2124
} else {
2225
traceInfo('Pyenv not found, turning off pyenv features.');
26+
await notifyMissingManagerIfDefault('ms-python.python:pyenv', projectManager, api);
2327
}
2428
} catch (ex) {
2529
traceInfo('Pyenv not found, turning off pyenv features.', ex);
30+
await notifyMissingManagerIfDefault('ms-python.python:pyenv', projectManager, api);
2631
}
2732
}

0 commit comments

Comments
 (0)