jupyter.nix is built on top of the NixOS module system (lib.evalModules).
A user’s configuration is evaluated against a set of modules, and the result is
a single derivation: a Python environment with Jupyter Lab, the configured
kernels, and the required extensions.
jupyter/
├── lib.nix Library entry point (exposed as `jupyter.lib`)
├── config/
│ └── module.nix Top-level configuration module
├── kernel/
│ └── module.nix The kernel “interface” (output contract)
├── kernelspec/
│ ├── module.nix Options describing a raw Jupyter kernelspec
│ └── lib.nix Helpers for building kernelspecs (`specKernel`, …)
├── kernel-types.nix A registry of built-in kernel types
└── kernel-types/
├── ipykernel.nix Built-in Python kernel type
└── ihaskell.nix Built-in Haskell kernel type
jupyter/lib.nix is what gets exposed as jupyter.lib. It:
- defines the registry of built-in kernel types (
kernelspec,ipykernel,ihaskell); - exposes
makeJupyterLab, the main user-facing function; - exposes helpers for kernel-type authors (
kernelspecKernel,buildKernelSpec).
makeJupyterLab config simply evaluates the module tree against config and
returns the resulting output derivation (config.outDrv).
This is the top-level module and the most complex part of the codebase. It:
- declares the user-facing options (
pkgs,pythonInterpreter,kernels,jupyterExtensions,enableNativeKernel, …); - declares the
kernelsoption, whose type is built dynamically from the registeredkernelTypes(see below); - assembles the final
outDrvderivation: apython.buildEnvcontaining Jupyter Lab plus, for each kernel, a symlink intoshare/jupyter/kernels/, and a symlinked tree of Jupyter Lab extensions.
Each kernel is configured as kernels.<name>.<type>. The type of the option is
generated from the kernelTypes registry using lib.types.attrTag: every
registered kernel type becomes a possible tag, and the value under that tag is a
submodule built from the kernel type’s module plus the common kernel interface
(jupyter/kernel/module.nix).
A consequence of using attrTag is that the kernel type’s name argument (the
last attribute name in the NixOS module sense) is the kernel type, not the
kernel name. The kernel name (the second-to-last attribute) is passed
separately as kernelName. See the kernel authoring guide
for details.
This module defines the output contract that every kernel type must satisfy:
outDir– a directory containing the Jupyter kernel spec (kernel.json, logos, …);jupyterEnvPackages– Python packages to add to the Jupyter environment;jupyterExtensions– Jupyter Lab extension packages to install.
A kernel type is free to produce outDir however it likes. Most kernel types,
however, build it from a declarative spec — see below.
module.nixdeclares options mirroring the fields of a Jupyter kernel spec (argv,display_name,language, logos, …) and turns them into anoutDirdirectory with akernel.jsonand logo files.lib.nixprovidesspecKernel(a module mixin that adds conveniences such asextraPathon top of the kernelspec module) andbuildKernelSpec/evalKernelSpechelpers for building/testing a kernelspec directly.
The kernelspecKernel helper in jupyter/lib.nix wires a kernel type module
together with specKernel, so that a kernel type only needs to fill in the
spec option and outDir is produced automatically.
makeJupyterLab config
└─ evalModules [ config/module.nix, { kernelTypes }, config ]
├─ kernels.<name>.<type> ──▶ kernel type module (kernel-types/*.nix)
│ └─ produces `spec` ──▶ specKernel
│ └─ outDir (kernel.json + logos)
└─ config.outDrv ──▶ python.buildEnv
├─ jupyterlab + jupyterEnvPackages
├─ symlink each kernel’s outDir into share/jupyter/kernels/
└─ symlink jupyterExtensions into share/jupyter/labextensions/