Add a Home Manager module

It turns out using Home Manager's `programs.emacs` module does not work
correctly. Document that in HACKING.md for future reference.
This commit is contained in:
Marien Zwart 2024-04-29 14:58:41 +10:00
parent 7848f69115
commit b3ed7ea627
No known key found for this signature in database
4 changed files with 215 additions and 5 deletions

View file

@ -53,3 +53,37 @@ module live in the Nix store is the least bad option.
This does break things that write to DOOMDIR at runtime. `custom-file` is an
obvious example, but there are probably a few more.
## `programs.emacs.package` / nesting emacsWithPackages
Home Manager's `programs.emacs` wraps its Emacs package with emacsWithPackages.
We don't work as an input to emacsWithPackages.
First bug: emacsWithPackages writes a site-start that loads
`$emacs/share/site-lisp/site-start` first. That is: it assumes the emacs package
it wraps has its own site-start. That's true if it's an actual emacs but at
first glance might also break if it's another emacsWithPackages, because its
site-start goes in a separate `emacs-packages-deps` derivation (I didn't test
this further).
We could fix that (by adding a trivial site-start.el of our own), but there's a
second bug: when Doom loads its profile, it overwrites `load-path`. This defeats
the purpose of having that outer emacsWithPackages in the first place.
During normal interactive startup, the second bug masks the first: site-start
gets loaded from the Doom profile's load path, skipping the outer
emacsWithPackages entirely. So at first glance the Home Manager `programs.emacs`
module will seem to work...
During non-interactive startup, the first bug surfaces. The easiest way of
triggering this is `doom` cli, which fails with:
```
Unexpected error in Dooms core: "/nix/store/3hr4amd670vbf5h1w1jw18y3a9hv1689-source/lisp/doom-cli.el", (file-missing "Cannot open load file" "No such file or directory" "/nix/store/7vvp8axf8h4qrx7mj3mh1dsxj80393k2-emacs-pgtk-with-doom-29.3/share/emacs/site-lisp/site-start")
```
Setting DEBUG=1 makes it more obvious where this fails (doom-cli.el loads
site-start).
Non-interactive use of emacs also seems to trigger this: in particular it breaks
flycheck of elisp code with a similar error message about site-start.

View file

@ -37,6 +37,40 @@ doom-config.url = "...";
doom-config.flake = false;
```
Next, you have two options:
#### Home Manager
Add Unstraightened's home-manager module:
``` nix
imports = [ inputs.nix-doom-emacs-unstraightened.hmModule ];
```
Configure it:
``` nix
programs.doom-emacs = {
enable = true;
doomDir = inputs.doom-config;
# Any Emacs >= 29 should work. Defaults to pkgs.emacs.
emacs = pkgs.emacs29-pgtk;
};
```
There are a few other options, see below.
If you set `services.emacs.enable = true`, that will run Unstraightened as well
(Unstraightened sets itself as `services.emacs.package`). Set
`programs.doom-emacs.provideEmacs = false` or override `services.emacs.package`
if you want a vanilla Emacs daemon instead.
> [!WARNING]
> Using the overlay described below with `programs.emacs.package` will not work
> correctly (see HACKING.md for details).
#### Overlay
Add Unstraightened's overlay. Typically that means adding:
``` nix
@ -45,7 +79,7 @@ nixpkgs.overlays = [ inputs.nix-doom-emacs-unstraightened.overlays.default ];
to a home-manager or NixOS module.
Next, you have two options:
The overlay adds two packages:
- To install Unstraightened in parallel with a normal Emacs, add:
@ -69,10 +103,6 @@ Next, you have two options:
`emacsclient` and other helpers (similar to [`emacsWithPackages` in
nixpkgs](https://nixos.org/manual/nixos/stable/#module-services-emacs-adding-packages)).
If you use home-manager, setting `programs.emacs.package = pkgs.emacsWithDoom
{ ... };` should work (and `services.emacs` should be able to use this
package).
### Without flakes
This is currently not explicitly supported, but should be possible (use
@ -109,6 +139,12 @@ welcome, as are (within reason) changes necessary to support use without flakes.
There are a few other settings but they are not typically useful. See the
source.
The home-manager module supports the same options, as well as:
- `provideEmacs`: disable this to only provide a `doom-emacs` binary, not an
`emacs` binary (that is: it switches from `emacsWithDoom` to `doomEmacs`). Use
this if you want to install vanilla Emacs in parallel.
## Comparison to "normal" Doom Emacs
- Unstraightened updates Doom and its dependencies along with the rest of your
@ -261,6 +297,22 @@ Safe to ignore, for the same reason as the previous warning.
## Frequently Anticipated Questions
### How do I add more packages?
Add `(package! foo)` to `packages.el`.
Do not wrap emacsWithDoom in emacsWithPackages. See HACKING.md for why this will
not work.
If this is not sufficient, file an issue. I can add a hook to add more packages
from Nix: I just don't want to add that hook unless someone has a use for it.
### How do I add packages not in Emacs overlay?
Add `(package! foo :recipe ...)` to `packages.el`.
If this is not sufficient, file an issue explaining what you're trying to do.
### What's wrong with `straight.el`?
`straight.el` is great, but its features are somewhat at odds with Nix:

View file

@ -62,5 +62,9 @@
doomEmacs = args: (callPackages args).doomEmacs;
emacsWithDoom = args: (callPackages args).emacsWithDoom;
};
hmModule = import ./home-manager.nix {
doomSource = doomemacs;
emacsOverlay = emacs-overlay.overlays.package;
};
};
}

120
home-manager.nix Normal file
View file

@ -0,0 +1,120 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
{ doomSource, emacsOverlay }:
{ config, options, lib, pkgs, ... }:
let
cfg = config.programs.doom-emacs;
inherit (lib) literalExpression mkEnableOption mkIf mkMerge mkOption types;
in {
options = {
programs.doom-emacs = {
enable = mkEnableOption "Doom Emacs";
emacs = mkOption {
type = types.package;
default = pkgs.emacs;
defaultText = literalExpression "pkgs.emacs";
example = literalExpression "pkgs.emacs29-pgtk";
description = "The Emacs package to wrap.";
};
doomDir = mkOption {
type = types.path;
example = literalExpression "./doom";
description = "The DOOMDIR to build from and bundle.";
};
doomLocalDir = mkOption {
type = types.path;
default = "${config.xdg.dataHome}/nix-doom";
defaultText = literalExpression ''"''${config.xdg.dataHome}/nix-doom"'';
example = literalExpression "~/.local/state/doom";
description = ''
DOOMLOCALDIR.
`~` is expanded, but shell variables are not! Use `config.xdg.*`, not
`XDG_DATA_*`.'';
};
profileName = mkOption {
type = types.str;
default = "nix";
example = literalExpression "";
description = "Doom profile. Set to the empty string to disable.";
};
noProfileHack = mkOption {
type = types.bool;
default = false;
example = true;
description = ''
Use a hack to make Doom use normal paths (relative to DOOMLOCALDIR).
Has no effect if doomProfile is unset (set to the empty string).
Currently not recommended: unset doomProfile instead;
'';
};
provideEmacs = mkOption {
type = types.bool;
default = true;
example = false;
description = ''
If enabled (the default), provide "emacs" (and "emacsclient", etc).
If disabled, provide a "doom-emacs" binary.
Disable this to install doom-emacs in parallel with vanilla Emacs.
'';
};
finalEmacsPackage = mkOption {
type = types.package;
visible = false;
readOnly = true;
description = "The final Emacs-compatible package";
};
finalDoomPackage = mkOption {
type = types.package;
visible = false;
readOnly = true;
description = "The final doom-emacs package";
};
};
};
config = mkIf cfg.enable (mkMerge [
(let
doomPackages = (pkgs.extend emacsOverlay).callPackages ./doom.nix {
inherit doomSource;
inherit (cfg) emacs doomDir doomLocalDir profileName noProfileHack;
};
in
{
programs.doom-emacs.finalDoomPackage = doomPackages.doomEmacs;
programs.doom-emacs.finalEmacsPackage = doomPackages.emacsWithDoom;
})
{
home.packages = [(
if cfg.provideEmacs then cfg.finalEmacsPackage else cfg.finalDoomPackage
)];
}
(mkIf (options.services ? emacs && cfg.provideEmacs) {
services.emacs.package = cfg.finalEmacsPackage;
})
]);
}