godependency-management

Preventing go.mod changes from running `go fmt ./...`


I'm running a GitHub pipeline that (among other tests) ensures that code is properly formatted. I do that by running go fmt ./... on my source and then checking that this doesn't change anything with git diff -q. In addition to that, I'm running the pipeline using different versions of Go (1.20 to 1.22) to ensure compatibility. Please take into consideration that I may be misunderstanding the content of the go.mod file and that I'm not using it properly to express my intentions.

Anyhow, the format check used to work fine in the past, but I'm having issues with it now. I can't get this to work without triggering unwanted changes to go.mod. Apart from the actual dependencies, I have this in the go.mod:

module my-module
 
go 1.20

require (
   ....
)

My intention with the go 1.20 is to mark this as compatible with version 1.20. If I run go fmt ./... with Go 1.22, it fails and tells me to run go mod tidy first. Doing so, changes go.mod to this:

module my-module
 
go 1.22

toolchain go1.22.4

require (
   ....
)

This conflicts with my intentions, because Go 1.20 doesn't recognize toolchain and fails on that then.

Questions

  1. Is the go directive in go.mod even the correct way to say that the module is compatible with that Go version?
  2. Is there a way I could run go fmt with different versions as described above?
  3. I'm wondering, should the behaviour of Go 1.22's go mod tidy or go get be considered a bug? After all, it's actively ignoring the go 1.20 directive and breaking the code for older versions.

Workaround

As a workaround, I could exclude changes to go.mod from my checks in the pipeline. That's IMHO not a proper fix but rather a hack though. In any case, I have a feeling that I don't fully understand Go's dependency management infrastructure yet, so if you think I don't, give me a nudge in the right direction! :)

Example For Broken Dependency

  1. Create a new directory go-version-test.
  2. Change into that directory.
  3. Run go mod init go-version-test using Go 1.20.
  4. Create main.go as below.
  5. Run go get -u -v using Go 1.20.
  6. Run go mod tidy -go=1.20 using Go 1.22.

The content of main.go is:

package main

import (
    "go.mongodb.org/mongo-driver/mongo"
)

func main() {
    // Note: This won't compile, but that doesn't matter!
    _, _ := mongo.Connect(ctx, opts)
}

The last command will generate an error:

go: github.com/youmark/pkcs8@v0.0.0-20240424034433-3c2c7870ae76 requires go@1.22, but 1.20 is requested

As I understand it, the MongoDB driver package, pulls in the pkcs8 package. Both work well with Go 1.20, but the pkcs8 package has a go 1.22 directive in its go.mod file. Others already reported this as an issue, too.


Solution

  • This is a change introduced with go 1.21:

    So my guess would be that you have some module requiring go 1.22. It might or might not still work with go 1.20, but for example be aware of the loopvar experiment - programs written for go 1.22 might compile with go 1.20, but do not run correctly.

    In order to find the dependency requiring Go 1.22, you can run go mod tidy -go=1.20 using Go 1.22. This will give you an error in the form go: some/dependent/package requires go@1.22, but 1.20 is requested.