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.nixusescanProduceInfoto detect.texi,.texinfo, and.infofiles, conditionally adding aninfooutput to the derivation. - Compilation: The
buildInfohook runsmakeinfo --no-splitduring the build phase to generate.infofiles from Texinfo sources. - Installation: The
installInfohook copies compiled.infofiles into$info/share/info, making documentation available as a separate Nix store path. - Aggregation: The wrapper in
pkgs/emacs/wrapper.nixsymlinks allshare/infodirectories, runsinstall-infoto update thedirindex, and setsINFOPATHviawrapProgram. - Self-Documentation: The
doc/flake.nixapp generates the Twist.nix manual usingmakeinfo, 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →