forked from microsoft/vscode-python-environments
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhelpers.ts
More file actions
294 lines (272 loc) · 13 KB
/
helpers.ts
File metadata and controls
294 lines (272 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
import { ExtensionContext, extensions, QuickInputButtons, Uri, window, workspace } from 'vscode';
import { PythonEnvironment, PythonEnvironmentApi } from './api';
import { traceError, traceInfo, traceWarn } from './common/logging';
import { normalizePath } from './common/utils/pathUtils';
import { isWindows } from './common/utils/platformUtils';
import { createTerminal, showInputBoxWithButtons } from './common/window.apis';
import { getConfiguration } from './common/workspace.apis';
import { getAutoActivationType } from './features/terminal/utils';
import { EnvironmentManagers, PythonProjectManager } from './internal.api';
import { getNativePythonToolsPath, 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<string> {
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<string>('defaultEnvManager');
const pkgManagerInspect = config.inspect<string>('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',
},
};
}
/**
* Returns the user-configured value for a configuration key if set at any level (workspace folder, workspace, or global),
* otherwise returns undefined.
*/
export function getUserConfiguredSetting<T>(section: string, key: string): T | undefined {
const config = getConfiguration(section);
const inspect = config.inspect<T>(key);
if (!inspect) {
return undefined;
}
if (inspect.workspaceFolderValue !== undefined) {
return inspect.workspaceFolderValue;
}
if (inspect.workspaceValue !== undefined) {
return inspect.workspaceValue;
}
if (inspect.globalValue !== undefined) {
return inspect.globalValue;
}
return undefined;
}
/**
* Runs the Python Environment Tool (PET) in a terminal window, allowing users to
* execute various PET commands like finding all Python environments or resolving
* the details of a specific environment.
*
*
* @returns A Promise that resolves when the PET command has been executed or cancelled
*/
export async function runPetInTerminalImpl(): Promise<void> {
const petPath = await getNativePythonToolsPath();
// Show quick pick menu for PET operation selection
const selectedOption = await window.showQuickPick(
[
{
label: 'Find All Environments',
description: 'Finds all environments and reports them to the standard output',
detail: 'Runs: pet find --verbose',
},
{
label: 'Resolve Environment...',
description: 'Resolves & reports the details of the environment to the standard output',
detail: 'Runs: pet resolve <path>',
},
],
{
placeHolder: 'Select a Python Environment Tool (PET) operation',
ignoreFocusOut: true,
},
);
if (!selectedOption) {
return; // User cancelled
}
if (selectedOption.label === 'Find All Environments') {
// Create and show terminal immediately for 'Find All Environments' option
const terminal = createTerminal({
name: 'Python Environment Tool (PET)',
});
terminal.show();
// Run pet find --verbose
terminal.sendText(`"${petPath}" find --verbose`, true);
traceInfo(`Running PET find command: ${petPath} find --verbose`);
} else if (selectedOption.label === 'Resolve Environment...') {
try {
// Show input box for path with back button
const placeholder = isWindows() ? 'C:\\path\\to\\python\\executable' : '/path/to/python/executable';
const inputPath = await showInputBoxWithButtons({
prompt: 'Enter the path to the Python executable to resolve',
placeHolder: placeholder,
ignoreFocusOut: true,
showBackButton: true,
validateInput: (value) => {
if (!value || value.trim().length === 0) {
return 'Please enter a valid path';
}
return null;
},
});
if (inputPath) {
// Only create and show terminal after path has been entered
const terminal = createTerminal({
name: 'Python Environment Tool (PET)',
});
terminal.show();
// Run pet resolve with the provided path
terminal.sendText(`"${petPath}" resolve "${inputPath.trim()}"`, true);
traceInfo(`Running PET resolve command: ${petPath} resolve "${inputPath.trim()}"`);
}
} catch (ex) {
if (ex === QuickInputButtons.Back) {
// If back button was clicked, restart the flow
await runPetInTerminalImpl();
return;
}
throw ex; // Re-throw other errors
}
}
}
/**
* Sets the default Python interpreter for the workspace if the user has not explicitly set 'defaultEnvManager'.
* @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 userSetdefaultInterpreter = getUserConfiguredSetting<string>('python', 'defaultInterpreterPath');
const userSetDefaultManager = getUserConfiguredSetting<string>('python-envs', 'defaultEnvManager');
traceInfo(
`[resolveDefaultInterpreter] User configured defaultInterpreterPath: ${userSetdefaultInterpreter} and defaultEnvManager: ${userSetDefaultManager}`,
);
// Only proceed if the user has explicitly set defaultInterpreterPath but nothing is saved for defaultEnvManager
if (userSetdefaultInterpreter && !userSetDefaultManager) {
try {
const resolved: NativeEnvInfo = await nativeFinder.resolve(userSetdefaultInterpreter);
if (resolved && resolved.executable) {
if (normalizePath(resolved.executable) === normalizePath(userSetdefaultInterpreter)) {
// 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');
}
const randomString = Math.random().toString(36).substring(2, 15);
if (resolvedEnv) {
const newEnv: PythonEnvironment = {
envId: {
id: `${userSetdefaultInterpreter}_${randomString}`,
managerId: resolvedEnv?.envId.managerId ?? '',
},
name: 'defaultInterpreterPath: ' + (resolved.version ?? ''),
displayName: 'defaultInterpreterPath: ' + (resolved.version ?? ''),
version: resolved.version ?? '',
displayPath: userSetdefaultInterpreter ?? '',
environmentPath: userSetdefaultInterpreter ? Uri.file(userSetdefaultInterpreter) : Uri.file(''),
sysPrefix: resolved.arch ?? '',
execInfo: {
run: {
executable: userSetdefaultInterpreter ?? '',
},
},
};
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: ${userSetdefaultInterpreter}`,
);
}
} catch (err) {
traceError(`[resolveDefaultInterpreter] Error resolving default interpreter: ${err}`);
}
}
}