Skip to content

Instantly share code, notes, and snippets.

@Integralist
Last active January 13, 2022 12:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Integralist/0f11d3d94bcf564dcd7ad414a2611d83 to your computer and use it in GitHub Desktop.
Save Integralist/0f11d3d94bcf564dcd7ad414a2611d83 to your computer and use it in GitHub Desktop.
[Go Module Versioning] #go #golang #modules #versioning #semver

Reference: https://blog.golang.org/v2-go-modules

You have a dependency you want to use, like github.com/integralist/delete-just-testing.

// delete-just-testing/foo/foo.go 
//
package foo

import "fmt"

func Bar() {
	fmt.Println("bar")
}

That dependency has the following go.mod file:

module github.com/integralist/delete-just-testing

go 1.15

Note: Major version suffixes are not allowed at major versions v0 or v1 (e.g. you can't do module github.com/integralist/delete-just-testing/v1). There is no need to change the module path between v0 and v1 because v0 versions are unstable and have no compatibility guarantee. Additionally, for most modules, v1 is backwards compatible with the last v0 version; a v1 version acts as a commitment to compatibility, rather than an indication of incompatible changes compared with v0. -- official reference.

We want to create a new program, so we start by initializing a new go module for our project:

go mod init testing_gomodules

We create an app.go main package:

package main

import (
	"fmt"

	"github.com/integralist/delete-just-testing/foo"
)

func main() {
	fmt.Println("main")

	foo.Bar()
}

We execute go run app.go and find our go.mod file is updated:

$ go run app.go
go: finding module for package github.com/integralist/delete-just-testing/foo
go: downloading github.com/integralist/delete-just-testing v0.0.0-20200921145455-530f3130809d
go: found github.com/integralist/delete-just-testing/foo in github.com/integralist/delete-just-testing v0.0.0-20200921145455-530f3130809d
main
bar

The go.mod file for our project now looks like:

module testing_gomodules

go 1.15

require github.com/integralist/delete-just-testing v0.0.0-20200921145455-530f3130809d // indirect

Note: when I was running through this example I had pulled in the dependency into my project before I had actually assigned a v1.0.0 tag, so the go toolchain generates a pseudo-version for you. If I had tagged the commit with v1.0.0 before trying to use the dependency then instead of the pseudo-version I would have seen v1.0.0 after the module path.

If we (as the dependency author) want to update our code to be v2, then we have two strategies:

  1. create a new v2/ directory and copy our package into it (this is for backwards compatibility with go versions below 1.13 that don't understand go modules).
  2. just rename the module in the go.mod file (which will require you to update all explicit imports in your code's test files + consumers will need to do the same).

Note: go get -u doesn't help consumers of our dependency get the latest major version. If you run go list -u -m all you'll see minor and patch updates but not major version updates. This goes back to how the go team perceive v2 and higher major version releases (i.e. they should be a different directory or new module path).

Now let's say we take the second approach of just changing the go.mod name in our dependency repository:

module github.com/integralist/delete-just-testing/v2 // << updated to append /v2

go 1.15

Then the consumer will need to update their import path to get that change:

package main

import (
	"fmt"

	"github.com/integralist/delete-just-testing/v2/foo" // << updated to include /v2
)

func main() {
	fmt.Println("main")

	foo.Bar()
}

...they'll also discover they have an updated go.mod:

module testing_gomodules

go 1.15

require (
	github.com/integralist/delete-just-testing v0.0.0-20200921145455-530f3130809d
	github.com/integralist/delete-just-testing/v2 v2.0.0
)

You'll see we have the tagged v2.0.0 pulled in alongside the original. Running go mod tidy will remove the old version.

Note: if you change the import path back to the non /v2 version you'll discover the old v0.0.0... pseudo-version is put back into your go.mod file and you can again use go mod tidy to remove the v2.0.0 dependency. This is cool, because although the old pre-v2 code doesn't exist any more at HEAD in the dependency's master branch, the go toolchain is still able to retrieve it.

Easy module path update in Vim

Imagine you're using a package called go-fastly and it's currently defined at version 2.0.0 so it uses /v2 in its module path. But then an upgrade to 3.0.0 is released so you need to bump your imports from v2 to reference v3.

:Ack! --go 'go-fastly/v2'
:cdo s/v2/v3/ | update

If you don't have Ack! because you're using a basic vim configuration (e.g. vim -u ~/.vimrc-basic), then use the following instead...

:vimgrep /go\-fastly\/v2/j fastly/*go
:copen
:cdo s/v2/v3/ | update

NOTE: The reason to use vim -u ~/.vimrc-basic is because of vim-go and the golang LSP can cause the Vim quickfix window to get updated (various warnings/errors etc about the code) and this means :cdo fails to complete as the quickfix list it was working with has changed. So using a basic Vim configuration means I can use :cdo and have the files updated in a fraction of the time as no LSP means much faster processing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment