-
-
Notifications
You must be signed in to change notification settings - Fork 377
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve semantic versioning #777
Comments
I have been thinking about "end-user versions" versus "library versions" for a while too. I agree that your own versioning scheme might have benefits over semver for end users, but I also have to admit that most users will expect semver, so there's some value in following that expectation. There is one semver feature supported by Go that you might not be considering - build metadata. For example, following your example from option 1b, the tag Something else that comes to mind is a versioning scheme that's somewhere in between yours and semver - something like Chrome's or Firefox's, where the "main version" string is just a number that gets incremented at regular intervals (such as every three months). Then, if you're on staticcheck This is not me advocating for the Chrome/Firefox version numbering scheme. Just another idea to add to the pile, since it could mean replacing |
I'm not sure you're actually supposed to include build metadata in tags? I was always under the impression that it's an artifact of the build process itself, but I've never tried to see how Go behaves. They also can't contain dots, and we may want to use them for their intended purpose at some time, for example to offer downloads built with different versions of Go.
I'm not sure I quite understand how that is different from option 1a? In option 1a, we'd have the following feature releases: 0.2.0, 0.3.0, ... – with bugfix releases as 0.2.1, 0.2.2 and so on. How is that different from Firefox's scheme, other than Firefox having absurdly large numbers? |
Well, if Go modules didn't let you put metadata in the tag strings, where could you possibly put them instead? As far as I know it should work, though I haven't used it myself yet. Also, I'm pretty sure they can contain dots; from https://semver.org/#spec-item-10:
I guess this depends on what you want to prioritise more. You could also do both at the same time; there is no limit to how many "dot separated identifiers" you can have, as far as I can tell. You could do
You said "users can tell how old their version of Staticcheck is, in terms of time". I'm saying that you can accomplish practically the same if you increase the "major" version number at regular intervals, like every three months. That could replace a version like |
Well, as far as semver is concerned, build metadata is just noise appended to a version. That is v1.0.0 and v1.0.0+foobar are the exact same version. To that end, you can do You could put build metadata in the names of your binary downloads, for example, or a version output by the program. For example, you could have I don't think Go likes you including build metadata in git tags, either. If you have an actual tag called
Are you suggesting that the version number increases on a schedule, even if there are no actual releases being made? So one release would be v33 and the next might be v36? |
Ah, I might have a wrong understanding when it comes to how Go handles build metadata appended to versions.
Yes. That works for large products like Chrome/Firefox as they have lots of engineers and want to release regularly, anyway. That might not work for you, but you are already tying your release names to dates anyway. If you didn't have any release in 2021, you would jump from 2020 to 2022, I presume. |
I would, and have. But a gap in year numbers seems easier to digest than a gap in a simple counter. People recognize years. They won't know, just from looking at it, that v30 is a number that increments by 1 every 3 months. As a user I'd probably wonder where v31 and v32 have gone. (Also, Firefox and Chrome have been mocked for their absurd version numbers :P) |
Please no Chrome-like major version circus, I beg you! As for the problems with the current versioning scheme… You'll
probably consider this proposal (option 2c?) “ugly” but you could spell
|
I would be inclined to use sequential minor versions for releases, and just dual-tag the repo: once with the semantic version, and once with the date string. If you want to encode the date in the version string as well, perhaps you could move it to the patch string? That would keep the “minor” comparison easy to eyeball, but still encode the date information as well, and would continue to encode “minor” bumps as minor versions and “patch” bumps as patches to the same minor version. For example: |
I guess that does imply a hard upper bound on the number of patch releases per main release, but I hope you don't hit 100 patches in half a year anyway... 😅 |
I'm not sure how illegal this abuse of the patch level is. The spec says this:
Though technically no rules at all apply for major version 0. Nevertheless. I feel like you're showing me these workarounds to convince me not to use any of them :) While they're quite clever, I don't think any of them are user friendly, only causing more confusion. At this point, I'm very inclined to just go with
|
I was tempted to drop our |
Here are my plans going forward:
We do not retag old releases, because we don't want them to sort higher than the latest We will continue using our year-based version numbers in documentation, the output of We will use After the change to the new tagging scheme we can start making pre-releases, such as |
Overall, I like the plan and the future versions. As discussed over chat, I think you should avoid retroactively giving existing older releases (all but the latest current release, 2020.1.4) a semantic version that sorts higher than their current version, as that can cause a problem for modules using the current version scheme for the existing releases. As a minor note, |
Thank you, Dmitri. I've thought about tagging 2020.1.4, but ultimately decided against it. Having a 0.1.4 but no 0.1.0–0.1.3 would be too confusing IMO. I think it's preferable to just leave old releases alone and only use our new scheme for new releases. |
We are switching to using two versioning schemes: our original one and a proper Semantic Versioning compatible one. See #777 for the motivation.
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 atv0.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 usedv0.0.1-2020.2-beta.1
, then this version would sort higher thanv0.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 cmd/go: go mod tidy reverts upgrade golang/go#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 theminor
component, resulting inv0.202001.0
andv0.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
and2020.2
point to the same commit. This would allowgo get ...@2020.2
to work, transparently resolving tov0.2.0
. However, users will now seev0.2.0
in theirgo.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
The text was updated successfully, but these errors were encountered: