Skip to content

Commit

Permalink
Allow mounting jupyterlite content (#49)
Browse files Browse the repository at this point in the history
* Allow mounting jupyterlite content

* Cd into /files if it exists

* Automatically mount jupyterlite content when using voici

* Improve descriptiont
  • Loading branch information
martinRenou authored Jan 25, 2024
1 parent 5d8c08f commit 7f79a59
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 17 deletions.
70 changes: 53 additions & 17 deletions jupyterlite_xeus/add_on.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
SHARE_LABEXTENSIONS,
UTF8,
)
from traitlets import List, Unicode
from traitlets import Bool, List, Unicode

from .create_conda_env import (
create_conda_env_from_env_file,
Expand All @@ -36,6 +36,8 @@
)
from empack.file_patterns import PkgFileFilter, pkg_file_filter_from_yaml

EMPACK_ENV_META = "empack_env_meta.json"


def get_kernel_binaries(path):
"""return path to the kernel binary (js and wasm) if they exist, else None"""
Expand Down Expand Up @@ -86,6 +88,13 @@ class XeusAddon(FederatedExtensionAddon):
description="The path to the wasm prefix",
)

mount_jupyterlite_content = Bool(
None,
allow_none=True,
config=True,
description="Whether or not to mount the jupyterlite content into the kernel. This would make the jupyterlite content available under the '/files' directory, and the kernels will automatically be started from there.",
)

mounts = MountPoints(
[],
config=True,
Expand Down Expand Up @@ -277,21 +286,27 @@ def pack_prefix(self, kernel_dir):
file_filters=file_filters,
)

empack_env_meta = "empack_env_meta.json"
# pack extra dirs
# Pack user defined mount points
for mount_index, mount in enumerate(self.mounts):
if mount.count(":") != 1:
msg = f"invalid mount {mount}, must be <host_path>:<mount_path>"
raise ValueError(msg)
raise ValueError(
f"invalid mount {mount}, must be <host_path>:<mount_path>"
)

host_path, mount_path = mount.split(":")
mount_path = Path(mount_path)

if not mount_path.is_absolute():
msg = f"mount_path {mount_path} needs to be absolute"
raise ValueError(msg)
raise ValueError(f"mount_path {mount_path} needs to be absolute")

if str(mount_path).startswith("/files"):
raise ValueError(
f"Mount point '/files' is reserved for jupyterlite content. Cannot mount {mount}"
)

outname = f"mount_{mount_index}.tar.gz"

if host_path.is_dir():

pack_directory(
host_dir=host_path,
mount_dir=mount_path,
Expand All @@ -306,32 +321,53 @@ def pack_prefix(self, kernel_dir):
outdir=out_path,
)
else:
msg = f"host_path {host_path} needs to be a file or a directory"
raise ValueError(msg)
raise ValueError(
f"host_path {host_path} needs to be a file or a directory"
)

add_tarfile_to_env_meta(
env_meta_filename=out_path / EMPACK_ENV_META, tarfile=out_path / outname
)

# 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 (
self.manager.apps == ["voici"] and self.mount_jupyterlite_content is None
):
contents_dir = self.manager.output_dir / "files"

outname = f"mount_{len(self.mounts)}.tar.gz"

pack_directory(
host_dir=contents_dir,
mount_dir="/files",
outname=outname,
outdir=out_path,
)

add_tarfile_to_env_meta(
env_meta_filename=out_path / empack_env_meta, tarfile=out_path / outname
env_meta_filename=out_path / EMPACK_ENV_META, tarfile=out_path / outname
)

# copy all the packages to the packages dir
# (this is shared between multiple xeus-python kernels)
# (this is shared between multiple kernels)
for pkg_path in out_path.iterdir():
if pkg_path.name.endswith(".tar.gz"):
yield dict(
name=f"xeus:{kernel_name}:copy_package:{pkg_path.name}",
name=f"xeus:{kernel_name}:copy:{pkg_path.name}",
actions=[(self.copy_one, [pkg_path, packages_dir / pkg_path.name])],
)

# copy the empack_env_meta.json
# this is individual for xeus-python kernel
# this is individual for each kernel
yield dict(
name=f"xeus:{kernel_name}:copy_env_file:{empack_env_meta}",
name=f"xeus:{kernel_name}:copy_env_file:{EMPACK_ENV_META}",
actions=[
(
self.copy_one,
[
out_path / empack_env_meta,
Path(full_kernel_dir) / empack_env_meta,
out_path / EMPACK_ENV_META,
Path(full_kernel_dir) / EMPACK_ENV_META,
],
)
],
Expand Down
7 changes: 7 additions & 0 deletions src/web_worker_kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ interface IXeusKernel {

cd(path: string): Promise<void>;

isDir(path: string): Promise<boolean>;

processMessage(msg: any): Promise<void>;
}

Expand Down Expand Up @@ -183,6 +185,11 @@ export class WebWorkerKernel implements IKernel {

if (options.mountDrive) {
await this._remote.mount(driveName, '/drive', PageConfig.getBaseUrl());
}

if (await this._remote.isDir('/files')) {
await this._remote.cd('/files');
} else if (options.mountDrive) {
await this._remote.cd(localPath);
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,15 @@ class XeusKernel {
globalThis.Module.FS.chdir(path);
}

isDir(path: string) {
try {
const lookup = globalThis.Module.FS.lookupPath(path);
return globalThis.Module.FS.isDir(lookup.node.mode);
} catch (e) {
return false;
}
}

async processMessage(event: any): Promise<void> {
const msg_type = event.msg.header.msg_type;

Expand Down

0 comments on commit 7f79a59

Please sign in to comment.