Skip to content

Commit 3686a0d

Browse files
committed
enhance error handling and user notifications for Python Locator failures
1 parent 72bb721 commit 3686a0d

File tree

1 file changed

+72
-3
lines changed

1 file changed

+72
-3
lines changed

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

Lines changed: 72 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 } 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,40 @@ 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(
445+
'Missing Windows runtime dependencies detected. ' +
446+
'The Python Locator requires the Microsoft Visual C++ Redistributable. ' +
447+
'This is often missing on clean Windows installations.',
448+
);
449+
} else if (isWindows()) {
450+
this.outputChannel.error(
451+
'Python Locator failed to start on Windows. ' +
452+
'This might be due to missing system dependencies such as the Microsoft Visual C++ Redistributable.',
453+
);
454+
}
455+
456+
// Show notification to user
457+
const selection = await showWarningMessage(
458+
'Python Locator failed to start. Python environment discovery may not work correctly.',
459+
Common.openOutputPanel,
460+
Common.doNotShowAgain,
461+
);
462+
463+
if (selection === Common.openOutputPanel) {
464+
await executeCommand(Commands.ViewOutput);
465+
} else if (selection === Common.doNotShowAgain) {
466+
await this.suppressErrorNotification.set(true);
467+
}
468+
}
400469
}
401470

402471
type ConfigurationOptions = {
@@ -461,7 +530,7 @@ export function getNativePythonFinder(context?: IExtensionContext): NativePython
461530
}
462531
if (!_finder) {
463532
const cacheDirectory = context ? getCacheDirectory(context) : undefined;
464-
_finder = new NativePythonFinderImpl(cacheDirectory);
533+
_finder = new NativePythonFinderImpl(cacheDirectory, context);
465534
if (context) {
466535
context.subscriptions.push(_finder);
467536
}

0 commit comments

Comments
 (0)