Major Versions in Go Modules Explained
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
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
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
We would rather have the tagged version, like:
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):
This may seem obscure at right now. The next step will show why this is needed.
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
We will need to update our imports to the new package name (including test files):
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.
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
test while refactoring.
Now commit all changes including the
Updating The Packages That Depend On Luigi
For any packages that rely on
luigi, we have to update them to point to
There are two ways to do this:
- Simply run (notice the version is now in the package name):
go get email@example.com
- Or, edit the
go.modmanually and change the version and package name, from:
When you run
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.
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
Let’s say I had to manage both
v7 (the latest) of
luigi. Before embarking on the changes above you should copy the
go.sum into a folder called
cp go.mod go.sum v6
Any consumers that wish to remain on v6can with the import:
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.