Skip to content

Instantly share code, notes, and snippets.

@warpfork
Last active January 2, 2023 12:06
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save warpfork/98d2f4060c68a565e8ad18ea4814c25f to your computer and use it in GitHub Desktop.
Save warpfork/98d2f4060c68a565e8ad18ea4814c25f to your computer and use it in GitHub Desktop.
WarpVer -- it's not semver

WarpVer Versioning

A software versioning scheme.

It's not semver ™️.

The Rules

  1. the number bumps when the lead maintainer says it does.
  2. even numbers should be easy upgrades; odd numbers may change things.

That's it.

An example sequence of WarpVer version names might look something like v0.1, v0.1.2, v0.2.5, v0.200, v0.201, v1.0, etc.

Bigger increases to the number may suggest bigger conceptual changes, but it's a hint, not a contract.

Why?

These rules are enough to create good influences and clear communication, and they introduce no complexity.

  1. The even/odd cadence naturally produces a very healthy rhythm.
  2. Easy to implement: nothing to bikeshed. Time spent arguing about what's "breaking" and "how big" is wasted (just let your compiler tell you).
  3. Easy to say forward-looking things like "this will be removed in v0.9" with simplicity and confidence -- take the current number, advance it to the next odd number, done.

It's good for developmental velocity: Changes never get stuck in indefinite limbo waiting for the next "major" change.

It's good for developmental constance: There's a reminder to make a stable checkpoint and do a "calm" period, frequently.

It's good for consumers: Consumers interested in updating always know what to expect: advancing to the next even number should be easy; advancing to an odd number may require code changes.

It's utterly lacking in perverse incentives: you can make the number go up. Even if you're only shipping fixes and having incredibly stable code -- yes, you too, quiet honorable glamorless stabledev, you can have rising numbers that look good in the mirror and on the performance review, and you can do it without falsely signalling breaking changes.

Simple. Clear.

Why not $alternative?

Because all the alternatives are worse.

Semantic versioning ("semver") falls short of its promise by so many leagues and miles that it's time to admit it and quit it. It claims to be simple, but in fact it's baroque and it's meaningless: it spawns endless arguments about what's a "major" and "minor" change, proponents can get infinitely carried away in spirallingly complex arguments about what qualifies as "breaking" and thus "major", and so on... ultimately, SemVer doesn't solve nearly as many problems as those arguments create. Meanwhile, it is observed that even libraries that claim to support SemVer typically don't; and while SemVer is sometimes claimed to enable automatic updating, it doesn't. (Only tests do that!)

Sentimental Versioning (HN discussion) is an alternative that covers the sheer the whimsy of the reality. (The HN discussion includes plenty of ammunition against semver, too, and all of it is valid.)

Most of the idea of WarpVer comes from the approach taken by Linux, which is probably explained in many places, but for example is noted here during a release: https://lkml.org/lkml/2019/3/3/236 WarpVer is really just giving this pattern a name and an authoritative reference document.

Doing more is Worse

We have compilers for detecting incompatibilities. We should use them.

Attempting to cram perfect information about compatibilities or the lack thereof into a version number is a fool's errand. If you carried this to infinity, you'd invent a Gödelean Numbering for API surface area, and while that's beautiful, you should also recognize a battlefield that stretches onto the infinite planes beyond Yggdrasil when one is staring you in this face. There's a much better option: stop it, and just let a compiler help you. Let the compiler into your heart.

If you don't have a compiler, write test code, and let that help you.

Working with the compiler and working to produce test code is infinitely higher value than arguing about version numbers.

Can I parse it like SemVer?

Yeah, sure.

In SemVer, the number goes up. In WarpVer, the number also goes up, we just argue about it less.

It's fine to use SemVer parsers and sorters on WarpVer versions.

Variations and Flavors

Always Zero

The "always zero" rule is simple: versions always begin with a "v0." prefix.

The reason to consider doing this is to suggest "this software is in perpetual development". The reason to consider suggesting that is because it's probably true, and if you think it isn't, you're probably lying to yourself (and you should consider quitting that).

Lots of good software in the world is incredibly stable while also following the "always zero" rule: for example, PuTTY is famously well-aged and stable, but to this day is releasing somewhere in the ballpark "v0.97".

A statement which may be used to justify the always-zero approach is:

When we release a major version number >= 1, we will be claiming that there will be a long-lived support process, and active backports branches starting from that point and carrying on for a large support window.

Since the above never happens for 99.995% of software projects -- because it's an insane amount of work to promise! Great congratulations/condolences to you if you're working on something so widely used it's legitimately in the 0.005% of projects where this work is both worth it and happening! -- it effectively justifies an "always zero" version. (And it's very truthful! Good comunication! Is good!)

The "always zero" rule doesn't have to be used when you use WarpVer. But if it appeals to you, it stacks with WarpVer's basic rules pretty trivially.

WarpVer for Golang

WarpVer for Golang typically adds the "always zero" rule: versions always begin with a "v0." prefix.

This is because Golang tools in the "go mod" era have decided that major versions belong in your import paths... but only when the major number is greater than one.

This creates all sorts of fun problems if you're not actively signing up for the maintinence work it implies, and it means many many things become breaking changes even if they might not have been (e.g. consider a downstream consumer only being exposed to part of a library -- perhaps not a part that changed). (It may also be elucidative to consider this in contrast to the long-lived support branches concept described in the always-zero section -- are you actually going to make support branches? No? Then maybe don't make stability claims that might suggest you are.)

The solution is simple: always-zero version naming.

You don't have to follow this rule if you're using WarpVer and you're using it in a golang project. But it's probably a safe and reasonable default unless you know what you're doing, and you're sure you want those import path behaviors.

Here's some only-moderately-salty copypasta that can be put in your readme to describe and justify this approach, if you wish to take it:

The version of this library will start with "v0." in perpetuity, or, until such time as the lead maintainer decides to attempt to derive value from the golang mod tool's behavior around major versions being injected into import paths.

At this time, the lead maintainer does not expect there to be a time when the lead maintainer is interested in trying to extract value from major versions being injected into import paths.

Gophers also beware: go mod has a strangely strong opinion about versions containing at least three numbers. In other words, a tag of v0.5 isn't considered a valid version tag in the eyes of go mod -- you'll have to tag v0.5.0 instead. Why? Eh.

@ianklatzco
Copy link

"you know who else does this?

the linux kernel"

"well u should say that first"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment