Skip to content

Commit f943f26

Browse files
authored
Scrape output to get the details of the registered kernel (microsoft#9448)
* Scrape output to get the details of the registered kernel * Fixes for formatting * Fix linter * Fix linter issues * More stuff to ignore
1 parent cb4191b commit f943f26

File tree

5 files changed

+74
-10
lines changed

5 files changed

+74
-10
lines changed

news/2 Fixes/9444.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Scrape output to get the details of the registered kernel.

src/client/common/process/pythonExecutionFactory.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,12 @@ export class PythonExecutionFactory implements IPythonExecutionFactory {
7474
const disposables = this.serviceContainer.get<IDisposableRegistry>(IDisposableRegistry);
7575
const interpreterService = this.serviceContainer.get<IInterpreterService>(IInterpreterService);
7676
const logger = this.serviceContainer.get<IProcessLogger>(IProcessLogger);
77-
const activatedProcPromise = this.createActivatedEnvironment({ allowEnvironmentFetchExceptions: true, pythonPath: pythonPath, resource: options.resource });
77+
const activatedProcPromise = this.createActivatedEnvironment({
78+
allowEnvironmentFetchExceptions: true,
79+
pythonPath: pythonPath,
80+
resource: options.resource,
81+
bypassCondaExecution: true
82+
});
7883

7984
// No daemon support in Python 2.7.
8085
const interpreter = await interpreterService.getInterpreterDetails(pythonPath);
@@ -123,9 +128,12 @@ export class PythonExecutionFactory implements IPythonExecutionFactory {
123128
processService.on('exec', processLogger.logProcess.bind(processLogger));
124129
this.serviceContainer.get<IDisposableRegistry>(IDisposableRegistry).push(processService);
125130

126-
const condaExecutionService = await this.createCondaExecutionService(pythonPath, processService);
127-
if (condaExecutionService) {
128-
return condaExecutionService;
131+
// Allow parts of the code to ignore conda run.
132+
if (!options.bypassCondaExecution) {
133+
const condaExecutionService = await this.createCondaExecutionService(pythonPath, processService);
134+
if (condaExecutionService) {
135+
return condaExecutionService;
136+
}
129137
}
130138

131139
return new PythonExecutionService(this.serviceContainer, processService, pythonPath);

src/client/common/process/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ export type ExecutionFactoryCreateWithEnvironmentOptions = {
101101
pythonPath?: string;
102102
interpreter?: PythonInterpreter;
103103
allowEnvironmentFetchExceptions?: boolean;
104+
/**
105+
* Ignore running `conda run` when running code.
106+
* It is known to fail in certain scenarios. Where necessary we might want to bypass this.
107+
*
108+
* @type {boolean}
109+
*/
110+
bypassCondaExecution?: boolean;
104111
};
105112
export interface IPythonExecutionFactory {
106113
create(options: ExecutionFactoryCreationOptions): Promise<IPythonExecutionService>;

src/client/datascience/jupyter/jupyterCommand.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ export class InterpreterJupyterKernelSpecCommand extends InterpreterJupyterComma
209209
}
210210
try {
211211
// Try getting kernels using python script, if that fails (even if there's output in stderr) rethrow original exception.
212-
const activatedEnv = await this.pythonExecutionFactory.createActivatedEnvironment({ interpreter });
212+
const activatedEnv = await this.pythonExecutionFactory.createActivatedEnvironment({ interpreter, bypassCondaExecution: true });
213213
return activatedEnv.exec([path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'datascience', 'getJupyterKernels.py')], { ...options, throwOnStdErr: true });
214214
} catch (innerEx) {
215215
traceError('Failed to get a list of the kernelspec using python script', innerEx);

src/client/datascience/jupyter/kernels/kernelService.ts

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { JupyterKernelSpec } from './jupyterKernelSpec';
2828
import { LiveKernelModel } from './types';
2929

3030
// tslint:disable-next-line: no-var-requires no-require-imports
31-
const NamedRegexp = require('named-js-regexp');
31+
const NamedRegexp = require('named-js-regexp') as typeof import('named-js-regexp');
3232

3333
/**
3434
* Helper to ensure we can differentiate between two types in union types, keeping typing information.
@@ -153,10 +153,10 @@ export class KernelService {
153153
// Check if kernel is `Python2` or `Python3` or a similar generic kernel.
154154
const regEx = NamedRegexp('python\\s*(?<version>(\\d+))', 'g');
155155
const match = regEx.exec(kernelSpec.name.toLowerCase());
156-
if (match) {
156+
if (match && match.groups()) {
157157
// 3. Look for interpreter with same major version
158158

159-
const majorVersion = parseInt(match.groups().version, 10) || 0;
159+
const majorVersion = parseInt(match.groups()!.version, 10) || 0;
160160
// If the major versions match, that's sufficient.
161161
if (!majorVersion || (activeInterpreter?.version && activeInterpreter.version.major === majorVersion)) {
162162
traceInfo(`Using current interpreter for kernel ${kernelSpec.name}, ${kernelSpec.display_name}`);
@@ -219,6 +219,7 @@ export class KernelService {
219219
* @returns {Promise<IJupyterKernelSpec>}
220220
* @memberof KernelService
221221
*/
222+
// tslint:disable-next-line: max-func-body-length
222223
@captureTelemetry(Telemetry.RegisterInterpreterAsKernel, undefined, true)
223224
@traceDecorators.error('Failed to register an interpreter as a kernel')
224225
public async registerKernel(interpreter: PythonInterpreter, cancelToken?: CancellationToken): Promise<IJupyterKernelSpec | undefined> {
@@ -269,6 +270,13 @@ export class KernelService {
269270
await sleep(500);
270271
kernel = await this.findMatchingKernelSpec({ display_name: interpreter.displayName, name }, undefined, cancelToken);
271272
}
273+
if (!kernel) {
274+
// Possible user doesn't have kernelspec installed.
275+
kernel = await this.getKernelSpecFromStdOut(output.stdout).catch(ex => {
276+
traceError('Failed to get kernelspec from stdout', ex);
277+
return undefined;
278+
});
279+
}
272280
if (!kernel) {
273281
const error = `Kernel not created with the name ${name}, display_name ${interpreter.displayName}. Output is ${output.stdout}`;
274282
throw new Error(error);
@@ -347,7 +355,47 @@ export class KernelService {
347355
return `${interpreter.displayName || ''}${uuid()}`.replace(/[^A-Za-z0-9]/g, '').toLowerCase();
348356
}
349357

350-
private enumerateSpecs = async (_cancelToken?: CancellationToken): Promise<JupyterKernelSpec[]> => {
358+
/**
359+
* Will scrape kernelspec info from the output when a new kernel is created.
360+
*
361+
* @private
362+
* @param {string} output
363+
* @returns {JupyterKernelSpec}
364+
* @memberof KernelService
365+
*/
366+
@traceDecorators.error('Failed to parse kernel creation stdout')
367+
private async getKernelSpecFromStdOut(output: string): Promise<JupyterKernelSpec | undefined> {
368+
if (!output) {
369+
return;
370+
}
371+
372+
// Output should be of the form
373+
// `Installed kernel <kernelname> in <path>`
374+
const regEx = NamedRegexp('Installed\\skernelspec\\s(?<name>\\w*)\\sin\\s(?<path>.*)', 'g');
375+
const match = regEx.exec(output);
376+
if (!match || !match.groups()) {
377+
return;
378+
}
379+
380+
type RegExGroup = { name: string; path: string };
381+
const groups = match.groups() as RegExGroup | undefined;
382+
383+
if (!groups || !groups.name || !groups.path) {
384+
traceError('Kernel Output not parsed', output);
385+
throw new Error('Unable to parse output to get the kernel info');
386+
}
387+
388+
const specFile = path.join(groups.path, 'kernel.json');
389+
if (!(await this.fileSystem.fileExists(specFile))) {
390+
throw new Error('KernelSpec file not found');
391+
}
392+
393+
const kernelModel = JSON.parse(await this.fileSystem.readFile(specFile));
394+
kernelModel.name = groups.name;
395+
return new JupyterKernelSpec(kernelModel as Kernel.ISpecModel, specFile);
396+
}
397+
398+
private async enumerateSpecs(_cancelToken?: CancellationToken): Promise<JupyterKernelSpec[]> {
351399
// Ignore errors if there are no kernels.
352400
const kernelSpecCommand = await this.commandFinder.findBestCommand(JupyterCommands.KernelSpecCommand).catch(noop);
353401

@@ -387,5 +435,5 @@ export class KernelService {
387435
// This is failing for some folks. In that case return nothing
388436
return [];
389437
}
390-
};
438+
}
391439
}

0 commit comments

Comments
 (0)