Downloading Emacs packages ad-hoc with Nix
[Blog Index] [RSS] [Prev: Developing Java applications with Emacs using Nix] [Next: "Russian Bullshit" and its variant card games]
Posted on 2024-09-02 in emacs nix .
It's often nice to ad-hoc install packages with package-install
, but
why accumulate packages in $HOME/.emacs.d
when they could simply be
installed (and eventually garbage-collected) from the Nix store?
Here is some janky Emacs lisp code I wrote to build and load packages
directly from the store. To be clear, I use these only if I want to
ad-hoc install packages in my current Emacs session. (If I want to
actually use a package in my Emacs configuration, I add it to the
extraEmacsPackages
attribute of my Emacs nix derivation.)
To get a package, we need to build it from nixpkgs
. To do this, here
is a function that takes any flake reference to a derivation and runs
nix build
on it. I added a process sentinel to allow the caller to
register a callback (cont
).
(defun sid/nix-build (what &optional cont) "Build WHAT with `nix build' Once done, if provided call CONT." (declare (indent defun)) (let ((proc (start-process "nixbuild" "*nix build*" "nix" "build" "--no-link" "--log-format" "raw" "--quiet" "--print-out-paths" what))) (set-process-sentinel proc (sid/nix-build-process-sentinel (or cont (lambda ())))))) (defun sid/nix-build-process-sentinel (cont) (lambda (proc _msg) (let ((status (process-status proc)) (exit-status (process-exit-status proc))) (cond ((eq status 'signal) (error "Nix build failed to run")) ((eq status 'exit) (if (= exit-status 0) (funcall cont) (error "Nix build exited with code %d" exit-status))) (t nil)))))
Then, we need to get a list of all things that need to be added to
Emacs' load-path
. This uses nix path-info
to recursively get the
path of all dependencies of the Emacs package we're interested in.
A couple of find
commands convert the output of nix path-info
to
the actual Emacs package directories that need to be added to
load-path
.
(defun sid/nix-get-paths (what) "Add all paths from WHAT to `load-path'. WARNING: janky. WHAT should be a derivation in flake reference format, like \"nixpkgs#emacsPackages.helm\"." (string-split (string-trim (shell-command-to-string (concat "find $(find $(nix path-info --recursive --quiet " what ") -name site-lisp -type d) -mindepth 2 -maxdepth 2 -name \\* -type d"))) "\n"))
Finally, put it all together. Take an Emacs package name, build it,
and add all of its paths to load-path
in the callback.
(defun sid/nix-load (name &optional package-name) "Load NAME from nixpkgs, specifically nixpkgs#emacsPackages.NAME. If specified, PACKAGE-NAME can override NAME if the Nix package name isn't the same as the emacs package name." (let ((pkg (or package-name (concat "nixpkgs#emacsPackages." (symbol-name name))))) (sid/nix-build pkg (lambda () (mapcar (lambda (x) (add-to-list 'load-path x)) (sid/nix-get-paths pkg)) (require name)))) name) (provide 'nix-load)
Test it out with a package that isn't already installed:
(sid/nix-load 'helm)
This is still asynchronous, so any code that expects the package to be
loaded needs to be wrapped in with-eval-after-load
like this:
(with-eval-after-load 'helm ;; do something with helm ... (message "Helm loaded! Hooray!"))
[Blog Index] [RSS] [Prev: Developing Java applications with Emacs using Nix] [Next: "Russian Bullshit" and its variant card games]