Skip to content
Merged
16 changes: 14 additions & 2 deletions jupyterlite_xeus/add_on.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""a JupyterLite addon for creating the env for xeus kernels"""

import json
import os
from pathlib import Path
Expand All @@ -18,7 +19,7 @@
SHARE_LABEXTENSIONS,
UTF8,
)
from traitlets import Bool, List, Unicode
from traitlets import Bool, Callable, List, Unicode

from .create_conda_env import (
create_conda_env_from_env_file,
Expand Down Expand Up @@ -102,6 +103,13 @@ class XeusAddon(FederatedExtensionAddon):
description="A comma-separated list of mount points, in the form <host_path>:<mount_path> to mount in the wasm prefix",
)

package_url_factory = Callable(
None,
allow_none=True,
config=True,
description="Factory to generate package download URL from package metadata. This is used to load python packages from external host",
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.xeus_output_dir = Path(self.manager.output_dir) / "xeus"
Expand Down Expand Up @@ -285,6 +293,9 @@ def pack_prefix(self, kernel_dir):
else:
pack_kwargs["file_filters"] = pkg_file_filter_from_yaml(DEFAULT_CONFIG_PATH)

if self.package_url_factory is not None:
pack_kwargs["package_url_factory"] = self.package_url_factory

pack_env(
env_prefix=self.prefix,
relocate_prefix="/",
Expand Down Expand Up @@ -342,7 +353,8 @@ def pack_prefix(self, kernel_dir):
# Pack JupyterLite content if enabled
# If we only build a voici output, mount jupyterlite content into the kernel by default
if self.mount_jupyterlite_content or (
list(self.manager.apps) == ["voici"] and self.mount_jupyterlite_content is None
list(self.manager.apps) == ["voici"]
and self.mount_jupyterlite_content is None
):
contents_dir = self.manager.output_dir / "files"

Expand Down
129 changes: 80 additions & 49 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { IBroadcastChannelWrapper } from '@jupyterlite/contents';
import { IKernel, IKernelSpecs } from '@jupyterlite/kernel';

import { WebWorkerKernel } from './web_worker_kernel';
import { IEmpackEnvMetaFile } from './tokens';

function getJson(url: string) {
const json_url = URLExt.join(PageConfig.getBaseUrl(), url);
Expand All @@ -29,59 +30,89 @@ try {
throw err;
}

const plugins = kernel_list.map((kernel): JupyterLiteServerPlugin<void> => {
return {
id: `@jupyterlite/xeus-${kernel}:register`,
autoStart: true,
requires: [IKernelSpecs],
optional: [IServiceWorkerManager, IBroadcastChannelWrapper],
activate: (
app: JupyterLiteServer,
kernelspecs: IKernelSpecs,
serviceWorker?: IServiceWorkerManager,
broadcastChannel?: IBroadcastChannelWrapper
) => {
// Fetch kernel spec
const kernelspec = getJson('xeus/kernels/' + kernel + '/kernel.json');
kernelspec.name = kernel;
kernelspec.dir = kernel;
for (const [key, value] of Object.entries(kernelspec.resources)) {
kernelspec.resources[key] = URLExt.join(
PageConfig.getBaseUrl(),
value as string
);
}

const contentsManager = app.serviceManager.contents;

kernelspecs.register({
spec: kernelspec,
create: async (options: IKernel.IOptions): Promise<IKernel> => {
const mountDrive = !!(
(serviceWorker?.enabled && broadcastChannel?.enabled) ||
crossOriginIsolated
const plugins = kernel_list.map(
(kernel): JupyterLiteServerPlugin<void | IEmpackEnvMetaFile> => {
return {
id: `@jupyterlite/xeus-${kernel}:register`,
autoStart: true,
requires: [IKernelSpecs],
optional: [
IServiceWorkerManager,
IBroadcastChannelWrapper,
IEmpackEnvMetaFile
],
activate: (
app: JupyterLiteServer,
kernelspecs: IKernelSpecs,
serviceWorker?: IServiceWorkerManager,
broadcastChannel?: IBroadcastChannelWrapper,
empackEnvMetaFile?: IEmpackEnvMetaFile
) => {
// Fetch kernel spec
const kernelspec = getJson('xeus/kernels/' + kernel + '/kernel.json');
kernelspec.name = kernel;
kernelspec.dir = kernel;
for (const [key, value] of Object.entries(kernelspec.resources)) {
kernelspec.resources[key] = URLExt.join(
PageConfig.getBaseUrl(),
value as string
);
}

if (mountDrive) {
console.info(
`${kernelspec.name} contents will be synced with Jupyter Contents`
);
} else {
console.warn(
`${kernelspec.name} contents will NOT be synced with Jupyter Contents`
const contentsManager = app.serviceManager.contents;
kernelspecs.register({
spec: kernelspec,
create: async (options: IKernel.IOptions): Promise<IKernel> => {
const mountDrive = !!(
(serviceWorker?.enabled && broadcastChannel?.enabled) ||
crossOriginIsolated
);

if (mountDrive) {
console.info(
`${kernelspec.name} contents will be synced with Jupyter Contents`
);
} else {
console.warn(
`${kernelspec.name} contents will NOT be synced with Jupyter Contents`
);
}
const link = empackEnvMetaFile
? await empackEnvMetaFile.getLink(kernelspec)
: '';

return new WebWorkerKernel({
...options,
contentsManager,
mountDrive,
kernelSpec: kernelspec,
empackEnvMetaLink: link
});
}
});
}
};
}
);

return new WebWorkerKernel({
...options,
contentsManager,
mountDrive,
kernelSpec: kernelspec
});
}
});
}
};
});
const empackEnvMetaPlugin: JupyterLiteServerPlugin<IEmpackEnvMetaFile> = {
id: '@jupyterlite/xeus-python:empack-env-meta',
autoStart: true,
provides: IEmpackEnvMetaFile,
activate: (): IEmpackEnvMetaFile => {
return {
getLink: async (kernelspec: Record<string, any>) => {
const kernelName = kernelspec.name;
const kernel_root_url = URLExt.join(
PageConfig.getBaseUrl(),
`xeus/kernels/${kernelName}`
);
return `${kernel_root_url}`;
}
};
}
};

plugins.push(empackEnvMetaPlugin);

export default plugins;
13 changes: 13 additions & 0 deletions src/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '@jupyterlite/contents';

import { IWorkerKernel } from '@jupyterlite/kernel';
import { Token } from '@lumino/coreutils';

/**
* An interface for Xeus workers.
Expand Down Expand Up @@ -85,5 +86,17 @@ export namespace IXeusWorkerKernel {
baseUrl: string;
kernelSpec: any;
mountDrive: boolean;
empackEnvMetaLink?: string;
}
}

export interface IEmpackEnvMetaFile {
/**
* Get empack_env_meta link.
*/
getLink: (kernelspec: Record<string, any>) => Promise<string>;
}

export const IEmpackEnvMetaFile = new Token<IEmpackEnvMetaFile>(
'@jupyterlite/xeus-python:IEmpackEnvMetaFile'
);
19 changes: 15 additions & 4 deletions src/web_worker_kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,24 @@ export class WebWorkerKernel implements IKernel {
* @param options The instantiation options for a new WebWorkerKernel
*/
constructor(options: WebWorkerKernel.IOptions) {
const { id, name, sendMessage, location, kernelSpec, contentsManager } =
options;
const {
id,
name,
sendMessage,
location,
kernelSpec,
contentsManager,
empackEnvMetaLink
} = options;
this._id = id;
this._name = name;
this._location = location;
this._kernelSpec = kernelSpec;
this._contentsManager = contentsManager;
this._sendMessage = sendMessage;
this._empackEnvMetaLink = empackEnvMetaLink;
this._worker = this.initWorker(options);
this._remoteKernel = this.initRemote(options);

this.initFileSystem(options);
}

Expand Down Expand Up @@ -99,11 +106,13 @@ export class WebWorkerKernel implements IKernel {
};
remote = wrap(this._worker) as Remote<IXeusWorkerKernel>;
}

remote
.initialize({
kernelSpec: this._kernelSpec,
baseUrl: PageConfig.getBaseUrl(),
mountDrive: options.mountDrive
mountDrive: options.mountDrive,
empackEnvMetaLink: this._empackEnvMetaLink
})
.then(this._ready.resolve.bind(this._ready));

Expand Down Expand Up @@ -290,6 +299,7 @@ export class WebWorkerKernel implements IKernel {
| undefined = undefined;
private _parent: KernelMessage.IMessage | undefined = undefined;
private _ready = new PromiseDelegate<void>();
private _empackEnvMetaLink: string | undefined;
}

/**
Expand All @@ -303,5 +313,6 @@ export namespace WebWorkerKernel {
contentsManager: Contents.IManager;
mountDrive: boolean;
kernelSpec: any;
empackEnvMetaLink?: string | undefined;
}
}
9 changes: 4 additions & 5 deletions src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export class XeusRemoteKernel {
}

async initialize(options: IXeusWorkerKernel.IOptions): Promise<void> {
const { baseUrl, kernelSpec } = options;
const { baseUrl, kernelSpec, empackEnvMetaLink } = options;
// location of the kernel binary on the server
const binary_js = URLExt.join(baseUrl, kernelSpec.argv[0]);
const binary_wasm = binary_js.replace('.js', '.wasm');
Expand All @@ -125,10 +125,9 @@ export class XeusRemoteKernel {
// This function is usually implemented in the pre/post.js
// in the emscripten build of that kernel
if (globalThis.Module['async_init'] !== undefined) {
const kernel_root_url = URLExt.join(
baseUrl,
`xeus/kernels/${kernelSpec.dir}`
);
const kernel_root_url = empackEnvMetaLink
? empackEnvMetaLink
: URLExt.join(baseUrl, `xeus/kernels/${kernelSpec.dir}`);
const pkg_root_url = URLExt.join(baseUrl, 'xeus/kernel_packages');
const verbose = true;
await globalThis.Module['async_init'](
Expand Down
Loading