How do I create an FHS environment for emacs that I can include in `configuration.nix`?

I would like to create an FHS environment in an external file, maybe a flake, for my Emacs configuration since it requires some binaries that I don’t need outside that environment.

I want to load this environment in my configuration.nix so it gets included in nixos-rebuild switch. I want this environment to be included in my application launcher, similar to what vscodium-fhs does.

I copied this flake since they’re still pretty arcane to me:

# https://www.alexghr.me/blog/til-nix-flake-fhs/
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }:
    let
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};
      fhs = pkgs.buildFHSUserEnv {
        name = "fhs-shell";
        targetPkgs = pkgs: with pkgs; [
          emacs29-pgtk

          # vterm
          cmake
          # go
          gopls
          gomodifytags
          gotests
          gore
          gotools
          # markdown
          python311Packages.grip
          discount
          # nix
          nixfmt
          # sh
          shfmt
          shellcheck
        ];
      };
    in
      {
        devShells.${system}.default = fhs.env;
      };
}

Also, it appears that Emacs is being executed from an X environment, instead of from Wayland. It gives me this warning when I launch it from the nix develop shell: You are trying to run Emacs configured with the "pure-GTK" interface under the X Window System. That configuration is unsupported and will lead to sporadic crashes during transfer of large selection data. It will also lead to various problems with keyboard input.

Can anyone give me some pointers on how to fix this and how to include it in my main configuration file?

Thanks :slight_smile:

Writing from mobile, please excuse my “brevity”. As a minor adjustment to what you’ve defined there, if you expose that fhs as a packages output alongside the devShell, you can readily consume that from another flake in a pretty straightforward way.

Tangents below.


As an aside, do you know for sure that you specifically need an FHS environment to fulfill your goals? I avoid them when I can. As a different tack you could also try symlinkJoin/wrapProgram, where your emacs and emacsclient binaries are placed behind a wrapper that can prepend or append to PATH, but only affecting that command. I use this with my own editor config to fill in things much like your list, such as a CLI helpers or LSP implementations. Here are some more general documentation:

https://nixos.wiki/wiki/Nix_Cookbook#Wrapping_packages

@viperML also has a nifty project here I’ve been meaning to try, and you might learn from its implementation code or choose to adopt it.

3 Likes

I do not. I thought it’s the only way to create what I’m thinking of.

Thank you for both these recommendations, however they are way above my nix skills. Generally I’m just mucking about in configuration.nix :face_exhaling:

Could you share this? Having a practical example helps me understand things better. Thanks!

A much easier alternative would be using the emacs daemon and setting its path explicitly, something like:

services.emacs = {
  enable = true;
  install = true;
};

systemd.user.services.emacs.path = [
  # All your favorite editor-specific packages
];

Then only that systemd unit will have the extra packages in its path.

Not that I’ve not tested it, I’m just assuming that the env setup script doesn’t completely override an existing PATH.

1 Like

I’d rather not use the daemon, but thanks for the suggestion

Right, in that case you’ll have to override the package, something like this (there are more advanced/arguably cleaner options, like stacking emacs behind a symlinkJoin as @shanesveller mentions, but I think that’s unnecessary in this case):

# Can use an overlay instead if you prefer, but if you just install emacs
# in `environment.systemPackages` this should be enough
environment.systemPackages = [
  (pkgs.emacs.overrideAttrs (old: let
    extraPackages = lib.makeBinPath [
      # All your favorite editor-specific packages
    ];
  in {
    postInstall =
      "wrapProgram $out/bin/emacs --prefix PATH : ${extraPackages}\n" 
      + old.postInstall;
  }))
];

Currently the emacs package doesn’t use any wrappers afaict, so this override shouldn’t cause issues. It does already contain the makeWrapper input, though, so it probably used them in the past. You’ll need to fix this if upstream ever removes the redundant (?) input or starts using it.

Again, I have not actually tested it, syntax is likely wrong and it might need other adjustments.

The actual documentation for makeWrapper isn’t on the wiki, it lives here, by the way: Nixpkgs 23.11 manual | Nix & NixOS

2 Likes

As an aside, do you know for sure that you specifically need an FHS environment to fulfill your goals?

I do not. I thought it’s the only way to create what I’m thinking of.

I have needed to create an FHS no more than once or twice while I’ve been using Nix since ~2017 and flakes since 2020, so I’d say try to get as far as you can without them and you might be pleasantly surprised.

overrideAttrs(old: …)

Overrides that differ from what’s in nixpkgs will produce a new derivation and new store path, and in this specific example would actually cause you to re-compile Emacs anew from source, just to alter the postInstall phase. Seems a poor trade-off in my eyes, especially for someone who probably doesn’t have or want to run a personal binary cache yet.

Meanwhile symlinkJoin and approaches based on it will reuse that original Emacs store path, that’s sort of its whole raison d’etre. That means symlinkJoin will load pre-built Emacs from the binary caches when a match exists. IME this technique usually but not always leads the fastest builds and least store bloat. Based on that, I feel symlinkJoin is the least intrusive/most portable approach to problems expressed in the form of “I want PATH to contain XYZ whenever I run ABC”. I tried to lean into suggestions that aren’t at all specific to Emacs because it’s a generally useful tactic.

@sylvester-roos Mine is Neovim instead and using flake-parts like I do elsewhere, so IMO it’s a poor teaching resource. Nevertheless it’s public here if you’d like to see. That specific file is structured to work with callPackage, and the next layer out is here.

If you’d like to see various implications of what a store path produces, check out nix-tree and point it at i.e. a store path or result symlink. It shows me for example that my Neovim closure is about 825MB altogether but the editor itself is only about 82 of that.

2 Likes

Good point, I overlook this a lot because I end up building stuff downstream all the time anyway.