Introduction to SemVer posted Tuesday, April 30, 2019 by irina
(this post was originally released on the Greenkeeper Blog)
Why does software have version numbers? Version numbers come in many shapes and forms, but they all have one thing in common: they signify that one release of a piece of software is different from another release of the same software. So one of the reasons software has version numbers is to show progress, e.g version 2.0.0 comes after version 1.0.0.
Another reason to have version numbers for software is marketing: “Version 2.0.0 has 100 new features over version 1.0.0” and this concept translates from software development all the way up to product marketing where new versions of software are released with much fanfare.
Software developers use version numbers for a third reason: compatibility. That means that by looking at two version numbers for the same piece of software, a software developer can make a decision on how compatible these two versions are.
The encoding of that compatibility between versions is what gives version numbers meaning and since semantics is just a fancy word for meaning, this is called Semantic Versioning, or SemVer for short.
SemWhat?
A semantic version number consists of three numbers separate by full stops (or periods). Like 2.5.8 (out loud, we say “two dot five dot eight”, or “two five eight”). Each of the numbers in a version number has a specified meaning that signifies something about the compatibility of the software.
Say we have a software package at 1.0.0 and now 1.0.1 comes out. What does the difference in the last number signify? The last number in a SemVer string signifies a difference in terms of bugfixes. In our example, version 1.0.1 includes one or more bugfixes over 1.0.0, but otherwise works exactly like 1.0.0. If you are using version 1.0.0 today, it is safe to upgrade to version 1.0.1, none of your software will break and you will get better working code on top.
Now version 1.1.0 comes out. What does that mean? Compared to 1.0.0 and 1.0.1, it has one or more new features. It also includes the bugfixes from 1.0.1. Going right to left, if you update one of the numbers, if there’s one to the right of it, it gets reset to zero and we can expect all the bugfixes to be included as well. It is still safe to upgrade for you coming from 1.0.0 or 1.0.1, all you get is new features that you can use in the future.
Things move fast and version 2.0.0 comes out. First, we know all the features from 1.1.0 and the bugfixes from 1.0.1 are in there as well, because the feature version and the fix version have been reset to 0. But what does incrementing the leftmost number mean? It means, there are breaking API changes and that it is not safe to upgrade to the new version without you putting in some work to make sure things stay compatible.
In summary, SemVer can be viewed as Breaking . Feature . Fix
.
Breaking version = includes incompatible changes to the API
Feature version = adds new feature(s) in a backwards-compatible manner
Fix version = includes backwards-compatible bug fixes
Breaking.Feature.Fix is more commonly known as Major.Minor.Patch, but we believe that Breaking.Feature.Fix is a more precise way of describing a version number that leaves less room for interpretation. That’s important because software developers often can not agree on what the different parts of a version number mean and a more precise naming standard can help. This goes so far that the group that maintains semver.org, the home of Semantic Versioning, started discussing making this change official in SemVer’s GitHub issue #411.
Why Breaking.Feature.Fix is clearer than Major.Minor.Patch
The example below shows a simple (and extremely hypothetical) module that exposes a single method which changes the case of a string.
// Version 1.0.0
function changeCase(theString) {
return theString.toUpperCase();
}
// Version 1.1.0 (Feature)
function changeCase(theString, allowLowerCasing = false) {
if (allowLowerCasing && theString.toUpperCase === theString) {
return theString.toLowerCase();
}
return theString.toUpperCase();
}
The first change introduces an optional flag to the method, which allows reversing the case change from uppercase to lowercase.
This is truly a non-breaking feature, because all existing users can update to 1.1.0 and the change will definitely not break their code, because the feature needs to be explicitly enabled.
Now, is this a major, minor or patch change? Hard to say. The module does twice as many things! Seems pretty major. But it’s not a fix, it’s not breaking, and it behaves like a feature, so with Breaking.Feature.Fix it’s very clear which part of the version number needs to be incremented: the Feature number.
// Version 2.0.0 (Breaking)
function changeCase(theString, allowLowerCasing = false) {
if (theString.match(/\d+/g)) {
return new Error("No Numbers allowed!");
}
if (allowLowerCasing && theString.toUpperCase === theString) {
return theString.toLowerCase();
}
return theString.toUpperCase();
}
The second change throws an error when the method is passed a string with any numbers. The module’s author could easily interpret this as a minor change, or even a patch, since it just affects error handling and some edge cases. It’s not adding a proper feature, and the method still does what it did before: change the case of strings. It’s just a bit more strict about it. But to anyone actually using this module, all these distinctions are useless if their strings contain numbers.
The change breaks their code, and a non-semantic version number would easily hide this fact. With Breaking.Feature.Fix, however, it’s absolutely clear.
What’s relevant to the users isn’t the author’s intention, but the effect the change will have on their code.
Start with 1.0.0
While we are on the topic of version numbers. It’s been a long-held tradition to start software versions at 0.0.1 and slowly increment. Mainly to signify in the early stages of a project that it is not finished, not stable yet, not a one point oh.
But that’s a marketing concern as we saw in the introduction above. This has very little to do with the compatibility of your software. In fact, the SemVer spec defines that anything starting with “0.” doesn’t have to apply any of the SemVer rules.
This is not very great if folks start using your software early on, which is supported by one of the big mantras of Open Source: Release Early, Release Often.
Especially the early adopters of your software deserve clear instructions on the compatibility between early versions of your and their software.
As a result, we encourage everyone to start out with the version 1.0.0 and use the rules we learned about above to increment the version number. In addition to supporting your early adopters, you get into the habit in thinking about software changes in the SemVer-way from the get go and habits are hard to beat in terms of doing things efficiently, so start as early as possible to acquire the right ones.
If you need to communicate software stability, use modifiers like -alpha, -beta, or -preview.
From the spec:
Numeric identifiers MUST NOT include leading zeroes. Pre-release versions have a lower precedence than the associated normal version. A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version. Examples: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0–0.3.7, 1.0.0-x.7.z.92. —< https://semver.org/>
Semantic Release
Adopting the clear rules of SemVer has one final advantage. Who’s really good at following clearly defined rules? — Computers! And who’s habitually not very good at that? — Humans! Cue Semantic Release, a little module that takes the pain of deciding which version number to assign to your software away completely, giving you more mental capacity on the things you really enjoy: writing code, writing documentation or helping your users.
This is but a small teaser, and we've written a proper introduction to Semantic Release that you can read here.
« Back to the blog post overview