I have now successfully managed to build evcxr_jupyter
so that it also runs in the notebook as Rust
kernel and correct code cells in Rust also produce correct output.
the corresponding default.nix
looks like this:
{
lib,
stdenv,
fetchFromGitHub,
rustPlatform,
jupyter,
cmake,
pkg-config,
openssl,
zlib,
zeromq,
darwin,
}:
rustPlatform.buildRustPackage rec {
pname = "evcxr-jupyter";
version = "0.19.0";
src = fetchFromGitHub {
owner = "evcxr";
repo = "evcxr";
tag = "v${version}";
hash = "sha256-8PjZFWUH76QrA8EI9Cx0sBCzocvSmnp84VD7Nv9QMc8=";
};
cargoHash = "sha256-hE/O6lHC0o+nrN4vaQ155Nn2gZscpfsZ6o7IDi/IEjI=";
doCheck = false;
nativeBuildInputs = [
cmake
pkg-config
jupyter
];
buildInputs =
[
openssl
zlib
zeromq # Explicitly add ZeroMQ which is required
]
++ lib.optionals stdenv.isDarwin [
darwin.apple_sdk.frameworks.Security
];
cargoBuildFlags = [
"-p"
"evcxr_jupyter"
];
# Fixed postInstall script without the --version check
postInstall = ''
# Create the directory structure
mkdir -p $out/share/jupyter/kernels/rust
# Create a kernel.json file manually with the exact binary path
cat > $out/share/jupyter/kernels/rust/kernel.json << EOF
{
"argv": ["$out/bin/evcxr_jupyter", "--control_file", "{connection_file}"],
"display_name": "Rust",
"language": "rust",
"interrupt_mode": "message"
}
EOF
# Verify the binary exists and is executable
if [ ! -x "$out/bin/evcxr_jupyter" ]; then
echo "Error: evcxr_jupyter binary not found or not executable at $out/bin/evcxr_jupyter"
ls -la $out/bin/
exit 1
fi
# Just check if the binary exists, don't try to run it with --version
echo "evcxr_jupyter binary found at $out/bin/evcxr_jupyter"
'';
meta = {
description = "A Jupyter kernel for Rust";
homepage = "https://github.com/evcxr/evcxr";
license = lib.licenses.mit;
maintainers = with lib.maintainers; [ guelakais ];
};
}
the used flake.nix
looks like this:
{
description = "Jupyter environment with Rust and Julia kernels";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{
self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
# Build evcxr-jupyter with a specific version that's known to work
evcxr-jupyter = pkgs.callPackage ./default.nix { };
# Create a custom Python environment with Jupyter and necessary dependencies
pythonEnv = pkgs.python3.withPackages (
ps: with ps; [
jupyterlab
notebook
ipykernel
# Add useful Jupyter extensions
ipywidgets
matplotlib
numpy
pandas
]
);
# Create a script to clean up stale kernel files
cleanKernels = pkgs.writeScriptBin "clean-jupyter-kernels" ''
#!${pkgs.bash}/bin/bash
echo "Cleaning up stale Jupyter kernel files..."
rm -rf ~/.local/share/jupyter/runtime/*
echo "Done!"
'';
in
{
packages = {
default = evcxr-jupyter;
evcxr-jupyter = evcxr-jupyter;
clean-jupyter-kernels = cleanKernels;
};
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
evcxr-jupyter
# Add Rust compiler and tools
rustc
cargo
rustfmt
rust-analyzer
# Julia
julia-bin
# Python environment
pythonEnv
# Dependencies
zeromq
openssl
pkg-config
cmake
# Utility scripts
cleanKernels
];
# Shell hook with improved setup
shellHook = ''
# Set up environment variables
export JUPYTER_PATH=${evcxr-jupyter}/share/jupyter:$JUPYTER_PATH
export PATH=${evcxr-jupyter}/bin:$PATH
# Create a debug function to help diagnose issues
debug_kernel() {
echo "Debugging Jupyter kernels..."
echo "Checking available kernels:"
jupyter kernelspec list
echo "Checking if evcxr_jupyter is in PATH:"
which evcxr_jupyter || echo "evcxr_jupyter not found in PATH"
echo "Checking if rustc is in PATH:"
which rustc || echo "rustc not found in PATH"
echo "Checking Rust kernel spec:"
find ${evcxr-jupyter}/share/jupyter -type f -name "kernel.json" | xargs cat
echo "Checking Julia kernel installation:"
julia -e 'println("Julia version: ", VERSION); using Pkg; Pkg.status("IJulia")' || echo "Julia check failed"
echo "Checking Jupyter runtime directory:"
ls -la ~/.local/share/jupyter/runtime/ || echo "No runtime directory found"
}
# Create Julia depot directory if it doesn't exist
export JULIA_DEPOT_PATH=$PWD/.julia
mkdir -p $JULIA_DEPOT_PATH
# Install IJulia if not already installed
if [ ! -d "$JULIA_DEPOT_PATH/packages/IJulia" ]; then
echo "Installing IJulia package for Julia kernel..."
julia -e 'using Pkg; Pkg.add("IJulia"); using IJulia; installkernel("Julia")'
fi
# Create a wrapper script for jupyter to ensure proper environment
mkdir -p $PWD/.bin
cat > $PWD/.bin/jupyter-with-kernels << EOF
#!/bin/sh
export JUPYTER_PATH=${evcxr-jupyter}/share/jupyter:\$JUPYTER_PATH
export PATH=${evcxr-jupyter}/bin:${pkgs.rustc}/bin:${pkgs.cargo}/bin:\$PATH
export LD_LIBRARY_PATH=${pkgs.zeromq}/lib:${pkgs.openssl}/lib:\$LD_LIBRARY_PATH
# Clean up any stale kernel files before starting
rm -rf ~/.local/share/jupyter/runtime/*
exec jupyter "\$@"
EOF
chmod +x $PWD/.bin/jupyter-with-kernels
export PATH=$PWD/.bin:$PATH
echo "Jupyter environment is ready!"
echo "Run 'jupyter-with-kernels lab' to start JupyterLab"
echo "Run 'jupyter-with-kernels notebook' to start the classic notebook interface"
echo "If you encounter issues with kernels, run 'debug_kernel' for diagnostics"
echo "To clean up stale kernel files, run 'clean-jupyter-kernels'"
'';
};
# Add an app that can be run with 'nix run'
apps.default = {
type = "app";
program = "${pkgs.writeShellScript "start-jupyter" ''
export JUPYTER_PATH=${evcxr-jupyter}/share/jupyter:$JUPYTER_PATH
export PATH=${evcxr-jupyter}/bin:${pkgs.rustc}/bin:${pkgs.cargo}/bin:$PATH
export LD_LIBRARY_PATH=${pkgs.zeromq}/lib:${pkgs.openssl}/lib:$LD_LIBRARY_PATH
# Clean up any stale kernel files before starting
rm -rf ~/.local/share/jupyter/runtime/*
${pythonEnv}/bin/jupyter lab
''}";
};
}
);
}
As you can see, the flake still has to do a lot of work for the rust in the jupyter notebook to work properly. I would like to submit the whole thing to nixpkgs
. But for that the packet would probably have to be more idiomatic through jupyter notebook, does anyone know about that?