Skip to content
This repository was archived by the owner on Feb 14, 2024. It is now read-only.

Commit 1b14dd8

Browse files
committed
Update jupyterlite and enable file-system access
+ Use comlink
1 parent 7b7f048 commit 1b14dd8

8 files changed

+261
-152
lines changed

.gitignore

-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ python_data.js
117117
python_data.data
118118
xpython_wasm.js
119119
xpython_wasm.wasm
120-
xeus_python.worker.js
121120

122121
# Labextension
123122
share

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ The [xeus-python](https://github.com/jupyter-xeus/xeus-python) Python kernel for
1313

1414
## Requirements
1515

16-
- JupyterLite >= 0.1.0a14
16+
- JupyterLite >= 0.1.0b9
1717

1818
## Install
1919

package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"build:dockerimage": "docker build -t mydockerimage . ",
3333
"build:dockerimage_no_cache": "docker build -t mydockerimage --no-cache . ",
3434
"build:emscripten": "echo $(pwd) && docker run --rm -v $(pwd):/src -u $(id -u):$(id -g) mydockerimage copy_output.sh",
35-
"copy-files": "copyfiles -u 1 src/xpython_wasm.wasm src/xpython_wasm.worker.js src/xpython_wasm.js src/python_data.js src/python_data.data lib",
35+
"copy-files": "copyfiles -u 1 src/xpython_wasm.wasm src/xpython_wasm.js src/python_data.js src/python_data.data lib",
3636
"build:wasm": "jlpm run build:dockerimage && jlpm run build:emscripten",
3737
"build:wasm_no_cache": "jlpm run build:dockerimage_no_cache && jlpm run build:emscripten",
3838
"build": "jlpm run build:lib && jlpm run copy-files && jlpm run build:labextension:dev",
@@ -56,7 +56,9 @@
5656
"watch:labextension": "jupyter labextension watch ."
5757
},
5858
"dependencies": {
59-
"@jupyterlite/server": "^0.1.0-alpha.14"
59+
"@jupyterlite/contents": "^0.1.0-beta.9",
60+
"@jupyterlite/server": "^0.1.0-beta.9",
61+
"comlink": "^4.3.1"
6062
},
6163
"devDependencies": {
6264
"@jupyterlab/builder": "^3.4.1",

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ build_cmd = "build:prod"
1414
npm = ["jlpm"]
1515

1616
[tool.check-manifest]
17-
ignore = ["share/jupyter/labextensions/@jupyterlite/xeus-python-kernel/**", "yarn.lock", ".*", "package-lock.json", "Dockerfile", "src/xeus_python.worker.js", "src/xpython_wasm.js", "src/xpython_wasm.wasm", "src/python_data.data", "src/python_data.js", "*.sh"]
17+
ignore = ["share/jupyter/labextensions/@jupyterlite/xeus-python-kernel/**", "yarn.lock", ".*", "package-lock.json", "Dockerfile", "src/xpython_wasm.js", "src/xpython_wasm.wasm", "src/python_data.data", "src/python_data.js", "*.sh"]

src/index.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Distributed under the terms of the Modified BSD License.
44

55
import {
6+
IServiceWorkerRegistrationWrapper,
67
JupyterLiteServer,
78
JupyterLiteServerPlugin
89
} from '@jupyterlite/server';
@@ -18,8 +19,12 @@ import logo64 from '../style/logos/python-logo-64x64.png';
1819
const server_kernel: JupyterLiteServerPlugin<void> = {
1920
id: '@jupyterlite/xeus-kernel-extension:kernel',
2021
autoStart: true,
21-
requires: [IKernelSpecs],
22-
activate: (app: JupyterLiteServer, kernelspecs: IKernelSpecs) => {
22+
requires: [IKernelSpecs, IServiceWorkerRegistrationWrapper],
23+
activate: (
24+
app: JupyterLiteServer,
25+
kernelspecs: IKernelSpecs,
26+
serviceWorkerRegistrationWrapper: IServiceWorkerRegistrationWrapper
27+
) => {
2328
kernelspecs.register({
2429
spec: {
2530
name: 'xeus-python',
@@ -41,7 +46,8 @@ const server_kernel: JupyterLiteServerPlugin<void> = {
4146
},
4247
create: async (options: IKernel.IOptions): Promise<IKernel> => {
4348
return new XeusServerKernel({
44-
...options
49+
...options,
50+
mountDrive: serviceWorkerRegistrationWrapper.enabled
4551
});
4652
}
4753
});

src/worker.ts

+96-55
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
// Copyright (c) JupyterLite Contributors
33
// Distributed under the terms of the Modified BSD License.
44

5+
import { expose } from 'comlink';
6+
7+
import { DriveFS } from '@jupyterlite/contents';
8+
59
declare function createXeusModule(options: any): any;
610

711
globalThis.Module = {};
@@ -17,27 +21,7 @@ globalThis.Module = {};
1721
globalThis.toplevel_promise = null;
1822
globalThis.toplevel_promise_py_proxy = null;
1923

20-
// We alias self to ctx and give it our newly created type
21-
const ctx: Worker = self as any;
22-
let raw_xkernel: any;
23-
let raw_xserver: any;
24-
25-
async function waitRunDependency() {
26-
const promise = new Promise((r: any) => {
27-
globalThis.Module.monitorRunDependencies = (n: number) => {
28-
if (n === 0) {
29-
console.log('all `RunDependencies` loaded');
30-
r();
31-
}
32-
};
33-
});
34-
// If there are no pending dependencies left, monitorRunDependencies will
35-
// never be called. Since we can't check the number of dependencies,
36-
// manually trigger a call.
37-
globalThis.Module.addRunDependency('dummy');
38-
globalThis.Module.removeRunDependency('dummy');
39-
return promise;
40-
}
24+
let resolveInputReply: any;
4125

4226
async function get_stdin() {
4327
const replyPromise = new Promise(resolve => {
@@ -46,49 +30,106 @@ async function get_stdin() {
4630
return replyPromise;
4731
}
4832

49-
// eslint-disable-next-line
50-
// @ts-ignore: breaks typedoc
51-
ctx.get_stdin = get_stdin;
33+
(self as any).get_stdin = get_stdin;
5234

53-
let resolveInputReply: any;
35+
class XeusPythonKernel {
36+
constructor() {
37+
this._ready = new Promise(resolve => {
38+
this.initialize(resolve);
39+
});
40+
}
5441

55-
async function load() {
56-
const options: any = {};
42+
async ready(): Promise<void> {
43+
return await this._ready;
44+
}
5745

58-
importScripts('./xpython_wasm.js');
46+
mount(driveName: string, mountpoint: string, baseUrl: string): void {
47+
const { FS, PATH, ERRNO_CODES } = globalThis.Module;
48+
49+
this._drive = new DriveFS({
50+
FS,
51+
PATH,
52+
ERRNO_CODES,
53+
baseUrl,
54+
driveName,
55+
mountpoint
56+
});
57+
58+
FS.mkdir(mountpoint);
59+
FS.mount(this._drive, {}, mountpoint);
60+
FS.chdir(mountpoint);
61+
}
5962

60-
globalThis.Module = await createXeusModule(options);
63+
cd(path: string) {
64+
const { FS } = globalThis.Module;
6165

62-
importScripts('./python_data.js');
66+
FS.chdir(path);
67+
}
6368

64-
await waitRunDependency();
65-
raw_xkernel = new globalThis.Module.xkernel();
66-
raw_xserver = raw_xkernel.get_server();
67-
raw_xkernel!.start();
68-
}
69+
async processMessage(msg: any): Promise<void> {
70+
await this._ready;
71+
72+
if (
73+
globalThis.toplevel_promise !== null &&
74+
globalThis.toplevel_promise_py_proxy !== null
75+
) {
76+
await globalThis.toplevel_promise;
77+
globalThis.toplevel_promise_py_proxy.delete();
78+
globalThis.toplevel_promise_py_proxy = null;
79+
globalThis.toplevel_promise = null;
80+
}
81+
82+
const msg_type = msg.header.msg_type;
83+
84+
if (msg_type === 'input_reply') {
85+
resolveInputReply(msg);
86+
} else {
87+
this._raw_xserver.notify_listener(msg);
88+
}
89+
}
6990

70-
const loadCppModulePromise = load();
91+
private async initialize(resolve: () => void) {
92+
importScripts('./xpython_wasm.js');
7193

72-
ctx.onmessage = async (event: MessageEvent): Promise<void> => {
73-
await loadCppModulePromise;
94+
globalThis.Module = await createXeusModule({});
7495

75-
if (
76-
globalThis.toplevel_promise !== null &&
77-
globalThis.toplevel_promise_py_proxy !== null
78-
) {
79-
await globalThis.toplevel_promise;
80-
globalThis.toplevel_promise_py_proxy.delete();
81-
globalThis.toplevel_promise_py_proxy = null;
82-
globalThis.toplevel_promise = null;
83-
}
96+
importScripts('./python_data.js');
97+
98+
await this.waitRunDependency();
99+
100+
this._raw_xkernel = new globalThis.Module.xkernel();
101+
this._raw_xserver = this._raw_xkernel.get_server();
102+
103+
if (!this._raw_xkernel) {
104+
console.error('Failed to start kernel!');
105+
}
84106

85-
const data = event.data;
86-
const msg = data.msg;
87-
const msg_type = msg.header.msg_type;
107+
this._raw_xkernel.start();
88108

89-
if (msg_type === 'input_reply') {
90-
resolveInputReply(msg);
91-
} else {
92-
raw_xserver!.notify_listener(msg);
109+
resolve();
93110
}
94-
};
111+
112+
private async waitRunDependency() {
113+
const promise = new Promise((resolve: any) => {
114+
globalThis.Module.monitorRunDependencies = (n: number) => {
115+
if (n === 0) {
116+
console.log('all `RunDependencies` loaded');
117+
resolve();
118+
}
119+
};
120+
});
121+
// If there are no pending dependencies left, monitorRunDependencies will
122+
// never be called. Since we can't check the number of dependencies,
123+
// manually trigger a call.
124+
globalThis.Module.addRunDependency('dummy');
125+
globalThis.Module.removeRunDependency('dummy');
126+
return promise;
127+
}
128+
129+
private _raw_xkernel: any;
130+
private _raw_xserver: any;
131+
private _drive: DriveFS | null = null;
132+
private _ready: PromiseLike<void>;
133+
}
134+
135+
expose(new XeusPythonKernel());

src/xeus_server_kernel.ts

+61-7
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,26 @@
22
// Copyright (c) JupyterLite Contributors
33
// Distributed under the terms of the Modified BSD License.
44

5+
import { wrap } from 'comlink';
6+
import type { Remote } from 'comlink';
7+
8+
import { ISignal, Signal } from '@lumino/signaling';
9+
import { PromiseDelegate } from '@lumino/coreutils';
10+
11+
import { PageConfig } from '@jupyterlab/coreutils';
512
import { KernelMessage } from '@jupyterlab/services';
13+
614
import { IKernel } from '@jupyterlite/kernel';
7-
import { ISignal, Signal } from '@lumino/signaling';
815

9-
import { PromiseDelegate } from '@lumino/coreutils';
16+
interface IXeusKernel {
17+
ready(): Promise<void>;
18+
19+
mount(driveName: string, mountpoint: string, baseUrl: string): Promise<void>;
20+
21+
cd(path: string): Promise<void>;
22+
23+
processMessage(msg: any): Promise<void>;
24+
}
1025

1126
export class XeusServerKernel implements IKernel {
1227
/**
@@ -17,26 +32,33 @@ export class XeusServerKernel implements IKernel {
1732

1833
xeus_interpreter: any;
1934
constructor(options: XeusServerKernel.IOptions) {
20-
const { id, name, sendMessage } = options;
35+
const { id, name, sendMessage, location } = options;
2136
this._id = id;
2237
this._name = name;
38+
this._location = location;
2339
this._sendMessage = sendMessage;
24-
this._worker = new Worker(new URL('./worker.js', import.meta.url));
40+
this._worker = new Worker(new URL('./worker.js', import.meta.url), {
41+
type: 'module',
42+
});
2543
this._worker.onmessage = e => {
2644
this._processWorkerMessage(e.data);
2745
};
46+
this._remote = wrap(this._worker);
47+
this.initFileSytem(options);
2848
}
49+
2950
async handleMessage(msg: KernelMessage.IMessage): Promise<void> {
3051
this._parent = msg;
3152
this._parentHeader = msg.header;
3253
await this._sendMessageToWorker(msg);
3354
}
3455

3556
private async _sendMessageToWorker(msg: any): Promise<void> {
57+
// TODO Remove this??
3658
if (msg.header.msg_type !== 'input_reply') {
3759
this._executeDelegate = new PromiseDelegate<void>();
3860
}
39-
this._worker.postMessage({ msg, parent: this.parent });
61+
await this._remote.processMessage({ msg, parent: this.parent });
4062
if (msg.header.msg_type !== 'input_reply') {
4163
return await this._executeDelegate.promise;
4264
}
@@ -58,6 +80,13 @@ export class XeusServerKernel implements IKernel {
5880
return this._parent;
5981
}
6082

83+
/**
84+
* Get the kernel location
85+
*/
86+
get location(): string {
87+
return this._location;
88+
}
89+
6190
/**
6291
* Process a message coming from the pyodide web worker.
6392
*
@@ -81,7 +110,7 @@ export class XeusServerKernel implements IKernel {
81110
* A promise that is fulfilled when the kernel is ready.
82111
*/
83112
get ready(): Promise<void> {
84-
return Promise.resolve();
113+
return this._remote.ready();
85114
}
86115

87116
/**
@@ -123,8 +152,31 @@ export class XeusServerKernel implements IKernel {
123152
return this._name;
124153
}
125154

155+
private async initFileSytem(options: XeusServerKernel.IOptions) {
156+
let driveName: string;
157+
let localPath: string;
158+
159+
if (options.location.includes(':')) {
160+
const parts = options.location.split(':');
161+
driveName = parts[0];
162+
localPath = parts[1];
163+
} else {
164+
driveName = '';
165+
localPath = options.location;
166+
}
167+
168+
await this._remote.ready();
169+
170+
if (options.mountDrive) {
171+
await this._remote.mount(driveName, '/drive', PageConfig.getBaseUrl());
172+
await this._remote.cd(localPath);
173+
}
174+
}
175+
126176
private _id: string;
127177
private _name: string;
178+
private _location: string;
179+
private _remote: Remote<IXeusKernel>;
128180
private _isDisposed = false;
129181
private _disposed = new Signal<this, void>(this);
130182
private _worker: Worker;
@@ -143,5 +195,7 @@ export namespace XeusServerKernel {
143195
/**
144196
* The instantiation options for a Pyodide kernel
145197
*/
146-
export interface IOptions extends IKernel.IOptions {}
198+
export interface IOptions extends IKernel.IOptions {
199+
mountDrive: boolean;
200+
}
147201
}

0 commit comments

Comments
 (0)