Skip to content

Commit 336d699

Browse files
authored
enhance error handling and user notifications for Python Locator failures (microsoft#25717)
fixes microsoft#25689
1 parent 92fbff4 commit 336d699

File tree

2 files changed

+77
-3
lines changed

2 files changed

+77
-3
lines changed

src/client/common/utils/localize.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,3 +516,15 @@ export namespace CreateEnv {
516516
);
517517
}
518518
}
519+
520+
export namespace PythonLocator {
521+
export const startupFailedNotification = l10n.t(
522+
'Python Locator failed to start. Python environment discovery may not work correctly.',
523+
);
524+
export const windowsRuntimeMissing = l10n.t(
525+
'Missing Windows runtime dependencies detected. The Python Locator requires the Microsoft Visual C++ Redistributable. This is often missing on clean Windows installations.',
526+
);
527+
export const windowsStartupFailed = l10n.t(
528+
'Python Locator failed to start on Windows. This might be due to missing system dependencies such as the Microsoft Visual C++ Redistributable.',
529+
);
530+
}

src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,24 @@ import { noop } from '../../../../common/utils/misc';
1515
import { getConfiguration, getWorkspaceFolderPaths, isTrusted } from '../../../../common/vscodeApis/workspaceApis';
1616
import { CONDAPATH_SETTING_KEY } from '../../../common/environmentManagers/conda';
1717
import { VENVFOLDERS_SETTING_KEY, VENVPATH_SETTING_KEY } from '../lowLevel/customVirtualEnvLocator';
18-
import { createLogOutputChannel } from '../../../../common/vscodeApis/windowApis';
18+
import { createLogOutputChannel, showWarningMessage } from '../../../../common/vscodeApis/windowApis';
1919
import { sendNativeTelemetry, NativePythonTelemetry } from './nativePythonTelemetry';
2020
import { NativePythonEnvironmentKind } from './nativePythonUtils';
2121
import type { IExtensionContext } from '../../../../common/types';
2222
import { StopWatch } from '../../../../common/utils/stopWatch';
2323
import { untildify } from '../../../../common/helpers';
2424
import { traceError } from '../../../../logging';
25+
import { Common, PythonLocator } from '../../../../common/utils/localize';
26+
import { Commands } from '../../../../common/constants';
27+
import { executeCommand } from '../../../../common/vscodeApis/commandApis';
28+
import { getGlobalStorage, IPersistentStorage } from '../../../../common/persistentState';
2529

2630
const PYTHON_ENV_TOOLS_PATH = isWindows()
2731
? path.join(EXTENSION_ROOT_DIR, 'python-env-tools', 'bin', 'pet.exe')
2832
: path.join(EXTENSION_ROOT_DIR, 'python-env-tools', 'bin', 'pet');
2933

34+
const DONT_SHOW_SPAWN_ERROR_AGAIN = 'DONT_SHOW_NATIVE_FINDER_SPAWN_ERROR_AGAIN';
35+
3036
export interface NativeEnvInfo {
3137
displayName?: string;
3238
name?: string;
@@ -106,8 +112,13 @@ class NativePythonFinderImpl extends DisposableBase implements NativePythonFinde
106112
timeToRefresh: 0,
107113
};
108114

109-
constructor(private readonly cacheDirectory?: Uri) {
115+
private readonly suppressErrorNotification: IPersistentStorage<boolean>;
116+
117+
constructor(private readonly cacheDirectory?: Uri, private readonly context?: IExtensionContext) {
110118
super();
119+
this.suppressErrorNotification = this.context
120+
? getGlobalStorage<boolean>(this.context, DONT_SHOW_SPAWN_ERROR_AGAIN, false)
121+
: ({ get: () => false, set: async () => {} } as IPersistentStorage<boolean>);
111122
this.connection = this.start();
112123
void this.configure();
113124
this.firstRefreshResults = this.refreshFirstTime();
@@ -212,6 +223,30 @@ class NativePythonFinderImpl extends DisposableBase implements NativePythonFinde
212223
proc.stderr.on('data', (data) => this.outputChannel.error(data.toString()));
213224
writable.pipe(proc.stdin, { end: false });
214225

226+
// Handle spawn errors (e.g., missing DLLs on Windows)
227+
proc.on('error', (error) => {
228+
this.outputChannel.error(`Python Locator process error: ${error.message}`);
229+
this.outputChannel.error(`Error details: ${JSON.stringify(error)}`);
230+
this.handleSpawnError(error.message);
231+
});
232+
233+
// Handle immediate exits with error codes
234+
let hasStarted = false;
235+
setTimeout(() => {
236+
hasStarted = true;
237+
}, 1000);
238+
239+
proc.on('exit', (code, signal) => {
240+
if (!hasStarted && code !== null && code !== 0) {
241+
const errorMessage = `Python Locator process exited immediately with code ${code}`;
242+
this.outputChannel.error(errorMessage);
243+
if (signal) {
244+
this.outputChannel.error(`Exit signal: ${signal}`);
245+
}
246+
this.handleSpawnError(errorMessage);
247+
}
248+
});
249+
215250
disposables.push({
216251
dispose: () => {
217252
try {
@@ -397,6 +432,33 @@ class NativePythonFinderImpl extends DisposableBase implements NativePythonFinde
397432
async getCondaInfo(): Promise<NativeCondaInfo> {
398433
return this.connection.sendRequest<NativeCondaInfo>('condaInfo');
399434
}
435+
436+
private async handleSpawnError(errorMessage: string): Promise<void> {
437+
// Check if user has chosen to not see this error again
438+
if (this.suppressErrorNotification.get()) {
439+
return;
440+
}
441+
442+
// Check for Windows runtime DLL issues
443+
if (isWindows() && errorMessage.toLowerCase().includes('vcruntime')) {
444+
this.outputChannel.error(PythonLocator.windowsRuntimeMissing);
445+
} else if (isWindows()) {
446+
this.outputChannel.error(PythonLocator.windowsStartupFailed);
447+
}
448+
449+
// Show notification to user
450+
const selection = await showWarningMessage(
451+
PythonLocator.startupFailedNotification,
452+
Common.openOutputPanel,
453+
Common.doNotShowAgain,
454+
);
455+
456+
if (selection === Common.openOutputPanel) {
457+
await executeCommand(Commands.ViewOutput);
458+
} else if (selection === Common.doNotShowAgain) {
459+
await this.suppressErrorNotification.set(true);
460+
}
461+
}
400462
}
401463

402464
type ConfigurationOptions = {
@@ -461,7 +523,7 @@ export function getNativePythonFinder(context?: IExtensionContext): NativePython
461523
}
462524
if (!_finder) {
463525
const cacheDirectory = context ? getCacheDirectory(context) : undefined;
464-
_finder = new NativePythonFinderImpl(cacheDirectory);
526+
_finder = new NativePythonFinderImpl(cacheDirectory, context);
465527
if (context) {
466528
context.subscriptions.push(_finder);
467529
}

0 commit comments

Comments
 (0)