Skip to content

Commit f536e12

Browse files
authored
Use interpreter info stored in kernelspec.json (#5500)
1 parent ca61a8c commit f536e12

File tree

2 files changed

+62
-31
lines changed

2 files changed

+62
-31
lines changed

news/2 Fixes/5495.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Use interpreter information stored in kernelspec.json file when starting kernels.

src/client/datascience/kernel-launcher/localKernelFinder.ts

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { IWorkspaceService } from '../../common/application/types';
1111
import { PYTHON_LANGUAGE } from '../../common/constants';
1212
import { traceDecorators, traceError, traceInfo, traceInfoIf } from '../../common/logger';
1313
import { IFileSystem, IPlatformService } from '../../common/platform/types';
14-
import { IExtensionContext, IPathUtils, Resource } from '../../common/types';
14+
import { IExtensionContext, IPathUtils, ReadWrite, Resource } from '../../common/types';
1515
import { IEnvironmentVariablesProvider } from '../../common/variables/types';
1616
import { IInterpreterService } from '../../interpreter/contracts';
1717
import { PythonEnvironment } from '../../pythonEnvironments/info';
@@ -189,35 +189,59 @@ export class LocalKernelFinder implements ILocalKernelFinder {
189189
let filteredInterpreters = [...interpreters];
190190

191191
// Then go through all of the kernels and generate their metadata
192-
const kernelMetadata = kernelSpecs.map((k) => {
193-
// Find the interpreter that matches. If we find one, we want to use
194-
// this to start the kernel.
195-
const matchingInterpreters = this.findMatchingInterpreters(k, interpreters);
196-
if (matchingInterpreters && matchingInterpreters.length) {
197-
const result: PythonKernelConnectionMetadata = {
198-
kind: 'startUsingPythonInterpreter',
199-
kernelSpec: k,
200-
interpreter: matchingInterpreters[0],
201-
id: getKernelId(k, matchingInterpreters[0])
202-
};
203-
204-
// If interpreters were found, remove them from the interpreter list we'll eventually
205-
// return as interpreter only items
206-
filteredInterpreters = filteredInterpreters.filter((i) => !matchingInterpreters.includes(i));
192+
const kernelMetadata = await Promise.all(
193+
kernelSpecs.map(async (k) => {
194+
// Find the interpreter that matches. If we find one, we want to use
195+
// this to start the kernel.
196+
const matchingInterpreters = this.findMatchingInterpreters(k, interpreters);
197+
if (matchingInterpreters && matchingInterpreters.length) {
198+
const result: PythonKernelConnectionMetadata = {
199+
kind: 'startUsingPythonInterpreter',
200+
kernelSpec: k,
201+
interpreter: matchingInterpreters[0],
202+
id: getKernelId(k, matchingInterpreters[0])
203+
};
207204

208-
// Return our metadata that uses an interpreter to start
209-
return result;
210-
} else {
211-
// No interpreter found. If python, use the active interpreter anyway
212-
const result: KernelSpecConnectionMetadata = {
213-
kind: 'startUsingKernelSpec',
214-
kernelSpec: k,
215-
interpreter: k.language === PYTHON_LANGUAGE ? activeInterpreter : undefined,
216-
id: getKernelId(k, activeInterpreter)
217-
};
218-
return result;
219-
}
220-
});
205+
// If interpreters were found, remove them from the interpreter list we'll eventually
206+
// return as interpreter only items
207+
filteredInterpreters = filteredInterpreters.filter((i) => !matchingInterpreters.includes(i));
208+
209+
// Return our metadata that uses an interpreter to start
210+
return result;
211+
} else {
212+
let interpreter = k.language === PYTHON_LANGUAGE ? activeInterpreter : undefined;
213+
// If the interpreter information is stored in kernelspec.json then use that to determine the interpreter.
214+
// This can happen under the following circumstances:
215+
// 1. Open workspace folder XYZ, and create a virtual environment named venvA
216+
// 2. Now assume we don't have raw kernels, and a kernel gets registered for venvA in kernelspecs folder.
217+
// 3. The kernel spec will contain metadata pointing to venvA.
218+
// 4. Now open a different folder (e.g. a sub directory of XYZ or a completely different folder).
219+
// 5. Now venvA will not be listed as an interpreter as Python will not discover this.
220+
// 6. However the kernel we registered against venvA will be in global kernels folder
221+
// In such an instance the interpreter information is stored in the kernelspec.json file.
222+
if (
223+
k.language === PYTHON_LANGUAGE &&
224+
this.extensionChecker.isPythonExtensionInstalled &&
225+
k.metadata?.interpreter?.path &&
226+
k.metadata?.interpreter?.path !== activeInterpreter?.path
227+
) {
228+
interpreter = await this.interpreterService
229+
.getInterpreterDetails(k.metadata?.interpreter?.path)
230+
.catch((ex) => {
231+
traceError(`Failed to get interpreter details for Kernel Spec ${k.specFile}`, ex);
232+
return interpreter;
233+
});
234+
}
235+
const result: KernelSpecConnectionMetadata = {
236+
kind: 'startUsingKernelSpec',
237+
kernelSpec: k,
238+
interpreter,
239+
id: getKernelId(k, activeInterpreter)
240+
};
241+
return result;
242+
}
243+
})
244+
);
221245

222246
// Combine the two into our list
223247
const results = [
@@ -424,7 +448,7 @@ export class LocalKernelFinder implements ILocalKernelFinder {
424448
interpreter?: PythonEnvironment,
425449
cancelToken?: CancellationToken
426450
): Promise<IJupyterKernelSpec | undefined> {
427-
let kernelJson;
451+
let kernelJson: ReadWrite<IJupyterKernelSpec>;
428452
try {
429453
traceInfo(`Loading kernelspec from ${specPath} for ${interpreter?.path}`);
430454
kernelJson = JSON.parse(await this.fs.readLocalFile(specPath));
@@ -447,7 +471,13 @@ export class LocalKernelFinder implements ILocalKernelFinder {
447471
? interpreter?.displayName || kernelJson.display_name
448472
: kernelJson.display_name;
449473

450-
const kernelSpec: IJupyterKernelSpec = new JupyterKernelSpec(kernelJson, specPath, interpreter?.path);
474+
const kernelSpec: IJupyterKernelSpec = new JupyterKernelSpec(
475+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
476+
kernelJson as any,
477+
specPath,
478+
// Interpreter information may be saved in the metadata (if this is a kernel spec created/registered by us).
479+
interpreter?.path || kernelJson?.metadata?.interpreter?.path
480+
);
451481

452482
// Some registered kernel specs do not have a name, in this case use the last part of the path
453483
kernelSpec.name = kernelJson?.name || path.basename(path.dirname(specPath));

0 commit comments

Comments
 (0)