go-vendor-patch-prefix-rewrite¶
Overlays
Apply an upstream patch to a Nix-vendored Go dependency whose vendor-tree layout doesn't match the patch's path prefixes.
The problem¶
You want to carry a small upstream fix for a Go library that your package pulls
in as a vendored dependency. You grab the patch from the library's repo. It
applies cleanly there but fails inside your Nix build with something like
can't find file to patch.
Two things bite you, and they compound:
-
Prefix mismatch. The patch is authored against the dependency's own repo layout — its diff headers read
a/net/foo.go,b/net/foo.go. But once vendored, that same file lives under a module-qualified path:vendor/example.com/dep/net/foo.go. Thea//b/prefixes never carry the module segment, sopatch -p1looks fornet/foo.goand finds nothing. -
Read-only vendor tree. The
vendor/directory produced by Nix's Go fetcher (fetchVendorDir/vendorHash) is read-only. Even a correctly targeted patch fails to write into it.
The fix¶
In order:
chmod -R +wthe exact vendor subtree you're about to patch.sed-rewrite thea//b/prefixes so they include the module qualifier.- Pipe the rewritten patch into
patch -d vendor -p1.
The -d vendor -p1 combination is what makes it resolve: -p1 strips the
leading a/ / b/, leaving <module>/<subpath>/..., and -d vendor roots
that at the vendor directory — landing exactly on
vendor/<module>/<subpath>/....
Usage¶
Import default.nix as an overlay (or copy the patchVendoredGoDep helper into
your own). The helper emits the chmod + sed + patch shell snippet; drop it into
an overrideAttrs buildPhase (or postPatch):
buildPhase = ''
if [ -f patches/my-upstream-fix.patch ]; then
${patchVendoredGoDep {
patch = "patches/my-upstream-fix.patch";
module = "example.com/somedep"; # vendored module import path
subpath = "internal/thing/"; # path inside the dep the patch touches
}}
fi
go build -mod=vendor -o out ./cmd/tool
'';
Options¶
| arg | meaning |
|---|---|
patch |
path to the upstream patch, as authored against the dependency repo. |
module |
Go module import path the dep is vendored under (example.com/foo). |
subpath |
path inside the dep repo that the patch touches (internal/thing/). Keep the trailing slash. |
vendorDir |
vendor root relative to the source. Defaults to vendor. |
The overlay also re-exports the helper itself as pkgs.patchVendoredGoDep, so a
downstream overlay can inherit (prev) patchVendoredGoDep; instead of copying
it. (The example-go-package attribute in default.nix is a worked example,
not something you are meant to build — replace it with your own package.)
Caveats¶
- Keep the
if [ -f ... ]guard. After an upstream bump the fix may land in the pinned version and the patch becomes redundant. Guarding on the file's existence means dropping the patch doesn't break the build. - Scope the chmod narrowly.
chmod -R +w vendor/<module>/<subpath>touches only what you patch. Don'tchmod -R +w vendorwholesale. - Bumping the dep resets the vendor hash. Any change to the dependency set
invalidates
vendorHash; the patch step is independent of that but runs after the vendor tree exists. - Multi-file patches: if one patch touches several
subpaths, either widen thesedto cover each prefix or split it —-p1only strips one leading segment, so everya//b/in the diff must gain the samemodule/qualifier.
Source¶
overlays/go-vendor-patch-prefix-rewrite/default.nix
# go-vendor-patch-prefix-rewrite
#
# Apply an upstream patch to a Nix-vendored Go dependency.
#
# The trap this solves: an upstream patch is authored against the DEPENDENCY's
# own repo layout (e.g. "a/net/foo.go"), but once that dependency is vendored
# into your module it lives under a module-qualified path
# (e.g. "vendor/example.com/dep/net/foo.go"). The a//b/ prefixes in the patch
# therefore never match the vendor tree, and `patch` fails with
# "can't find file to patch". On top of that, the vendor directory produced by
# the Nix Go fetcher is READ-ONLY, so even a correctly-targeted patch fails to
# write.
#
# The fix, in order:
# 1. chmod -R +w the exact vendor subtree you touch (fetcher output is r/o).
# 2. sed-rewrite the a//b/ prefixes in the patch so they carry the vendor
# module qualifier.
# 3. Feed the rewritten patch to `patch -d vendor -p1`.
#
# This file exposes a reusable helper `patchVendoredGoDep` (a shell snippet
# generator) plus an example overlay showing how to wire it into an
# overrideAttrs buildPhase. Import the overlay into nixpkgs, or copy the helper.
final: prev:
let
# patchVendoredGoDep :: attrs -> string (shell)
#
# Emits a shell snippet that rewrites an upstream patch's path prefixes to the
# vendored module layout and applies it. Call it inside a buildPhase/postPatch,
# once per patch.
#
# Arguments:
# patch : path to the upstream patch file (as authored against the dep).
# module : the Go module import path the dep is vendored under, e.g.
# "example.com/foo" or "github.com/you/bar". This is the prefix
# that gets inserted between "a/"/"b/" and the in-repo path.
# subpath : the path INSIDE the dependency repo that the patch touches,
# e.g. "net/http/". Used both for the sed rewrite and to
# scope the chmod. Keep the trailing slash.
# vendorDir : vendor root, relative to the source. Defaults to "vendor".
#
# The rewrite turns:
# a/<subpath> -> a/<module>/<subpath>
# b/<subpath> -> b/<module>/<subpath>
# so that `patch -d vendor -p1` resolves to vendor/<module>/<subpath>.
patchVendoredGoDep =
{
patch,
module,
subpath,
vendorDir ? "vendor",
}:
''
# 1. The Go fetcher's vendor tree is read-only; make the touched subtree writable.
chmod -R +w ${vendorDir}/${module}/${subpath}
# 2. Rewrite a//b/ prefixes to the vendored module layout, then apply.
# -p1 strips the leading a//b/ so paths resolve under ${vendorDir}/.
sed 's|a/${subpath}|a/${module}/${subpath}|g;s|b/${subpath}|b/${module}/${subpath}|g' \
${patch} | patch -d ${vendorDir} -p1
'';
in
{
# Example: patch a vendored dependency inside a Go package build.
#
# Replace `example-go-package` with the attribute you are overriding, and the
# patchVendoredGoDep arguments with your dependency's real module/subpath.
#
# The `if [ -f ... ]` guard keeps the build working if the patch is dropped
# later (e.g. after an upstream bump makes it redundant) without editing here.
example-go-package = prev.example-go-package.overrideAttrs (old: {
buildPhase = ''
# (env such as GOOS/GOARCH goes here if you cross-compile, e.g. wasm)
if [ -f patches/my-upstream-fix.patch ]; then
${patchVendoredGoDep {
patch = "patches/my-upstream-fix.patch";
module = "example.com/somedep"; # the vendored module import path
subpath = "internal/thing/"; # path inside the dep the patch touches
}}
fi
go build -mod=vendor -o out ./cmd/tool
'';
});
# Re-export the helper so downstream overlays can reuse it directly:
# inherit (pkgs) patchVendoredGoDep;
inherit patchVendoredGoDep;
}