Nix Build System
Jotain uses Nix for reproducible Emacs builds with fine-grained control over compile options. The source of nixpkgs is the revision pinned inflake.lock by default; both default.nix and emacs.nix read it directly via fetchTarball, so non-flake nix-build consumers get the same pin. Pass --arg pkgs '<nixpkgs>' or override pkgs to use a different one.
Two-Layer Architecture
emacs.nix
The core build expression. It selects a base package per variant — nixpkgs’ defaultemacs attribute for the default mainline, emacs-overlay’s emacs-git/emacs-unstable/emacs-igc for the git-based variants, and nixpkgs’ emacs-macport alias for macport — and calls .override { ... } with every upstream build flag exposed as a file-level argument. The nix-community/emacs-overlay is added in flake.nix/devenv.nix to supply the git-based variants.
Supported:
- Source variants:
mainline(nixpkgs’ defaultemacsattribute, currently the Emacs 30 release, default),git(emacs-overlay’semacs-git, current master),unstable(emacs-unstable, latest tagged release),macport(emacs-macport→ nixpkgsemacs30-macport, jdtsmith/emacs-mac fork),igc(emacs-igc, the feature/igc3 Memory Pool System incremental GC branch). - GUI toolkits: GTK3, pgtk (pure GTK for Wayland), NS (Cocoa/NeXTstep on macOS), Motif, Athena, X11, or no GUI at all (
noGui = true). - Compilation: native compilation (libgccjit AOT, default when the build platform can execute the host), compressed install, C sources for
find-function-C-source,srcRepo(run autoreconf on git-based sources). - Image formats: WebP (default), Cairo (X11 default), optionally ImageMagick.
- Libraries: tree-sitter, SQLite3, dbus, selinux, gpm, ALSA, ACL, mailutils, systemd, GLib networking.
- Darwin patches: optional
system-appearance,round-undecorated-frame, andadjust-ns-init-colors(master/32+ only) patches fetched fromnix-giant/nix-darwin-emacs, applied viaoverrideAttrs.
Cache-parity invariant
emacs.nix is written so that every argument default matches the corresponding default in upstream nixpkgs’ make-emacs.nix (and the explicit args emacs-overlay passes to its prebuilt attrs). As long as that holds,
pkgs.emacs(.override { ... }) — and variant = "git"/"unstable"/"igc" the store paths of the matching emacs-overlay attrs — so the Hydra, nix-community.cachix.org, and project jylhis binary caches hit and nothing recompiles from source. Only custom rev pins and the Darwin patch flags are expected to diverge — those paths run through overrideAttrs and intentionally bust the cache.
Verify after any change to defaults:
lib.intersectAttrs (lib.functionArgs basePackage.override), so only the flags the base make-emacs.nix actually defines are forwarded. This keeps the build evaluating when a downstream flake overrides nixpkgs with an older release (24.05+): arguments that newer make-emacs.nix versions added are dropped rather than throwing “called with unexpected argument”. On the pinned unstable every argument is accepted, so the intersection is a no-op and cache parity is unaffected.
emacs-jylhis.nix
The fork build expression. It builds the pinnedgithub:jylhis/emacs Meson branch and stays separate from emacs.nix so the default cache-parity path remains unchanged. In the flake, the fork source comes from the non-flake input jylhis-emacs; standalone nix-build emacs-jylhis.nix falls back to the pinned rev / hash arguments in the file.
The overlay exposes both bare and full fork-backed packages:
jylhisEmacs/packages.${system}.jylhis-emacsjylhisEmacsPackages— overlay attribute only; intentionally not exposed as a flakepackagesoutput
services.jotain.emacsBackend = "jylhis" option, which consumes the jylhisEmacsPackages overlay attribute directly.
default.nix
The distribution layer. It importsemacs.nix (forwarding every argument it does not consume itself) and, when withTreeSitterGrammars is true (default), wraps the result with emacsPackagesFor emacs |> withPackages (epkgs: [ epkgs.treesit-grammars.with-all-grammars ]). The resulting Emacs loads all ~275 grammars out of the box; early-init.el wires them in via TREE_SITTER_DIR / treesit-extra-load-path.
Key Build Options
| Option | Default | Description |
|---|---|---|
variant | "mainline" | Emacs source variant — mainline / git / unstable / macport / igc |
withTreeSitterGrammars | true | (default.nix) include all tree-sitter grammars |
noGui | false | Terminal only — --without-x --without-ns |
withPgtk | false | Pure GTK (Wayland) — --with-pgtk |
withGTK3 | withPgtk && !noGui | GTK3 toolkit — --with-x-toolkit=gtk3 |
withNativeCompilation | auto | libgccjit AOT compilation |
withTreeSitter | true | Built-in tree-sitter support |
withSystemd | Linux | --with-systemd (journal support) |
withSystemAppearancePatch | false | (Darwin) add ns-system-appearance hooks |
withRoundUndecoratedFramePatch | false | (Darwin) rounded borderless frames |
withFixWindowRolePatch | false | (Darwin, Emacs 30 only) fix NSAccessibility role for tiling WMs |
rev / hash | null | Pin a specific commit for git / unstable / igc / macport variants |
emacs.nix for the complete argument list and defaults.
IGC Variant
Theigc variant builds Emacs’s feature/igc3 branch, which replaces the default mark-and-sweep garbage collector with the Memory Pool System. The base package is emacs-overlay’s emacs-igc, which already carries --with-mps=yes and the mps build input, so no manual steps are needed — the overlay-pinned revision is a binary-cache hit.
Git Variants
Thegit, unstable, and igc variants build the revision pinned inside emacs-overlay (updated daily upstream, advanced here by just update) and are binary-cache hits from nix-community.cachix.org. To pin a different commit, pass --argstr rev "..." — emacs.nix then fetches it from https://git.savannah.gnu.org/git/emacs.git via fetchgit; the first build reports the correct hash to pass back via --argstr hash "sha256-...". In that case postPatch substitutes the pinned revision into lisp/loadup.el so emacs-repository-get-version returns the expected value without a .git directory in the build tree. On aarch64-linux, the overlay’s bases include --enable-check-lisp-object-type to avoid segfaults.
Consumer Flake Backend Selection
Home Manager, NixOS, and nix-darwin consumers choose the fork with:"mainline". Use "custom" only when also setting services.jotain.package.
Downstream flakes can replace the pinned fork input:
nix-on-droid
nixOnDroidModules.default (from module-nix-on-droid.nix) installs Jotain on Android via nix-on-droid. Because Android runs headless under proot, the module is a trimmed cousin of module-system.nix: it pkgs.extends the overlay and adds a terminal-only build (jotainEmacsPackagesNoGui, a noGui = true Emacs) plus an emacsclient EDITOR/VISUAL wrapper to environment.packages and environment.sessionVariables. There is no systemd service, launchd agent, fonts.packages, or GUI frame.
flake.nix exposes an example nixOnDroidConfigurations.default (aarch64-linux). It activates only on-device or under aarch64 emulation, so nix flake check does not realise it (CI is x86_64); the module itself is eval-checked on x86_64 via nix-on-droid-module-eval in nix/checks.nix.
Overriding nixpkgs
A downstream flake can follow a different nixpkgs (release branches 24.05+ through unstable):pkgs.emacs is Emacs 29 while Jotain’s Elisp targets 30/31, so the build succeeds but runtime behaviour is only guaranteed on the pinned unstable.