Skip to content

Commit

Permalink
Restrict conda binary to be from PATH or Settings (#24709)
Browse files Browse the repository at this point in the history
Closes #24627

---------

Co-authored-by: Eleanor Boyd <eleanorboyd@microsoft.com>
  • Loading branch information
karthiknadig and eleanorjboyd authored Jan 10, 2025
1 parent 9bc9f68 commit 74a5cad
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 6 deletions.
8 changes: 8 additions & 0 deletions src/client/common/utils/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,11 @@ export function getUserHomeDir(): string | undefined {
export function isWindows(): boolean {
return getOSType() === OSType.Windows;
}

export function getPathEnvVariable(): string[] {
const value = getEnvironmentVariable('PATH') || getEnvironmentVariable('Path');
if (value) {
return value.split(isWindows() ? ';' : ':');
}
return [];
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { OUTPUT_MARKER_SCRIPT } from '../../../common/process/internal/scripts';
import { splitLines } from '../../../common/stringUtils';
import { SpawnOptions } from '../../../common/process/types';
import { sleep } from '../../../common/utils/async';
import { getConfiguration } from '../../../common/vscodeApis/workspaceApis';

export const AnacondaCompanyName = 'Anaconda, Inc.';
export const CONDAPATH_SETTING_KEY = 'condaPath';
Expand Down Expand Up @@ -633,3 +634,8 @@ export async function getCondaEnvDirs(): Promise<string[] | undefined> {
const conda = await Conda.getConda();
return conda?.getEnvDirs();
}

export function getCondaPathSetting(): string | undefined {
const config = getConfiguration('python');
return config.get<string>(CONDAPATH_SETTING_KEY, '');
}
37 changes: 33 additions & 4 deletions src/client/pythonEnvironments/nativeAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ import {
NativePythonFinder,
} from './base/locators/common/nativePythonFinder';
import { createDeferred, Deferred } from '../common/utils/async';
import { Architecture, getUserHomeDir } from '../common/utils/platform';
import { Architecture, getPathEnvVariable, getUserHomeDir } from '../common/utils/platform';
import { parseVersion } from './base/info/pythonVersion';
import { cache } from '../common/utils/decorators';
import { traceError, traceInfo, traceLog, traceWarn } from '../logging';
import { StopWatch } from '../common/utils/stopWatch';
import { FileChangeType } from '../common/platform/fileSystemWatcher';
import { categoryToKind, NativePythonEnvironmentKind } from './base/locators/common/nativePythonUtils';
import { getCondaEnvDirs, setCondaBinary } from './common/environmentManagers/conda';
import { getCondaEnvDirs, getCondaPathSetting, setCondaBinary } from './common/environmentManagers/conda';
import { setPyEnvBinary } from './common/environmentManagers/pyenv';
import {
createPythonWatcher,
Expand Down Expand Up @@ -166,6 +166,12 @@ function isSubDir(pathToCheck: string | undefined, parents: string[]): boolean {
});
}

function foundOnPath(fsPath: string): boolean {
const paths = getPathEnvVariable().map((p) => path.normalize(p).toLowerCase());
const normalized = path.normalize(fsPath).toLowerCase();
return paths.some((p) => normalized.includes(p));
}

function getName(nativeEnv: NativeEnvInfo, kind: PythonEnvKind, condaEnvDirs: string[]): string {
if (nativeEnv.name) {
return nativeEnv.name;
Expand Down Expand Up @@ -387,13 +393,36 @@ class NativePythonEnvironments implements IDiscoveryAPI, Disposable {
return undefined;
}

private condaPathAlreadySet: string | undefined;

// eslint-disable-next-line class-methods-use-this
private processEnvManager(native: NativeEnvManagerInfo) {
const tool = native.tool.toLowerCase();
switch (tool) {
case 'conda':
traceLog(`Conda environment manager found at: ${native.executable}`);
setCondaBinary(native.executable);
{
traceLog(`Conda environment manager found at: ${native.executable}`);
const settingPath = getCondaPathSetting();
if (!this.condaPathAlreadySet) {
if (settingPath === '' || settingPath === undefined) {
if (foundOnPath(native.executable)) {
setCondaBinary(native.executable);
this.condaPathAlreadySet = native.executable;
traceInfo(`Using conda: ${native.executable}`);
} else {
traceInfo(`Conda not found on PATH, skipping: ${native.executable}`);
traceInfo(
'You can set the path to conda using the setting: `python.condaPath` if you want to use a different conda binary',
);
}
} else {
traceInfo(`Using conda from setting: ${settingPath}`);
this.condaPathAlreadySet = settingPath;
}
} else {
traceInfo(`Conda set to: ${this.condaPathAlreadySet}`);
}
}
break;
case 'pyenv':
traceLog(`Pyenv environment manager found at: ${native.executable}`);
Expand Down
11 changes: 9 additions & 2 deletions src/test/pythonEnvironments/nativeAPI.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
NativeEnvManagerInfo,
NativePythonFinder,
} from '../../client/pythonEnvironments/base/locators/common/nativePythonFinder';
import { Architecture, isWindows } from '../../client/common/utils/platform';
import { Architecture, getPathEnvVariable, isWindows } from '../../client/common/utils/platform';
import { PythonEnvInfo, PythonEnvKind, PythonEnvType } from '../../client/pythonEnvironments/base/info';
import { NativePythonEnvironmentKind } from '../../client/pythonEnvironments/base/locators/common/nativePythonUtils';
import * as condaApi from '../../client/pythonEnvironments/common/environmentManagers/conda';
Expand All @@ -25,6 +25,8 @@ suite('Native Python API', () => {
let api: IDiscoveryAPI;
let mockFinder: typemoq.IMock<NativePythonFinder>;
let setCondaBinaryStub: sinon.SinonStub;
let getCondaPathSettingStub: sinon.SinonStub;
let getCondaEnvDirsStub: sinon.SinonStub;
let setPyEnvBinaryStub: sinon.SinonStub;
let createPythonWatcherStub: sinon.SinonStub;
let mockWatcher: typemoq.IMock<pw.PythonWatcher>;
Expand Down Expand Up @@ -136,6 +138,8 @@ suite('Native Python API', () => {

setup(() => {
setCondaBinaryStub = sinon.stub(condaApi, 'setCondaBinary');
getCondaEnvDirsStub = sinon.stub(condaApi, 'getCondaEnvDirs');
getCondaPathSettingStub = sinon.stub(condaApi, 'getCondaPathSetting');
setPyEnvBinaryStub = sinon.stub(pyenvApi, 'setPyEnvBinary');
getWorkspaceFoldersStub = sinon.stub(ws, 'getWorkspaceFolders');
getWorkspaceFoldersStub.returns([]);
Expand Down Expand Up @@ -294,9 +298,12 @@ suite('Native Python API', () => {
});

test('Setting conda binary', async () => {
getCondaPathSettingStub.returns(undefined);
getCondaEnvDirsStub.resolves(undefined);
const condaFakeDir = getPathEnvVariable()[0];
const condaMgr: NativeEnvManagerInfo = {
tool: 'Conda',
executable: '/usr/bin/conda',
executable: path.join(condaFakeDir, 'conda'),
};
mockFinder
.setup((f) => f.refresh())
Expand Down

0 comments on commit 74a5cad

Please sign in to comment.