From 19f239d549d8e891c90aceb14d5f458f8375260d Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Tue, 9 Sep 2025 09:31:06 -0700 Subject: [PATCH] debt: refactor environment info collection and add helper file --- src/extension.ts | 176 ++++---------------------------------------- src/helpers.ts | 188 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+), 160 deletions(-) create mode 100644 src/helpers.ts diff --git a/src/extension.ts b/src/extension.ts index e7372df8..3506e87b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,7 +1,8 @@ -import { commands, ExtensionContext, extensions, LogOutputChannel, Terminal, Uri, window, workspace } from 'vscode'; +import { commands, ExtensionContext, LogOutputChannel, Terminal, Uri, window } from 'vscode'; +import { version as extensionVersion } from '../package.json'; import { PythonEnvironment, PythonEnvironmentApi, PythonProjectCreator } from './api'; import { ensureCorrectVersion } from './common/extVersion'; -import { registerLogger, traceError, traceInfo, traceWarn } from './common/logging'; +import { registerLogger, traceError, traceInfo, traceVerbose, traceWarn } from './common/logging'; import { clearPersistentState, setPersistentState } from './common/persistentState'; import { newProjectSelection } from './common/pickers/managers'; import { StopWatch } from './common/stopWatch'; @@ -9,7 +10,6 @@ import { EventNames } from './common/telemetry/constants'; import { sendManagerSelectionTelemetry } from './common/telemetry/helpers'; import { sendTelemetryEvent } from './common/telemetry/sender'; import { createDeferred } from './common/utils/deferred'; -import { normalizePath } from './common/utils/pathUtils'; import { isWindows } from './common/utils/platformUtils'; import { activeTerminal, @@ -61,19 +61,23 @@ import { cleanupStartupScripts } from './features/terminal/shellStartupSetupHand import { TerminalActivationImpl } from './features/terminal/terminalActivationState'; import { TerminalEnvVarInjector } from './features/terminal/terminalEnvVarInjector'; import { TerminalManager, TerminalManagerImpl } from './features/terminal/terminalManager'; -import { getAutoActivationType, getEnvironmentForTerminal } from './features/terminal/utils'; +import { getEnvironmentForTerminal } from './features/terminal/utils'; import { EnvManagerView } from './features/views/envManagersView'; import { ProjectView } from './features/views/projectView'; import { PythonStatusBarImpl } from './features/views/pythonStatusBar'; import { updateViewsAndStatus } from './features/views/revealHandler'; import { ProjectItem } from './features/views/treeViewItems'; +import { + collectEnvironmentInfo, + getEnvManagerAndPackageManagerConfigLevels, + resolveDefaultInterpreter, +} from './helpers'; import { EnvironmentManagers, ProjectCreators, PythonProjectManager } from './internal.api'; import { registerSystemPythonFeatures } from './managers/builtin/main'; import { SysPythonManager } from './managers/builtin/sysPythonManager'; import { createNativePythonFinder, getNativePythonToolsPath, - NativeEnvInfo, NativePythonFinder, } from './managers/common/nativePythonFinder'; import { IDisposable } from './managers/common/types'; @@ -82,89 +86,6 @@ import { registerPipenvFeatures } from './managers/pipenv/main'; import { registerPoetryFeatures } from './managers/poetry/main'; import { registerPyenvFeatures } from './managers/pyenv/main'; -/** - * Collects relevant Python environment information for issue reporting - */ -async function collectEnvironmentInfo( - context: ExtensionContext, - envManagers: EnvironmentManagers, - projectManager: PythonProjectManager, -): Promise { - const info: string[] = []; - - try { - // Extension version - const extensionVersion = context.extension?.packageJSON?.version || 'unknown'; - info.push(`Extension Version: ${extensionVersion}`); - - // Python extension version - const pythonExtension = extensions.getExtension('ms-python.python'); - const pythonVersion = pythonExtension?.packageJSON?.version || 'not installed'; - info.push(`Python Extension Version: ${pythonVersion}`); - - // Environment managers - const managers = envManagers.managers; - info.push(`\nRegistered Environment Managers (${managers.length}):`); - managers.forEach((manager) => { - info.push(` - ${manager.id} (${manager.displayName})`); - }); - - // Available environments - const allEnvironments: PythonEnvironment[] = []; - for (const manager of managers) { - try { - const envs = await manager.getEnvironments('all'); - allEnvironments.push(...envs); - } catch (err) { - info.push(` Error getting environments from ${manager.id}: ${err}`); - } - } - - info.push(`\nTotal Available Environments: ${allEnvironments.length}`); - if (allEnvironments.length > 0) { - info.push('Environment Details:'); - allEnvironments.slice(0, 10).forEach((env, index) => { - info.push(` ${index + 1}. ${env.displayName} (${env.version}) - ${env.displayPath}`); - }); - if (allEnvironments.length > 10) { - info.push(` ... and ${allEnvironments.length - 10} more environments`); - } - } - - // Python projects - const projects = projectManager.getProjects(); - info.push(`\nPython Projects (${projects.length}):`); - for (let index = 0; index < projects.length; index++) { - const project = projects[index]; - info.push(` ${index + 1}. ${project.uri.fsPath}`); - try { - const env = await envManagers.getEnvironment(project.uri); - if (env) { - info.push(` Environment: ${env.displayName}`); - } - } catch (err) { - info.push(` Error getting environment: ${err}`); - } - } - - // Current settings (non-sensitive) - const config = workspace.getConfiguration('python-envs'); - const pyConfig = workspace.getConfiguration('python'); - info.push('\nExtension Settings:'); - info.push(` Default Environment Manager: ${config.get('defaultEnvManager')}`); - info.push(` Default Package Manager: ${config.get('defaultPackageManager')}`); - const pyenvAct = config.get('terminal.autoActivationType', undefined); - const pythonAct = pyConfig.get('terminal.activateEnvironment', undefined); - info.push( - `Auto-activation is "${getAutoActivationType()}". Activation based on first 'py-env.terminal.autoActivationType' setting which is '${pyenvAct}' and 'python.terminal.activateEnvironment' if the first is undefined which is '${pythonAct}'.\n`, - ); - } catch (err) { - info.push(`\nError collecting environment information: ${err}`); - } - - return info.join('\n'); -} - export async function activate(context: ExtensionContext): Promise { const useEnvironmentsExtension = getConfiguration('python').get('useEnvironmentsExtension', true); traceInfo(`Experiment Status: useEnvironmentsExtension setting set to ${useEnvironmentsExtension}`); @@ -183,6 +104,13 @@ export async function activate(context: ExtensionContext): Promise { ); } -/** - * Sets the default Python interpreter for the workspace if the user has not explicitly set 'defaultEnvManager' or it is set to venv. - * @param nativeFinder - used to resolve interpreter paths. - * @param envManagers - contains all registered managers. - * @param api - The PythonEnvironmentApi for environment resolution and setting. - */ -async function resolveDefaultInterpreter( - nativeFinder: NativePythonFinder, - envManagers: EnvironmentManagers, - api: PythonEnvironmentApi, -) { - const defaultInterpreterPath = getConfiguration('python').get('defaultInterpreterPath'); - - if (defaultInterpreterPath) { - const config = getConfiguration('python-envs'); - const inspect = config.inspect('defaultEnvManager'); - const userDefinedDefaultManager = - inspect?.workspaceFolderValue !== undefined || - inspect?.workspaceValue !== undefined || - inspect?.globalValue !== undefined; - if (!userDefinedDefaultManager) { - try { - const resolved: NativeEnvInfo = await nativeFinder.resolve(defaultInterpreterPath); - if (resolved && resolved.executable) { - if (normalizePath(resolved.executable) === normalizePath(defaultInterpreterPath)) { - // no action required, the path is already correct - return; - } - const resolvedEnv = await api.resolveEnvironment(Uri.file(resolved.executable)); - traceInfo(`[resolveDefaultInterpreter] API resolved environment: ${JSON.stringify(resolvedEnv)}`); - - let findEnvManager = envManagers.managers.find((m) => m.id === resolvedEnv?.envId.managerId); - if (!findEnvManager) { - findEnvManager = envManagers.managers.find((m) => m.id === 'ms-python.python:system'); - } - if (resolvedEnv) { - const newEnv: PythonEnvironment = { - envId: { - id: resolvedEnv?.envId.id, - managerId: resolvedEnv?.envId.managerId ?? '', - }, - name: 'defaultInterpreterPath: ' + (resolved.version ?? ''), - displayName: 'defaultInterpreterPath: ' + (resolved.version ?? ''), - version: resolved.version ?? '', - displayPath: defaultInterpreterPath ?? '', - environmentPath: defaultInterpreterPath ? Uri.file(defaultInterpreterPath) : Uri.file(''), - sysPrefix: resolved.arch ?? '', - execInfo: { - run: { - executable: defaultInterpreterPath ?? '', - }, - }, - }; - if (workspace.workspaceFolders?.[0] && findEnvManager) { - traceInfo( - `[resolveDefaultInterpreter] Setting environment for workspace: ${workspace.workspaceFolders[0].uri.fsPath}`, - ); - await api.setEnvironment(workspace.workspaceFolders[0].uri, newEnv); - } - } - } else { - traceWarn( - `[resolveDefaultInterpreter] NativeFinder did not resolve an executable for path: ${defaultInterpreterPath}`, - ); - } - } catch (err) { - traceError(`[resolveDefaultInterpreter] Error resolving default interpreter: ${err}`); - } - } - } -} - export async function deactivate(context: ExtensionContext) { await disposeAll(context.subscriptions); context.subscriptions.length = 0; // Clear subscriptions to prevent memory leaks diff --git a/src/helpers.ts b/src/helpers.ts new file mode 100644 index 00000000..5a1e8977 --- /dev/null +++ b/src/helpers.ts @@ -0,0 +1,188 @@ +import { ExtensionContext, extensions, Uri, workspace } from 'vscode'; +import { PythonEnvironment, PythonEnvironmentApi } from './api'; +import { traceError, traceInfo, traceWarn } from './common/logging'; +import { normalizePath } from './common/utils/pathUtils'; +import { getConfiguration } from './common/workspace.apis'; +import { getAutoActivationType } from './features/terminal/utils'; +import { EnvironmentManagers, PythonProjectManager } from './internal.api'; +import { NativeEnvInfo, NativePythonFinder } from './managers/common/nativePythonFinder'; + +/** + * Collects relevant Python environment information for issue reporting + */ +export async function collectEnvironmentInfo( + context: ExtensionContext, + envManagers: EnvironmentManagers, + projectManager: PythonProjectManager, +): Promise { + const info: string[] = []; + + try { + // Extension version + const extensionVersion = context.extension?.packageJSON?.version || 'unknown'; + info.push(`Extension Version: ${extensionVersion}`); + + // Python extension version + const pythonExtension = extensions.getExtension('ms-python.python'); + const pythonVersion = pythonExtension?.packageJSON?.version || 'not installed'; + info.push(`Python Extension Version: ${pythonVersion}`); + + // Environment managers + const managers = envManagers.managers; + info.push(`\nRegistered Environment Managers (${managers.length}):`); + managers.forEach((manager) => { + info.push(` - ${manager.id} (${manager.displayName})`); + }); + + // Available environments + const allEnvironments: PythonEnvironment[] = []; + for (const manager of managers) { + try { + const envs = await manager.getEnvironments('all'); + allEnvironments.push(...envs); + } catch (err) { + info.push(` Error getting environments from ${manager.id}: ${err}`); + } + } + + info.push(`\nTotal Available Environments: ${allEnvironments.length}`); + if (allEnvironments.length > 0) { + info.push('Environment Details:'); + allEnvironments.slice(0, 10).forEach((env, index) => { + info.push(` ${index + 1}. ${env.displayName} (${env.version}) - ${env.displayPath}`); + }); + if (allEnvironments.length > 10) { + info.push(` ... and ${allEnvironments.length - 10} more environments`); + } + } + + // Python projects + const projects = projectManager.getProjects(); + info.push(`\nPython Projects (${projects.length}):`); + for (let index = 0; index < projects.length; index++) { + const project = projects[index]; + info.push(` ${index + 1}. ${project.uri.fsPath}`); + try { + const env = await envManagers.getEnvironment(project.uri); + if (env) { + info.push(` Environment: ${env.displayName}`); + } + } catch (err) { + info.push(` Error getting environment: ${err}`); + } + } + + // Current settings (non-sensitive) + const config = workspace.getConfiguration('python-envs'); + const pyConfig = workspace.getConfiguration('python'); + info.push('\nExtension Settings:'); + info.push(` Default Environment Manager: ${config.get('defaultEnvManager')}`); + info.push(` Default Package Manager: ${config.get('defaultPackageManager')}`); + const pyenvAct = config.get('terminal.autoActivationType', undefined); + const pythonAct = pyConfig.get('terminal.activateEnvironment', undefined); + info.push( + `Auto-activation is "${getAutoActivationType()}". Activation based on first 'py-env.terminal.autoActivationType' setting which is '${pyenvAct}' and 'python.terminal.activateEnvironment' if the first is undefined which is '${pythonAct}'.\n`, + ); + } catch (err) { + info.push(`\nError collecting environment information: ${err}`); + } + + return info.join('\n'); +} + +/** + * Logs the values of defaultPackageManager and defaultEnvManager at all configuration levels (workspace folder, workspace, user/global, default). + */ +export function getEnvManagerAndPackageManagerConfigLevels() { + const config = getConfiguration('python-envs'); + const envManagerInspect = config.inspect('defaultEnvManager'); + const pkgManagerInspect = config.inspect('defaultPackageManager'); + + return { + section: 'Python Envs Configuration Levels', + defaultEnvManager: { + workspaceFolderValue: envManagerInspect?.workspaceFolderValue ?? 'undefined', + workspaceValue: envManagerInspect?.workspaceValue ?? 'undefined', + globalValue: envManagerInspect?.globalValue ?? 'undefined', + defaultValue: envManagerInspect?.defaultValue ?? 'undefined', + }, + defaultPackageManager: { + workspaceFolderValue: pkgManagerInspect?.workspaceFolderValue ?? 'undefined', + workspaceValue: pkgManagerInspect?.workspaceValue ?? 'undefined', + globalValue: pkgManagerInspect?.globalValue ?? 'undefined', + defaultValue: pkgManagerInspect?.defaultValue ?? 'undefined', + }, + }; +} + +/** + * Sets the default Python interpreter for the workspace if the user has not explicitly set 'defaultEnvManager' or it is set to venv. + * @param nativeFinder - used to resolve interpreter paths. + * @param envManagers - contains all registered managers. + * @param api - The PythonEnvironmentApi for environment resolution and setting. + */ +export async function resolveDefaultInterpreter( + nativeFinder: NativePythonFinder, + envManagers: EnvironmentManagers, + api: PythonEnvironmentApi, +) { + const defaultInterpreterPath = getConfiguration('python').get('defaultInterpreterPath'); + + if (defaultInterpreterPath) { + const config = getConfiguration('python-envs'); + const inspect = config.inspect('defaultEnvManager'); + const userDefinedDefaultManager = + inspect?.workspaceFolderValue !== undefined || + inspect?.workspaceValue !== undefined || + inspect?.globalValue !== undefined; + if (!userDefinedDefaultManager) { + try { + const resolved: NativeEnvInfo = await nativeFinder.resolve(defaultInterpreterPath); + if (resolved && resolved.executable) { + if (normalizePath(resolved.executable) === normalizePath(defaultInterpreterPath)) { + // no action required, the path is already correct + return; + } + const resolvedEnv = await api.resolveEnvironment(Uri.file(resolved.executable)); + traceInfo(`[resolveDefaultInterpreter] API resolved environment: ${JSON.stringify(resolvedEnv)}`); + + let findEnvManager = envManagers.managers.find((m) => m.id === resolvedEnv?.envId.managerId); + if (!findEnvManager) { + findEnvManager = envManagers.managers.find((m) => m.id === 'ms-python.python:system'); + } + if (resolvedEnv) { + const newEnv: PythonEnvironment = { + envId: { + id: resolvedEnv?.envId.id, + managerId: resolvedEnv?.envId.managerId ?? '', + }, + name: 'defaultInterpreterPath: ' + (resolved.version ?? ''), + displayName: 'defaultInterpreterPath: ' + (resolved.version ?? ''), + version: resolved.version ?? '', + displayPath: defaultInterpreterPath ?? '', + environmentPath: defaultInterpreterPath ? Uri.file(defaultInterpreterPath) : Uri.file(''), + sysPrefix: resolved.arch ?? '', + execInfo: { + run: { + executable: defaultInterpreterPath ?? '', + }, + }, + }; + if (workspace.workspaceFolders?.[0] && findEnvManager) { + traceInfo( + `[resolveDefaultInterpreter] Setting environment for workspace: ${workspace.workspaceFolders[0].uri.fsPath}`, + ); + await api.setEnvironment(workspace.workspaceFolders[0].uri, newEnv); + } + } + } else { + traceWarn( + `[resolveDefaultInterpreter] NativeFinder did not resolve an executable for path: ${defaultInterpreterPath}`, + ); + } + } catch (err) { + traceError(`[resolveDefaultInterpreter] Error resolving default interpreter: ${err}`); + } + } + } +}