From 1449b95f1e0093eb24ec5fcd205f845d36811986 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 4 Sep 2026 14:59:01 +0000 Subject: [PATCH] tools/bump: drop dependencies that outrun the nixpkgs Go tailscale.com@main raised the go directive to 1.27.1 while nixpkgs was still on 1.27.0. go build downloads the newer toolchain and looks fine; the nix builders set GOTOOLCHAIN=local and fail, so the whole bump was being thrown away one area at a time. --- tools/bump/gomod.go | 39 +++++++++++++++++++++++++++++++++++++++ tools/bump/verify.go | 23 ++++------------------- 2 files changed, 43 insertions(+), 19 deletions(-) diff --git a/tools/bump/gomod.go b/tools/bump/gomod.go index 255d3674..204ac921 100644 --- a/tools/bump/gomod.go +++ b/tools/bump/gomod.go @@ -9,6 +9,7 @@ import ( "strings" "golang.org/x/mod/modfile" + "golang.org/x/mod/semver" ) // Modules whose versions are not independent. Each pair moves as one unit or @@ -335,6 +336,39 @@ func noteAttached(f *modfile.File, module, needle string) bool { return false } +var errToolchainAhead = errors.New("dependencies require a newer Go than the devShell provides") + +// checkToolchain catches a dependency that dragged go.mod's go directive above +// the toolchain nixpkgs ships. +// +// The go command papers over this by downloading the newer toolchain, so +// `go build` succeeds and nothing looks wrong. The nix builders set +// GOTOOLCHAIN=local and fail outright, which is why this has to be an explicit +// check rather than something the build would surface on its own. +func checkToolchain(ctx context.Context, r *repo) error { + goMod, err := r.readFile("go.mod") + if err != nil { + return err + } + + want, err := goDirective(goMod) + if err != nil { + return err + } + + have, err := goVersion(ctx, r) + if err != nil { + return err + } + + if semver.Compare("v"+want, "v"+have) > 0 { + return fmt.Errorf("%w: go.mod now requires go %s, the devShell provides %s", + errToolchainAhead, want, have) + } + + return nil +} + // settle runs the steps every dependency change needs before it can be judged: // tidy, restore the lockstep pins that the upgrade may have disturbed, tidy // again, then assert go.mod's hand-written rules survived. @@ -359,6 +393,11 @@ func settle(ctx context.Context, r *repo) error { return err } + err = checkToolchain(ctx, r) + if err != nil { + return err + } + return checkModComments(r) } diff --git a/tools/bump/verify.go b/tools/bump/verify.go index 81df23fc..58eb7cae 100644 --- a/tools/bump/verify.go +++ b/tools/bump/verify.go @@ -116,30 +116,15 @@ func verifyBuilders(ctx context.Context, r *repo) []finding { return findings } -// verifyToolchain reports, but never edits, a go.mod directive that has fallen -// behind the toolchain the build actually uses. Raising it is a promise to -// downstream packagers, so it stays a human decision. +// verifyToolchain reports, but never edits, a go directive that has outrun the +// toolchain nixpkgs ships. Raising it is a promise to downstream packagers, so +// the decision stays with a human; lowering it is not this tool's call either. func verifyToolchain(ctx context.Context, r *repo) []finding { - nixGo, err := goVersion(ctx, r) + err := checkToolchain(ctx, r) if err != nil { return []finding{finding(err.Error())} } - ourMod, err := r.readFile("go.mod") - if err != nil { - return []finding{finding(err.Error())} - } - - ourGo, err := goDirective(ourMod) - if err != nil { - return []finding{finding(err.Error())} - } - - if semver.Compare("v"+ourGo, "v"+nixGo) > 0 { - return []finding{finding(fmt.Sprintf( - "go.mod requires go %s but the devShell provides %s", ourGo, nixGo))} - } - return nil }