Go modules, for all intents and purposes, require projects to use Semantic Versioning. Because of the way many people install staticcheck – by including it in their go.mod as a dependency – we are not exempt from this requirement.
Staticcheck does not provide a stable API (but doesn't hide all packages in /internal/
– users are free to use our APIs, without any guarantees), nor does it provide a stable CLI – Staticcheck releases can make backwards incompatible changes. If we were to use a major version >= 1, then most releases would require bumping the major version, which in turn would require changing our import paths. This creates unnecessary churn – both for us and our users. This means we can only use major version 0, as it carries no backwards compatibility guarantee.
Staticcheck adopted its official versioning scheme long before the introduction of Go modules. We chose the format <year>.<seq>.<patch>
. <year>
would correspond to the current year, <seq>
would increment with each feature release and reset at the beginning of the year, and <patch>
would denote bugfix releases of a given feature release. This versioning scheme was chosen to add some meaning to versions – based on the <year>
component, users can tell how old their version of Staticcheck is, in terms of time. This contrasts with Semantic Versioning, which carries no such information. It was and is my belief that Semantic Versioning is primarily of use to libraries, not end-user software. Staticcheck is end-user software.
However, under Go modules, we cannot simply use our versioning scheme. v2020.1.0
would be major version 2020, and we've established that only major version 0 is viable for us. This leaves us with two options:
-
Abandon our versioning scheme, use plain Semantic Versioning, incrementing the minor version with each feature release.
-
Somehow encode our versioning scheme in Semantic Versioning.
Currently, we use a form of option 2 (let's call it option 2a): versions are tagged as v0.0.1-<year>.<seq>.<patch>
– that is, we store our own version in the pre-release portion of Semantic Versioning. The actual version is fixed at v0.0.1
and doesn't change. This scheme has worked somewhat well, but suffers from two (related) problems.
-
We can't make actual pre-releases. Say we wanted to release version 2020.2-beta.1
– there is no way for us to do so. If we used v0.0.1-2020.2-beta.1
, then this version would sort higher than v0.0.1-2020.1
, despite being a beta release, defaulting people to using the beta.
-
Because we use release branches, and tags are on commits that are unique to these release branches, our current versioning scheme makes it difficult to use the master branch, as can be seen in https://github.com/golang/go/issues/38985. In short, the pseudo-versions that Go generates for commits on master will sort lower than the latest actual release, even if these commits are chronologically newer. To fix that, while still using release branches, would require pre-release tags on the master branch. And that runs into the first problem.
The other way (option 2b) of implementing option 2 is to store the <year>.<seq>
portion in the minor
component, resulting in v0.202001.0
and v0.202002.0-beta.1
. These versions would constitute "proper" Semantic Versions, sort as expected and allow us to make bugfix releases as well as pre-releases. Their downside is that they're exceptionally ugly and difficult to read for at least all of 2020, and probably in general.
Option 1 can be split into two sub-options:
a. completely abandon our versioning scheme
b. use basic semantic versions for Go, but use our existing versioning scheme officially
Option a is not very satisfying. We would switch from our established versioning scheme to an inferior one. We would also be stuck on major version 0, which would make Staticcheck look less stable than it is. It would also create a break in our versions, going from 2019.1 to 2019.2 to 2020.1 to… 0.2.0.
Option b requires maintaining a mapping, and will ienvitably confuse users. We could tag our releases with both versions, so that v0.2.0
and 2020.2
point to the same commit. This would allow go get [email protected]
to work, transparently resolving to v0.2.0
. However, users will now see v0.2.0
in their go.mod
files and have no idea what version that corresponds to.
In summary: Option 1a is unsatisfying, option 1b is confusing, Option 2a is insufficient and option 2b is ugly.
Is there another option I have missed? If not, which of these mediocre options is the least bad?
/cc @dmitshur @bcmills @myitcv @mvdan