Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/devcontainers/typescript-node:18-bookworm
FROM mcr.microsoft.com/devcontainers/typescript-node:22-bookworm

RUN apt-get install -y wget bzip2

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
permissions: {}

env:
NODE_VERSION: 22.17.0
NODE_VERSION: 22.21.1
PYTHON_VERSION: '3.10' # YML treats 3.10 the number as 3.1, so quotes around 3.10
# Force a path with spaces and to test extension works in these scenarios
# Unicode characters are causing 2.7 failures so skip that for now.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
permissions: {}

env:
NODE_VERSION: 22.17.0
NODE_VERSION: 22.21.1
PYTHON_VERSION: '3.10' # YML treats 3.10 the number as 3.1, so quotes around 3.10
MOCHA_REPORTER_JUNIT: true # Use the mocha-multi-reporters and send output to both console (spec) and JUnit (mocha-junit-reporter). Also enables a reporter which exits the process running the tests if it haven't already.
ARTIFACT_NAME_VSIX: ms-python-insiders-vsix
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v20.18.0
v22.21.1
18 changes: 18 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@
"problemMatcher": ["$python"],
"label": "npm: check-python",
"detail": "npm run check-python:ruff && npm run check-python:pyright"
},
{
"label": "npm: check-python (venv)",
"type": "shell",
"command": "bash",
"args": ["-lc", "source .venv/bin/activate && npm run check-python"],
"problemMatcher": [],
"detail": "Activates the repo .venv first (avoids pyenv/shim Python) then runs: npm run check-python",
"windows": {
"command": "pwsh",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-Command",
".\\.venv\\Scripts\\Activate.ps1; npm run check-python"
]
}
}
]
}
6 changes: 3 additions & 3 deletions build/azure-pipelines/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ extends:
testPlatforms:
- name: Linux
nodeVersions:
- 22.17.0
- 22.21.1
- name: MacOS
nodeVersions:
- 22.17.0
- 22.21.1
- name: Windows
nodeVersions:
- 22.17.0
- 22.21.1
testSteps:
- template: /build/azure-pipelines/templates/test-steps.yml@self
parameters:
Expand Down
34 changes: 18 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1746,7 +1746,7 @@
"@types/glob": "^7.2.0",
"@types/lodash": "^4.14.104",
"@types/mocha": "^9.1.0",
"@types/node": "^22.5.0",
"@types/node": "^22.19.1",
"@types/semver": "^5.5.0",
"@types/shortid": "^0.0.29",
"@types/sinon": "^17.0.3",
Expand Down
2 changes: 1 addition & 1 deletion pythonExtensionApi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"main": "./out/main.js",
"types": "./out/main.d.ts",
"engines": {
"node": ">=22.17.0",
"node": ">=22.21.1",
"vscode": "^1.93.0"
},
"license": "MIT",
Expand Down
3 changes: 2 additions & 1 deletion src/client/common/process/rawProcessApis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ export function shellExec(
const shellOptions = getDefaultOptions(options, defaultEnv);
if (!options.doNotLog) {
const processLogger = new ProcessLogger(new WorkspaceService());
processLogger.logProcess(command, undefined, shellOptions);
const loggingOptions = { ...shellOptions, encoding: shellOptions.encoding ?? undefined };
processLogger.logProcess(command, undefined, loggingOptions);
}
return new Promise((resolve, reject) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
12 changes: 12 additions & 0 deletions src/client/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -516,3 +516,15 @@ export namespace CreateEnv {
);
}
}

export namespace PythonLocator {
export const startupFailedNotification = l10n.t(
'Python Locator failed to start. Python environment discovery may not work correctly.',
);
export const windowsRuntimeMissing = l10n.t(
'Missing Windows runtime dependencies detected. The Python Locator requires the Microsoft Visual C++ Redistributable. This is often missing on clean Windows installations.',
);
export const windowsStartupFailed = l10n.t(
'Python Locator failed to start on Windows. This might be due to missing system dependencies such as the Microsoft Visual C++ Redistributable.',
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,24 @@ import { noop } from '../../../../common/utils/misc';
import { getConfiguration, getWorkspaceFolderPaths, isTrusted } from '../../../../common/vscodeApis/workspaceApis';
import { CONDAPATH_SETTING_KEY } from '../../../common/environmentManagers/conda';
import { VENVFOLDERS_SETTING_KEY, VENVPATH_SETTING_KEY } from '../lowLevel/customVirtualEnvLocator';
import { createLogOutputChannel } from '../../../../common/vscodeApis/windowApis';
import { createLogOutputChannel, showWarningMessage } from '../../../../common/vscodeApis/windowApis';
import { sendNativeTelemetry, NativePythonTelemetry } from './nativePythonTelemetry';
import { NativePythonEnvironmentKind } from './nativePythonUtils';
import type { IExtensionContext } from '../../../../common/types';
import { StopWatch } from '../../../../common/utils/stopWatch';
import { untildify } from '../../../../common/helpers';
import { traceError } from '../../../../logging';
import { Common, PythonLocator } from '../../../../common/utils/localize';
import { Commands } from '../../../../common/constants';
import { executeCommand } from '../../../../common/vscodeApis/commandApis';
import { getGlobalStorage, IPersistentStorage } from '../../../../common/persistentState';

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

const DONT_SHOW_SPAWN_ERROR_AGAIN = 'DONT_SHOW_NATIVE_FINDER_SPAWN_ERROR_AGAIN';

export interface NativeEnvInfo {
displayName?: string;
name?: string;
Expand Down Expand Up @@ -106,8 +112,13 @@ class NativePythonFinderImpl extends DisposableBase implements NativePythonFinde
timeToRefresh: 0,
};

constructor(private readonly cacheDirectory?: Uri) {
private readonly suppressErrorNotification: IPersistentStorage<boolean>;

constructor(private readonly cacheDirectory?: Uri, private readonly context?: IExtensionContext) {
super();
this.suppressErrorNotification = this.context
? getGlobalStorage<boolean>(this.context, DONT_SHOW_SPAWN_ERROR_AGAIN, false)
: ({ get: () => false, set: async () => {} } as IPersistentStorage<boolean>);
this.connection = this.start();
void this.configure();
this.firstRefreshResults = this.refreshFirstTime();
Expand Down Expand Up @@ -212,6 +223,30 @@ class NativePythonFinderImpl extends DisposableBase implements NativePythonFinde
proc.stderr.on('data', (data) => this.outputChannel.error(data.toString()));
writable.pipe(proc.stdin, { end: false });

// Handle spawn errors (e.g., missing DLLs on Windows)
proc.on('error', (error) => {
this.outputChannel.error(`Python Locator process error: ${error.message}`);
this.outputChannel.error(`Error details: ${JSON.stringify(error)}`);
this.handleSpawnError(error.message);
});

// Handle immediate exits with error codes
let hasStarted = false;
setTimeout(() => {
hasStarted = true;
}, 1000);

proc.on('exit', (code, signal) => {
if (!hasStarted && code !== null && code !== 0) {
const errorMessage = `Python Locator process exited immediately with code ${code}`;
this.outputChannel.error(errorMessage);
if (signal) {
this.outputChannel.error(`Exit signal: ${signal}`);
}
this.handleSpawnError(errorMessage);
}
});

disposables.push({
dispose: () => {
try {
Expand Down Expand Up @@ -397,6 +432,33 @@ class NativePythonFinderImpl extends DisposableBase implements NativePythonFinde
async getCondaInfo(): Promise<NativeCondaInfo> {
return this.connection.sendRequest<NativeCondaInfo>('condaInfo');
}

private async handleSpawnError(errorMessage: string): Promise<void> {
// Check if user has chosen to not see this error again
if (this.suppressErrorNotification.get()) {
return;
}

// Check for Windows runtime DLL issues
if (isWindows() && errorMessage.toLowerCase().includes('vcruntime')) {
this.outputChannel.error(PythonLocator.windowsRuntimeMissing);
} else if (isWindows()) {
this.outputChannel.error(PythonLocator.windowsStartupFailed);
}

// Show notification to user
const selection = await showWarningMessage(
PythonLocator.startupFailedNotification,
Common.openOutputPanel,
Common.doNotShowAgain,
);

if (selection === Common.openOutputPanel) {
await executeCommand(Commands.ViewOutput);
} else if (selection === Common.doNotShowAgain) {
await this.suppressErrorNotification.set(true);
}
}
}

type ConfigurationOptions = {
Expand Down Expand Up @@ -461,7 +523,7 @@ export function getNativePythonFinder(context?: IExtensionContext): NativePython
}
if (!_finder) {
const cacheDirectory = context ? getCacheDirectory(context) : undefined;
_finder = new NativePythonFinderImpl(cacheDirectory);
_finder = new NativePythonFinderImpl(cacheDirectory, context);
if (context) {
context.subscriptions.push(_finder);
}
Expand Down
Loading