Major Versions in Go Modules Explained

Elliot Chance
4 min readMar 29, 2019
Photo by Samuel Zeller on Unsplash

Go Modules is still a relatively new feature and some of the documentation surrounding it is detailed and technical, but not easily digestible as a quick start.

One issue I ran into is dealing with packages that have a version of 2.x.x or higher. It’s not immediately obvious but Go modules handles packages that have a major version that is not v0.x.x or v1.x.x in a special way.

I won’t go into why this is so. The bottom line is that if you publish a package with a version of v2.0.0+ or publish a new major version there are some extra steps in your package and the packages that depend on it.

In this example I will be publishing v7.0.0 of github.com/kounta/luigi (one of our private repositories, but you can substitute the name with your own). It already uses go modules, but it has never had its major version taken into special consideration. This causes packages that depend on it to force a specific version in their go.mod:

github.com/kounta/luigi v0.0.0–20190328234950-d6354a0da43d

We would rather have the tagged version, like:

github.com/kounta/luigi v7.0.1

The odd thing is that if I change the version manually (in go.mod), Go will just revert it back to the specific hash and checksum. What’s going on!?

Before we start there is an important consideration here. If you want to support multiple major versions or not. In my case we only want to support the current major version since it is an internal library. Any projects that depend on it will eventually need to update their dependencies which will trigger them to update luigi to the latest version.

However, if you need to support multiple major versions at the same time I will explain this separately later in Maintaining Multiple Major Versions.

Updating the Package

We already have a go.mod file. We have to modify the first line of go.mod to include the major version (notice we do not care about the minor or patch):

module github.com/kounta/luigi/v7

This may seem obscure at right now. The next step will show why this is needed.

Run go build(or go test if you have tests) and you will receive errors like:

./log_entry_test.go:14:14: undefined: luigi.NewLogEntry
./log_http_test.go:15:78: undefined: luigi.Environment

This is because we have changed the name of the current package. Technically Go is giving these errors because there is no longer a luigi@v0.x.x or luigi@v1.0.0 available.

We will need to update our imports to the new package name (including test files):

import “github.com/kounta/luigi/v7”

My first through was, “Oh, no! Will I have to reference v7.xinstead of luigi.xeverywhere?” Actually, no. Only the import name changes, and Go understands we are talking about the version on the end. Phew.

It could be painful if you have many imports to update. A global search and replace might be necessary.

Once go build/test passes you should run go mod tidy, not only because it’s a good idea but also because it will remove any references to the old version of this package that might have been added from a go build/test while refactoring.

Now commit all changes including the go.mod and go.sum.

Updating The Packages That Depend On Luigi

For any packages that rely on luigi, we have to update them to point to v7 explicitly.

There are two ways to do this:

  1. Simply run (notice the version is now in the package name):
    go get github.com/kounta/luigi/v7@v7.0.1
  2. Or, edit the go.mod manually and change the version and package name, from:
    github.com/kounta/luigi v0.0.0–20190328234950-d6354a0da43d
    to:
    github.com/kounta/luigi/v7 v7.0.1

When you run go build/test you will get the same kind of error as before. It requires the same fix. That is, changing the import names to include the version number on the end.

Finally, run go mod tidy for good measure. You are done!

Maintaining Multiple Major Versions

If you need to support multiple major versions at the same time you will need to use multiple go.mod files.

Let’s say I had to manage both v6 and v7 (the latest) of luigi. Before embarking on the changes above you should copy the go.mod and go.sum into a folder called v6:

mkdir v6
cp go.mod go.sum v6

Any consumers that wish to remain on v6can with the import:

import “github.com/kounta/luigi/v6”

Go will automatically resolve this to the versioned folder in the repository. Easy. It’s also a nice way to indicate how many major versions back your package officially supports.

Originally published at http://elliot.land on March 29, 2019.

--

--

Elliot Chance

I’m a data nerd and TDD enthusiast originally from Sydney. Currently working for Uber in New York. My thoughts here are my own. 🤓 elliotchance@gmail.com