Demonstration management of 3rd party dependencies & dotfile integration for Emacs using Nix and Home-Manager.
Maintain all of your dependencies and dotfiles for Emacs, reproducibly, portably.
This style does not require home-manager switch
just to pick up changes in
your elisp files. It does not rely on Nix to manage elisp packages. Elpaca
is used for package locking.
Different tools are appropriate for different aspects of propagating configuring for Emacs and your home environment around Emacs.
Elpaca elisp dependency management
Elpaca is used to obtain elisp dependencies and freeze versions into reproducible lock files
Home Manager dotfile integration
Many Emacs extensions like vterm benefit from terminal configuration or the inclusion of files such as graphics and fonts into specific directories. Home manager also can perform systemd integration and start the Emacs server.
A hunk of shell configuration for vterm:
programs.bash.initExtra = ''
vterm_printf(){
if [ -n "$TMUX" ]; then
# Tell tmux to pass the escape sequences through
# (Source: http://permalink.gmane.org/gmane.comp.terminal-emulators.tmux.user/1324)
printf "\ePtmux;\e\e]%s\007\e\\" "$1"
elif [ "''${TERM%%-*}" = "screen" ]; then
# GNU screen (screen, screen-256color, screen-256color-bce)
printf "\eP\e]%s\007\e\\" "$1"
else
printf "\e]%s\e\\" "$1"
fi
}
''
Home manager uses a module structure similar to NixOS that makes it easy for many configurations to be declared independently and then get recursively merged together either without caring about each other or with explicit cross-settings behavior.
Posimacs modules are also an example of configuring a program, its shell aliases, systemd units, accompanying files and programs as a fully self-contained declaration for easy distribution among other nix users.
Nix is really good at managing non-elisp dependencies. Instead of downloading binaries from some CI server that may or may not be compatible or even link correctly, you can build and modify packages for your platform however you need. Nixpkgs has roughly everything and changing any dependency is usually quite straightforward. Public caches are available for many popular packages and most packages from nixpkgs itself will be returned from a cache by default.
Modifying the version of guile scheme available in nixpkgs:
# Don't like something in nixpkgs? Override it!
guile-3 = pkgs.guile.overrideAttrs( drv: {
version = "3.0.7";
src = builtins.fetchurl {
url = "https://ftp.gnu.org/gnu/guile/guile-3.0.7.tar.xz";
sha256 = "1dwiwsrpm4f96alfnz6wibq378242z4f16vsxgy1n9r00v3qczgm";
};
});
This is not meant to be an excellent distribution of Emacs yet. The practice of distributing hunks of configuration by burying them in git repos is anachronistic. Work is ongoing to update this anachronism to 2003-2004 level technology.
However, using these anachronistic rituals, Posimacs does attempt to maintain a decent setup for:
- LSP and a Rust Analyzer build included, integration via Rustic
- Minibuffer completions with Ivy + AMX + Counsel
- Vterm integration & nix build of emacs-libvterm
Much of my actual setup is unpublished. It’s still unclear which parts will be best as elisp packages or hunks of configuration bound for distribution via some other mechanism. Bear with me.
There are three parts to implement the full scheme:
- Setting up home manager and including this repository as a flake input (local development style against a clone is strongly recommended).
- Loading the posimacs shims into
init.el
andearly-init.el
- Rehydrating package versions from the elpaca lock file. Optional.
Clone this repository and symlink it into your ~/.emacs.d
directory.
Home manager can use this repo as a flake input. The input contains a nixosModule that you simply pass into your home manager module expression.
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-21.11";
home-manager.url = "github:nix-community/home-manager";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
# set path to your cloned posimacs repo, which is a nix flake
posimacs = {
url = "path:~/my/cloned/posimacs";
inputs.nixpkgs.follows = "nixpkgs";
}
};
outputs = inputs:
let
system = "x86_64-linux"; # or x86_64-darwin etc
username = "emacslegend"; # change this to your user name
in {
homeConfigurations = {
${username} = inputs.home-manager.lib.homeManagerConfiguration {
import username system;
homeDirectory = "/home/${username}";
configuration.imports = [
import ./home.nix { posimacs = inputs.posimacs.homeConfigurations.${system}.default; }
];
};
};
};
}
Your home.nix
should be a function that accepts the various inputs and
returns a module function.
{ posimacs }:
{ pkgs, ... }:
{
imports = [
posimacs
];
# Module configuration via options (found in each module.nix file)
#
# Use the emacs daemon, enabling `emacsclient` aliases if aliases are turned on
# services.emacs.enable = true;
#
# don't provide alias shortcuts for client commands or EDITOR setting
# posimacs.aliases = false;
#
}
In order for posimacs modules to have their lisp files loaded, you can load the shims or do something similar to their contents.
;; load the posimacs early-init shim
(load (expand-file-name "posimacs/posimacs-early-init.el" user-emacs-directory))
;; load the posimacs init shim
(load (expand-file-name "posimacs/posimacs-init.el" user-emacs-directory))
;; Now that you have some basics configured, learn to use ielm or program in
;; buffer with M-x eval-region etc and customize the rest of the owl
Elpaca does provide facilities to lock packages at the recipe level.
You can also freeze and rehydrate versions from a lock file. The versions used
at the time of this update are in the lock file distributed with these files.
Call elpaca-load-lockfile
and point to elpaca-lock.el.
Because these packages are across a whole Emacs session, you will want to maintain your own lock file.
TODO it’s currently not automatic to use a lock file for installation, so versions will float, meaning you could pick up unwanted supply chain contents until you feed the lockfile to Elpaca. Further work is needed on checking how Elpaca behaves when new versions are available.
Tell home manager to update your nix environment
home-manager switch --flake ~/.config/nixpkgs
If the daemon is enabled (default) it will start and, if the
~/.emacs.d/
elpaca cache is cold, elpaca
will begin downloading
your dependencies and building packages. Connect to the daemon with
emacsclient
.
Otherwise you can just run emacs
to watch the process manually.
Copy the font files from ~/.nix-profile/share/fonts/
to ~/Library/Fonts/
You will still need to run M-x all-the-icons-install-fonts
for icons to
begin working. Can’t pre-install them as they are not picked up in
~/Library/Fonts/
The daemon is extremely convenient for fast loading and keeping all buffers
accessible to all panes by launching clients to a central emacs server. This
might not available on OSX yet. On Linux, you can disable it by, in addition to
the installation instructions above, setting services.emacs.enable = false;
in
your home.nix
.
Whenever you reload, the daemon will not restart because you might have open
files that needs saving and systemd knows nothing about these. Therefore,
restart your daemon manually (it prints this instruction after home-manager
switch
) by running systemctl --user restart emacs
.
The optimum Nix integration appears to be slightly different than what is currently implemented.
Emacs can drive its own nix profile if it wants to, and this can be done to provide 3rd party dependencies with or without home manager integrations.
Emacs can also write its own module and have the home.nix import it so that when Emacs makes changes to that module, it can ensure that they are picked up by running the switch command on its own.
Distributing hunks by copy & pasting configurations from various literate org files around the internet is a form of implicit dependencies that virtually ensures cargo culting & decay. Posimacs is being used to develop a better way.
There’s 2-3 layers of modularization and customization we can use to achieve cooperative customization:
- Git branches
- Home manager modules
- Emacs packages (from a package repo or custom source)
- Emacs files in non-package format
You likely don’t need to change a variable setting in this repo. Configure the
relevant variable in your custom.el
file by using M-x customize
or C-h v
<variable name>
and save it the way you like. If it’s a matter of opinion, we
don’t need to fix it in source.
If something is in your way, attempt to extract it to a new `.el` file
or parameterize it. Maintaining an independent branch may become too
painful over time, but could be viable if you are doing local
development on posimacs (recommended for faster iteration). If your
lisp files grow into a first-class package, of course try to publish
it on Melpa or where elpaca can use it from git source. For the
last-mile configuration, bare .el
files are appropriate.