Astute readers using panicparse will ask themselves: Marc-Antoine, haven’t you released v2.0.1 several months ago and now you are releasing v1.6.0?
Yep I did.
But oh boy I didn’t know what was coming.
I’m the kind of person who likes to support things for a long time. I don’t enjoy breaking my users. In short, I wanted the following to work:
go get golang.org/dl/go1.9.7
go1.9.7 download
go1.9.7 get github.com/maruel/panicparse/cmd/pp
go get golang.org/dl/go1.16beta1
go1.16beta1 download
go1.16beta1 get github.com/maruel/panicparse/cmd/pp
In practice, I only needed go1.11 to work, but I got up to 1.9.7 for free. I hit a few challenges:
go get github.com/maruel/panicparse/cmd/pp
did
the wrong thing (fetch v1) silently from the perspective of the users and
had a hard time figuring out a way to fix this.
go get
will fetch the previous
version without any mention of the more recent broken one. The only way to
know if to specifically request a broken version. It will print you an error
message. Thankfully I had a OSS toy project that I knew nobody dependended
on that was already at v3 that I could test on. The error looks like this
when toying with versioned subdirectories (which is incorrect):
$ go get -u github.com/maruel/msgbus/v3@v3.1.1
go get github.com/maruel/msgbus/v3@v3.1.1: github.com/maruel/msgbus@v3.1.1: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v3
In addition to the challenges mentioned above, I also found many annoyances but I’ll save them for another day.
My goal was simple:
pp
executable) independently
from the library
stack so that
getting the v1 CLI tool provides the v2 (race detector parsing)
functionality.
I worked it out with a combination of a few tricks.
I think my main take away is that in go modules days, you should seriously consider using separate git repositories for your executables and the packages. That’s all. In abstract it makes sense, you make separate versions, and it’s generally surprising for users if they get bumped to a new major version silently. Where this breaks down is in the following format (which panicparse follows):
- LICENSE
- go.mod
- cmd/
- tool_foo/
- main.go
- foo/
- foo.go
and you want to make a breaking change to the reusable package foo
without
having to bump the version of the CLI tool tool_foo
.
The challenge is that often I’ll write a toy CLI tool, then will carve out a
package that I think has potential for reuse, which leads to a period of
uncertainty for the API. Is it too specific for the tool use case or is it too
generic? Fleshing an API out requires time, but in the meantime as a tool
maintainer you have a useful CLI tool that you don’t want to break in the
process. That’s where I find the status quo hurts the most, because the git tag
and the versions must match starting with v3 so versioning directories do not
help, and having go.mod files in subdirectories breaks go test ./...
.
Anyhow, I’m glad I found a way and hope the learnings here will help another package maintainer write Go packages that predate Go modules.