Understanding '+incompatible' in Go module version names
As we all know, when Go code uses modules, you specify a version
of the module, and the Go tooling will record its take on this
version in places like
go.mod. The exact details are covered in
the "Versions" section of the documentation and the definition of canonical
version. Since Go
modules came in, I've seen a number of modules with a (canonical)
version that included '+incompatible', and encountered Go generating
them itself when I tried to experimentally modularize a non-modular
third party Go program by hand. However, for a long time I didn't
really understand what it meant and what you could do with this.
The short version is that it's there to deal with a corner case
for pre-modular packages.
To start with, there are a few ways to have properly set up Go modules (or at least a few common ways). If you have
an improperly set up Go module, such as one where the version tags
go.mod disagree with each other about what the version is
(eg if your 2.0.0 version has a
go.mod that claims the module is
'example.org/cks/mymod' instead of 'example.org/cks/mymod/v2'), I
believe that the Go tools will just throw errors. And generally if
you're using go.mod, you need to get it right.
But there is one 'improper' case that you can get into with a non-modularized Go package, and that is if the package has a version tag with a major version above 1 (with no go.mod, since it's a package instead of a module). In the pre-modular Go world, this was perfectly valid thing to do, although not necessarily friendly to your users (since 'example.org/cks/mymod' could silently move major versions on them, presumably with API incompatibility). This isn't just theoretical and there were a variety of Go packages that did this, such as github.com/google/go-github (which got up to v17 before it modularized).
In the world of Go modules, the +incompatible suffix to a version is how Go still allows you to specify and use such a package in your go.mod. You can only use +incompatible with packages, not modules; the moment a repository adds a go.mod in some version, you stop using +incompatible from that version onward. To make this concrete, if you do 'go get github.com/google/go-github@latest', you will get the version v17.0.0+incompatible, although as I write this v48.0.0 is the latest version. The reason Go stops at 'v17.0.0' is that that was the last version before go-github modularized in v18.0.0.
As you might expect, +incompatible can appear on pseudo-versions, if you want to set a (minimum) version in your go.mod to some commit instead of just a version tag.
As covered in the "+incompatible versions" section of the
major version number in the version is basically decorative. If you
ask Go to update the version of the package, for example, it will
feel free to upgrade you from 'v16.0.0' to 'v17.0.0'. You could
phrase this as that all versions of a package have the same major
version as Go tools see it, or you could say instead that Go is
mirroring the pre-modular behavior of '
go get -u', where if you
upgraded you always got the latest (VCS) version of the package and
sorting out the actual package versions and any API changes were
(In general the "+incompatible versions" section of the documentation isn't very long and is worth reading for the technical details, although you'll wind up going through various cross references to the rest of the Go Modules Reference.)
This means that you can't use +incompatible as part of modularizing a program to perfectly duplicate what you would have gotten with a non modular build in Go 1.17 and earlier, as I've discovered before. In fact, not even 'go install <package>/cmd/something@latest' will do this, which means that in Go 1.18 and later it may be impossible to properly build and install some old, non-modularized Go programs. I haven't run into this yet, though, and I suspect it would be relatively uncommon.
(It would require a package to modularize, change major version, and change its API, and then a non-modularized program to be updated to use the new API. At that point, the +incompatible version of the modularized package has an API that's not compatible with the program.)