go-minor-pin-overlay¶
Overlays
Pin a single Go package back one Go minor when it fails to build on the Go toolchain your nixpkgs currently ships — without waiting for upstream and without rolling back the whole nixpkgs pin.
The problem¶
nixpkgs bumps its default Go compiler (say 1.25 → 1.26) for the entire tree
at once. Most Go packages follow along fine. A few don't: cgo-heavy code, or
code that leans on unstable/internal Go APIs, often fails to compile on a
fresh minor for weeks until upstream catches up. gvisor's runsc is a
recurring example; caddy and others show up from time to time too.
Your options look bad: pin the whole nixpkgs back a Go generation (affects everything), or wait for a fix (blocks your build now).
The insight¶
nixpkgs keeps several Go minors packaged in parallel, exposed as
buildGo123Module, buildGo124Module, buildGo125Module, … alongside the
default buildGoModule. A Go package built with buildGoModule takes that
builder as an overridable argument. So you can rebuild just the one broken
package against an older, known-good minor and leave the rest of the tree on
the new default.
The entire fix is:
Because it's .override (re-invoking the package function with a swapped
argument), not overrideAttrs (patching the built derivation), the package
is rebuilt cleanly — passthru, tests, and the rest of the derivation stay
intact.
Usage¶
- Edit the two bindings at the top of
default.nix: package— the attribute name of the Go package (e.g."gvisor").-
goMinorBuilder— thebuildGoNNNModuleto pin to. Choose the last minor on which the package built (usually one below the new default). -
Add the overlay to your config:
- Rebuild. Remove the overlay once upstream builds on the new default.
To confirm which buildGoNNNModule aliases your nixpkgs exposes:
Caveats¶
-
Not every package accepts
buildGoModuleunder that name. If.override { buildGoModule = ...; }seems to do nothing, inspectprev.<pkg>.override.__functionArgsto find the real argument name the package'scallPackagesignature uses. -
Minor, not patch.
buildGoNNNModulepins a Go minor; the patch version is whatever nixpkgs currently ships for it. If you need an exact Go point release, build a custom module builder and pass that instead:
let
goPinned = prev.go_1_26.overrideAttrs (_: rec {
version = "1.26.2";
src = prev.fetchurl {
url = "https://go.dev/dl/go${version}.src.tar.gz";
hash = "sha256-..."; # fill in the real hash
};
});
buildGoPinned = prev.buildGoModule.override { go = goPinned; };
in {
yourpkg = prev.yourpkg.override { buildGoModule = buildGoPinned; };
}
- This is a stopgap. An older Go minor may miss security fixes present in the new default. Drop the pin as soon as the package builds on the current toolchain.
Source¶
overlays/go-minor-pin-overlay/default.nix
# go-minor-pin-overlay
#
# Pin a single Go package back one Go minor when it fails to build on the
# Go toolchain your nixpkgs currently ships.
#
# The trap: nixpkgs bumps the default Go (say 1.25 -> 1.26) for the whole
# tree at once. Most Go packages follow along fine, but some (cgo-heavy or
# using unstable/internal Go APIs — gvisor's `runsc`, occasionally caddy,
# etc.) don't compile cleanly on the new minor for a while. You don't have
# to wait for an upstream fix or roll the entire nixpkgs pin back: nixpkgs
# keeps several Go minors packaged in parallel as `buildGoNNNModule`
# (buildGo123Module, buildGo124Module, buildGo125Module, ...). A Go package
# built with `buildGoModule` takes that builder as an overridable argument,
# so you can swap in an older, known-good minor for that one package and
# leave everything else on the new default.
#
# The whole fix is a `.override { buildGoModule = prev.buildGoNNNModule; }`.
# Because it's an .override (not overrideAttrs), the package is re-invoked
# from scratch with the pinned builder — passthru, tests, and the rest of
# the derivation stay intact.
#
# This file is a plain nixpkgs overlay (`final: prev: { ... }`). Add it to
# your `nixpkgs.overlays` (NixOS) or `import nixpkgs { overlays = [ ... ]; }`.
#
# --- Parameters you edit -----------------------------------------------------
#
# package the attribute name of the Go package to pin, e.g. "gvisor"
# goMinorBuilder the buildGoNNNModule to pin it to, e.g. "buildGo125Module"
#
# Pick `goMinorBuilder` = the last minor on which the package built. Check
# what your nixpkgs exposes with: nix eval nixpkgs#buildGo125Module --apply builtins.typeOf
# (or grep pkgs/development/compilers/go for the buildGoNNNModule aliases).
let
# Edit these two lines for your package.
package = "gvisor";
goMinorBuilder = "buildGo125Module";
in
final: prev: {
${package} = prev.${package}.override {
buildGoModule = prev.${goMinorBuilder};
};
}
# --- Notes / variations ------------------------------------------------------
#
# * Multiple packages: repeat the attr, or fold a list:
#
# let pins = { gvisor = "buildGo125Module"; foo = "buildGo124Module"; };
# in final: prev:
# builtins.mapAttrs
# (name: builder: prev.${name}.override { buildGoModule = prev.${builder}; })
# pins
#
# * Exact patch version (not just a minor): if you need a specific Go point
# release rather than whatever `buildGoNNNModule` currently pins, build a
# custom builder first and pass that instead — see README "Caveats".
#
# * Some packages call the builder under a different argument name
# (e.g. `buildGo123Module` directly in their `callPackage` signature). If
# `.override { buildGoModule = ...; }` has no effect, inspect the package's
# `override.__functionArgs` to find the real argument name.