Skip to content

Commit f09ab3f

Browse files
authored
Ensure env manager executable is set (microsoft#23845)
1 parent 226ba0a commit f09ab3f

File tree

3 files changed

+112
-40
lines changed

3 files changed

+112
-40
lines changed

src/client/pythonEnvironments/common/environmentManagers/conda.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,3 +615,7 @@ export class Conda {
615615
return true;
616616
}
617617
}
618+
619+
export function setCondaBinary(executable: string): void {
620+
Conda.setConda(executable);
621+
}

src/client/pythonEnvironments/nativeAPI.ts

Lines changed: 64 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,22 @@ import {
1313
TriggerRefreshOptions,
1414
} from './base/locator';
1515
import { PythonEnvCollectionChangedEvent } from './base/watcher';
16-
import { isNativeEnvInfo, NativeEnvInfo, NativePythonFinder } from './base/locators/common/nativePythonFinder';
16+
import {
17+
isNativeEnvInfo,
18+
NativeEnvInfo,
19+
NativeEnvManagerInfo,
20+
NativePythonFinder,
21+
} from './base/locators/common/nativePythonFinder';
1722
import { createDeferred, Deferred } from '../common/utils/async';
1823
import { Architecture } from '../common/utils/platform';
1924
import { parseVersion } from './base/info/pythonVersion';
2025
import { cache } from '../common/utils/decorators';
21-
import { traceError, traceLog } from '../logging';
26+
import { traceError, traceLog, traceWarn } from '../logging';
2227
import { StopWatch } from '../common/utils/stopWatch';
2328
import { FileChangeType } from '../common/platform/fileSystemWatcher';
2429
import { categoryToKind } from './base/locators/common/nativePythonUtils';
30+
import { setCondaBinary } from './common/environmentManagers/conda';
31+
import { setPyEnvBinary } from './common/environmentManagers/pyenv';
2532

2633
function makeExecutablePath(prefix?: string): string {
2734
if (!prefix) {
@@ -232,44 +239,10 @@ class NativePythonEnvironments implements IDiscoveryAPI, Disposable {
232239
setImmediate(async () => {
233240
try {
234241
for await (const native of this.finder.refresh()) {
235-
if (!isNativeEnvInfo(native) || !validEnv(native)) {
236-
// eslint-disable-next-line no-continue
237-
continue;
238-
}
239-
try {
240-
const envPath = native.executable ?? native.prefix;
241-
const version = native.version ? parseVersion(native.version) : undefined;
242-
243-
if (categoryToKind(native.kind) === PythonEnvKind.Conda && !native.executable) {
244-
// This is a conda env without python, no point trying to resolve this.
245-
// There is nothing to resolve
246-
this.addEnv(native);
247-
} else if (
248-
envPath &&
249-
(!version || version.major < 0 || version.minor < 0 || version.micro < 0)
250-
) {
251-
// We have a path, but no version info, try to resolve the environment.
252-
this.finder
253-
.resolve(envPath)
254-
.then((env) => {
255-
if (env) {
256-
this.addEnv(env);
257-
}
258-
})
259-
.ignoreErrors();
260-
} else if (
261-
envPath &&
262-
version &&
263-
version.major >= 0 &&
264-
version.minor >= 0 &&
265-
version.micro >= 0
266-
) {
267-
this.addEnv(native);
268-
} else {
269-
traceError(`Failed to process environment: ${JSON.stringify(native)}`);
270-
}
271-
} catch (err) {
272-
traceError(`Failed to process environment: ${err}`);
242+
if (isNativeEnvInfo(native)) {
243+
this.processEnv(native);
244+
} else {
245+
this.processEnvManager(native);
273246
}
274247
}
275248
this._refreshPromise?.resolve();
@@ -286,6 +259,57 @@ class NativePythonEnvironments implements IDiscoveryAPI, Disposable {
286259
return this._refreshPromise?.promise;
287260
}
288261

262+
private processEnv(native: NativeEnvInfo): void {
263+
if (!validEnv(native)) {
264+
return;
265+
}
266+
267+
try {
268+
const envPath = native.executable ?? native.prefix;
269+
const version = native.version ? parseVersion(native.version) : undefined;
270+
271+
if (categoryToKind(native.kind) === PythonEnvKind.Conda && !native.executable) {
272+
// This is a conda env without python, no point trying to resolve this.
273+
// There is nothing to resolve
274+
this.addEnv(native);
275+
} else if (envPath && (!version || version.major < 0 || version.minor < 0 || version.micro < 0)) {
276+
// We have a path, but no version info, try to resolve the environment.
277+
this.finder
278+
.resolve(envPath)
279+
.then((env) => {
280+
if (env) {
281+
this.addEnv(env);
282+
}
283+
})
284+
.ignoreErrors();
285+
} else if (envPath && version && version.major >= 0 && version.minor >= 0 && version.micro >= 0) {
286+
this.addEnv(native);
287+
} else {
288+
traceError(`Failed to process environment: ${JSON.stringify(native)}`);
289+
}
290+
} catch (err) {
291+
traceError(`Failed to process environment: ${err}`);
292+
}
293+
}
294+
295+
// eslint-disable-next-line class-methods-use-this
296+
private processEnvManager(native: NativeEnvManagerInfo) {
297+
const tool = native.tool.toLowerCase();
298+
switch (tool) {
299+
case 'conda':
300+
traceLog(`Conda environment manager found at: ${native.executable}`);
301+
setCondaBinary(native.executable);
302+
break;
303+
case 'pyenv':
304+
traceLog(`Pyenv environment manager found at: ${native.executable}`);
305+
setPyEnvBinary(native.executable);
306+
break;
307+
default:
308+
traceWarn(`Unknown environment manager: ${native.tool}`);
309+
break;
310+
}
311+
}
312+
289313
getEnvs(_query?: PythonLocatorQuery): PythonEnvInfo[] {
290314
return this._envs;
291315
}

src/test/pythonEnvironments/nativeAPI.unit.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,21 @@ import * as nativeAPI from '../../client/pythonEnvironments/nativeAPI';
1010
import { IDiscoveryAPI } from '../../client/pythonEnvironments/base/locator';
1111
import {
1212
NativeEnvInfo,
13+
NativeEnvManagerInfo,
1314
NativePythonFinder,
1415
} from '../../client/pythonEnvironments/base/locators/common/nativePythonFinder';
1516
import { Architecture } from '../../client/common/utils/platform';
1617
import { PythonEnvInfo, PythonEnvKind, PythonEnvType } from '../../client/pythonEnvironments/base/info';
1718
import { isWindows } from '../../client/common/platform/platformService';
1819
import { NativePythonEnvironmentKind } from '../../client/pythonEnvironments/base/locators/common/nativePythonUtils';
20+
import * as condaApi from '../../client/pythonEnvironments/common/environmentManagers/conda';
21+
import * as pyenvApi from '../../client/pythonEnvironments/common/environmentManagers/pyenv';
1922

2023
suite('Native Python API', () => {
2124
let api: IDiscoveryAPI;
2225
let mockFinder: typemoq.IMock<NativePythonFinder>;
26+
let setCondaBinaryStub: sinon.SinonStub;
27+
let setPyEnvBinaryStub: sinon.SinonStub;
2328

2429
const basicEnv: NativeEnvInfo = {
2530
displayName: 'Basic Python',
@@ -128,6 +133,9 @@ suite('Native Python API', () => {
128133
setup(() => {
129134
mockFinder = typemoq.Mock.ofType<NativePythonFinder>();
130135
api = nativeAPI.createNativeEnvironmentsApi(mockFinder.object);
136+
137+
setCondaBinaryStub = sinon.stub(condaApi, 'setCondaBinary');
138+
setPyEnvBinaryStub = sinon.stub(pyenvApi, 'setPyEnvBinary');
131139
});
132140

133141
teardown(() => {
@@ -248,4 +256,40 @@ suite('Native Python API', () => {
248256
await api.triggerRefresh();
249257
assert.isUndefined(api.getRefreshPromise());
250258
});
259+
260+
test('Setting conda binary', async () => {
261+
const condaMgr: NativeEnvManagerInfo = {
262+
tool: 'Conda',
263+
executable: '/usr/bin/conda',
264+
};
265+
mockFinder
266+
.setup((f) => f.refresh())
267+
.returns(() => {
268+
async function* generator() {
269+
yield* [condaMgr];
270+
}
271+
return generator();
272+
})
273+
.verifiable(typemoq.Times.once());
274+
await api.triggerRefresh();
275+
assert.isTrue(setCondaBinaryStub.calledOnceWith(condaMgr.executable));
276+
});
277+
278+
test('Setting pyenv binary', async () => {
279+
const pyenvMgr: NativeEnvManagerInfo = {
280+
tool: 'PyEnv',
281+
executable: '/usr/bin/pyenv',
282+
};
283+
mockFinder
284+
.setup((f) => f.refresh())
285+
.returns(() => {
286+
async function* generator() {
287+
yield* [pyenvMgr];
288+
}
289+
return generator();
290+
})
291+
.verifiable(typemoq.Times.once());
292+
await api.triggerRefresh();
293+
assert.isTrue(setPyEnvBinaryStub.calledOnceWith(pyenvMgr.executable));
294+
});
251295
});

0 commit comments

Comments
 (0)