How twist.nix Handles Info Documentation Generation: A Technical Deep Dive

Twist.nix automatically detects Texinfo sources in Emacs packages, compiles them to .info files using makeinfo, and aggregates them into a unified INFOPATH through a Nix wrapper that maintains the dir index.

Twist.nix is a Nix-based Emacs package manager that treats GNU Info documentation as a first-class build output. When building Emacs packages, the system identifies Texinfo sources, generates .info files as a separate Nix output, and wires them into the wrapped Emacs environment for seamless access via the info command.

Building Info Files in the Package Builder

The core logic for Info generation resides in pkgs/emacs/build/default.nix. This builder runs as a hook during the standard Emacs package build process and handles the detection, compilation, and installation of documentation.

Identifying Texinfo Sources

Before building, the derivation checks whether the package contains any documentation sources. The canProduceInfo predicate uses a regex to identify files ending in .info, .texi, or .texinfo.

canProduceInfo = hasFile (f: match ".+\\.(info|texi(nfo)?)" f != null);

If the derivation is configured to produce extra outputs (wantExtraOutputs) and canProduceInfo returns true, the builder appends "info" to the derivation's outputs list. This creates a separate Nix store path for the documentation.

outputs = ["out"] ++ lib.optional (wantExtraOutputs && canProduceInfo) "info";

Compiling Texi Files with makeinfo

During the buildPhase, the buildInfo hook runs only when the info output is requested. It traverses the source tree, locates every .texi and .texinfo file, and invokes makeinfo --no-split to generate the corresponding .info files in the temporary build directory.

buildInfo = ''
  cwd="$PWD"
  cd $src
  for d in $(find -name '*.texi' -o -name '*.texinfo')
  do
    local basename=$(basename $d)
    local i=$cwd/''${basename%%.*}.info
    if [[ ! -e "$i" ]]
    then
      cd $src/$(dirname $d)
      makeinfo --no-split "$basename" -o "$i"
    fi
  done
  cd $cwd
'';

Installing Info Files to the Nix Store

After compilation, the installInfo hook copies the generated .info files into the dedicated info output directory. This makes the documentation available as a separate Nix store path that can be referenced independently of the main package output.

installInfo = ''
  mkdir -p $info/share
  install -d $info/share/info
  for i in ${ename}*.info
  do
    install -t $info/share/info $i
  done
'';

Aggregating Info Documentation in the Emacs Wrapper

Once individual packages produce their .info files, the pkgs/emacs/wrapper.nix component aggregates these into a cohesive environment. The wrapper constructs a custom Emacs distribution where all package documentation is accessible via the info command.

Building the Package Environment

The wrapper creates a package environment that symlinks the share/info directories from all dependent packages. By setting pathsToLink = [ "/share/info" ], the builder ensures that every package's documentation output is incorporated into the final environment.

Updating the dir Index with install-info

After the environment is built, a postBuild hook runs install-info on every .info and .info.gz file found in the aggregated share/info directory. This updates the dir index file, which serves as the master table of contents for the info system.

postBuild = ''
  if [[ -w $out/share/info ]]
  then
    shopt -s nullglob
    for i in $out/share/info/*.info $out/share/info/*.info.gz; do
      install-info $i $out/share/info/dir
    done
  fi
'';

Setting INFOPATH for Wrapped Binaries

Each generated Emacs binary is wrapped using wrapProgram to inject the correct INFOPATH environment variable. This path includes the Emacs system info directory, the wrapper's own aggregated info directory, and the combined infoPath of all packages.

wrapProgram $bin \
  --prefix INFOPATH : ${emacs}/share/info:$out/share/info:${infoPath} \
  …

The wrapper also installs Twist.nix's own documentation (emacs-twist.info) into $out/share/info and registers it with install-info, ensuring the package manager's manual is available alongside other package documentation.

Generating the twist.nix Manual

Twist.nix includes a dedicated Flake app for building its own manual. Defined in doc/flake.nix, the generate-info app invokes Emacs in batch mode to process doc/emacs-twist.texi with makeinfo, producing the .info file.

apps.generate-info = flake-utils.lib.mkApp {
  name = "generate-info";
  program = builtins.toString (pkgs.runCommand "generate-info" {
    nativeBuildInputs = [ texinfo ];
  } ''
    cd ${self}
    for t in *.texi *.texinfo; do
      makeinfo --no-split "$t" -o "''${t%%.*}.info"
    done
  '');
};

Running nix run .#generate-info produces doc/emacs-twist.info, which the wrapper subsequently installs into the final Emacs environment alongside other package documentation.

Summary

  • Automatic Detection: The builder in pkgs/emacs/build/default.nix uses canProduceInfo to detect .texi, .texinfo, and .info files, conditionally adding an info output to the derivation.
  • Compilation: The buildInfo hook runs makeinfo --no-split during the build phase to generate .info files from Texinfo sources.
  • Installation: The installInfo hook copies compiled .info files into $info/share/info, making documentation available as a separate Nix store path.
  • Aggregation: The wrapper in pkgs/emacs/wrapper.nix symlinks all share/info directories, runs install-info to update the dir index, and sets INFOPATH via wrapProgram.
  • Self-Documentation: The doc/flake.nix app generates the Twist.nix manual using makeinfo, integrating it into the wrapped environment.

Frequently Asked Questions

How does twist.nix detect if a package contains Texinfo sources?

The builder evaluates canProduceInfo = hasFile (f: match ".+\\.(info|texi(nfo)?)" f != null) to check for files ending in .info, .texi, or .texinfo. If found, and if wantExtraOutputs is enabled, the derivation adds "info" to its outputs list, triggering the documentation build process.

Where are the generated .info files stored in the Nix store?

The installInfo hook in pkgs/emacs/build/default.nix installs files to $info/share/info within a separate info output. This creates a distinct Nix store path (e.g., /nix/store/...-emacs-package-info/) containing only the documentation, which can be referenced independently or browsed directly.

How does the Emacs wrapper ensure all package manuals are accessible?

The wrapper in pkgs/emacs/wrapper.nix aggregates documentation by setting pathsToLink = [ "/share/info" ] to symlink all package info directories. It then runs install-info in a postBuild hook to update the dir index, and uses wrapProgram to prepend the aggregated paths to the INFOPATH environment variable.

Can I generate the twist.nix manual locally?

Yes. Running nix run .#generate-info executes the Flake app defined in doc/flake.nix, which processes doc/emacs-twist.texi with makeinfo --no-split to produce doc/emacs-twist.info. This manual is then integrated into the wrapped Emacs environment alongside other package documentation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →