-
Notifications
You must be signed in to change notification settings - Fork 42
Expand file tree
/
Copy pathhelpers.ts
More file actions
153 lines (141 loc) · 5.24 KB
/
helpers.ts
File metadata and controls
153 lines (141 loc) · 5.24 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
import { CancellationError, CancellationToken, ConfigurationScope, LogOutputChannel } from 'vscode';
import { spawnProcess } from '../../common/childProcess.apis';
import { EventNames } from '../../common/telemetry/constants';
import { sendTelemetryEvent } from '../../common/telemetry/sender';
import { createDeferred } from '../../common/utils/deferred';
import { getConfiguration } from '../../common/workspace.apis';
import { getUvEnvironments } from './uvEnvironments';
let available = createDeferred<boolean>();
/**
* Reset the UV installation cache.
*/
export function resetUvInstallationCache(): void {
available = createDeferred<boolean>();
}
export async function isUvInstalled(log?: LogOutputChannel): Promise<boolean> {
if (available.completed) {
return available.promise;
}
log?.info(`Running: uv --version`);
const proc = spawnProcess('uv', ['--version']);
proc.on('error', () => {
available.resolve(false);
});
proc.stdout?.on('data', (d) => log?.info(d.toString()));
proc.on('exit', (code) => {
if (code === 0) {
sendTelemetryEvent(EventNames.VENV_USING_UV);
}
available.resolve(code === 0);
});
return available.promise;
}
/**
* Determines if uv should be used for managing a virtual environment.
* @param log - Optional log output channel for logging operations.
* @param envPath - Optional environment path to check against the known uv environments list.
* @param scope - Optional configuration scope used when reading the `python-envs.alwaysUseUv` setting.
* Pass the relevant project or workspace-folder `Uri` when available so VS Code resolves settings
* using normal precedence: workspace folder, then workspace, then user/global. If omitted, the
* user/global value is used unless VS Code can infer a broader scope.
* @returns True if uv should be used, false otherwise. For uv-managed environments, returns true
* if uv is installed. For other environments, checks the `python-envs.alwaysUseUv` setting for
* the provided scope and uv availability.
*/
export async function shouldUseUv(log?: LogOutputChannel, envPath?: string, scope?: ConfigurationScope): Promise<boolean> {
if (envPath) {
// always use uv if the given environment is stored as a uv env
const uvEnvs = await getUvEnvironments();
if (uvEnvs.includes(envPath)) {
return await isUvInstalled(log);
}
}
// For other environments, check the user setting
const config = getConfiguration('python-envs', scope);
const alwaysUseUv = config.get<boolean>('alwaysUseUv', true);
if (alwaysUseUv) {
return await isUvInstalled(log);
}
return false;
}
export async function runUV(
args: string[],
cwd?: string,
log?: LogOutputChannel,
token?: CancellationToken,
timeout?: number,
): Promise<string> {
log?.info(`Running: uv ${args.join(' ')}`);
return new Promise<string>((resolve, reject) => {
const spawnOptions: { cwd?: string; timeout?: number } = { cwd };
if (timeout !== undefined) {
spawnOptions.timeout = timeout;
}
const proc = spawnProcess('uv', args, spawnOptions);
token?.onCancellationRequested(() => {
proc.kill();
reject(new CancellationError());
});
proc.on('error', (err) => {
log?.error(`Error spawning uv: ${err}`);
reject(new Error(`Error spawning uv: ${err.message}`));
});
let builder = '';
proc.stdout?.on('data', (data) => {
const s = data.toString('utf-8');
builder += s;
log?.append(s);
});
proc.stderr?.on('data', (data) => {
log?.append(data.toString('utf-8'));
});
proc.on('close', () => {
resolve(builder);
});
proc.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`Failed to run uv ${args.join(' ')}`));
}
});
});
}
export async function runPython(
python: string,
args: string[],
cwd?: string,
log?: LogOutputChannel,
token?: CancellationToken,
timeout?: number,
): Promise<string> {
log?.info(`Running: ${python} ${args.join(' ')}`);
return new Promise<string>((resolve, reject) => {
const proc = spawnProcess(python, args, { cwd: cwd, timeout });
token?.onCancellationRequested(() => {
proc.kill();
reject(new CancellationError());
});
proc.on('error', (err) => {
log?.error(`Error spawning python: ${err}`);
reject(new Error(`Error spawning python: ${err.message}`));
});
let builder = '';
proc.stdout?.on('data', (data) => {
const s = data.toString('utf-8');
builder += s;
log?.append(`python: ${s}`);
});
proc.stderr?.on('data', (data) => {
const s = data.toString('utf-8');
builder += s;
log?.append(`python: ${s}`);
});
proc.on('close', () => {
resolve(builder);
});
proc.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`Failed to run python ${args.join(' ')}`));
}
});
});
}