Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/2 Fixes/18989.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix error `No such file or directory` on conda activate, and simplify the environment activation code.
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,35 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman
if (versionInfo.minor >= CondaRequiredMinor) {
// New version.
const interpreterPath = await this.condaService.getInterpreterPathForEnvironment(envInfo);
const condaPath = await this.condaService.getCondaFileFromInterpreter(interpreterPath, envInfo.name);
if (condaPath) {
const activatePath = path
.join(path.dirname(condaPath), 'activate')
.fileToCommandArgumentForPythonExt();
const firstActivate = this.platform.isWindows ? activatePath : `source ${activatePath}`;
return [firstActivate, `conda activate ${condaEnv.toCommandArgumentForPythonExt()}`];
const activatePath = await this.condaService.getActivationScriptFromInterpreter(
interpreterPath,
envInfo.name,
);
// eslint-disable-next-line camelcase
if (activatePath?.path) {
if (this.platform.isWindows) {
return [activatePath.path, `conda activate ${condaEnv.toCommandArgumentForPythonExt()}`];
}

const condaInfo = await this.condaService.getCondaInfo();

if (
activatePath.type !== 'global' ||
// eslint-disable-next-line camelcase
condaInfo?.conda_shlvl === undefined ||
condaInfo.conda_shlvl === -1
) {
// activatePath is not the global activate path, or we don't have a shlvl, or it's -1(conda never sourced).
// and we need to source the activate path.
if (activatePath.path === 'activate') {
return [
`source ${activatePath.path}`,
`conda activate ${condaEnv.toCommandArgumentForPythonExt()}`,
];
}
return [`source ${activatePath.path} ${condaEnv.toCommandArgumentForPythonExt()}`];
Comment thread
karrtikr marked this conversation as resolved.
}
return [`conda activate ${condaEnv.toCommandArgumentForPythonExt()}`];
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/client/interpreter/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
PythonLocatorQuery,
TriggerRefreshOptions,
} from '../pythonEnvironments/base/locator';
import { CondaEnvironmentInfo } from '../pythonEnvironments/common/environmentManagers/conda';
import { CondaEnvironmentInfo, CondaInfo } from '../pythonEnvironments/common/environmentManagers/conda';
import { EnvironmentType, PythonEnvironment } from '../pythonEnvironments/info';

export type PythonEnvironmentsChangedEvent = {
Expand Down Expand Up @@ -59,10 +59,15 @@ export const ICondaService = Symbol('ICondaService');
*/
export interface ICondaService {
getCondaFile(forShellExecution?: boolean): Promise<string>;
getCondaInfo(): Promise<CondaInfo | undefined>;
isCondaAvailable(): Promise<boolean>;
getCondaVersion(): Promise<SemVer | undefined>;
getInterpreterPathForEnvironment(condaEnv: CondaEnvironmentInfo): Promise<string | undefined>;
getCondaFileFromInterpreter(interpreterPath?: string, envName?: string): Promise<string | undefined>;
getActivationScriptFromInterpreter(
interpreterPath?: string,
envName?: string,
): Promise<{ path: string | undefined; type: 'local' | 'global' } | undefined>;
}

export const IInterpreterService = Symbol('IInterpreterService');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export type CondaInfo = {
default_prefix?: string; // eslint-disable-line camelcase
root_prefix?: string; // eslint-disable-line camelcase
conda_version?: string; // eslint-disable-line camelcase
conda_shlvl?: number; // eslint-disable-line camelcase
};

type CondaEnvInfo = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,39 @@ export class CondaService implements ICondaService {
@inject(IFileSystem) private fileSystem: IFileSystem,
) {}

public async getActivationScriptFromInterpreter(
interpreterPath?: string,
envName?: string,
): Promise<{ path: string | undefined; type: 'local' | 'global' } | undefined> {
const condaPath = await this.getCondaFileFromInterpreter(interpreterPath, envName);

const activatePath = (condaPath
? path.join(path.dirname(condaPath), 'activate')
: 'activate'
).fileToCommandArgumentForPythonExt(); // maybe global activate?

// try to find the activate script in the global conda root prefix.
if (this.platform.isLinux || this.platform.isMac) {
const condaInfo = await this.getCondaInfo();
Comment thread
mokeyish marked this conversation as resolved.
// eslint-disable-next-line camelcase
if (condaInfo?.root_prefix) {
const globalActivatePath = path
// eslint-disable-next-line camelcase
.join(condaInfo.root_prefix, this.platform.virtualEnvBinName, 'activate')
.fileToCommandArgumentForPythonExt();

if (activatePath === globalActivatePath || !(await this.fileSystem.fileExists(activatePath))) {
return {
path: globalActivatePath,
type: 'global',
};
}
}
}

return { path: activatePath, type: 'local' }; // return the default activate script wether it exists or not.
}

/**
* Return the path to the "conda file".
*/
Expand Down Expand Up @@ -111,7 +144,7 @@ export class CondaService implements ICondaService {
*/
@cache(60_000)
// eslint-disable-next-line class-methods-use-this
public async _getCondaInfo(): Promise<CondaInfo | undefined> {
public async getCondaInfo(): Promise<CondaInfo | undefined> {
const conda = await Conda.getConda();
return conda?.getInfo();
}
Expand Down
61 changes: 57 additions & 4 deletions src/test/common/terminals/activation.conda.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,17 @@ suite('Terminal Environment Activation conda', () => {
const interpreterPath = path.join('path', 'to', 'interpreter');
const environmentName = 'Env';
const environmentNameHasSpaces = 'Env with spaces';
const testsForActivationUsingInterpreterPath = [
const testsForActivationUsingInterpreterPath: {
testName: string;
envName: string;
condaScope?: 'global' | 'local';
condaInfo?: {
// eslint-disable-next-line camelcase
conda_shlvl?: number;
};
expectedResult: string[];
isWindows: boolean;
}[] = [
{
testName:
'Activation provides correct activation commands (windows) after 4.4.0 given interpreter path is provided, with no spaces in env name',
Expand All @@ -222,7 +232,7 @@ suite('Terminal Environment Activation conda', () => {
testName:
'Activation provides correct activation commands (non-windows) after 4.4.0 given interpreter path is provided, with no spaces in env name',
envName: environmentName,
expectedResult: ['source path/to/activate', 'conda activate Env'],
expectedResult: ['source path/to/activate Env'],
isWindows: false,
},
{
Expand All @@ -236,7 +246,7 @@ suite('Terminal Environment Activation conda', () => {
testName:
'Activation provides correct activation commands (non-windows) after 4.4.0 given interpreter path is provided, with spaces in env name',
envName: environmentNameHasSpaces,
expectedResult: ['source path/to/activate', 'conda activate "Env with spaces"'],
expectedResult: ['source path/to/activate "Env with spaces"'],
isWindows: false,
},
{
Expand All @@ -250,7 +260,37 @@ suite('Terminal Environment Activation conda', () => {
testName:
'Activation provides correct activation commands (non-windows) after 4.4.0 given interpreter path is provided, and no env name',
envName: '',
expectedResult: ['source path/to/activate', `conda activate .`],
expectedResult: ['source path/to/activate .'],
isWindows: false,
},
{
testName:
'Activation provides correct activation commands (non-windows) after 4.4.0 given interpreter path is provided, global conda, conda not sourced and with no spaces in env name',
envName: environmentName,
expectedResult: ['source path/to/activate Env'],
condaScope: 'global',
isWindows: false,
},
{
testName:
'Activation provides correct activation commands (non-windows) after 4.4.0 given interpreter path is provided, global conda, conda sourced and with no spaces in env name',
envName: environmentName,
expectedResult: ['conda activate Env'],
condaInfo: {
conda_shlvl: 1,
},
condaScope: 'global',
isWindows: false,
},
{
testName:
'Activation provides correct activation commands (non-windows) after 4.4.0 given interpreter path is provided, local conda, conda sourced and with no spaces in env name',
envName: environmentName,
expectedResult: ['source path/to/activate Env'],
condaInfo: {
conda_shlvl: 1,
},
condaScope: 'local',
isWindows: false,
},
];
Expand All @@ -272,13 +312,26 @@ suite('Terminal Environment Activation conda', () => {
condaService
.setup((c) => c.getCondaFileFromInterpreter(TypeMoq.It.isAny(), TypeMoq.It.isAny()))
.returns(() => Promise.resolve(interpreterPath));
condaService
.setup((c) => c.getActivationScriptFromInterpreter(TypeMoq.It.isAny(), TypeMoq.It.isAny()))
.returns(() =>
Promise.resolve({
path: path.join(path.dirname(interpreterPath), 'activate').fileToCommandArgumentForPythonExt(),
type: testParams.condaScope ?? 'local',
}),
);

condaService.setup((c) => c.getCondaInfo()).returns(() => Promise.resolve(testParams.condaInfo));

// getActivationScriptFromInterpreter

const provider = new CondaActivationCommandProvider(
condaService.object,
platformService.object,
configService.object,
componentAdapter.object,
);

const activationCommands = await provider.getActivationCommands(undefined, TerminalShellType.bash);

expect(activationCommands).to.deep.equal(testParams.expectedResult, 'Incorrect Activation command');
Expand Down