Nix Plugin (resolveGoPackages)
Default mode needs to know the Go package graph at eval time so it can
turn each package into a separate derivation. Nix has no builtin that can
run go list, so go2nix ships a Nix plugin that adds one:
builtins.resolveGoPackages.
If you only use experimental mode (buildGoApplicationExperimental), you
do not need the plugin — that mode discovers the graph at build time inside
a recursive-nix sandbox.
What it provides
The plugin registers a single primop:
builtins.resolveGoPackages {
src = ./.; # source tree
subPackages = [ "./..." ]; # optional, default ["./..."]
modRoot = "."; # optional
tags = [ ]; # optional build tags
goos = null; # optional cross target
goarch = null; # optional cross target
goProxy = null; # optional GOPROXY override
cgoEnabled = null; # optional CGO_ENABLED value
doCheck = false; # also resolve test-only deps
resolveHashes = false; # also compute module NAR hashes
}
go is intentionally omitted: the plugin defaults to the Go toolchain baked
in at its own build time, so the call carries no derivation context and the
default-mode evaluation stays IFD-free. You may pass
go = "/path/to/bin/go" to override the toolchain (e.g. a nix-shell Go).
A derivation-backed value like "${pkgs.go}/bin/go" is accepted but emits a
warning and forces the plugin to realise the derivation at eval time — i.e.
opt-in IFD, still gated by allow-import-from-derivation.
It runs go list -json -deps against src and returns:
packages— third-party package metadata (modKey,subdir,imports,drvName,isCgo)localPackages— local package metadata (dir,localImports,thirdPartyImports,isCgo)modulePath— the main module’s import pathgoVersion— the main module’sgodirective (e.g."1.25"); the dag builder threads this as-langto local-package compilesreplacements—replacedirectives fromgo.modtestPackages— test-only third-party packages (whendoCheck = true)moduleHashes— module NAR hashes (whenresolveHashes = true; see Lockfile-free builds)
You normally never call this directly — buildGoApplication does.
Architecture
The plugin lives under packages/go2nix-nix-plugin/ and is built in two
halves:
- a Rust core (
rust/) that wrapsgo list, parses its JSON output, classifies packages and computes module hashes; - a C++ shim (
plugin/resolveGoPackages.cc) that registers the primop with the Nix evaluator and marshals the Rust output back into Nix values.
Nix’s plugin ABI is unstable across releases, so the plugin must be built
against the same Nix version you evaluate with. The package defaults to
nixVersions.nix_2_34; override nixComponents in
packages/go2nix-nix-plugin/default.nix to match your Nix.
Loading the plugin
Build it from this flake:
nix build github:numtide/go2nix#go2nix-nix-plugin
Then make the evaluator load it. Either set it globally in nix.conf:
plugin-files = /nix/store/.../lib/nix/plugins/libgo2nix_plugin.so
or pass it per-invocation:
nix build --option plugin-files /nix/store/.../lib/nix/plugins/libgo2nix_plugin.so .#my-app
The latter is what the bench-incremental harness does internally.
Loading the plugin from a flake
Rather than hand-pasting a store path, derive it from the flake input.
On NixOS:
{ inputs, pkgs, ... }: {
nix.settings.plugin-files = [
"${inputs.go2nix.packages.${pkgs.system}.go2nix-nix-plugin}/lib/nix/plugins/libgo2nix_plugin.so"
];
}
Ad-hoc on the command line:
nix build .#my-app \
--option plugin-files \
"$(nix build --no-link --print-out-paths github:numtide/go2nix#go2nix-nix-plugin)/lib/nix/plugins/libgo2nix_plugin.so"
If the plugin is not loaded, evaluating buildGoApplication fails with:
error: attribute 'resolveGoPackages' missing
Purity
builtins.resolveGoPackages is impure: it shells out to go list, which
reads GOMODCACHE for module sources (and may consult GOPROXY for module
metadata). The Nix evaluator does not cache its result across
invocations, so the plugin runs once per evaluation.
This is the dominant per-eval cost of default mode; see Incremental Builds for timings.