Announcing GoReleaser v2.5 - multi languages, 9th anniversary edition
Merry Christmas - the last release of 2024 is here!
I thought I would share some quick bits about how to do go.mod
version bumps.
Modules start in v0.0.0
by default.
If you never tag them, their version will be something like
v0.0.0-{timestamp}-{commithash}
, for example:
require github.com/fake/mod v0.0.0-20240514230400-03fa26f5508f
If you want to tag a v0.1.0
, for example, tagging is all that you need to do.
Users would then be able to get it with:
go get github.com/fake/mod@v0.1.0
v1
You can tag v1.0.0
without any other changes:
Users would get it with:
go get github.com/fake/mod@v1.0.0
Now, here things start to get a bit more complicated.
Let’s say we want to tag v2.0.0
.
We will have to also change the module path, adding a /v2
to it.
module github.com/fake/mod/v2
Users would have to require the module adding the /v2
path, and the @v2.0.0
version:
module github.com/fake/app
require github.com/fake/mod/v2 v2.0.0
Users would get it with:
go get github.com/fake/mod/v2@v2.0.0
That looks a bit repetitive, I know.
If you want to keep both v1
and v2
, you might want to create a v2
branch, and
work on your v2 there.
You can read more about it here.
One side effect of changing your module path is that you now need to change all your imports as well.
You need to be careful to do this before actually tagging, otherwise it might
happen that your v2
will depend on your v1
.
I recently launched GoReleaser v2
, and had probably thousands of import
statements to update.
I ended up updating them all with this script:
#!/usr/bin/env bash
#
# ./gomod-rename example.com/old/module example.com/new-module
#
go mod edit -module "${2}"
find . -type f -name '*.go' -exec sed -i -e "s,\"${1}/,\"${2}/,g" {} \;
find . -type f -name '*.go' -exec sed -i -e "s,\"${1}\",\"${2}\",g" {} \;
Run it as:
./gomod-rename github.com/goreleaser/goreleaser github.com/goreleaser/goreleaser/v2
And that was it.
At Charm, we have our very own charmbracelet/x repository, which contains many modules that we deem either not worth or not ready to be their own repositories yet.
Here’s an example go.mod
:
module github.com/charmbracelet/x/ansi
To version it, it is recommended to include the module name in the tag:
git tag ansi/v0.1.0
Then, users would get it with:
go get github.com/charmbracelet/x/ansi@v0.1.0
I love that I don’t have to repeat the
ansi/
bit in the version.
The Go module system knows to search for the ansi/
tag prefix.
This is pretty much how GoReleaser’s monorepo feature works,
by the way.
Finally, in the go.mod
file, it’ll look like this:
require github.com/charmbracelet/x/ansi v0.1.0
Sometimes you fuck things up. Just recently, I did fuck up in caarlos0/env: I pushed what could in some cases be a breaking change in a patch release, without realizing it.
Luckily, you can easily retract versions:
module github.com/caarlos0/env/v11
retract v11.0.1 // explain why it is being retracted.
Then, if users try to get it:
go: warning: github.com/caarlos0/env/v11@v11.0.1: retracted by module author: explain why it is being retracted.
go: to switch to the latest unretracted version, run:
go get github.com/caarlos0/env/v11@latest
This is a good way to communicate to your users when you accidentally pushed a bad version.
That’s all folks. 🎉
Just some quick tips, hopefully useful for more people than just myself a couple of months from now.