bauer: an Emacs+Nix IDE

6 min read Original article ↗

Finally, we can actually build the environment. This just uses Nixpkgs buildEnv to generate Nix paths for the different packages. Some need special handling.

First, we build the info pages. This takes all of the packages listed in userPackages and runs install-info on them. Info pages are usually found in the $out/share/info directory, but in Emacs packages can be found anywhere in $out/share/emacs.

infopages = buildEnv {
  name = "info-pages";
  buildInputs = [ pkgs.texinfoInteractive ];
  paths = userPackages ++ [customEmacsPackages.emacs]
    ++ lib.optional hostPlatform.isLinux pkgs.glibcInfo;
  extraOutputsToInstall = [ "info" "doc" "devdoc" ];
  pathsToLink = [ "/share/info" ];
  postBuild = ''
    shopt -s nullglob
    find -L ${myEmacsPackages}/share/emacs/ -name '*.info*' -exec ln -s {} $out/share/info \;
    for i in $out/share/info/*.info*; do
      install-info $i $out/share/info/dir
    done
  '';
};

Next, we build the man pages. Again, these come from userPackages and are in the /share/man directory. In addition, some extra man pages are added like the POSIX man pages, and the C++ stdlib man pages. Other OS-specific ones are also included where appropriate Linux man pages (man-pages), and the ones from the Apple SDK.

manpages = buildEnv {
  name = "man-pages";
  ignoreCollisions = (!(config.bauer.small or false));
  paths = userPackages ++ [pkgs.posix_man_pages
         pkgs.stdman
         
         
         ]
    ++ lib.optional (hostPlatform.isDarwin && big && allowUnfree)
         "${apple_sdk}/usr"
    ++ lib.optional hostPlatform.isLinux pkgs.man-pages;
  extraOutputsToInstall = [ "man" "doc" "devdoc" "devman" ];
  pathsToLink = [ "/share/man" ];
};

Next, build the XDG / FreeDesktop directory paths. These come from userPackages and include relevnt information for Desktop files, MIME info, menus, and icons. This updates the caches where appropriate as well. The location of these directories is defined in the FreeDesktop specification.

xdg-data = buildEnv {
  name = "xdg-data-dirs";
  buildInputs = [ pkgs.desktop-file-utils pkgs.shared-mime-info ];
  paths = userPackages ++ [ customEmacsPackages.emacs pkgs.zsh ];
  pathsToLink = [
    "/share/applications"
    "/share/mime"
    "/share/menus"
    "/share/icons"
  ];
  postBuild = ''
    export XDG_DATA_DIRS=$out/share

    if [ -w $out/share/applications ]; then
      update-desktop-database $out/share/applications
    fi

    if [ -w $out/share/mime ] \
       && [ -w $out/share/mime/packages ]; then
      update-mime-database -V $out/share/mime
    fi
  '';
};

Next, ZSH completions are built. These all reside in the $out/share/zsh/site-functions directory.

zsh-completions = buildEnv {
  name = "zsh-completions";
  paths = [ pkgs.zsh-completions pkgs.nix-zsh-completions ] ++ userPackages;
  pathsToLink = [ "/share/zsh" ];
};

Next, setup the binary directory. This will be put in the user’s PATH. We also remove binaries starting with ., as they are used in Nixpkgs for the “wrapped” version of executables.

bins = buildEnv {
  name = "bins";
  paths = userPackages;
  extraOutputsToInstall = [ "bin" ];
  pathsToLink = [ "/bin" ];
  postBuild = ''
    find $out/bin -maxdepth 1 -name ".*" -type l -delete
  '';
};

Setup the system headers path. This is delibirately light to avoid huge closures. Right now, only libc and libcxx are included by default.

sysheaders = buildEnv {
  name = "headers";
  pathsToLink = [ "/include" ];
  extraOutputsToInstall = [ "dev" ];
  paths = [ stdenv.cc.libc ]
    ++ lib.optional hostPlatform.isDarwin pkgs.libcxx
    ++ lib.optional (hostPlatform.isDarwin && big && allowUnfree)
        "${apple_sdk}/usr";
};

Also setup the Apple framework paths. These are a mix between lib directory and include directory. This is oinly useful on macOS/Darwin machines.

apple_sdk = "${pkgs.darwin.xcode}/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk";
sysframeworks = buildEnv {
  name = "frameworks";
  paths = lib.optionals (hostPlatform.isDarwin && big && allowUnfree)
    [ "${apple_sdk}/System/Library/Frameworks"
      "${apple_sdk}/System/Library/PrivateFrameworks" ];
};

Setup XML schema needed for editing some docbooks in Emacs. Not entirely sure why this is necessary, but nXML complains otherwise.

schemas = writeText "schemas.xml" ''
<locatingRules
    xmlns="http://thaiopensource.com/ns/locating-rules/1.0"
>
  <documentElement localName="section" typeId="DocBook"/>
  <documentElement localName="chapter" typeId="DocBook"/>
  <documentElement localName="article" typeId="DocBook"/>
  <documentElement localName="book" typeId="DocBook"/>
  <typeId id="DocBook"
    uri="${pkgs.docbook5}/xml/rng/docbook/docbookxi.rnc"
  />
</locatingRules>
'';

Full listing of packages that will be made available.

userPackages = (with pkgs; [
  
  coreutils findutils diffutils gnused gnugrep gawk gnutar
  gzip bzip2 gnumake bashInteractive patch xz

  
  curl zsh cacert file lsof pstree which rsync
  unzip man openssh less calc ag ripgrep
  tree gnutls
] ++ lib.optionals big ([
  
  isync notmuch graphviz indent
  graphviz imagemagick

  bazaar mercurial

  
  myTex rEnv perl python lua coq ocaml
  openjdk nodejs gcc gdb

  travis v8
] ++ (with netbsd; [ getent getconf ])
  ++ (with nodePackages; [ tern heroku node2nix ])
  ++ (with gitAndTools; [ hub ])
  ++ (with haskellPackages; [ ghc jq nix-diff cabal2nix cabal-install ])
  ++ (with unixtools; [ utillinux nettools procps ])
));

Setup global environment variables & directories. Most of the global directories will not exist, but the way search paths work, means that is okay. We aim to support all directories in a common system, prioritizing ones that the user has the most direct access to. Global directories should only contain system paths.

global-dirs = [ "/nix/var/nix/profiles/default"
    "/run/wrappers"
    "/run/current-system/sw"
    "/usr/local"
    "/usr"
    "" ];

PATH = lib.concatStringsSep ":" [
   (lib.makeBinPath ([ bins customEmacsPackages.emacs pkgs.zsh ]
         ++ global-dirs))
   (lib.makeSearchPath "sbin" [ "/usr" "" ])
       ];
MANPATH = ":" + lib.makeSearchPathOutput "man" "share/man"
  ([ manpages customEmacsPackages.emacs pkgs.zsh ] ++ global-dirs);
INFOPATH = "${infopages}/share/info";
XDG_DATA_DIRS = lib.makeSearchPathOutput "xdg" "share"
  ([ xdg-data ] ++ global-dirs);
TERMINFO_DIRS = lib.makeSearchPathOutput "terminfo" "share/terminfo"
  ([ pkgs.ncurses ]);

INPUTRC = builtins.toFile "inputrc" ''
  $include /etc/inputrc

  "\C-p":history-search-backward
  "\C-n":history-search-forward

  set colored-stats on
  set completion-ignore-case on
  set completion-prefix-display-length 3
  set mark-symlinked-directories on
  set show-all-if-ambiguous on
  set show-all-if-unmodified on
  set visible-stats on
  set editing-mode emacs

  Space:magic-space
'';

Finally, build the final environment. This only contains Emacs and ZSH, which have been configured to use the paths above.

in buildEnv {
  name = "bauer-1.5.1";
  buildInputs = [ pkgs.makeWrapper pkgs.bash ];
  postBuild = ''
    mkdir -p $out/etc
    substituteAll ${./zshrc.sh} $out/etc/.zshrc
    substituteInPlace $out/etc/.zshrc \
      --subst-var-by completions ${zsh-completions} \
      --subst-var-by coreutils ${pkgs.coreutils}
    ln -s $out/etc/.zshrc $out/etc/zshrc
    makeWrapper ${pkgs.zsh}/bin/zsh $out/bin/zsh --set ZDOTDIR $out/etc
    substituteAll ${./etc-profile.sh} $out/etc/profile
    substituteInPlace $out/etc/profile \
      --subst-var-by coreutils ${pkgs.coreutils}
    substitute ${./runemacs.sh} $out/bin/run \
      --subst-var-by emacs ${myEmacs}
    chmod +x $out/bin/run
    patchShebangs $out/bin/run
    ln -s $out/bin/run $out/bin/bauer
  '';
  pathsToLink = [
    "/bin"
    "/etc/profile.d"
    "/share/applications"
  ] ++ lib.optional hostPlatform.isDarwin "/Applications";
  meta = with lib; {
    description = "Bauer's automated unified Emacs realm";
    maintainers = with maintainers; [ matthewbauer ];
    platforms = platforms.all;
    priority = 4;
  };
  passthru = with lib; {
    shellPath = "/bin/zsh";
    run = "/bin/run";
    sourceFile = "/etc/profile.d/profile";
    PATH = makeSearchPath "bin" [bins];
    MANPATH = makeSearchPath "share/man" [manpages];
    INFOPATH = makeSearchPath "share/info" [infopages];
    XDG_DATA_DIRS = makeSearchPath "share" [xdg-data];
    TERMINFO_DIRS = makeSearchPath "share/terminfo" [terminfo];
  };
  paths = [
    myEmacs
    (runCommand "my-profile" {
      emacs = myEmacs;
      inherit (pkgs) cacert;
      inherit PATH MANPATH XDG_DATA_DIRS INFOPATH TERMINFO_DIRS;
    } ''
      mkdir -p $out/etc/profile.d
      substituteAll ${./profile.sh} $out/etc/profile.d/my-profile.sh
      substituteInPlace $out/etc/profile.d/my-profile.sh \
  --subst-var-by PATH ${PATH} \
  --subst-var-by INFOPATH ${INFOPATH} \
  --subst-var-by MANPATH ${MANPATH} \
  --subst-var-by XDG_DATA_DIRS ${XDG_DATA_DIRS} \
  --subst-var-by TERMINFO_DIRS ${TERMINFO_DIRS} \
  --subst-var-by INPUTRC ${INPUTRC}
    '')
    pkgs.git
  ];
}