Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Why Semantic Versioning Isn't

Spurred by recent events (https://news.ycombinator.com/item?id=8244700), this is a quick set of jotted-down thoughts about the state of "Semantic" Versioning, and why we should be fighting the good fight against it.

For a long time in the history of software, version numbers indicated the relative progress and change in a given piece of software. A major release (1.x.x) was major, a minor release (x.1.x) was minor, and a patch release was just a small patch. You could evaluate a given piece of software by name + version, and get a feeling for how far away version 2.0.1 was from version 2.8.0.

But Semantic Versioning (henceforth, SemVer), as specified at http://semver.org/, changes this to prioritize a mechanistic understanding of a codebase over a human one. Any "breaking" change to the software must be accompanied with a new major version number. It's alright for robots, but bad for us.

SemVer tries to compress a huge amount of information — the nature of the change, the percentage of users that will be affected by the change, the severity of the change (Is it easy to fix my code? Or do I have to rewrite everything?) — into a single number. And unsurprisingly, it's impossible for that single number to contain enough meaningful information.

If your package has a minor change in behavior that will "break" for 1% of your users, is that a breaking change? Does that change if the number of affected users is 10%? or 20? How about if instead, it's only a small number of users that will have to change their code, but the change for them will be difficult? — a common event with deprecated unpopular features. Semantic versioning treats all of these scenarios in the same way, even though in a perfect world the consumers of your codebase should be reacting to them in quite different ways.

Breaking changes are no fun, and we should strive to avoid them when possible. To the extent that SemVer encourages us to avoid changing our public API, it's all for the better. But to the extent that SemVer encourages us to pretend like minor changes in behavior aren't happening all the time; and that it's safe to blindly update packages — it needs to be re-evaluated.

Some pieces of software are like icebergs: a small surface area that's visible, and a mountain of private code hidden beneath. For those types of packages, something like SemVer can be helpful. But much of the code on the web, and in repositories like npm, isn't code like that at all — there's a lot of surface area, and minor changes happen frequently.

Ultimately, SemVer is a false promise that appeals to many developers — the promise of pain-free, don't-have-to-think-about-it, updates to dependencies. But it simply isn't true. Node doesn't follow SemVer, Rails doesn't do it, Python doesn't do it, Ruby doesn't do it, jQuery doesn't (really) do it, even npm doesn't follow SemVer. There's a distinction that can be drawn here between large packages and tiny ones — but that only goes to show how inappropriate it is for a single number to "define" the compatibility of any large body of code. If you've ever had trouble reconciling your npm dependencies, then you know that it's a false promise. If you've ever depended on a package that attempted to do SemVer, you've missed out on getting updates that probably would have been lovely to get, because of a minor change in behavior that almost certainly wouldn't have affected you.

If at this point you're hopping on one foot and saying — wait a minute, Node is 0.x.x — SemVer allows pre-1.0 packages to change anything at any time! You're right! And you're also missing the forest for the trees! Keeping a system that's in heavy production use at pre-1.0 levels for many years is effectively the same thing as not using SemVer in the first place.

The responsible way to upgrade isn't to blindly pull in dependencies and assume that all is well just because a version number says so — the responsible way is to set aside five or ten minutes, every once in a while, to go through and update your dependencies, and make any minor changes that need to be made at that time. If an important security fix happens in a version that also contains a breaking change for your app — you still need to adjust your app to get the fix, right?

SemVer is woefully inadequate as a scheme that determines compatibility between two pieces of code — even a textual changelog is better. Perhaps a better automated compatibility scheme is possible. One based on matching type signatures against a public API, or comparing the runs of a project's public test suite — imagine a package manager that ran the test suite of the version you're currently using against the code of the version you'd like to upgrade to, and told you exactly what wasn't going to work. But SemVer isn't that. SemVer is pretty close to the most reductive compatibility check you would be able to dream up if you tried.

If you pretend like SemVer is going to save you from ever having to deal with a breaking change — you're going to be disappointed. It's better to keep version numbers that reflect the real state and progress of a project, use descriptive changelogs to mark and annotate changes in behavior as they occur, avoid creating breaking changes in the first place whenever possible, and responsibly update your dependencies instead of blindly doing so.

Basically, Romantic Versioning, not Semantic Versioning.

All that said, okay, okay, fine — Underscore 1.7.0 can be Underscore 2.0.0. Uncle.

(typed in haste, excuse any grammar-os, will correct later)

Debate aside, "romantic versioning" is pretty funny =D

If your package has a minor change in behavior that will "break" for 1% of your users, is that a breaking change? Does that change if the number of affected users is 10%? or 20? How about if instead, it's only a small number of users that will have to change their code, but the change for them will be difficult? — a common event with deprecated unpopular features.

How is it hard/problematic to just bump the major version in these cases? If you know it's going to break somebody, bump the major version. What is the downside to bumping the major version?

I think you have a point here but breaking change is something you can know while the number of affected users is not.

You could evaluate a given piece of software by name + version, and get a feeling for how far away version 2.0.1 was from version 2.8.0.

This is not true, if you can't define precisely what those numbers are. What's far?

tj commented Aug 29, 2014

I agree with @jashkenas for the most part, the only thing you can really do to keep yourself safe is pin, or maybe try your luck at n.n.x to include fixes, but it's not too uncommon to introduce side-effects or unwanted behaviour. Sometimes your application might even be depending on a faulty behaviour, so the fix screws you over.

On a side note does anyone know why semver suggests rolling patch on a minor? Ex: 1.2.3 -> 1.3.0, when both feature(s) and fix(es) are involved? It seems very arbitrary, and what about breaking compat but adding 50 features? I'm tempted to start rolling them all independently so you can extract more information out of the number, then you would know how many features or fixes are in each release.

Semver provides a less ambiguous way of versioning libraries, and sets up a contract between the developer and the consumer. If people fail to honour that contract, at least you can more easily point out where they went wrong.

FWIW, we use semver + SOLID + TDD on a library with a fairly large surface area. We rarely break the SemVer rules, and people are generally confident in upgrading our library. It is easy to add new features, and nearly impossible to accidentally change existing ones. SOLID is append only.

We do not pretend SemVer cures all, but it does set up some simple rules you can easily reason about.

Semantic Versioning is a versioning scheme for developers, the users and choosers of your library.

It does summarize information about the transition from one commit to another. Namely, whether the author has (hopefully) improved or added functionality to look forward to (a possible reason to upgrade) . whether the change affects how it's called (a possible reason not to upgrade, since there's now more work involved in upgrading) . and whether the change fixes something.

Whether it breaks 1% or 100% of functionality, someone depending on your program and reading of your update will have to decide whether to go along with your change.

What better tool for assisting in that decision than
a) whether there's something new
b) whether how it works in relation to you changed
c) whether something that's been broken works now?

There's nothing stopping you from varying the version number increments and following semantic versioning.

SemVer tries to compress a huge amount of information — the nature of the change, the percentage of users that will be affected by the change, the severity of the change (Is it easy to fix my code? Or do I have to rewrite everything?) — into a single number. And unsurprisingly, it's impossible for that single number to contain enough meaningful information.

SemVer asks you to compress a huge amount of information, but not necessarily the nature of the change, and definitely not the percentage of users that will be affected by the change -- neither does it claim to. It also won't tell you what has changed, so if someone decides they need to upgrade, they will still read your release notes or look at the commit.

Semantic Versioning plays well with build tools and package managers. An alternative to Semantic Versioning would be rigorous test suites for all consumed APIs. Is notifying the people who like your program of changes that might affect them really that difficult?

If you've ever depended on a package that attempted to do SemVer, you've missed out on getting updates that probably would have been lovely to get, because of a minor change

If you're really that concerned about missing out on updates, you can depend on the version *, this will install all updates as soon as they are available.

The distinction between bugs, minor updates & major updates will always be subjective. I usually just pull all updates (depend on *). If a package continually is breaking, I'll lock its version down to something specific.

If your package has a minor change in behavior that will "break" for 1% of your users, is that a breaking change? Does that change if the number of affected users is 10%? or 20?

Irrelevant. If you change the public api you need to notify users with a major version number bump.

the nature of the change, the percentage of users that will be affected by the change, the severity of the change (Is it easy to fix my code? Or do I have to rewrite everything?) — into a single number.

What semantic versioning spec are you reading? None of this is listed as a reason to change a version number. Here's the checklist for changing a major version number:

  • Did you break a public API?

If you answer "Yes" to any of the above, you need to let others know (and avoid them having to deal with issues) by bumping the major version number.

Ruby doesn't do it

It actually does now.

m50d commented Aug 30, 2014

Node doesn't follow SemVer, Rails doesn't do it, Python doesn't do it, Ruby doesn't do it, jQuery doesn't (really) do it, even npm doesn't follow SemVer.

Then they're bad. So?

If you've ever depended on a package that attempted to do SemVer, you've missed out on getting updates that probably would have been lovely to get, because of a minor change in behavior that almost certainly wouldn't have affected you.

Huhwhat? Anything you can do without SemVer, you can do with it. I don't think anyone's advocating "never upgrade your dependencies if it's a major version bump". Just be aware that a major bump may have broken compatibility for the APIs you use, and respond appropriately. If you make the major/minor distinction semantic, you have more information than if you don't.

If you have a more informative semantics in mind for the major/minor distinction, then by all means propose it - the version number is a prominent thing and should be as informative as possible. But to me "the package maintainer guesses that the incompatibilities in this release won't affect many people" is far less informative than "this release does not contain any breaking changes".

Um, yeah, semantic versioning is for machines. That's the point. You're primarily writing software to be consumed by other software, not humans.

If you're actually shipping a product, then go ahead and have your human version number. No one needs to know the semantic version.

Just don't try to put your round humanized version peg into the square semantic version hole.

nevir commented Aug 30, 2014

Unfortunately, our tooling has gotten to the point where machines have to rule the versioning system to an extent.

As a developer, I care about:

  • When I <package manager> install, do I get a reproducible build of my project?
  • Do I have an avenue for picking up all the latest and greatest security/bug fixes that are not breaking? (i.e. "free" upgrades - patches).
  • Similarly, do I have an avenue for picking up newer versions that include new features, possibly requiring some extra work on my part? minor changes.
  • And finally, do I have an avenue for picking up major versions that represent a new way of thinking about a dependency (requiring a lot of effort on my part)? major changes.

Whatever versioning scheme people use: it both needs to make sense to humans and tools, unfortunately.

If you go w/ ~ style version constraints, you run into situations where resolving dependencies becomes incredibly frustrating (see the Ruby community).

If you go with ^ style version constraints, you veer into a world where major versions need to be frequently bumped, and they don't match the human notion of what a major version is.

Where's the middle ground?

imagine a package manager that ran the test suite of the version you're currently using against the code of the version you'd like to upgrade to, and told you exactly what wasn't going to work.

Which is why we need to ship tests for packages. Tests are now part of contract and the type system.

What we need is something like git bisect that can run our own tests while upgrading dependencies along with go tool fix that can apply small program transformations. At some point we will stop working with reified text and operate on the AST via semantic program transformations, code will be a stream of edit operations.

In the meantime, SemVer is pretty good. Don't fear the major version bump.

I'm struggling to rationalise the humanistic argument, as well as the fear of major version bump.

Using SemVer I, a human, can quickly get an idea of release quality (2.7.4 -> 2.8.0 versus 2.7.999 -> 2.8.0), can quickly infer the safety of moving from 2.7.x to 2.8.x, and quickly assess the likelihood of me needing to make code changes if a dependency bumps from 2.7.x -> 3.x.x. I agree with your point about being responsible for your dependencies and reading changelogs, but in Node at least the practicality of doing so is somewhat diminished.

Although one may equate Semantic Versioning to Romantic Versioning, it's still far better than the Subjective Versioning described in the second paragraph (although nowhere near as nice a gag ;). For a long time in the history of software there was no consistency around versioning from one product to the next, to the point that version numbers were meaningless when comparing software produced by 2 different people (let alone companies). The lack of a consistent set of rules around versioning is what gave birth to 'dependency hell'. SemVer is trying to address that by wrapping formal definitions around major, minor and patch so that everybody (ie humans) can have a common understanding, not unlike SI units did for science.

I don't think SemVer had iceberg style code in mind when it was conceived, more that it had modular code in mind. Yes, adopting a modular philosophy (and the deep dependencies that usually accompany it) is asking for trouble if you don't have something like SemVer to ensure everybody is working to the same rules. The Node community probably takes this to the extreme (which is one of the things I like about it), and so too are we fairly religious about SemVer. I would much rather we all worked to the same set of rules, allowing us to have the same set of expectations around versioning. Speaking of which, Node does follow SemVer, because it is still pre-1.0.0. It's still pre-1.0.0 because it still has an unstable API and doesn't make any promises about maintaining backward compatibility. As per SemVer.

Finally, it's good to read alternative views on this, especially yours. You've made a dent in the JS universe that few people have to date, any hate over this is not just disrespectful but totally unwarranted. You're obviously free to version things however you like, but given at least one of your libraries has been massively adopted by a community for which SemVer is very important, I'm sure you can understand the calls for respecting it.

oisin commented Aug 30, 2014

It's quite important to have some way to inform users of your software that there is a level of difference between releases that could impact them greatly in terms of code changes. Ideally, a project will produce a comprehensive ChangeLog, with an accompanying set of release notes, which will be diligently read by its consumers, and will allow them to make an estimate of the cost of updating.

Before TPW wrote the SemVer thing down, and before there was a GitHub to host it on, software one shipped to API-using customers needed written-as-contract commitments to compatibility - and these commitments were usually codified in terms of how a version number change would affect the customer. In addition, for most cases, you needed to publish a clearly defined deprecation and end-of-life timeline for APIs, for example -- once 4.x was released, kiss goodbye to any patches on the 1.x stream.

I've seen the semantic versioning thing as a useful page to point people at when they ask about how to version their software, but the fact remains -- any software you ship is yours, so really you can do whatever you damn please with the version numbers. It's your responsibility, should you accept it, to explain the compatibility approach to consumers, or to risk their displeasure when they have to update the codebase and recompile everything and run all their big test suites. Maybe Ruby, Python, npm, et alii don't follow this approach to versioning, but so what? You make your own decision for your own software, regardless of your judgement of anyone else's approach to versioning, right? If you want to do deep changelogs, hash the APIs, or choose another way, then off you go.

Switching over to the consumer side of things - there is no real excuse not to track all the projects that you use, follow the changes as they happen and run automated tests on pre-releases of all consumed libraries against your develop. The project may announce semantic version, but who knows if they are doing it right - you can only believe what you can prove in your own tests.

reiz commented Aug 30, 2014

I like SemVer! I like it because it gives me pretty good idea how big the changes are. Sure. Maybe my software is not affected from the new major version of dependency X. I will find out by looking into the changelogs and by running my tests with updated dependencies.

Version numbers are created by humans and they make mistakes. Sometimes a project ships a minor version, but they changed an API and actually it should be a major version. Shit like that happens, but it's not the rule, it's an exception. For me it works very well!

Maybe we should think about better tool support. I'm using jeweler for my Gem projects and it has a couple very usefull rake tasks to bump up versions. But it would be really awesome if it would track and understand my changes. So that it actually could decide (based on my changes) if the next version is a patch, minor or major version. For compiled languages that should be quiet easy. Not sure how difficult that would be for dynamic languages.

The responsible way to upgrade isn't to blindly pull in dependencies and assume that all is well just because a version number says so — the responsible way is to set aside five or ten minutes, every once in a while, to go through and update your dependencies, and make any minor changes that need to be made at that time.

Agree with that 100%.

alexkli commented Aug 30, 2014

Perhaps a better automated compatibility scheme is possible. One based on matching type signatures against a public API, or comparing the runs of a project's public test suite

With OSGi/Java such tooling already exists.

The responsible way to upgrade isn't to blindly pull in dependencies and assume that all is well just because a version number says so — the responsible way is to set aside five or ten minutes, every once in a while, to go through and update your dependencies, and make any minor changes that need to be made at that time.

Agree with that 100%.

Each of the 3 active projects I am working on right now has about 40 direct dependencies and 400 when counting nested dependencies . In 5-10 minutes I can barely go through a list of all current versions and compare them with the latest releases, using SemVer to guide me which updates I can try with little risk and for which I need to reserve more time. Without SemVer I would not be able to update nearly as often because I had to assume every update is breaking, which costs way more time.

dylang commented Aug 31, 2014

even npm doesn't follow SemVer

Breaking changes are in the works that will bump it to 2.0.0.

wprl commented Sep 3, 2014

If you have a lot of breaking changes, you probably shouldn't be at 1.x.x. If you need to deprecate something, add the extra features alongside and remove the deprecated features in the next major release. If you need to add something to code that is >= 1.x.x and you think it will need breaking changes, you should mark the feature as experimental in the documentation until it has stabilized.

Semver works great for humans and robots if you put a smidgeon of planning and consideration into the process!

troywarr commented Jan 2, 2015

I think I had the same gut reaction to SemVer when it first caught on. It makes a lot of sense, but it doesn't quite convey the full character of a software release, at least to a human.

It's kind of analogous to using a percentage to describe precipitation in a weather forecast. I'd always wondered whether it was an indication of the probability of rain on a given day, or the amount or intensity of rain to expect. One day I looked it up, and it turns out that amount and intensity have nothing to do with it. It's a combination of probability and extent - the likelihood of measurable rain in the viewing area, and the portion of that area likely to receive it.

Thus, a 50% chance might indicate that most of the viewing area has a small chance of seeing rain, or a small portion has a very high chance, or anywhere in between. By reducing two pieces of information about rain (extent and probability) to a single number, you've created simplicity, but at the cost of ambiguity. There's also nothing in that number to tell me whether to bring an umbrella or a paddle with me to work if it is likely to rain.

Personally, I'd like to see a breakdown of these components in the forecast. For example, this paints a very clear picture:

  • Probability: 40%
  • Intensity: 80%
  • Extent: 10%
  • Amount: 30%

In other words, it's moderately likely that a small part of the viewing area will see a small amount of hard rain.

That's more information for me to digest at a glance, but at least I still get a concise summary, and I can always focus only on what portion(s) is pertinent to my interests. 80% intensity? Hmm, better bring a poncho just in case I'm stuck outside. 10% extent? Well, that's probably not going to cover my house. I should probably go ahead and water my lawn.

In a similar vein, perhaps SemVer could be enhanced simply by adding more information to paint a clearer picture to human users. We're developers - we can handle it. There are other metrics that convey the maturity, stability, and momentum of software, and these are as important to programmers as compatibility issues are to computers. Perhaps both the "Semantic" and "Romantic" versions could form a single number chain or non-cryptic code. Even this is a major improvement, IMO:

4.0.2-8

If the trailing number indicates the total number of releases, then I can infer that this is a young, unstable package; If starting from 0.0.0-0, then fully half of the relatively few releases have not been backward-compatible. Conversely:

4.0.2-231

This paints a very different picture - this is more likely a mature piece of software that has introduced breaking changes very sparingly over its relatively long life. We're probably dealing with a fairly new major version that's had a couple of patch fixes, but isn't going to reach 5.0.0 anytime soon.

Sure, this isn't as concise - but I think it's a lot more useful.

Spudley commented Jan 5, 2015

Ultimately, the real point here (and what SemVer is trying to encourage) is that you should avoid making changes that break your API, and if you must make those kind of changes then they should be done as part of a major update. In this context, Semantic Versioning is really just a formalisation of existing version numbering practice.
The differences arise when you start making changes that are relatively minor conceptually but still need to break the API. Yeah, it happens. We all know it does, especially when you're working with a newish piece of software. But the ideal case would be hold those changes back (maybe in a separate branch) until you've got enough of them to really justify a new major release number.

lolmaus commented Jan 10, 2015

Please contribute to this suggestion: mojombo/semver#213

The real issue is Transitive Closure. I stumbled against the 1.9.0 @Param change in coffeescript (which I have no problem with, BTW .. it should however at least give warning messages .. or deprecations).

But it wasn't coffeescript that was the problem. It was gulp-coffee, which I held to major changes in my package.json. But what does it do? It has dependencies that it does not manage and in particular uses the latest coffeescript. So I was bit because I used semver and presumed it would help me.

Transitive closure is required: me and all my dependencies are held to restrict to the same semver package.json syntax. This is trivial mathematics. A relation on a set. Recursion. You know, Computer Science!

So....what's your suggested alternative?

I'll take "it helps mostly, but makes it look overly easy" over "let's see what the random number generator gave us today".

@pauldraper One alternative is Chronological Versioning as well as100% backwards compatibility for the vast majority of consumers.

There are many examples of 100% backwards compatibility, e.g. download cshar.gz from Volume 1 of comp.sources.unix. Build it and cshar itself. You'll get exactly the same output Gary Perlman got in 1985. Consider how many APIs have had to be backwards compatible for this program and its compilation environment to work, three decades later. (The one minor change is that you need to make CFLAGS=-w, which is perfectly tolerable in today's world of ever changing APIs.)

More important, Chronological Versioning gives you globally relatable information about all software. For example, Ubuntu 15.04 was released in Apr 2015 and openssl 1.0.1o was released in June 2015. You know that openssl is not included in the initial release of Ubuntu 15.04. In order to find out this fact, you have to go to the release notes for openssl 1.0.1o to determine exactly when it was released whereas Ubuntu's Chronological Version is all I need to know to relate it to openssl's release date. This counterfactual is useful if you are trying to figure out what is not in a release, which is half the battle. Ideally, Ubuntu would use chronological versioning for all updates, too, so you could easily relate all packages to the current update version.

I don't understand why people believe anything about versions. Today, your software needs to deal with the fact that dependencies can change at any time. Recently I encountered a non-backwards compatible change to the GitHub API. Our software failed fast and output a warning. That's the best you can do, I believe, in today's ever-changing world of web APIs.

Chronological Versioning is not a panacea, but it does not make false promises. It is probably the simplest version numbering scheme that gives useful information while being defined by one easy-to-understand and easily-automated rule.

Great post... Totally agree. It's folly to try and reduce change to three digits.

Changing one method, in one class on your components API is a small backwards incompatible change. All backwards incompatible changes are major changes. So we have a small, Major change.

Re-writing 50%+ of your components implementation, but keeping the API the same is a minor change. A huge, minor change for the client components.

Personally, I prefer simple visioning. X.y. For every build of the master/main we step X. For every bug fix we have to release on X, we step y. You need to read the release note to figure out the impact on the change on your component.

jmelfi commented Jul 10, 2015

@krainboltgreene stated this pretty concisely and correctly. It doesn't matter the number of users or systems that have downloaded your software, it matters what's been done and where to the code.

Did you break the API between versions? Major version bump
Did you add a feature and the API functions the same? Minor version bump
Did you just patch to resolve a bug and the API/Features act the same? Patch version bump

If you make huge changes, you can always to jumps in major versions to make it more clear (if you really want to).

Chronological Versioning is great if you are needing to refer to dates as referred to the Ubuntu example above. It's not helpful when you want to know if you what types of changes were made from 14.10 to 15.04 without reading a huge changelog. Again, this is why a majority of enterprise software (especially opensource when you look at the Apache foundation, eclipse foundation, mozilla, list goes on....) uses SemVer. It's not difficult and it is fairly clear with minimal complexity.

I work on over 15 projects doing some form of testing or integration in our internal enterprise. SemVer has been a very good indicator if the version is going to take an hour or half a day depending on the type of version change.

@troywarr has an interesting suggestion. I like the idea that a hyphened number could indicate the release number. This could possibly give false sense of stability if many builds were done (such as the golang project or others that have weekly or nightly builds).

We'll be sticking with SemVer as it helps translate to our users and consumers the proper information without complexity. The best reference as always is found on their site directly (http://semver.org/)

You never know when your code inadvertently works because of a bug in a dependency until you get a bugfix and your code unexpectedly blows up.

@pauldraper There's not really an alternative to assigning version numbers per se, but building SemVer-esque login into package management systems like NPM is bound to cause problems from time to time. I would never expect a build with SemVer deps to be as repeatable as a built with pinned exact versions.

Node doesn't follow SemVer, Rails doesn't do it, Python doesn't do it, Ruby doesn't do it, jQuery doesn't (really) do it, even npm doesn't follow SemVer.

I think this is the real meat of the argument. The problem is not necessarily with SemVer itself, but applying it in the way where everyone else understands your intentions for the version number you selected. An alternative versioning strategy is going to suffer the same fate eventually.

I don't think the good fight is against SemVer. I think the good fight is towards educating the entire community that already use it how to use it better.

And as for the closing comment, yes, romance is exactly the problem! It's time for developers to stop dating SemVer and make the commitment to marry it (and please have very short engagements behind the 0 major version).

CME64 commented Aug 15, 2015

I believe it's a good idea to have a glance at the version number and know if it's backward compatible. however, semVer doesn't sound right when it causes your major version number to become big with ever changing external links (AKA API- although you have to declare your APIs too, what they are ...etc) and database changes (for software without APIs) can be considered backward incompatible sometimes and that changes a lot in some development phases causing the major version to fly high. I think the idea can be used without affecting the major version number, instead we may append an actual major version number (a meaningful number, like release number or date ...) in the beginning and use semVer methodology on the rest shifting the major to minor (i.e. v1.522.20.1-rc, and now it looks more like an IP but more meaningful and informative)

And unsurprisingly, it's impossible for that single number to contain enough meaningful information.

SemVer specs build and release metadata extensions in the event the number isn't enough for you, but if you want more elaboration behind each number, write a changelog. In any case you did not illustrate a design limitation that is unique to SemVer here.

...to prioritize a mechanistic understanding of a codebase over a human one

Why would I not want this? If versioning is not systematic, then you're asking for communication breakdown on large projects. If I have 100+ vendors on contract for an API, I want robots managing the squishy stuff, not humans running in circles screaming "But where's the love?!"

If I need an easy-to-understand overview, I'll prep a visualization. With a formal versioning scheme at least my data source would be easy to parse.

the responsible way is to set aside five or ten minutes, every once in a while, to go through and update your dependencies, and make any minor changes that need to be made at that time.

One minute you're complaining that a number doesn't convey enough information about changes, and the next you're acting like you know updating only ever entails minor revisions. If you didn't know enough about the changes to begin with, why do you know they would be minor?

Semantic versioning doesn't prevent you from releasing a MAJOR version if you have done serious underlying refactoring and restructuring, which COULD technically be released as a MINOR version.

But it does prevent you from releasing a new MINOR version that means all your users code no longer compiles because you made a breaking API change.

imagine a package manager that ran the test suite of the version you're currently using against the code of the version you'd like to upgrade to, and told you exactly what wasn't going to work.

Take a look at http://greenkeeper.io.

FYI @seanjensengrey

Not sure about the core reasoning:

SemVer tries to compress a huge amount of information — the nature of the change, the percentage of users that will be affected by the change, the severity of the change (Is it easy to fix my code? Or do I have to rewrite everything?) — into a single number. And unsurprisingly, it's impossible for that single number to contain enough meaningful information.

I don't agree that this is the purpose of SemVer. Any breaking change is equally breaking and requires a major version increase. The documentation, not the version number, should communicate how much work is required to migrate between major versions.

The essence of SemVer is to have a programmatically readable contract between the maintainer and the consumer which can be used to safely get bugfixes and new features.

The real issue with SemVer, in my opinion, is that few projects actually follow the spec when they increment versions. But that in itself does not invalidate SemVer. It only highlights that projects like Node are bad at versioning.

@troywarr, your scheme of including the "total number of versions" after a release number, e.g. 4.0.2-8 or 4.0.2-231, ignores the fact that releases are not linear. Bug fixes to the latest release are frequently backported to earlier versions (as they should be).

There is no perfect system, but semantic versioning is as close as we're likely to come for a quick encapsulation of the key information.

bartlettroscoe commented May 20, 2016 edited

For a long time in the history of software, version numbers indicated the relative progress and change in a given piece of software. A major release (1.x.x) was major, a minor release (x.1.x) was minor, and a patch release was just a small patch. You could evaluate a given piece of software by name + version, and get a feeling for how far away version 2.0.1 was from version 2.8.0.

If you want your first version number to give a warm and fuzzy feeling about how 3..... versions relates to 2....., versions, then just use four digits in your version number like W-X.Y.Z where W can be your fuzzy "intuitive" major version number. Then you leave the reaming three digits X.Y.Z to the Semantic Versioning standard. So if you want to mark a major new version of your code, you go from, for example, v1-3.4.2 to v2-0.0.0.0. Seems like that would be an easy amendment to the semantic versioning standard. That is consistent with the idea of a "prefix" that is added to the beginning of the semantic versioning numbers X.Y.Z. That is why some codes change their prefix from, for example, HDF4 to HDF5. Makes sense to me. Win, win, win.

Without something like the semantic versioning standard, we are never going to make progress on improving the sustainability of ecosystems of interdependent but independently developed and released software packages. I don't see any other way to do it in an automated way.

dhinged commented Jun 27, 2016

"prioritize a mechanistic understanding of a codebase over a human one"... OK we'll not use numbers and dots anymore, we'll call each version "Bob" and "Mary" and "Theodore the Third" and "Shmoopy" and "Lovely Flower Petals", because we need to "humanize" and "romaticize" source code versions (this is so ridiculous), which could be in the dozens, hundreds, or even thousands based on changes that we need to keep track of or revert to. Oh, and having a simple major version change often (like Chrome, now Firefox) only adds unnecessary complexity, as you now can't think of a "major" version anymore with it's own major stand-out features, such as "When the program looked and behaved in this fundamental way for this year or period of years", you're just going to have this fast-growing number that gets more meaningless and forgettable the bigger it is (imagine if Windows 10 were actually at version 1,345 now, even only internally).

Version numbers having dot delimiters isn't making it hard to understand for humans, it's pretty easy already. Sequence-based identifiers are easy to mentally catalog and remember what at least the first digit meant, and many of the minor changes too, and just one big number increasing quickly makes even major versions completely forgettable (instead of v1 to v2, you're v23 to v146 to v235 to v657)...

Oh, and if a user was relying on a bug that got patched, well guess what, they weren't using the product right and you didn't build it as intended. It's a correction on both ends; it's your own fault your users can cheat with a usable bug, and trying to say that a bug fix that breaks a cheat is the same as a major change that breaks an API is disingenuous and mixing contexts, slopping together an intentional major change with an unintended feature removal. The developer needs to fix the bug, the user needs to learn how to use the program properly. Saying developers should cowtow their entire way of versioning source code to a user's misuse or abuse of an unintended feature is not only chaotic but project-crushing, and puts the cart before the horse.

EdvardM commented Aug 3, 2016

There's this thing called changelog, or readme. If I introduce a breaking change to something I use semver with, I increment version appropriately and write elsewhere, that even though strictly speaking this is a breaking change, the change is actually only this foo here, so consider it before upgrading.

tl;dr: can't see all the bad press semver is getting. It is not perfect, but it fixes many present problems. Also, maybe even more important, you don't need to put aside all that information meant for human consumption. While it's true that semver cannot compress all the information one might need when releasing a new version, just don't. Add that other information elsewhere.

If we refuse to improve things because it's not perfect, it's just stupid.

That said, I probably missed many important points and likely said something stupid myself, but naturally I won't re-read what I just said because Internet :3

How does semver work for long term support releases? Say I published 1.2.3 many years ago, and now I am at 4.5.6, but I have a customer who found a bug with 1.2.3, and cannot possibly upgrade: they want development to continue (1.2.4, etc.). Except that 1.2.4 has been released a long time ago as well, and of course there can't be version number collisions in the same project (I track git sha1 with semver numbers in a table). Starting anew by forking the project comes at a cost (new project name (in Java it would mean new Maven coordinates), new CI build plans, etc).

Ideas?

@martinda - you should be incrementing your version numbers based on the last version that you have RELEASED, not on what version your customer currently has. So if v1.2.4 had already been released, then you'd release v1.2.5 for your bugfix, same as if the last version released was v1.2.54 you'd release v1.2.55 for the patched version. Do you get what I'm saying?

OJFord commented Oct 9, 2016

@martinda There should be no upgrade cost from 1.2.3 to 1.2.4 that they're less willing to do than the upgrade to the fix you now provide. Understandable that they might not want major or even minor version upgrades, but if you're just fixing a bug that's come up in an old version (say, a security vulnerability) then that's just another patch in the 1.2.x series; no collisions.

@martinda Not explicitly stated, but absolutely necessary, is the fact that you need to maintain a branch for every major version that you support. Since it should always be safe to upgrade minor versions, you can keep them in the same branch. But if you support 1.x, 2.x, 3.x, and 4.x, then you need at least 4 branches on which to back-port fixes. This is not a shortcoming of SemVer, but simply the reality of supporting so many major versions. It's much better to just support 2 major versions and tell your customers that they have to upgrade when you end-of-life an old major version, or they lose support.

@robnagler The versioning of programs and OSes is a much less compelling use case than the versioning of software components...i.e., libraries, frameworks, etc. Does it matter to anyone at all whether Windows is at version 10 or version 2016? No, no it doesn't. I don't expect to find any Gradle files with a dependency like: "com.microsoft:windows:[7.0,10.0)". But if you work on any project with more than a few dozen dependencies, nested more than 3 layers deep (very common in the enterprise, BTW), then you soon appreciate all the fuss about SemVer. My guess is that Mr. Ashkenaz does not have to maintain any such packages, which is why he fails to appreciate the power of SemVer.

The evolution of software engineering hinges on one property and one property alone: automation. Because it's not about us, the weak fleshbags of wetware with our horrendously slow chemical signalling pathways. It is about the machines, who don't eat or sleep or make mistakes because they stayed up too late or didn't get their morning or afternoon coffee. The evolution of programming languages is all about how we can make the compiler/interpreter/runtime do more so that we can do less. The explosion of build and deployment systems allow us to assemble ever larger systems of software, where entire programs are mere components, and a powerful rack server is just a minor computing element in the big picture. We cannot handle this level of complexity on our own, manually, in our feeble brains. Nor should we.

Only by abstracting away the petty details to machines can we realize our potential as craftsmen and builders. SemVer gives us a common language with the machines, with the dependency analyzers. They let machines decide which version of our dependency to pull in, which ones are safe. Because the world moves too fast for us to decide these things by hand. Because if we take that luxury, someone else solving the same problem as us will beat us by delegating this drudge work to the machine. Because, let's face it, at some point, we will make a mistake that the machine would never make. SemVer is not perfect, nor can it be. Engineering is not about perfection. It's about trade-offs. And SemVer is a damn good trade-off. It is better than Romantic Versioning because it scales. Because we cannot play John Henry and rage against the machine with our hand axes and sledge hammers. SemVer is the future because it has to be. Because we can't build bigger and bigger software without it. Versioning is a bottleneck. Manual versioning is an intolerable bottleneck. Just look how many JARs are in MavenCentral, and tell me how many of those you want to inspect manually when building your medium-sized Java REST service. Come back when you're finished. I'll be 20 releases ahead of you.

marado commented Oct 20, 2016

The responsible way to upgrade isn't to blindly pull in dependencies and assume that all is well just because a version number says so — the responsible way is to set aside five or ten minutes, every once in a while, to go through and update your dependencies, and make any minor changes that need to be made at that time. If an important security fix happens in a version that also contains a breaking change for your app — you still need to adjust your app to get the fix, right?

Interesting point. Being one who tends to agree with almost all this text, I find the part particularly curious -- and tend to not really agree with it.

The way I read this, you're arguing for depending on fixed versions/version ranges you know they work, and revise that (perhaps periodically) when new versions appear. This seems a... "non-romantic approach", and I doubted it so much that I went to actually look at underscore as an example, and it seems that you are not doing it either: at https://github.com/jashkenas/underscore/blob/master/package.json#L19 we can see several lax dependencies.

You might have done careful decisions regarding each of them, so let me try and figure them out...

The most suspicious was "docco": "*" -- the most lax of them all, but you're docco's maintainer, so you're possibly counting on remembering which of your own packages depend on it.

Then we had "coveralls": "^2.11.2" -- and here, the package's README itself says that the "one true way" (for the package itself? maybe not for those who depend on it; still...) is to depend on a particular version.

So... maybe you'd care to explore a bit more on what are your thoughts on this particular issue?

SemVer is woefully inadequate as a scheme that determines compatibility between two pieces of code — even a textual changelog is better. Perhaps a better automated compatibility scheme is possible. One based on matching type signatures against a public API, or comparing the runs of a project's public test suite — imagine a package manager that ran the test suite of the version you're currently using against the code of the version you'd like to upgrade to, and told you exactly what wasn't going to work.

You touch a very important point here. The issue doesn't belong to the packages, the issue belongs to the package managers, which should attempt to do some kind of cross-validation of package dependencies, perhaps at a new version submission time, like how it is done in Debian.

martinda commented Nov 15, 2016 edited

I am still not finding how to do long term support releases with semantic versioning the way it is defined today. When I look at Jenkins for example, they use Major.minor for development, and they use the patch number to mark long term support releases. If they wanted to use semver, how would they do it and still support LTS numbering?

Typically, my customers will want support of past releases, and they won't accept changes made elsewhere in the repository whatsoever. They want changes on their release, nothing else.

I can summarize the problem in the following diagram:
Git Workflow master with single release branch
If I continue numbering at 1.2.0, then I create discontinuity in the changelog, and that's hard to maintain. And if I put 1.2.0 on the release branch, what's the next number I can use on the master branch? I am thinking of using the pre-release field or the build meta field to number my LTS releases. But at this point, it is no longer as per semver.

Based on my experience with composer and npm, and how we've been using it in practice at the office this past year or so, I decided to draft a simplified convention based on SemVer and how the ^ constraint actually works. Feel free to comment :-)

Maybe what we need is for two numbers: The release number and the version number. The release number would be what is used to refer to a publicised / advertised announcement of a new... well er release. The version number would be the 'mechanical' compatibility number and abide by semver or some other similar system for tracking compatibility.

This would be similar to / practically the same as what current OS vendors tend to do i.e. consumers know MS Windows as Windows XP, Vista, 7, 8, 10, etc... but internally there is a more traditional version number in there. Similar applies to OSX, although they chose big cats as their defining series of identifiers. Well, that and they increment their minor version number for every new 'release'...

christian-weiss commented Nov 29, 2016 edited

Version number (SemVer) is for machines (dependency manager, etc.).
Release number is for marketing.
@martinda Many people begin to struggle when trying to combine sequences with git branches. This is not a SemVer issue. It is a mental issue.
Even if you have a sequence of 1.0.0, 1.0.1, 1.1.0 it feels like having virtually a branch when adding 2.0.0, 1.0.2 and 1.1.1 a month later, but in fact it is still a sequence 1.0.0, 1.0.1, 1.0.2, 1.1.0, 1.1.1 and 2.0.0. You just filled the gap between your first sequence (1.0.0, 1.0.1, 1.1.0) with new elements (2.0.0, 1.0.2 and 1.1.1). Each element of this sequence has one precurser and max. one sucessor. A sequence of version strings.

When you create a branch "release" from "master" in git the code is the same (the same commit) - by branching you just assign a second name to this commit. (i omit the technical details of git here to give you the big picture) Assigning a second branch name is like giving your software a second name. If you branch out some special customer releases from mainline i would recommend to call the branch "customerX", customerY" and so on. Same is true if you company is releasing a mainline version, a LTS version, some special editions. Even if your code base is 99,9% the same and do have a common precursor you now have siblings. Give each of them a uniqe name and let each of them have a own life story, a own sequence of version strings. It is absolutely ok to have a Tim 1.0.1 and a Alina 1.0.1 both with mainline 1.0.0 as parent. If you merge back into mainline, then a new version string on mainline will be created on mainline (e.g. 2.2.0).

On another project you can consume Tom to get only changes of Toms life story. You can consume Alina-only or mainline-only, too. But not all commits from all branches.

In a ideal world there is only one release branch, your mainline. Having a lot of release branches introduce a lot of management overhead, from development over testing and deployent to release, marketing and support. Most firms tend to limit the number of release editions as far as possible, because the additional overhead is often not payed back as a unique selling point. It just slow down your team. Implementing a configuration option, adding a skin or template engine or refactoring to have a plugin API often pays it efforts fast enought to be consired as the better option compared to having overhead for long living siblings.

SemVer is a useful convention. It is meant to quickly, and simply, convey an approximation of compatibility. I have found that most of the major libraries I use on NPM do a good job of following it. It was never meant to be a contract guaranteeing compatibility. There will always be regressions in code. Automated tests (unit and integration) catch both regressions and API changes for us. We don't have to read through the change logs for every release, we simply adjust our code where APIs change, or roll back when a regression is encountered.

Venryx commented Feb 2, 2017 edited

One possibility, for those who dislike the idea of major version bumps for (conceptually) minor changes, is to have a fake splitting of the major version for display purposes.

So let's say we're at SemVer version 1.0.0, and we make a conceptually small change which, however, breaks compatibility.

We could do the normal way of upping the major version to 2.0.0. But let's say we dislike this, because it gives the impression that it's changed substantially when (conceptually) it hasn't.

Instead, we up it to 10.0.0 (computers don't care) -- but display it as 1-0.0.0 in places where humans will be caring about it.

Same thing if we're at 14.2.8 (shown to users as: 1-4.2.8)

If we make a minor breaking change, we just follow standard SemVer and change the version to 15.0.0. The user-shown spin-off is displayed as 1-5.0.0, however, which conveys that it wasn't substantial conceptually/feature-wise.

If we make a major breaking change, we instead change the version to 20.0.0, The user-shown spin-off is displayed as 2-0.0.0, which conveys that it was substantial conceptually/feature-wise.

Thus we preserve the technical SemVer requirements (of major increasing anytime a breaking change is made), while also allowing for nice-looking versions for humans, showing the project's overall progression.

DunetsNM commented Feb 7, 2017

SemVer is for robots indeed and this is great because it helps to automate things like packages update, dependency conflicts detection etc. Romantic Versioning is a straight way to DLL Hell 2.0, it won't work for large projects with hundreds of external packages that form complex dependency graphs.

bobmagicii commented Mar 21, 2017 edited

i will forever and always use my human understanding of the code to decide how the version gets bumped. figure out how to use the dry run option in your package manager so you can see what is going to happen BEFORE it happens, take the time to actually learn about your dependencies.

you see that 0.x.0 version bump you better start reading changelogs.

this has been a free course "how to not be a bad dev 101"

[cue flamewar i probably will forget to come check back up on]

Just throwing it out there that Node.js uses semver as of September 2015. Likewise AngularJS is transitioning to Semver.

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