Skip to content

Commit da5f1bb

Browse files
Fixes to JUPYTER_PATH (#12960)
1 parent 374198b commit da5f1bb

File tree

2 files changed

+101
-34
lines changed

2 files changed

+101
-34
lines changed

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

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -234,27 +234,23 @@ export class KernelFinder implements IKernelFinder {
234234
return uniqueInterpreterPrefixPaths.map((prefixPath) => path.join(prefixPath, baseKernelPath));
235235
}
236236

237-
private async getDiskPaths(): Promise<string[]> {
238-
let paths: string[] = [];
237+
// Find any paths associated with the JUPYTER_PATH env var. Can be a list of dirs.
238+
// We need to look at the 'kernels' sub-directory and these paths are supposed to come first in the searching
239+
// https://jupyter.readthedocs.io/en/latest/projects/jupyter-directories.html#envvar-JUPYTER_PATH
240+
private async getJupyterPathPaths(): Promise<string[]> {
241+
const paths: string[] = [];
239242
const vars = await this.envVarsProvider.getEnvironmentVariables();
240-
const jupyterPathVar = vars.JUPYTER_PATH || '';
241-
242-
if (this.platformService.isWindows) {
243-
const activeInterpreter = await this.interpreterService.getActiveInterpreter();
244-
if (activeInterpreter) {
245-
const winPath = await getRealPath(
246-
this.file,
247-
this.exeFactory,
248-
activeInterpreter.path,
249-
path.join(this.pathUtils.home, winJupyterPath)
250-
);
251-
if (winPath) {
252-
paths = [winPath];
253-
}
254-
255-
if (jupyterPathVar !== '') {
256-
const jupyterPaths = jupyterPathVar.split(path.delimiter);
257-
jupyterPaths.forEach(async (jupyterPath) => {
243+
const jupyterPathVars = vars.JUPYTER_PATH
244+
? vars.JUPYTER_PATH.split(path.delimiter).map((jupyterPath) => {
245+
return path.join(jupyterPath, 'kernels');
246+
})
247+
: [];
248+
249+
if (jupyterPathVars.length > 0) {
250+
if (this.platformService.isWindows) {
251+
const activeInterpreter = await this.interpreterService.getActiveInterpreter();
252+
if (activeInterpreter) {
253+
jupyterPathVars.forEach(async (jupyterPath) => {
258254
const jupyterWinPath = await getRealPath(
259255
this.file,
260256
this.exeFactory,
@@ -266,13 +262,36 @@ export class KernelFinder implements IKernelFinder {
266262
paths.push(jupyterWinPath);
267263
}
268264
});
265+
} else {
266+
paths.push(...jupyterPathVars);
269267
}
270268
} else {
271-
paths = [path.join(this.pathUtils.home, winJupyterPath)];
272-
if (jupyterPathVar !== '') {
273-
const jupyterPaths = jupyterPathVar.split(path.delimiter);
274-
paths.push(...jupyterPaths);
269+
// Unix based
270+
paths.push(...jupyterPathVars);
271+
}
272+
}
273+
274+
return paths;
275+
}
276+
277+
private async getDiskPaths(): Promise<string[]> {
278+
// Paths specified in JUPYTER_PATH are supposed to come first in searching
279+
const paths: string[] = await this.getJupyterPathPaths();
280+
281+
if (this.platformService.isWindows) {
282+
const activeInterpreter = await this.interpreterService.getActiveInterpreter();
283+
if (activeInterpreter) {
284+
const winPath = await getRealPath(
285+
this.file,
286+
this.exeFactory,
287+
activeInterpreter.path,
288+
path.join(this.pathUtils.home, winJupyterPath)
289+
);
290+
if (winPath) {
291+
paths.push(winPath);
275292
}
293+
} else {
294+
paths.push(path.join(this.pathUtils.home, winJupyterPath));
276295
}
277296

278297
if (process.env.ALLUSERSPROFILE) {
@@ -282,16 +301,11 @@ export class KernelFinder implements IKernelFinder {
282301
// Unix based
283302
const secondPart = this.platformService.isMac ? macJupyterPath : linuxJupyterPath;
284303

285-
paths = [
304+
paths.push(
286305
path.join('usr', 'share', 'jupyter', 'kernels'),
287306
path.join('usr', 'local', 'share', 'jupyter', 'kernels'),
288307
path.join(this.pathUtils.home, secondPart)
289-
];
290-
291-
if (jupyterPathVar !== '') {
292-
const jupyterPaths = jupyterPathVar.split(path.delimiter);
293-
paths.push(...jupyterPaths);
294-
}
308+
);
295309
}
296310

297311
return paths;

src/test/datascience/kernelFinder.unit.test.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ suite('Kernel Finder', () => {
5151
specFile: path.join('1', 'share', 'jupyter', 'kernels', kernelName, 'kernel.json')
5252
};
5353
// Change this to your actual JUPYTER_PATH value and see it appearing on the paths in the kernelFinder
54-
const JupyterPathEnvVar = '';
54+
let JupyterPathEnvVar = '';
5555

5656
function setupFileSystem() {
5757
fileSystem
@@ -103,8 +103,12 @@ suite('Kernel Finder', () => {
103103
let interpreter0Kernel: IJupyterKernelSpec;
104104
let interpreter1Kernel: IJupyterKernelSpec;
105105
let globalKernel: IJupyterKernelSpec;
106+
let jupyterPathKernelA: IJupyterKernelSpec;
107+
let jupyterPathKernelB: IJupyterKernelSpec;
106108
let loadError = false;
107109
setup(() => {
110+
JupyterPathEnvVar = `Users/testuser/jupyterPathDirA${path.delimiter}Users/testuser/jupyterPathDirB`;
111+
108112
activeInterpreter = {
109113
path: context.object.globalStoragePath,
110114
displayName: 'activeInterpreter',
@@ -191,6 +195,26 @@ suite('Kernel Finder', () => {
191195
argv: ['<python path>', '-m', 'ipykernel_launcher', '-f', '{connection_file}']
192196
};
193197

198+
jupyterPathKernelA = {
199+
name: 'jupyterPathKernelA',
200+
language: 'python',
201+
path: '<python path>',
202+
display_name: 'Python 3',
203+
metadata: {},
204+
env: {},
205+
argv: ['<python path>', '-m', 'ipykernel_launcher', '-f', '{connection_file}']
206+
};
207+
208+
jupyterPathKernelB = {
209+
name: 'jupyterPathKernelB',
210+
language: 'python',
211+
path: '<python path>',
212+
display_name: 'Python 3',
213+
metadata: {},
214+
env: {},
215+
argv: ['<python path>', '-m', 'ipykernel_launcher', '-f', '{connection_file}']
216+
};
217+
194218
platformService.reset();
195219
platformService.setup((ps) => ps.isWindows).returns(() => false);
196220
platformService.setup((ps) => ps.isMac).returns(() => true);
@@ -229,6 +253,7 @@ suite('Kernel Finder', () => {
229253
.setup((fs) => fs.search(typemoq.It.isAnyString(), interpreter1Path, typemoq.It.isAny()))
230254
.returns(() => Promise.resolve([path.join(interpreter1Kernel.name, 'kernel.json')]));
231255

256+
// Global path setup
232257
const globalPath = path.join('usr', 'share', 'jupyter', 'kernels');
233258
const globalFullPath = path.join(globalPath, globalKernel.name, 'kernel.json');
234259
fileSystem
@@ -245,6 +270,26 @@ suite('Kernel Finder', () => {
245270
.setup((fs) => fs.search(typemoq.It.isAnyString(), globalBPath, typemoq.It.isAny()))
246271
.returns(() => Promise.resolve([]));
247272

273+
// Jupyter path setup
274+
const jupyterPathKernelAPath = path.join('Users', 'testuser', 'jupyterPathDirA', 'kernels');
275+
const jupyterPathKernelAFullPath = path.join(
276+
jupyterPathKernelAPath,
277+
jupyterPathKernelA.name,
278+
'kernel.json'
279+
);
280+
const jupyterPathKernelBPath = path.join('Users', 'testuser', 'jupyterPathDirB', 'kernels');
281+
const jupyterPathKernelBFullPath = path.join(
282+
jupyterPathKernelBPath,
283+
jupyterPathKernelB.name,
284+
'kernel.json'
285+
);
286+
fileSystem
287+
.setup((fs) => fs.search(typemoq.It.isAnyString(), jupyterPathKernelAPath, typemoq.It.isAny()))
288+
.returns(() => Promise.resolve([path.join(jupyterPathKernelA.name, 'kernel.json')]));
289+
fileSystem
290+
.setup((fs) => fs.search(typemoq.It.isAnyString(), jupyterPathKernelBPath, typemoq.It.isAny()))
291+
.returns(() => Promise.resolve([path.join(jupyterPathKernelB.name, 'kernel.json')]));
292+
248293
// Set the file system to return our kernelspec json
249294
fileSystem
250295
.setup((fs) => fs.readFile(typemoq.It.isAnyString()))
@@ -264,6 +309,10 @@ suite('Kernel Finder', () => {
264309
return Promise.resolve(JSON.stringify(interpreter1Kernel));
265310
case globalFullPath:
266311
return Promise.resolve(JSON.stringify(globalKernel));
312+
case jupyterPathKernelAFullPath:
313+
return Promise.resolve(JSON.stringify(jupyterPathKernelA));
314+
case jupyterPathKernelBFullPath:
315+
return Promise.resolve(JSON.stringify(jupyterPathKernelB));
267316
default:
268317
return Promise.resolve('');
269318
}
@@ -292,7 +341,9 @@ suite('Kernel Finder', () => {
292341
expect(specs[1]).to.deep.include(activeKernelB);
293342
expect(specs[2]).to.deep.include(interpreter0Kernel);
294343
expect(specs[3]).to.deep.include(interpreter1Kernel);
295-
expect(specs[4]).to.deep.include(globalKernel);
344+
expect(specs[4]).to.deep.include(jupyterPathKernelA);
345+
expect(specs[5]).to.deep.include(jupyterPathKernelB);
346+
expect(specs[6]).to.deep.include(globalKernel);
296347
fileSystem.reset();
297348
});
298349

@@ -303,7 +354,9 @@ suite('Kernel Finder', () => {
303354
expect(specs[0]).to.deep.include(activeKernelB);
304355
expect(specs[1]).to.deep.include(interpreter0Kernel);
305356
expect(specs[2]).to.deep.include(interpreter1Kernel);
306-
expect(specs[3]).to.deep.include(globalKernel);
357+
expect(specs[3]).to.deep.include(jupyterPathKernelA);
358+
expect(specs[4]).to.deep.include(jupyterPathKernelB);
359+
expect(specs[5]).to.deep.include(globalKernel);
307360
fileSystem.reset();
308361
});
309362
});

0 commit comments

Comments
 (0)