Bundling Emacs packages with Nix

Aside from managing an Emacs installation, Nix can also bundle Emacs packages. For example, to produce a development shell that has Emacs available with Evil mode installed, use emacsWithPackages. In this example flake, the build inputs include a version of Emacs based on emacs-nox with the Evil package added:

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { nixpkgs, flake-utils, ... } :
  flake-utils.lib.eachDefaultSystem (system:
  let
    pkgs = import nixpkgs { inherit system; };
  in
  {
    devShells.default = pkgs.mkShell {
      buildInputs = with pkgs; [
        ((emacsPackagesFor emacs-nox).emacsWithPackages(epkgs: [
          epkgs.evil
        ]))
      ];
    };
  });
}

With the flake in place, a development shell is started by running nix develop, which opens a shell with Emacs installed and available. To start Emacs directly, without having to start a sub shell first, use the --command flag:

nix develop --command emacs

Within this version of Emacs, the packages defined in the flake’s build inputs are available. This allows for reproducable setups of local development environments, or when using Emacs for batch processing, for example.

Nixpkgs automatically creates derivations based on the recipes available in Elpa, Elpa-devel, NonGNU Elpa and Melpa, which means most packages are available in Nix’s emacsPackage set1.

An Emacs package derivation

Emacs packages can be used even if they’re not available through Nixpkgs by writing a derivation.

For example, the ox-html-markdown-style-footnotes package is not available through any package manager, but it is useful for generating HTML from Org documents through batch processing. To bundle it with Emacs, write a derivation:

{ lib, fetchurl, trivialBuild }:

trivialBuild {
  pname = "ox-html-markdown-style-footnotes";
  version = "0.2.0";

  src = fetchurl {
    url = "https://raw.githubusercontent.com/jeffkreeftmeijer/ox-html-markdown-style-footnotes.el/0.2.0/ox-html-markdown-style-footnotes.el";
    sha256 = "sha256-S+lzFpGY44OgXAeM9Qzdhvceh8DvvOFiw5tgXoXDrsQ=";
  };

  meta = with lib; {
    description = "Markdown-style footnotes for ox-html.el";
    homepage = "https://jeffkreeftmeijer.com/ox-html-markdown-style-footnotes/";
    license = licenses.gpl3;
    platforms = platforms.all;
  };
}

The derivation only sets some general information about the package, like the name, file URL and fingerprint2. The trivialBuild function takes care of the rest.

This example uses the fetchurl fetcher, which could be swapped out for a more sophisticated option like fetchgit, fetchFromGitea or fetchFromGithub. However, with this package being contained in a single file, the simplest option will suffice.

Finally, update the flake to include the newly-added derivation3:

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { nixpkgs, flake-utils, ... } :
  flake-utils.lib.eachDefaultSystem (system:
  let
    pkgs = import nixpkgs { inherit system; };
  in
  {
    devShells.default = pkgs.mkShell {
      buildInputs = with pkgs; [
        ((emacsPackagesFor emacs-nox).emacsWithPackages(epkgs: [
          epkgs.evil
          (epkgs.callPackage ./ox-html-markdown-style-footnotes.nix {})
        ]))
      ];
    };
  });
}

  1. To determine if an Emacs package is available through Nixpkgs, use search.nixos.org, or the nix search command:

    nix search nixpkgs emacsPackages.evil
    
    * legacyPackages.aarch64-darwin.emacsPackages.evil (20231106.1213)
    
    * legacyPackages.aarch64-darwin.emacsPackages.evil-anzu (20220911.1939)
    
    * legacyPackages.aarch64-darwin.emacsPackages.evil-args (20220125.1626)
    
    * legacyPackages.aarch64-darwin.emacsPackages.evil-avy (20150908.748)
    
    ...
    
    ↩︎
  2. To get the sha256 value, simply omit it when running for the first time and take the correct value from the output:

    warning: found empty hash, assuming 'sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='
    error: hash mismatch in fixed-output derivation '/nix/store/144z7a0c6jckqrkrmdnaqdfwi37vqs8m-ox-html-markdown-style-footnotes.el.drv':
             specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
                got:    sha256-S+lzFpGY44OgXAeM9Qzdhvceh8DvvOFiw5tgXoXDrsQ=
    
    ↩︎
  3. In a pinch, the derivation can also be placed directly in the flake, as it’s just a function:

    {
      inputs = {
        nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
        flake-utils.url = "github:numtide/flake-utils";
      };
    
      outputs = { nixpkgs, flake-utils, ... } :
      flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs { inherit system; };
      in
      {
        devShells.default = pkgs.mkShell {
          buildInputs = with pkgs; [
            ((emacsPackagesFor emacs-nox).emacsWithPackages(epkgs: [
              epkgs.evil
              (epkgs.trivialBuild {
                pname = "ox-html-markdown-style-footnotes";
                version = "0.2.0";
    
                src = fetchurl {
                  url = "https://raw.githubusercontent.com/jeffkreeftmeijer/ox-html-markdown-style-footnotes.el/0.2.0/ox-html-markdown-style-footnotes.el";
                  sha256 = "sha256-S+lzFpGY44OgXAeM9Qzdhvceh8DvvOFiw5tgXoXDrsQ=";
                };
    
                meta = with lib; {
                  description = "Markdown-style footnotes for ox-html.el";
                  homepage = "https://jeffkreeftmeijer.com/ox-html-markdown-style-footnotes/";
                  license = licenses.gpl3;
                  platforms = platforms.all;
                };
              })
            ]))
          ];
        };
      });
    }
    
    ↩︎