Skip to content

Commit fc0c9f0

Browse files
committed
Create environment proposed api
1 parent 586d18f commit fc0c9f0

12 files changed

+242
-104
lines changed

src/client/environmentApi.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
ResolvedEnvironment,
3232
Resource,
3333
} from './apiTypes';
34+
import { buildEnvironmentCreationApi } from './pythonEnvironments/creation/createEnvApi';
3435

3536
type ActiveEnvironmentChangeEvent = {
3637
resource: WorkspaceFolder | undefined;
@@ -253,6 +254,7 @@ export function buildEnvironmentApi(
253254
sendApiTelemetry('onDidChangeEnvironments');
254255
return onEnvironmentsChanged.event;
255256
},
257+
...buildEnvironmentCreationApi(),
256258
};
257259
return environmentApi;
258260
}

src/client/proposedApiTypes.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
export interface ProposedExtensionAPI {}
4+
export interface ProposedExtensionAPI {
5+
/**
6+
* Top level poposed APIs should go here.
7+
*/
8+
}

src/client/pythonEnvironments/creation/createEnvApi.ts

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ import { IInterpreterQuickPick } from '../../interpreter/configuration/types';
99
import { getCreationEvents, handleCreateEnvironmentCommand } from './createEnvironment';
1010
import { condaCreationProvider } from './provider/condaCreationProvider';
1111
import { VenvCreationProvider } from './provider/venvCreationProvider';
12+
import { showInformationMessage } from '../../common/vscodeApis/windowApis';
13+
import { CreateEnv } from '../../common/utils/localize';
1214
import {
13-
CreateEnvironmentExitedEventArgs,
14-
CreateEnvironmentOptions,
1515
CreateEnvironmentProvider,
16+
CreateEnvironmentOptions,
1617
CreateEnvironmentResult,
17-
} from './types';
18-
import { showInformationMessage } from '../../common/vscodeApis/windowApis';
19-
import { CreateEnv } from '../../common/utils/localize';
18+
ProposedCreateEnvironmentAPI,
19+
DidCreateEnvironmentParams,
20+
} from './proposed.createEnvApis';
2021

2122
class CreateEnvironmentProviders {
2223
private _createEnvProviders: CreateEnvironmentProvider[] = [];
@@ -26,6 +27,9 @@ class CreateEnvironmentProviders {
2627
}
2728

2829
public add(provider: CreateEnvironmentProvider) {
30+
if (this._createEnvProviders.filter((p) => p.id === provider.id).length > 0) {
31+
throw new Error(`Create Environment provider with id ${provider.id} already registered`);
32+
}
2933
this._createEnvProviders.push(provider);
3034
}
3135

@@ -63,15 +67,32 @@ export function registerCreateEnvironmentFeatures(
6367
return handleCreateEnvironmentCommand(providers, options);
6468
},
6569
),
66-
);
67-
disposables.push(registerCreateEnvironmentProvider(new VenvCreationProvider(interpreterQuickPick)));
68-
disposables.push(registerCreateEnvironmentProvider(condaCreationProvider()));
69-
disposables.push(
70-
onCreateEnvironmentExited(async (e: CreateEnvironmentExitedEventArgs) => {
71-
if (e.result?.path && e.options?.selectEnvironment) {
72-
await interpreterPathService.update(e.result.uri, ConfigurationTarget.WorkspaceFolder, e.result.path);
73-
showInformationMessage(`${CreateEnv.informEnvCreation} ${pathUtils.getDisplayName(e.result.path)}`);
70+
registerCreateEnvironmentProvider(new VenvCreationProvider(interpreterQuickPick)),
71+
registerCreateEnvironmentProvider(condaCreationProvider()),
72+
onCreateEnvironmentExited(async (e: DidCreateEnvironmentParams) => {
73+
if (e.path && e.options?.selectEnvironment) {
74+
await interpreterPathService.update(e.uri, ConfigurationTarget.WorkspaceFolder, e.path);
75+
showInformationMessage(`${CreateEnv.informEnvCreation} ${pathUtils.getDisplayName(e.path)}`);
7476
}
7577
}),
7678
);
7779
}
80+
81+
export function buildEnvironmentCreationApi(): ProposedCreateEnvironmentAPI {
82+
return {
83+
onWillCreateEnvironment: onCreateEnvironmentStarted,
84+
onDidCreateEnvironment: onCreateEnvironmentExited,
85+
createEnvironment: async (
86+
options?: CreateEnvironmentOptions | undefined,
87+
): Promise<CreateEnvironmentResult | undefined> => {
88+
const providers = _createEnvironmentProviders.getAll();
89+
try {
90+
return await handleCreateEnvironmentCommand(providers, options);
91+
} catch (err) {
92+
return { error: err as Error };
93+
}
94+
},
95+
registerCreateEnvironmentProvider: (provider: CreateEnvironmentProvider) =>
96+
registerCreateEnvironmentProvider(provider),
97+
};
98+
}

src/client/pythonEnvironments/creation/createEnvironment.ts

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ import {
1111
} from '../../common/vscodeApis/windowApis';
1212
import { traceError, traceVerbose } from '../../logging';
1313
import {
14-
CreateEnvironmentExitedEventArgs,
1514
CreateEnvironmentOptions,
16-
CreateEnvironmentProvider,
1715
CreateEnvironmentResult,
18-
CreateEnvironmentStartedEventArgs,
19-
} from './types';
16+
CreateEnvironmentProvider,
17+
WillCreateEnvironmentParams,
18+
DidCreateEnvironmentParams,
19+
} from './proposed.createEnvApis';
2020

21-
const onCreateEnvironmentStartedEvent = new EventEmitter<CreateEnvironmentStartedEventArgs>();
22-
const onCreateEnvironmentExitedEvent = new EventEmitter<CreateEnvironmentExitedEventArgs>();
21+
const onCreateEnvironmentStartedEvent = new EventEmitter<WillCreateEnvironmentParams>();
22+
const onCreateEnvironmentExitedEvent = new EventEmitter<DidCreateEnvironmentParams>();
2323

2424
let startedEventCount = 0;
2525

@@ -32,14 +32,14 @@ function fireStartedEvent(options?: CreateEnvironmentOptions): void {
3232
startedEventCount += 1;
3333
}
3434

35-
function fireExitedEvent(result?: CreateEnvironmentResult, options?: CreateEnvironmentOptions, error?: unknown): void {
36-
onCreateEnvironmentExitedEvent.fire({ result, options, error });
35+
function fireExitedEvent(result?: CreateEnvironmentResult, options?: CreateEnvironmentOptions, error?: Error): void {
36+
onCreateEnvironmentExitedEvent.fire({ ...result, options, error });
3737
startedEventCount -= 1;
3838
}
3939

4040
export function getCreationEvents(): {
41-
onCreateEnvironmentStarted: Event<CreateEnvironmentStartedEventArgs>;
42-
onCreateEnvironmentExited: Event<CreateEnvironmentExitedEventArgs>;
41+
onCreateEnvironmentStarted: Event<WillCreateEnvironmentParams>;
42+
onCreateEnvironmentExited: Event<DidCreateEnvironmentParams>;
4343
isCreatingEnvironment: () => boolean;
4444
} {
4545
return {
@@ -54,7 +54,7 @@ async function createEnvironment(
5454
options: CreateEnvironmentOptions,
5555
): Promise<CreateEnvironmentResult | undefined> {
5656
let result: CreateEnvironmentResult | undefined;
57-
let err: unknown | undefined;
57+
let err: Error | undefined;
5858
try {
5959
fireStartedEvent(options);
6060
result = await provider.createEnvironment(options);
@@ -65,7 +65,7 @@ async function createEnvironment(
6565
return undefined;
6666
}
6767
}
68-
err = ex;
68+
err = ex as Error;
6969
throw err;
7070
} finally {
7171
fireExitedEvent(result, options, err);
@@ -185,11 +185,7 @@ export async function handleCreateEnvironmentCommand(
185185
const action = await MultiStepNode.run(envTypeStep);
186186
if (options?.showBackButton) {
187187
if (action === MultiStepAction.Back || action === MultiStepAction.Cancel) {
188-
result = {
189-
path: result?.path,
190-
uri: result?.uri,
191-
action: action === MultiStepAction.Back ? 'Back' : 'Cancel',
192-
};
188+
result = { action };
193189
}
194190
}
195191

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License
3+
4+
import { Uri, Event, Disposable } from 'vscode';
5+
6+
export type CreateEnvironmentUserActions = 'Back' | 'Cancel';
7+
export type CreateEnvironmentProviderId = string;
8+
9+
/**
10+
* Options used when creating a Python environment.
11+
*/
12+
export interface CreateEnvironmentOptions {
13+
/**
14+
* Default `true`. If `true`, the environment creation handler is expected to install packages.
15+
*/
16+
installPackages?: boolean;
17+
18+
/**
19+
* Default `true`. If `true`, the environment creation provider is expected to add the environment to ignore list
20+
* for the source control.
21+
*/
22+
ignoreSourceControl?: boolean;
23+
24+
/**
25+
* Default `false`. If `true` the creation provider should show back button when showing QuickPick or QuickInput.
26+
*/
27+
showBackButton?: boolean;
28+
29+
/**
30+
* Default `true`. If `true`, the environment will be selected as the environment to be used for the workspace.
31+
*/
32+
selectEnvironment?: boolean;
33+
}
34+
35+
/**
36+
* Params passed on `onWillCreateEnvironment` event handler.
37+
*/
38+
export interface WillCreateEnvironmentParams {
39+
/**
40+
* Options used to create a Python environment.
41+
*/
42+
options: CreateEnvironmentOptions | undefined;
43+
}
44+
45+
/**
46+
* Params passed on `onDidCreateEnvironment` event handler.
47+
*/
48+
export interface DidCreateEnvironmentParams {
49+
/**
50+
* Options used to create the Python environment.
51+
*/
52+
options?: CreateEnvironmentOptions;
53+
54+
/**
55+
* Workspace Uri associated with the environment.
56+
*/
57+
uri?: Uri;
58+
59+
/**
60+
* Path to the executable python in the environment
61+
*/
62+
path?: string;
63+
64+
/**
65+
* User action that resulted in exit from the create environment flow.
66+
*/
67+
action?: CreateEnvironmentUserActions;
68+
69+
/**
70+
* Error if any occurred during environment creation.
71+
*/
72+
error?: Error;
73+
}
74+
75+
export interface CreateEnvironmentResult {
76+
/**
77+
* Workspace Uri associated with the environment.
78+
*/
79+
uri?: Uri;
80+
81+
/**
82+
* Path to the python executable in the environment.
83+
*/
84+
path?: string;
85+
86+
/**
87+
* User action that resulted in exit from the create environment flow.
88+
*/
89+
action?: CreateEnvironmentUserActions;
90+
91+
/**
92+
* Error if any occurred during environment creation.
93+
*/
94+
error?: Error;
95+
}
96+
97+
/**
98+
* Extensions that want to contribute their own environment creation can do that by registering an object
99+
* that implements this interface.
100+
*/
101+
export interface CreateEnvironmentProvider {
102+
/**
103+
* This API is called when user selects this provider from a QuickPick to select the type of environment
104+
* user wants. This API is expected to show a QuickPick or QuickInput to get the user input and return
105+
* the path to the Python executable in the environment.
106+
*
107+
* @param {CreateEnvironmentOptions} [options] Options used to create a Python environment.
108+
*
109+
* @returns a promise that resolves to the path to the
110+
* Python executable in the environment. Or any action taken by the user, such as back or cancel.
111+
*/
112+
createEnvironment(options?: CreateEnvironmentOptions): Promise<CreateEnvironmentResult | undefined>;
113+
114+
/**
115+
* Unique ID for the creation provider, typically <ExtensionId>:<environment-type | guid>
116+
*/
117+
id: CreateEnvironmentProviderId;
118+
119+
/**
120+
* Display name for the creation provider.
121+
*/
122+
name: string;
123+
124+
/**
125+
* Description displayed to the user in the QuickPick to select environment provider.
126+
*/
127+
description: string;
128+
129+
/**
130+
* Tools used to manage this environment. e.g., ['conda']
131+
*/
132+
tools?: string[];
133+
}
134+
135+
export interface ProposedCreateEnvironmentAPI {
136+
/**
137+
* This API can be used to detect when the environment creation starts for any registered
138+
* provider (including internal providers). This will also receive any options passed in
139+
* or defaults used to create environment.
140+
*/
141+
onWillCreateEnvironment: Event<WillCreateEnvironmentParams>;
142+
143+
/**
144+
* This API can be used to detect when the environment provider exits for any registered
145+
* provider (including internal providers). This will also receive created environment path,
146+
* any errors, or user actions taken from the provider.
147+
*/
148+
onDidCreateEnvironment: Event<DidCreateEnvironmentParams>;
149+
150+
/**
151+
* This API will show a QuickPick to select an environment provider from available list of
152+
* providers. Based on the selection the `createEnvironment` will be called on the provider.
153+
*/
154+
createEnvironment(options: CreateEnvironmentOptions): Promise<CreateEnvironmentResult | undefined>;
155+
156+
/**
157+
* This API should be called to register an environment creation provider. It returns
158+
* a (@link Disposable} which can be used to remove the registration.
159+
*/
160+
registerCreateEnvironmentProvider(provider: CreateEnvironmentProvider): Disposable;
161+
}

src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,7 @@ import { CancellationToken, ProgressLocation, WorkspaceFolder } from 'vscode';
55
import * as path from 'path';
66
import { Commands, PVSC_EXTENSION_ID } from '../../../common/constants';
77
import { traceError, traceLog } from '../../../logging';
8-
import {
9-
CreateEnvironmentOptions,
10-
CreateEnvironmentProgress,
11-
CreateEnvironmentProvider,
12-
CreateEnvironmentResult,
13-
} from '../types';
8+
import { CreateEnvironmentProgress } from '../types';
149
import { pickWorkspaceFolder } from '../common/workspaceSelection';
1510
import { execObservable } from '../../../common/process/rawProcessApis';
1611
import { createDeferred } from '../../../common/utils/async';
@@ -28,6 +23,11 @@ import {
2823
CONDA_ENV_EXISTING_MARKER,
2924
} from './condaProgressAndTelemetry';
3025
import { splitLines } from '../../../common/stringUtils';
26+
import {
27+
CreateEnvironmentOptions,
28+
CreateEnvironmentResult,
29+
CreateEnvironmentProvider,
30+
} from '../proposed.createEnvApis';
3131

3232
function generateCommandArgs(version?: string, options?: CreateEnvironmentOptions): string[] {
3333
let addGitIgnore = true;

src/client/pythonEnvironments/creation/provider/venvCreationProvider.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,7 @@ import { execObservable } from '../../../common/process/rawProcessApis';
99
import { createDeferred } from '../../../common/utils/async';
1010
import { Common, CreateEnv } from '../../../common/utils/localize';
1111
import { traceError, traceInfo, traceLog } from '../../../logging';
12-
import {
13-
CreateEnvironmentOptions,
14-
CreateEnvironmentProgress,
15-
CreateEnvironmentProvider,
16-
CreateEnvironmentResult,
17-
} from '../types';
12+
import { CreateEnvironmentProgress } from '../types';
1813
import { pickWorkspaceFolder } from '../common/workspaceSelection';
1914
import { IInterpreterQuickPick } from '../../../interpreter/configuration/types';
2015
import { EnvironmentType, PythonEnvironment } from '../../info';
@@ -25,6 +20,11 @@ import { VenvProgressAndTelemetry, VENV_CREATED_MARKER, VENV_EXISTING_MARKER } f
2520
import { showErrorMessageWithLogs } from '../common/commonUtils';
2621
import { IPackageInstallSelection, pickPackagesToInstall } from './venvUtils';
2722
import { InputFlowAction } from '../../../common/utils/multiStepInput';
23+
import {
24+
CreateEnvironmentProvider,
25+
CreateEnvironmentOptions,
26+
CreateEnvironmentResult,
27+
} from '../proposed.createEnvApis';
2828

2929
function generateCommandArgs(installInfo?: IPackageInstallSelection[], addGitIgnore?: boolean): string[] {
3030
const command: string[] = [createVenvScript()];

0 commit comments

Comments
 (0)