Skip to content

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:

  1. 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. The a//b/ prefixes never carry the module segment, so patch -p1 looks for net/foo.go and finds nothing.

  2. 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:

  1. chmod -R +w the exact vendor subtree you're about to patch.
  2. sed-rewrite the a//b/ prefixes so they include the module qualifier.
  3. 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't chmod -R +w vendor wholesale.
  • 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 the sed to cover each prefix or split it — -p1 only strips one leading segment, so every a//b/ in the diff must gain the same module/ 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;
}