Skip to content

Instantly share code, notes, and snippets.

@adisbladis
Created November 30, 2025 02:51
Show Gist options
  • Select an option

  • Save adisbladis/63cefc315d6764df657b21e8819efe5a to your computer and use it in GitHub Desktop.

Select an option

Save adisbladis/63cefc315d6764df657b21e8819efe5a to your computer and use it in GitHub Desktop.
Example building a twist.nix based Emacs closure

I'm interested in changing my configuration to use twist.nix instead of nixpkgs for Emacs packages. It took me a while to figure out how to get everything to build so I'd figure I'd just dump out the relevant Nix code in a gist for others to possibly use as a reference. This gist will not be maintained.

{
description = "Using twist.nix to configure emacs";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
twist.url = "github:emacs-twist/twist.nix";
# Package registries for Twist
melpa = {
url = "github:melpa/melpa";
flake = false;
};
gnu-elpa = {
# Use a GitHub mirror for a higher availability
url = "github:elpa-mirrors/elpa";
# url = "git+https://git.savannah.gnu.org/git/emacs/elpa.git?ref=main";
flake = false;
};
nongnu-elpa = {
# Use a GitHub mirror for a higher availability
url = "github:elpa-mirrors/nongnu";
# url = "git+https://git.savannah.gnu.org/git/emacs/nongnu.git?ref=main";
flake = false;
};
gnu-elpa-archive = {
url = "file+https://elpa.gnu.org/packages/archive-contents";
flake = false;
};
nongnu-elpa-archive = {
url = "file+https://elpa.nongnu.org/nongnu/archive-contents";
flake = false;
};
};
outputs =
{
nixpkgs,
twist,
...
}@inputs:
let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
inherit (nixpkgs) lib;
inherit (pkgs) stdenv cmake libvterm-neovim;
inherit (pkgs) emacs;
registries = [
{
type = "melpa";
path = ./recipes;
}
{
type = "melpa";
path = inputs.melpa.outPath + "/recipes";
exclude = [ "async" ];
}
{
type = "elpa";
path = inputs.gnu-elpa.outPath + "/elpa-packages";
auto-sync-only = true;
exclude = [
# Use tarball, as it contains info
"org-transclusion"
"async"
"persist"
];
}
{
type = "archive-contents";
path = inputs.gnu-elpa-archive;
base-url = "https://elpa.gnu.org/packages/";
}
{
type = "elpa";
path = inputs.nongnu-elpa.outPath + "/elpa-packages";
}
{
type = "archive-contents";
path = inputs.nongnu-elpa-archive;
base-url = "https://elpa.nongnu.org/nongnu/";
}
];
overrides = tfinal: tprev: {
elispPackages = tprev.elispPackages.overrideScope (
final: prev: {
emacsql-sqlite = prev.emacsql-sqlite.overrideAttrs (old: {
buildInputs = old.buildInputs ++ [ pkgs.sqlite ];
postBuild = ''
cd sqlite
make
cd ..
'';
});
# TODO: Fix build of epdfinfo
pdf-tools = prev.pdf-tools.overrideAttrs (old: {
postInstall = (old.postInstall or "") + ''
cp -a ${pkgs.emacsPackages.pdf-tools}/share/emacs/site-lisp/elpa/pdf-tools-*/epdfinfo $out/share/emacs/site-lisp/epdfinfo
'';
});
jinx = prev.jinx.overrideAttrs (
old:
let
moduleSuffix = stdenv.targetPlatform.extensions.sharedLibrary;
in
{
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ pkgs.pkg-config ];
buildInputs = (old.buildInputs or [ ]) ++ [ pkgs.enchant2 ];
preBuild = ''
NIX_CFLAGS_COMPILE="$($PKG_CONFIG --cflags enchant-2) $NIX_CFLAGS_COMPILE"
$CC -I. -O2 -fPIC -shared -o jinx-mod${moduleSuffix} jinx-mod.c -lenchant-2
rm *.c *.h
'';
}
);
vterm = prev.vterm.overrideAttrs (old: {
nativeBuildInputs = [
cmake
stdenv.cc
];
buildInputs = old.buildInputs ++ [ libvterm-neovim ];
cmakeFlags = [ "-DEMACS_SOURCE=${emacs.src}" ];
preBuild = ''
mkdir -p build
cd build
cmake ..
make
install -m444 -t . ../*.so
install -m600 -t . ../*.el
cp -r -t . ../etc
rm -rf {CMake*,build,*.c,*.h,Makefile,*.cmake}
'';
});
}
);
};
env' = twist.lib.makeEnv {
inherit pkgs registries;
emacsPackage = emacs;
lockDir = ./lock;
initFiles = [
./emacs.el
./exwm.el
];
inputOverrides = {
helm = _: prev: {
packageRequires = prev.packageRequires // {
async = "0";
};
files = builtins.removeAttrs prev.files [
"helm-packages.el"
];
};
voicemacs = _: prev: {
packageRequires = {
avy = "0";
bind-key = "0";
cl-lib = "0";
company = "0";
company-quickhelp = "0";
default-text-scale = "0";
f = "0";
helm = "0";
json-rpc-server = "0";
nav-flash = "0";
porthole = "0";
yasnippet = "0";
el-patch = "0";
} // prev.packageRequires;
};
};
initialLibraries = [
"cl-lib"
"jsonrpc"
"let-alist"
"map"
"org"
"seq"
];
extraSiteStartElisp = ''
(add-to-list 'treesit-extra-load-path "${
pkgs.emacs.pkgs.treesit-grammars.with-grammars (
_:
pkgs.tree-sitter.allGrammars
)
}/lib/")
'';
initParser = twist.lib.parseUsePackages { inherit lib; } { alwaysEnsure = true; };
};
env = env'.overrideScope overrides;
in
{
packages.x86_64-linux.emacs = env;
};
}
(voicemacs :fetcher github :repo "jcaw/voicemacs")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment