> ## Documentation Index
> Fetch the complete documentation index at: https://docs.jylhis.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Nix Build System

> How Jotain builds Emacs from source using Nix

# 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 in `flake.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' default `emacs` 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`](https://github.com/nix-community/emacs-overlay) is added in `flake.nix`/`devenv.nix` to supply the git-based variants.

Supported:

* **Source variants**: `mainline` (nixpkgs' default `emacs` attribute, currently the Emacs 30 release, default), `git` (emacs-overlay's `emacs-git`, current master), `unstable` (`emacs-unstable`, latest tagged release), `macport` (`emacs-macport` → nixpkgs `emacs30-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`, and `adjust-ns-init-colors` (master/32+ only) patches fetched from `nix-giant/nix-darwin-emacs`, applied via `overrideAttrs`.

### 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,

```nix theme={}
import ./emacs.nix {}                  # mainline
import ./emacs.nix { noGui = true; }   # any standard override
```

produces the *exact* store path of `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:

```bash theme={}
nix-instantiate --eval --strict -E '
  let lock = builtins.fromJSON (builtins.readFile ./flake.lock);
      n = lock.nodes.nixpkgs.locked;
      ov = lock.nodes.emacs-overlay.locked;
      nixpkgs = fetchTarball {
        url = "https://github.com/${n.owner}/${n.repo}/archive/${n.rev}.tar.gz";
        sha256 = n.narHash;
      };
      overlay = fetchTarball {
        url = "https://github.com/${ov.owner}/${ov.repo}/archive/${ov.rev}.tar.gz";
        sha256 = ov.narHash;
      };
      pkgs = import nixpkgs { overlays = [ (import overlay) ]; };
  in {
    mainline = (import ./emacs.nix {}).outPath == pkgs.emacs.outPath;
    git      = (import ./emacs.nix { variant = "git"; }).outPath == pkgs.emacs-git.outPath;
    unstable = (import ./emacs.nix { variant = "unstable"; }).outPath == pkgs.emacs-unstable.outPath;
    igc      = (import ./emacs.nix { variant = "igc"; }).outPath == pkgs.emacs-igc.outPath;
  }'
```

The override arg set is filtered through `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 pinned `github: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-emacs`
* `jylhisEmacsPackages` — overlay attribute only; intentionally **not** exposed as a flake `packages` output

The full fork-backed package set is still experimental because the Meson fork has previously crashed while byte-compiling downstream Emacs packages, so it is not advertised as a buildable flake output. It remains reachable through the Home Manager `services.jotain.emacsBackend = "jylhis"` option, which consumes the `jylhisEmacsPackages` overlay attribute directly.

### default.nix

The distribution layer. It imports `emacs.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  |

See `emacs.nix` for the complete argument list and defaults.

## IGC Variant

The `igc` variant builds Emacs's `feature/igc3` branch, which replaces the default mark-and-sweep garbage collector with the [Memory Pool System](https://memory-pool-system.readthedocs.io). 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

The `git`, `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:

```nix theme={}
services.jotain.emacsBackend = "jylhis";
```

The default remains `"mainline"`. Use `"custom"` only when also setting `services.jotain.package`.

Downstream flakes can replace the pinned fork input:

```nix theme={}
inputs.jotain.url = "github:jylhis/jotain";
inputs.my-emacs-fork = {
  url = "github:jylhis/emacs/my-branch-or-rev";
  flake = false;
};
inputs.jotain.inputs.jylhis-emacs.follows = "my-emacs-fork";
```

## nix-on-droid

`nixOnDroidModules.default` (from `module-nix-on-droid.nix`) installs Jotain on Android via [nix-on-droid](https://github.com/nix-community/nix-on-droid). Because Android runs headless under proot, the module is a trimmed cousin of `module-system.nix`: it `pkgs.extend`s 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):

```nix theme={}
inputs.jotain.inputs.nixpkgs.follows = "nixpkgs";
```

The version-gated override split (see [Cache-parity invariant](#cache-parity-invariant)) keeps the modules evaluating and building on older releases. The caveat is Emacs version: 24.05's `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.
