forked from DonJayamanne/pythonVSCode
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
Copy pathcreateEnvironmentTrigger.ts
156 lines (137 loc) · 6.11 KB
/
createEnvironmentTrigger.ts
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
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { Disposable, Uri, WorkspaceFolder } from 'vscode';
import {
fileContainsInlineDependencies,
hasKnownFiles,
hasRequirementFiles,
isGlobalPythonSelected,
shouldPromptToCreateEnv,
isCreateEnvWorkspaceCheckNotRun,
disableCreateEnvironmentTrigger,
} from './common/createEnvTriggerUtils';
import { getWorkspaceFolder } from '../../common/vscodeApis/workspaceApis';
import { traceError, traceInfo, traceVerbose } from '../../logging';
import { hasPrefixCondaEnv, hasVenv } from './common/commonUtils';
import { showInformationMessage } from '../../common/vscodeApis/windowApis';
import { Common, CreateEnv } from '../../common/utils/localize';
import { executeCommand, registerCommand } from '../../common/vscodeApis/commandApis';
import { Commands } from '../../common/constants';
import { Resource } from '../../common/types';
import { sendTelemetryEvent } from '../../telemetry';
import { EventName } from '../../telemetry/constants';
export enum CreateEnvironmentCheckKind {
/**
* Checks if environment creation is needed based on file location and content.
*/
File = 'file',
/**
* Checks if environment creation is needed based on workspace contents.
*/
Workspace = 'workspace',
}
export interface CreateEnvironmentTriggerOptions {
force?: boolean;
}
async function createEnvironmentCheckForWorkspace(uri: Uri): Promise<void> {
const workspace = getWorkspaceFolder(uri);
if (!workspace) {
traceInfo(`CreateEnv Trigger - Workspace not found for ${uri.fsPath}`);
return;
}
const missingRequirements = async (workspaceFolder: WorkspaceFolder) =>
!(await hasRequirementFiles(workspaceFolder));
const isNonGlobalPythonSelected = async (workspaceFolder: WorkspaceFolder) =>
!(await isGlobalPythonSelected(workspaceFolder));
// Skip showing the Create Environment prompt if one of the following is True:
// 1. The workspace already has a ".venv" or ".conda" env
// 2. The workspace does NOT have "requirements.txt" or "requirements/*.txt" files
// 3. The workspace has known files for other environment types like environment.yml, conda.yml, poetry.lock, etc.
// 4. The selected python is NOT classified as a global python interpreter
const skipPrompt: boolean = (
await Promise.all([
hasVenv(workspace),
hasPrefixCondaEnv(workspace),
missingRequirements(workspace),
hasKnownFiles(workspace),
isNonGlobalPythonSelected(workspace),
])
).some((r) => r);
if (skipPrompt) {
sendTelemetryEvent(EventName.ENVIRONMENT_CHECK_RESULT, undefined, { result: 'criteria-not-met' });
traceInfo(`CreateEnv Trigger - Skipping for ${uri.fsPath}`);
return;
}
sendTelemetryEvent(EventName.ENVIRONMENT_CHECK_RESULT, undefined, { result: 'criteria-met' });
const selection = await showInformationMessage(
CreateEnv.Trigger.workspaceTriggerMessage,
CreateEnv.Trigger.createEnvironment,
Common.doNotShowAgain,
);
if (selection === CreateEnv.Trigger.createEnvironment) {
try {
await executeCommand(Commands.Create_Environment);
} catch (error) {
traceError('CreateEnv Trigger - Error while creating environment: ', error);
}
} else if (selection === Common.doNotShowAgain) {
disableCreateEnvironmentTrigger();
}
}
function runOnceWorkspaceCheck(uri: Uri, options: CreateEnvironmentTriggerOptions = {}): Promise<void> {
if (isCreateEnvWorkspaceCheckNotRun() || options?.force) {
return createEnvironmentCheckForWorkspace(uri);
}
sendTelemetryEvent(EventName.ENVIRONMENT_CHECK_RESULT, undefined, { result: 'already-ran' });
traceVerbose('CreateEnv Trigger - skipping this because it was already run');
return Promise.resolve();
}
async function createEnvironmentCheckForFile(uri: Uri, options?: CreateEnvironmentTriggerOptions): Promise<void> {
if (await fileContainsInlineDependencies(uri)) {
// TODO: Handle create environment for each file here.
// pending acceptance of PEP-722/PEP-723
// For now we do the same thing as for workspace.
await runOnceWorkspaceCheck(uri, options);
}
// If the file does not have any inline dependencies, then we do the same thing
// as for workspace.
await runOnceWorkspaceCheck(uri, options);
}
export async function triggerCreateEnvironmentCheck(
kind: CreateEnvironmentCheckKind,
uri: Resource,
options?: CreateEnvironmentTriggerOptions,
): Promise<void> {
if (!uri) {
sendTelemetryEvent(EventName.ENVIRONMENT_CHECK_RESULT, undefined, { result: 'no-uri' });
traceVerbose('CreateEnv Trigger - Skipping No URI provided');
return;
}
if (shouldPromptToCreateEnv()) {
if (kind === CreateEnvironmentCheckKind.File) {
await createEnvironmentCheckForFile(uri, options);
} else {
await runOnceWorkspaceCheck(uri, options);
}
} else {
sendTelemetryEvent(EventName.ENVIRONMENT_CHECK_RESULT, undefined, { result: 'turned-off' });
traceVerbose('CreateEnv Trigger - turned off in settings');
}
}
export function triggerCreateEnvironmentCheckNonBlocking(
kind: CreateEnvironmentCheckKind,
uri: Resource,
options?: CreateEnvironmentTriggerOptions,
): void {
// The Event loop for Node.js runs functions with setTimeout() with lower priority than setImmediate.
// This is done to intentionally avoid blocking anything that the user wants to do.
setTimeout(() => triggerCreateEnvironmentCheck(kind, uri, options).ignoreErrors(), 0);
}
export function registerCreateEnvironmentTriggers(disposables: Disposable[]): void {
disposables.push(
registerCommand(Commands.Create_Environment_Check, (file: Resource) => {
sendTelemetryEvent(EventName.ENVIRONMENT_CHECK_TRIGGER, undefined, { trigger: 'as-command' });
triggerCreateEnvironmentCheckNonBlocking(CreateEnvironmentCheckKind.File, file, { force: true });
}),
);
}