Skip to content

Commit 2908a5f

Browse files
committed
Fix for env creation errors.
1 parent a074e8b commit 2908a5f

File tree

9 files changed

+188
-30
lines changed

9 files changed

+188
-30
lines changed

src/client/pythonEnvironments/creation/common/commonUtils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import { executeCommand } from '../../../common/vscodeApis/commandApis';
66
import { showErrorMessage } from '../../../common/vscodeApis/windowApis';
77

88
export async function showErrorMessageWithLogs(message: string): Promise<void> {
9-
const result = await showErrorMessage(message, Common.openOutputPanel);
9+
const result = await showErrorMessage(message, Common.openOutputPanel, Common.selectPythonInterpreter);
1010
if (result === Common.openOutputPanel) {
1111
await executeCommand(Commands.ViewOutput);
12+
} else if (result === Common.selectPythonInterpreter) {
13+
await executeCommand(Commands.Set_Interpreter);
1214
}
1315
}

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

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { createDeferred } from '../../../common/utils/async';
1717
import { getEnvironmentVariable, getOSType, OSType } from '../../../common/utils/platform';
1818
import { createCondaScript } from '../../../common/process/internal/scripts';
1919
import { Common, CreateEnv } from '../../../common/utils/localize';
20-
import { getConda, pickPythonVersion } from './condaUtils';
20+
import { getCondaBaseEnv, pickPythonVersion } from './condaUtils';
2121
import { showErrorMessageWithLogs } from '../common/commonUtils';
2222
import { withProgress } from '../../../common/vscodeApis/windowApis';
2323
import { EventName } from '../../../telemetry/constants';
@@ -99,7 +99,7 @@ async function createCondaEnv(
9999
pathEnv = `${libPath}${path.delimiter}${pathEnv}`;
100100
}
101101
traceLog('Running Conda Env creation script: ', [command, ...args]);
102-
const { out, dispose } = execObservable(command, args, {
102+
const { proc, out, dispose } = execObservable(command, args, {
103103
mergeStdOutErr: true,
104104
token,
105105
cwd: workspace.uri.fsPath,
@@ -125,37 +125,33 @@ async function createCondaEnv(
125125
},
126126
() => {
127127
dispose();
128-
if (!deferred.rejected) {
128+
if (proc?.exitCode !== 0) {
129+
traceError('Error while running venv creation script: ', progressAndTelemetry.getLastError());
130+
deferred.reject(progressAndTelemetry.getLastError());
131+
} else {
129132
deferred.resolve(condaEnvPath);
130133
}
131134
},
132135
);
133136
return deferred.promise;
134137
}
135138

136-
function getExecutableCommand(condaPath: string): string {
139+
function getExecutableCommand(condaBaseEnvPath: string): string {
137140
if (getOSType() === OSType.Windows) {
138141
// Both Miniconda3 and Anaconda3 have the following structure:
139142
// Miniconda3 (or Anaconda3)
140-
// |- condabin
141-
// | |- conda.bat <--- this actually points to python.exe below,
142-
// | after adding few paths to PATH.
143-
// |- Scripts
144-
// | |- conda.exe <--- this is the path we get as condaPath,
145-
// | which is really a stub for `python.exe -m conda`.
146143
// |- python.exe <--- this is the python that we want.
147-
return path.join(path.dirname(path.dirname(condaPath)), 'python.exe');
144+
return path.join(condaBaseEnvPath, 'python.exe');
148145
}
149146
// On non-windows machines:
150147
// miniconda (or miniforge or anaconda3)
151148
// |- bin
152-
// |- conda <--- this is the path we get as condaPath.
153149
// |- python <--- this is the python that we want.
154-
return path.join(path.dirname(condaPath), 'python');
150+
return path.join(condaBaseEnvPath, 'bin', 'python');
155151
}
156152

157153
async function createEnvironment(options?: CreateEnvironmentOptions): Promise<CreateEnvironmentResult | undefined> {
158-
const conda = await getConda();
154+
const conda = await getCondaBaseEnv();
159155
if (!conda) {
160156
return undefined;
161157
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export class CondaProgressAndTelemetry {
2424

2525
private condaInstalledPackagesReported = false;
2626

27+
private lastError: string | undefined = undefined;
28+
2729
constructor(private readonly progress: CreateEnvironmentProgress) {}
2830

2931
public process(output: string): void {
@@ -51,6 +53,7 @@ export class CondaProgressAndTelemetry {
5153
environmentType: 'conda',
5254
reason: 'other',
5355
});
56+
this.lastError = CREATE_CONDA_FAILED_MARKER;
5457
} else if (!this.condaInstallingPackagesReported && output.includes(CONDA_INSTALLING_YML)) {
5558
this.condaInstallingPackagesReported = true;
5659
this.progress.report({
@@ -66,6 +69,7 @@ export class CondaProgressAndTelemetry {
6669
environmentType: 'conda',
6770
using: 'environment.yml',
6871
});
72+
this.lastError = CREATE_FAILED_INSTALL_YML;
6973
} else if (!this.condaInstalledPackagesReported && output.includes(CREATE_CONDA_INSTALLED_YML)) {
7074
this.condaInstalledPackagesReported = true;
7175
sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLED_PACKAGES, undefined, {
@@ -74,4 +78,8 @@ export class CondaProgressAndTelemetry {
7478
});
7579
}
7680
}
81+
82+
public getLastError(): string | undefined {
83+
return this.lastError;
84+
}
7785
}

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import { Common } from '../../../browser/localize';
66
import { CreateEnv } from '../../../common/utils/localize';
77
import { executeCommand } from '../../../common/vscodeApis/commandApis';
88
import { showErrorMessage, showQuickPick } from '../../../common/vscodeApis/windowApis';
9+
import { traceLog } from '../../../logging';
910
import { Conda } from '../../common/environmentManagers/conda';
1011

11-
export async function getConda(): Promise<string | undefined> {
12+
export async function getCondaBaseEnv(): Promise<string | undefined> {
1213
const conda = await Conda.getConda();
1314

1415
if (!conda) {
@@ -18,7 +19,20 @@ export async function getConda(): Promise<string | undefined> {
1819
}
1920
return undefined;
2021
}
21-
return conda.command;
22+
23+
const envs = (await conda.getEnvList()).filter((e) => e.name === 'base');
24+
if (envs.length === 1) {
25+
return envs[0].prefix;
26+
}
27+
if (envs.length > 1) {
28+
traceLog(
29+
'Multiple conda base envs detected: ',
30+
envs.map((e) => e.prefix),
31+
);
32+
return undefined;
33+
}
34+
35+
return undefined;
2236
}
2337

2438
export async function pickPythonVersion(token?: CancellationToken): Promise<string | undefined> {

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ async function createVenv(
7878

7979
const deferred = createDeferred<string | undefined>();
8080
traceLog('Running Env creation script: ', [command, ...args]);
81-
const { out, dispose } = execObservable(command, args, {
81+
const { proc, out, dispose } = execObservable(command, args, {
8282
mergeStdOutErr: true,
8383
token,
8484
cwd: workspace.uri.fsPath,
@@ -101,7 +101,10 @@ async function createVenv(
101101
},
102102
() => {
103103
dispose();
104-
if (!deferred.rejected) {
104+
if (proc?.exitCode !== 0) {
105+
traceError('Error while running venv creation script: ', progressAndTelemetry.getLastError());
106+
deferred.reject(progressAndTelemetry.getLastError());
107+
} else {
105108
deferred.resolve(venvPath);
106109
}
107110
},

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export class VenvProgressAndTelemetry {
3333

3434
private venvInstalledPackagesReported = false;
3535

36+
private lastError: string | undefined = undefined;
37+
3638
constructor(private readonly progress: CreateEnvironmentProgress) {}
3739

3840
public process(output: string): void {
@@ -60,18 +62,21 @@ export class VenvProgressAndTelemetry {
6062
environmentType: 'venv',
6163
reason: 'noVenv',
6264
});
65+
this.lastError = VENV_NOT_INSTALLED_MARKER;
6366
} else if (!this.venvOrPipMissingReported && output.includes(PIP_NOT_INSTALLED_MARKER)) {
6467
this.venvOrPipMissingReported = true;
6568
sendTelemetryEvent(EventName.ENVIRONMENT_FAILED, undefined, {
6669
environmentType: 'venv',
6770
reason: 'noPip',
6871
});
72+
this.lastError = PIP_NOT_INSTALLED_MARKER;
6973
} else if (!this.venvFailedReported && output.includes(CREATE_VENV_FAILED_MARKER)) {
7074
this.venvFailedReported = true;
7175
sendTelemetryEvent(EventName.ENVIRONMENT_FAILED, undefined, {
7276
environmentType: 'venv',
7377
reason: 'other',
7478
});
79+
this.lastError = CREATE_VENV_FAILED_MARKER;
7580
} else if (!this.venvInstallingPackagesReported && output.includes(INSTALLING_REQUIREMENTS)) {
7681
this.venvInstallingPackagesReported = true;
7782
this.progress.report({
@@ -96,18 +101,21 @@ export class VenvProgressAndTelemetry {
96101
environmentType: 'venv',
97102
using: 'pipUpgrade',
98103
});
104+
this.lastError = PIP_UPGRADE_FAILED_MARKER;
99105
} else if (!this.venvInstallingPackagesFailedReported && output.includes(INSTALL_REQUIREMENTS_FAILED_MARKER)) {
100106
this.venvInstallingPackagesFailedReported = true;
101107
sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLING_PACKAGES_FAILED, undefined, {
102108
environmentType: 'venv',
103109
using: 'requirements.txt',
104110
});
111+
this.lastError = INSTALL_REQUIREMENTS_FAILED_MARKER;
105112
} else if (!this.venvInstallingPackagesFailedReported && output.includes(INSTALL_PYPROJECT_FAILED_MARKER)) {
106113
this.venvInstallingPackagesFailedReported = true;
107114
sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLING_PACKAGES_FAILED, undefined, {
108115
environmentType: 'venv',
109116
using: 'pyproject.toml',
110117
});
118+
this.lastError = INSTALL_PYPROJECT_FAILED_MARKER;
111119
} else if (!this.venvInstalledPackagesReported && output.includes(INSTALLED_REQUIREMENTS_MARKER)) {
112120
this.venvInstalledPackagesReported = true;
113121
sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLED_PACKAGES, undefined, {
@@ -122,4 +130,8 @@ export class VenvProgressAndTelemetry {
122130
});
123131
}
124132
}
133+
134+
public getLastError(): string | undefined {
135+
return this.lastError;
136+
}
125137
}

src/client/telemetry/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export function sendTelemetryEvent<P extends IEventNamePropertyMapping, E extend
139139
break;
140140
}
141141
} catch (exception) {
142-
console.error(`Failed to serialize ${prop} for ${eventName}`, exception);
142+
console.error(`Failed to serialize ${prop} for ${String(eventName)}`, exception);
143143
}
144144
});
145145
}

src/test/pythonEnvironments/creation/provider/condaCreationProvider.unit.test.ts

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ chaiUse(chaiAsPromised);
2929
suite('Conda Creation provider tests', () => {
3030
let condaProvider: CreateEnvironmentProvider;
3131
let progressMock: typemoq.IMock<CreateEnvironmentProgress>;
32-
let getCondaStub: sinon.SinonStub;
32+
let getCondaBaseEnvStub: sinon.SinonStub;
3333
let pickPythonVersionStub: sinon.SinonStub;
3434
let pickWorkspaceFolderStub: sinon.SinonStub;
3535
let execObservableStub: sinon.SinonStub;
@@ -38,7 +38,7 @@ suite('Conda Creation provider tests', () => {
3838

3939
setup(() => {
4040
pickWorkspaceFolderStub = sinon.stub(wsSelect, 'pickWorkspaceFolder');
41-
getCondaStub = sinon.stub(condaUtils, 'getConda');
41+
getCondaBaseEnvStub = sinon.stub(condaUtils, 'getCondaBaseEnv');
4242
pickPythonVersionStub = sinon.stub(condaUtils, 'pickPythonVersion');
4343
execObservableStub = sinon.stub(rawProcessApis, 'execObservable');
4444
withProgressStub = sinon.stub(windowApis, 'withProgress');
@@ -55,20 +55,20 @@ suite('Conda Creation provider tests', () => {
5555
});
5656

5757
test('No conda installed', async () => {
58-
getCondaStub.resolves(undefined);
58+
getCondaBaseEnvStub.resolves(undefined);
5959

6060
assert.isUndefined(await condaProvider.createEnvironment());
6161
});
6262

6363
test('No workspace selected', async () => {
64-
getCondaStub.resolves('/usr/bin/conda');
64+
getCondaBaseEnvStub.resolves('/usr/bin/conda');
6565
pickWorkspaceFolderStub.resolves(undefined);
6666

6767
assert.isUndefined(await condaProvider.createEnvironment());
6868
});
6969

7070
test('No python version picked selected', async () => {
71-
getCondaStub.resolves('/usr/bin/conda');
71+
getCondaBaseEnvStub.resolves('/usr/bin/conda');
7272
pickWorkspaceFolderStub.resolves({
7373
uri: Uri.file(path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'src', 'testMultiRootWkspc', 'workspace1')),
7474
name: 'workspace1',
@@ -80,7 +80,7 @@ suite('Conda Creation provider tests', () => {
8080
});
8181

8282
test('Create conda environment', async () => {
83-
getCondaStub.resolves('/usr/bin/conda/conda_bin/conda');
83+
getCondaBaseEnvStub.resolves('/usr/bin/conda');
8484
const workspace1 = {
8585
uri: Uri.file(path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'src', 'testMultiRootWkspc', 'workspace1')),
8686
name: 'workspace1',
@@ -95,7 +95,9 @@ suite('Conda Creation provider tests', () => {
9595
execObservableStub.callsFake(() => {
9696
deferred.resolve();
9797
return {
98-
proc: undefined,
98+
proc: {
99+
exitCode: 0,
100+
},
99101
out: {
100102
subscribe: (
101103
next?: (value: Output<string>) => void,
@@ -134,7 +136,7 @@ suite('Conda Creation provider tests', () => {
134136
});
135137

136138
test('Create conda environment failed', async () => {
137-
getCondaStub.resolves('/usr/bin/conda/conda_bin/conda');
139+
getCondaBaseEnvStub.resolves('/usr/bin/conda');
138140
pickWorkspaceFolderStub.resolves({
139141
uri: Uri.file(path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'src', 'testMultiRootWkspc', 'workspace1')),
140142
name: 'workspace1',
@@ -183,4 +185,60 @@ suite('Conda Creation provider tests', () => {
183185
await assert.isRejected(promise);
184186
assert.isTrue(showErrorMessageWithLogsStub.calledOnce);
185187
});
188+
189+
test('Create conda environment failed (non-zero exit code)', async () => {
190+
getCondaBaseEnvStub.resolves('/usr/bin/conda');
191+
const workspace1 = {
192+
uri: Uri.file(path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'src', 'testMultiRootWkspc', 'workspace1')),
193+
name: 'workspace1',
194+
index: 0,
195+
};
196+
pickWorkspaceFolderStub.resolves(workspace1);
197+
pickPythonVersionStub.resolves('3.10');
198+
199+
const deferred = createDeferred();
200+
let _next: undefined | ((value: Output<string>) => void);
201+
let _complete: undefined | (() => void);
202+
execObservableStub.callsFake(() => {
203+
deferred.resolve();
204+
return {
205+
proc: {
206+
exitCode: 1,
207+
},
208+
out: {
209+
subscribe: (
210+
next?: (value: Output<string>) => void,
211+
_error?: (error: unknown) => void,
212+
complete?: () => void,
213+
) => {
214+
_next = next;
215+
_complete = complete;
216+
},
217+
},
218+
dispose: () => undefined,
219+
};
220+
});
221+
222+
progressMock.setup((p) => p.report({ message: CreateEnv.statusStarting })).verifiable(typemoq.Times.once());
223+
224+
withProgressStub.callsFake(
225+
(
226+
_options: ProgressOptions,
227+
task: (
228+
progress: CreateEnvironmentProgress,
229+
token?: CancellationToken,
230+
) => Thenable<CreateEnvironmentResult>,
231+
) => task(progressMock.object),
232+
);
233+
234+
const promise = condaProvider.createEnvironment();
235+
await deferred.promise;
236+
assert.isDefined(_next);
237+
assert.isDefined(_complete);
238+
239+
_next!({ out: `${CONDA_ENV_CREATED_MARKER}new_environment`, source: 'stdout' });
240+
_complete!();
241+
await assert.isRejected(promise);
242+
assert.isTrue(showErrorMessageWithLogsStub.calledOnce);
243+
});
186244
});

0 commit comments

Comments
 (0)