Skip to content

Commit f9c1f97

Browse files
authored
Activate conda environment using path when name is not available (#8397)
* Activate using path when name is not available * Add news item * Some cleanup
1 parent 6295bee commit f9c1f97

File tree

6 files changed

+78
-51
lines changed

6 files changed

+78
-51
lines changed

news/1 Enhancements/3834.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Activate conda environment using path when name is not available.

src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman
2626
@inject(ICondaService) private readonly condaService: ICondaService,
2727
@inject(IPlatformService) private platform: IPlatformService,
2828
@inject(IConfigurationService) private configService: IConfigurationService
29-
) { }
29+
) {}
3030

3131
/**
3232
* Is the given shell supported for activating a conda env?
@@ -53,43 +53,44 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman
5353
return;
5454
}
5555

56+
const condaEnv = envInfo.name.length > 0 ? envInfo.name : envInfo.path;
57+
5658
// Algorithm differs based on version
5759
// Old version, just call activate directly.
5860
// New version, call activate from the same path as our python path, then call it again to activate our environment.
5961
// -- note that the 'default' conda location won't allow activate to work for the environment sometimes.
6062
const versionInfo = await this.condaService.getCondaVersion();
6163
if (versionInfo && versionInfo.major >= CondaRequiredMajor) {
6264
// Conda added support for powershell in 4.6.
63-
if (versionInfo.minor >= CondaRequiredMinorForPowerShell &&
64-
(targetShell === TerminalShellType.powershell || targetShell === TerminalShellType.powershellCore)) {
65-
return this.getPowershellCommands(envInfo.name);
65+
if (versionInfo.minor >= CondaRequiredMinorForPowerShell && (targetShell === TerminalShellType.powershell || targetShell === TerminalShellType.powershellCore)) {
66+
return this.getPowershellCommands(condaEnv);
6667
}
6768
if (versionInfo.minor >= CondaRequiredMinor) {
6869
// New version.
6970
const interpreterPath = await this.condaService.getCondaFileFromInterpreter(pythonPath, envInfo.name);
7071
if (interpreterPath) {
7172
const activatePath = path.join(path.dirname(interpreterPath), 'activate').fileToCommandArgument();
7273
const firstActivate = this.platform.isWindows ? activatePath : `source ${activatePath}`;
73-
return [firstActivate, `conda activate ${envInfo.name.toCommandArgument()}`];
74+
return [firstActivate, `conda activate ${condaEnv.toCommandArgument()}`];
7475
}
7576
}
7677
}
7778

7879
switch (targetShell) {
7980
case TerminalShellType.powershell:
8081
case TerminalShellType.powershellCore:
81-
return this.getPowershellCommands(envInfo.name);
82+
return this.getPowershellCommands(condaEnv);
8283

8384
// tslint:disable-next-line:no-suspicious-comment
8485
// TODO: Do we really special-case fish on Windows?
8586
case TerminalShellType.fish:
86-
return this.getFishCommands(envInfo.name, await this.condaService.getCondaFile());
87+
return this.getFishCommands(condaEnv, await this.condaService.getCondaFile());
8788

8889
default:
8990
if (this.platform.isWindows) {
90-
return this.getWindowsCommands(envInfo.name);
91+
return this.getWindowsCommands(condaEnv);
9192
} else {
92-
return this.getUnixCommands(envInfo.name, await this.condaService.getCondaFile());
93+
return this.getUnixCommands(condaEnv, await this.condaService.getCondaFile());
9394
}
9495
}
9596
}
@@ -109,9 +110,9 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman
109110
return activateCmd;
110111
}
111112

112-
public async getWindowsCommands(envName: string): Promise<string[] | undefined> {
113+
public async getWindowsCommands(condaEnv: string): Promise<string[] | undefined> {
113114
const activate = await this.getWindowsActivateCommand();
114-
return [`${activate} ${envName.toCommandArgument()}`];
115+
return [`${activate} ${condaEnv.toCommandArgument()}`];
115116
}
116117
/**
117118
* The expectation is for the user to configure Powershell for Conda.
@@ -123,18 +124,18 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman
123124
* @returns {(Promise<string[] | undefined>)}
124125
* @memberof CondaActivationCommandProvider
125126
*/
126-
public async getPowershellCommands(envName: string): Promise<string[] | undefined> {
127-
return [`conda activate ${envName.toCommandArgument()}`];
127+
public async getPowershellCommands(condaEnv: string): Promise<string[] | undefined> {
128+
return [`conda activate ${condaEnv.toCommandArgument()}`];
128129
}
129130

130-
public async getFishCommands(envName: string, conda: string): Promise<string[] | undefined> {
131+
public async getFishCommands(condaEnv: string, condaFile: string): Promise<string[] | undefined> {
131132
// https://github.com/conda/conda/blob/be8c08c083f4d5e05b06bd2689d2cd0d410c2ffe/shell/etc/fish/conf.d/conda.fish#L18-L28
132-
return [`${conda.fileToCommandArgument()} activate ${envName.toCommandArgument()}`];
133+
return [`${condaFile.fileToCommandArgument()} activate ${condaEnv.toCommandArgument()}`];
133134
}
134135

135-
public async getUnixCommands(envName: string, conda: string): Promise<string[] | undefined> {
136-
const condaDir = path.dirname(conda);
136+
public async getUnixCommands(condaEnv: string, condaFile: string): Promise<string[] | undefined> {
137+
const condaDir = path.dirname(condaFile);
137138
const activateFile = path.join(condaDir, 'activate');
138-
return [`source ${activateFile.fileToCommandArgument()} ${envName.toCommandArgument()}`];
139+
return [`source ${activateFile.fileToCommandArgument()} ${condaEnv.toCommandArgument()}`];
139140
}
140141
}

src/client/interpreter/locators/services/conda.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ export const AnacondaCompanyName = 'Anaconda, Inc.';
55
// tslint:disable-next-line:variable-name
66
export const AnacondaDisplayName = 'Anaconda';
77
// tslint:disable-next-line:variable-name
8-
export const AnacondaIdentfiers = ['Anaconda', 'Conda', 'Continuum'];
8+
export const AnacondaIdentifiers = ['Anaconda', 'Conda', 'Continuum'];

src/client/interpreter/locators/services/condaHelper.ts

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

4-
import * as path from 'path';
54
import '../../../common/extensions';
65
import { CondaInfo } from '../../contracts';
7-
import { AnacondaDisplayName, AnacondaIdentfiers } from './conda';
6+
import { AnacondaDisplayName, AnacondaIdentifiers } from './conda';
87

98
export type EnvironmentPath = string;
109
export type EnvironmentName = string;
@@ -13,7 +12,6 @@ export type EnvironmentName = string;
1312
* Helpers for conda.
1413
*/
1514
export class CondaHelper {
16-
1715
/**
1816
* Return the string to display for the conda interpreter.
1917
*/
@@ -51,6 +49,8 @@ export class CondaHelper {
5149
* py27 /Users/donjayamanne/anaconda3/envs/py27
5250
* py36 /Users/donjayamanne/anaconda3/envs/py36
5351
* three /Users/donjayamanne/anaconda3/envs/three
52+
* /Users/donjayamanne/anaconda3/envs/four
53+
* /Users/donjayamanne/anaconda3/envs/five 5
5454
* @param {string} condaEnvironmentList
5555
* @param {CondaInfo} condaInfo
5656
* @returns {{ name: string, path: string }[] | undefined}
@@ -73,8 +73,7 @@ export class CondaHelper {
7373
name = name.substring(0, name.length - 1).trim();
7474
}
7575
const envPath = line.substring(pathStartIndex).trim();
76-
name = name.length === 0 ? path.basename(envPath) : name;
77-
if (name.length > 0 && envPath.length > 0) {
76+
if (envPath.length > 0) {
7877
envs.push({ name, path: envPath });
7978
}
8079
});
@@ -87,6 +86,6 @@ export class CondaHelper {
8786
*/
8887
private isIdentifiableAsAnaconda(value: string) {
8988
const valueToSearch = value.toLowerCase();
90-
return AnacondaIdentfiers.some(item => valueToSearch.indexOf(item.toLowerCase()) !== -1);
89+
return AnacondaIdentifiers.some(item => valueToSearch.indexOf(item.toLowerCase()) !== -1);
9190
}
9291
}

src/test/common/terminals/activation.conda.unit.test.ts

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,18 @@ suite('Terminal Environment Activation conda', () => {
189189
envName: environmentNameHasSpaces,
190190
expectedResult: ['source path/to/activate', 'conda activate "Env with spaces"'],
191191
isWindows: false
192+
},
193+
{
194+
testName: 'Activation provides correct activation commands (windows) after 4.4.0 given interpreter path is provided, and no env name',
195+
envName: '',
196+
expectedResult: ['path/to/activate', `conda activate .`],
197+
isWindows: true
198+
},
199+
{
200+
testName: 'Activation provides correct activation commands (non-windows) after 4.4.0 given interpreter path is provided, and no env name',
201+
envName: '',
202+
expectedResult: ['source path/to/activate', `conda activate .`],
203+
isWindows: false
192204
}
193205
];
194206

@@ -215,79 +227,93 @@ suite('Terminal Environment Activation conda', () => {
215227
});
216228
});
217229

218-
async function testCondaActivationCommands(
219-
isWindows: boolean,
220-
isOsx: boolean,
221-
isLinux: boolean,
222-
pythonPath: string,
223-
shellType: TerminalShellType,
224-
hasSpaceInEnvironmentName = false
225-
) {
230+
async function testCondaActivationCommands(isWindows: boolean, isOsx: boolean, isLinux: boolean, pythonPath: string, shellType: TerminalShellType, envName: string) {
226231
terminalSettings.setup(t => t.activateEnvironment).returns(() => true);
227232
platformService.setup(p => p.isLinux).returns(() => isLinux);
228233
platformService.setup(p => p.isWindows).returns(() => isWindows);
229234
platformService.setup(p => p.isMac).returns(() => isOsx);
230235
condaService.setup(c => c.isCondaEnvironment(TypeMoq.It.isAny())).returns(() => Promise.resolve(true));
231236
pythonSettings.setup(s => s.pythonPath).returns(() => pythonPath);
232-
const envName = hasSpaceInEnvironmentName ? 'EnvA' : 'Env A';
233237
condaService.setup(c => c.getCondaEnvironment(TypeMoq.It.isAny())).returns(() => Promise.resolve({ name: envName, path: path.dirname(pythonPath) }));
234238

235239
const activationCommands = await new CondaActivationCommandProvider(condaService.object, platformService.object, configService.object).getActivationCommands(
236240
undefined,
237241
shellType
238242
);
239-
let expectedActivationCommamnd: string[] | undefined;
243+
let expectedActivationCommand: string[] | undefined;
244+
const expectEnvActivatePath = path.dirname(pythonPath);
240245
switch (shellType) {
241246
case TerminalShellType.powershell:
242-
case TerminalShellType.powershellCore: {
243-
expectedActivationCommamnd = [`conda activate ${envName.toCommandArgument()}`];
244-
break;
245-
}
247+
case TerminalShellType.powershellCore:
246248
case TerminalShellType.fish: {
247-
expectedActivationCommamnd = [`conda activate ${envName.toCommandArgument()}`];
249+
if (envName !== '') {
250+
expectedActivationCommand = [`conda activate ${envName.toCommandArgument()}`];
251+
} else {
252+
expectedActivationCommand = [`conda activate ${expectEnvActivatePath}`];
253+
}
248254
break;
249255
}
250256
default: {
251-
expectedActivationCommamnd = isWindows ? [`activate ${envName.toCommandArgument()}`] : [`source activate ${envName.toCommandArgument()}`];
257+
if (envName !== '') {
258+
expectedActivationCommand = isWindows ? [`activate ${envName.toCommandArgument()}`] : [`source activate ${envName.toCommandArgument()}`];
259+
} else {
260+
expectedActivationCommand = isWindows ? [`activate ${expectEnvActivatePath}`] : [`source activate ${expectEnvActivatePath}`];
261+
}
252262
break;
253263
}
254264
}
255-
if (expectedActivationCommamnd) {
256-
expect(activationCommands).to.deep.equal(expectedActivationCommamnd, 'Incorrect Activation command');
265+
if (expectedActivationCommand) {
266+
expect(activationCommands).to.deep.equal(expectedActivationCommand, 'Incorrect Activation command');
257267
} else {
258268
expect(activationCommands).to.equal(undefined, 'Incorrect Activation command');
259269
}
260270
}
261271
getNamesAndValues<TerminalShellType>(TerminalShellType).forEach(shellType => {
262272
test(`Conda activation command for shell ${shellType.name} on (windows)`, async () => {
263273
const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'enva', 'python.exe');
264-
await testCondaActivationCommands(true, false, false, pythonPath, shellType.value);
274+
await testCondaActivationCommands(true, false, false, pythonPath, shellType.value, 'Env');
265275
});
266276

267277
test(`Conda activation command for shell ${shellType.name} on (linux)`, async () => {
268278
const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'bin', 'python');
269-
await testCondaActivationCommands(false, false, true, pythonPath, shellType.value);
279+
await testCondaActivationCommands(false, false, true, pythonPath, shellType.value, 'Env');
270280
});
271281

272282
test(`Conda activation command for shell ${shellType.name} on (mac)`, async () => {
273283
const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'bin', 'python');
274-
await testCondaActivationCommands(false, true, false, pythonPath, shellType.value);
284+
await testCondaActivationCommands(false, true, false, pythonPath, shellType.value, 'Env');
275285
});
276286
});
277287
getNamesAndValues<TerminalShellType>(TerminalShellType).forEach(shellType => {
278288
test(`Conda activation command for shell ${shellType.name} on (windows), containing spaces in environment name`, async () => {
279289
const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'enva', 'python.exe');
280-
await testCondaActivationCommands(true, false, false, pythonPath, shellType.value, true);
290+
await testCondaActivationCommands(true, false, false, pythonPath, shellType.value, 'Env A');
281291
});
282292

283293
test(`Conda activation command for shell ${shellType.name} on (linux), containing spaces in environment name`, async () => {
284294
const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'bin', 'python');
285-
await testCondaActivationCommands(false, false, true, pythonPath, shellType.value, true);
295+
await testCondaActivationCommands(false, false, true, pythonPath, shellType.value, 'Env A');
286296
});
287297

288298
test(`Conda activation command for shell ${shellType.name} on (mac), containing spaces in environment name`, async () => {
289299
const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'bin', 'python');
290-
await testCondaActivationCommands(false, true, false, pythonPath, shellType.value, true);
300+
await testCondaActivationCommands(false, true, false, pythonPath, shellType.value, 'Env A');
301+
});
302+
});
303+
getNamesAndValues<TerminalShellType>(TerminalShellType).forEach(shellType => {
304+
test(`Conda activation command for shell ${shellType.name} on (windows), containing no environment name`, async () => {
305+
const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'enva', 'python.exe');
306+
await testCondaActivationCommands(true, false, false, pythonPath, shellType.value, '');
307+
});
308+
309+
test(`Conda activation command for shell ${shellType.name} on (linux), containing no environment name`, async () => {
310+
const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'bin', 'python');
311+
await testCondaActivationCommands(false, false, true, pythonPath, shellType.value, '');
312+
});
313+
314+
test(`Conda activation command for shell ${shellType.name} on (mac), containing no environment name`, async () => {
315+
const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'bin', 'python');
316+
await testCondaActivationCommands(false, true, false, pythonPath, shellType.value, '');
291317
});
292318
});
293319
async function expectCondaActivationCommand(isWindows: boolean, isOsx: boolean, isLinux: boolean, pythonPath: string) {

src/test/interpreters/condaHelper.unit.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ three3 /Users/donjayamanne/anaconda3/envs/three
5858
{ name: 'one1', path: '/Users/donjayamanne/anaconda3/envs/one' },
5959
{ name: 'two2 2', path: '/Users/donjayamanne/anaconda3/envs/two 2' },
6060
{ name: 'three3', path: '/Users/donjayamanne/anaconda3/envs/three' },
61-
{ name: 'four', path: '/Users/donjayamanne/anaconda3/envs/four' },
62-
{ name: 'five 5', path: '/Users/donjayamanne/anaconda3/envs/five 5' }
61+
{ name: '', path: '/Users/donjayamanne/anaconda3/envs/four' },
62+
{ name: '', path: '/Users/donjayamanne/anaconda3/envs/five 5' }
6363
];
6464

6565
const list = condaHelper.parseCondaEnvironmentNames(environments);

0 commit comments

Comments
 (0)