stub-abandoned-package-overlay¶
Overlays
Replace an abandoned / no-longer-building package with an empty stub derivation so a dangling reference in your closure resolves without compiling anything — instead of hunting down every consumer that drags it in.
The problem¶
Your system or user closure still references a package that no longer builds:
upstream abandoned it, or it fails to build against your current nixpkgs pin.
You never install it directly — it arrives transitively. A classic case is a
terminfo aggregation that enumerates every terminal emulator (termite
among them) just to collect their terminfo entries. One dead leaf makes the
whole thing refuse to build or evaluate.
Tracking down and patching every consumer is tedious and brittle. The reference doesn't need a working package — it just needs a package. So give it an empty one.
The insight / trap¶
A package attribute is frequently used two ways at once:
- as a derivation itself —
pkgs.termite(the binary), and - via its sub-attributes —
pkgs.termite.terminfo(its terminfo files).
A naive stub (runCommand "…" {} "mkdir -p $out") only satisfies the first.
Any consumer reaching for .terminfo (or any other sub-attribute) then hits an
attribute missing error. The fix is the // operator: graft the extra
sub-attributes onto the stub derivation so both shapes of reference resolve:
final: prev: {
termite = prev.runCommand "termite-stub" { } "mkdir -p $out" // {
terminfo = prev.runCommand "termite-terminfo-stub" { } ''
mkdir -p $out/share/terminfo
'';
};
}
That empty derivation "builds" in milliseconds and the dead reference is gone.
Usage¶
default.nix exposes a small helper so you can parameterize the package name
and its sub-attributes:
let stub = import ./default.nix;
in {
nixpkgs.overlays = [
(stub {
name = "termite";
# sub-attributes consumers also touch -> relative dirs to create in $out
subAttrs.terminfo = [ "share/terminfo" ];
})
];
}
Options:
| arg | meaning |
|---|---|
name |
attribute name of the abandoned package to replace |
subAttrs |
attrset subAttrName -> [relativeDirs]; each becomes an empty grafted stub |
Pass subAttrs = {} (the default) if nothing reaches into the package's
sub-attributes.
Prefer no abstraction? The minimal copy-paste overlay at the bottom of
default.nix is the entire pattern — hardcode your package name and drop it
into nixpkgs.overlays.
Caveats¶
- This is a workaround, not a fix. The stub provides no actual files beyond the empty directories you ask for. If something genuinely uses the package at runtime, stubbing it will break that use — this is only safe when the reference is vestigial (a build-time enumeration, a dangling default, etc.).
- Prefer the narrowest stub. Only create the sub-attributes and directories that are actually referenced, so you don't accidentally mask a real regression.
- Once upstream (or your nixpkgs pin) provides a building version again, drop the overlay so you get the real package back.
Source¶
overlays/stub-abandoned-package-overlay/default.nix
# Stub an abandoned package so a dangling closure reference resolves without
# building anything.
#
# Problem: your system/user closure still references a package that no longer
# builds (upstream-abandoned, or it FTBFS on your nixpkgs pin), even though you
# never install it directly — it gets pulled in transitively (e.g. a terminfo
# aggregation that lists every terminal emulator). Chasing down every consumer
# is tedious and fragile. Instead, replace the package with an empty stub
# derivation so the reference resolves to something that "builds" instantly.
#
# The key trap: a package attribute is often used both as a derivation AND
# via its sub-attributes (e.g. `pkg` for the binary, `pkg.terminfo` for its
# terminfo files). A plain `runCommand` stub only satisfies the first. Use the
# `//` operator to graft the extra sub-attributes onto the stub so BOTH kinds
# of reference resolve.
#
# Two ways to use this file:
#
# 1. Import the helper and build a stub overlay for your package(s):
#
# let stub = import ./default.nix;
# in {
# nixpkgs.overlays = [
# (stub {
# name = "termite";
# # sub-attributes that consumers also reference; the value is the
# # relative path(s) to create inside the stub's $out.
# subAttrs.terminfo = [ "share/terminfo" ];
# })
# ];
# }
#
# 2. Copy the tiny overlay at the bottom of this file and hardcode your
# package name — that is all the original real-world use amounted to.
# ── The helper ──────────────────────────────────────────────────────────────
#
# stub { name; subAttrs ? {}; } -> overlay (final: prev: { ... })
#
# name : attribute name of the abandoned package to replace.
# subAttrs : attrset mapping sub-attribute name -> list of relative dirs to
# create inside that sub-attribute's $out. Each entry becomes an
# empty derivation grafted onto the stub via `//`.
{ name, subAttrs ? { } }:
final: prev:
let
# An empty derivation that just makes an (optionally populated) $out.
emptyDrv = drvName: dirs:
prev.runCommand drvName { } (
if dirs == [ ]
then "mkdir -p $out"
else "mkdir -p " + prev.lib.concatMapStringsSep " "
(d: "$out/" + d) dirs
);
# Build the sub-attribute stubs, e.g. { terminfo = <drv>; }.
subDrvs = prev.lib.mapAttrs
(attr: dirs: emptyDrv "${name}-${attr}-stub" dirs)
subAttrs;
in
{
# `//` grafts the sub-attribute stubs onto the top-level stub derivation, so
# both `pkgs.${name}` and `pkgs.${name}.<subAttr>` resolve.
${name} = emptyDrv "${name}-stub" [ ] // subDrvs;
}
# ── Minimal copy-paste version (no helper) ───────────────────────────────────
#
# If you only need to stub one package with one sub-attribute, the whole thing
# is just this overlay — drop it straight into `nixpkgs.overlays`:
#
# final: prev: {
# termite = prev.runCommand "termite-stub" { } "mkdir -p $out" // {
# terminfo = prev.runCommand "termite-terminfo-stub" { } ''
# mkdir -p $out/share/terminfo
# '';
# };
# }