162 comments

[ 5.6 ms ] story [ 231 ms ] thread
That feels a lot like giving up. You have a date and a hash, which tells you almost nothing. Yeah, semver isn't practiced perfectly, but it at least tries to give you useful information.
Honest question: How do you feel e.g. "1.2.1 => 1.6.2" is more useful than for instance incorporating dates, like: "2020.05.04 => 2020.06.20"?

From my perspective both schemes provide useful information but for different purposes.

You can issue a patch for 1.2 even after releasing 1.6.2 using semver
You can even maintain 1.2.x and 2.5.y and 3.3.z at the same time:) (It can be a right pain, yes, but we couldn't break our downstream so we were stuck with backports of new features... well, backports and living with the old versions)
Strict semver all too often ends up "47.0.0 => 53.0.0" because in the spirit of xkcd 1172 "every change breaks someone's workflow" almost everything can be seen as an API change. 47=>53 could be a quick succession of tiny but technically breaking fixes in some backwater part of the API that hardly anyone uses or it could include a once-in-a-decade major API redesign or two.
I honestly feel like that's a symptom of a poor API design in the first place, although I'm willing to agree that there can be a little wiggle room in the "is it a breaking change if someone depends on a bug?" middle. If you have to bump your major that often, at best maybe consider why, and at worst at least you're still making it clear to the downstream users that that's what's happening! Better to explicitly mark "yeah, we broke API compatibility again" rather than "we incremented the version integer again; good luck finding out why unless you read the complete changelog".
A lot depends on typed on typedness. If breaking changes are identified during compilation then the cost of minor breakage somewhere is low. The quantity of change is far more interesting than strict absence/presence. And it takes so little change to get unexpected client code broken that I'm very sceptical of attempts to prevent it in API design.

Take classic java for example. If you have a public interface adding a method can break someone's client code because nothing can keep them from implementing that interface and the implementing class might already have a method with a colliding interface. If you went completely over the top with compatibility expectations you could even argue that it's impossible (pre-modules I think?) to add any class or interface at all because someone at foo.com might have rudely put a client code class with the same name in your precious org.bar namespace. Nothing in the language prevents that.

Trying to design out the possibility of breakage isn't worth it, you'll end up with conventions that soften the definition of "breaking change" anyways (or with the antipattern of major-only increments)

PS: on the topic of timestamps, I think that the bugfix level would be much better served with a date than with an increment. A "2.7.3" could be both much older or much younger than the corresponding "2.5.6" or contain exactly the same important fixes. When assessing the risk of running something that contains an older version your most important information is often before or after $famousvulnerability and increments can be quite deceptive. I think I might be happy with major.minor.date, where both major and minor are allowed to be breaking, with meaning "most client codebases will need change" and minor means "most client codebases will be fine". For languages leaning towards typed I'd lump the "only added features" together with minor breakage, for more dynamic languages the existing semver (plus date instead of bugfix increment) is probably fine.
I would say that I would confidently update from: "1.2.1" -> "1.2.6" without batting an eyelash if my tests passed. "1.2.1" -> "1.6.2" would give me pause, but I wouldn't shy away from it.

Going from "2020.05.04 => 2020.06.20" doesn't tell me _anything what so ever_ about backwards compatibility or risk that I'm taking on. Is that the equivalent of going from "1.x to "2.x" or "1.x" to "8.x"? Who knows.

As I said, from my perspective they're useful for different purposes. I've worked on software (internal sales system) that the end-users were interacting directly with. They'd always be running the latest version and thus versioning by dates would have been a lot more tangible to them.

Over the course of 5 years and several thousand bug reports from end-users and 2nd/3rd line support I've never once seen a bug report that mentioned a version number. It was always "the version that was deployed last week" or something to that effect despite the version number being visible in the app at all times.

For APIs and libraries semver makes a lot of sense, I agree, but not all code is APIs or libraries.

Dates tell you squat all.
That certainly depends on who's using the code. I've worked on both APIs used by other developers and software used directly by end-users. For the latter, dates would have worked much better than version numbers since the end-users couldn't choose what version they used anyway and version numbers didn't mean anything to them.

For APIs or libraries, yeah, semver is cool and dandy, but not all code is APIs or libraries.

I is giving up and rightly so. Numbers along a few axes cannot convey even close to the information needed. Only an honest changelog can do that. I appreciate that people try to do their best in determining the next version number, but we humans are about as fallible at determining "did this break some API?" as we are about determining "did this break something?" (i.e. we introduced an error).

Then there's all the different versions of stdlibs, language, etc. etc.

We should actually give up and fix our dependencies. (Maybe not for libraries if you can 'recompile' as needed, but...)

Ubuntu 20.04 gives me as much information as CentOS 8.

No, in fact Ubuntu 20.04 gives me more information, as it includes the date!

I'm not going to advocate for completely abandoning semver, and I do think that people abuse it or try to use it in places where it might not have been originally intended.

That said, personally, my most hated anti-pattern (outside of not properly tagging broken interface versions), is people refusing to tag a 1.0. In theory, this allows folks to continue breaking the interface without calling it a broken interface. In practice, these packages are already widely deployed and NOT tagging a major version completely invalidates the versioning mechanism all together.

The versioning scheme cannot do its job unless if you use all three fields. So release a damn 1.0.0 already! Your software will never be perfect. it's ok.

Hold on?

My software isn't version 0.x because it isn't stable. It's version 0.x because there's never been a breaking change necessitating a major version bump.

That seems like an unusual interpretation of versioning. Version 0.x signals the product is pre-release and unstable. 1.0 is the first stable release.

If you’re saying your software is stable at 0.x, just drop the zero. Otherwise you’re likely giving the wrong impression.

Semver 0.x.y is by spec definition unstable & wild-wild west.

https://semver.org/spec/v2.0.0.html#spec-item-4

"Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable."

There is no code change that makes 0.x.y turn into 1.0.0. This is covered in the FAQ

https://semver.org/spec/v2.0.0.html#how-do-i-know-when-to-re...

It is a case of something vaguely along the lines of "0.3.5 has been running in prod for X time, I am now confident, I will now tag that same code as 1.0.0"

TIL! Thanks!

I always assumed that if you were doing Semver right, then you could end up at 0.124.2 or something. Glad to learn that’s not the case.

Perhaps I'm missing something obvious, but, why not?
Example: semver does not require libfoo 0.4.1 and libfoo 0.4.2 to be compatible in any way. Anything can change between them. It's not until MAJOR >= 1 that the MINOR and PATCH numbers have semantic meaning.

Say libfoo 0.4.1's furbinate() function draws a pretty picture. When libfoo 0.4.2 comes out and someone calls furbinate(), they may be surprised to have all their files deleted and their screen covered with filthy Latin poetry, but it is allowed per the semantic versioning spec.

Sure, but still doesn’t explain why one can’t end up at 0.142.2 as noted by GP.

Something is missing from this discussion, ninja edit?

A version 0.142.2 is perfectly possible with Semver, it just implies that the the developer has had a lot of iterations on the idea, but never got to the point that they consider stable.

If the software had a stable API at that point, but they were still using a 0.x.y version number, then they wouldn't be using Semver properly, since 0.x.y versions are for unstable initial development releases.

https://semver.org/#spec-item-4

Right, I know. But GP implied it was impossible, not just unlikely. Hence the confusion all around.
I guess my wording was unclear.

I thought with semver if you never did a major breaking change then you had to keep the major version 0 forever.

First code that goes to production is 1.0.0
Not that cut and tried as it depends what "goes into production" means.

As an example: Lets say I have an open-source library that I am developing. It is currently version 0.x.y and someone depends on it (without my knowledge because that is usually how it is).

Is that now in Production? Should I be forced to make it 1.0.0 even though I am still making large numbers of breaking changes?

That is a simple example off the top of my head. I am watching https://www.youtube.com/watch?v=tISy7EJQPzI (Linked from another comment) and he goes into good details about issues with Semver.

I guess you could say, "intended for production"
Semver 1.0.0 is the first code with an interface stability guarantee, that's it.

Whether that correlates with production is up to the engineering team putting it into production. If they budget for dealing with incompatible interface changes when they want/need to update and they don't have automated untested updates of the dependency, it is entirely reasonable to decide to depend on 0.x in production. In turn, it's entirely reasonable for the owner of the dependency to say, this is fine to go into production (it's stable, secure, feature-complete, etc.), I just have a couple of small planned changes to the interface so I don't want to commit to it yet.

Another way of thinking about this: semver doesn't say anything about the current version of the code. It says how that code compares to past and future versions.

Which just makes staying in production in 0.x as useful as not adopting semantic versioning.
>Should I be forced to make it 1.0.0 even though I am still making large numbers of breaking changes?

Yes, but then your next version could be 1.1.x and might even include the odd interface/API changes without feeling at all guilty.

I disagree, someone else deciding to use my 0.x.y version library does not obligate me to suddenly make it 1.0.0.

I think geofft sums it up best elsewhere in this thread where it is about interface stability guarantees being the deciding quality.

While officially `0.y.z` makes no guarantees, unofficially many people and some tools interpret `0.y.(z+1)` as being compatible with `0.y.z`, treating `y` as the equivalent of a major version and `z` as the equivalent of a minor version. This makes it easier to test pre-release libraries without treating every point release as a breaking change.
this makes no sense. If you're at 0.1 and you rewrite your own code and break it, by your logic it shouldn't be 0.2 but 1.0 then?

What semver is meant to symbolise is ä that minor and patches from 1.0 forward will remain stable and up until 2.0 during which breakage is to be expected. This means that whatever your project status was at 1.0 should function the same at the minimum level on all versions beginning with 1.

This couldn't possibly work if you start counting breaking changes from 0 because right after your first lines of code it's already breaking. You'd have to bump major versions every day.

Most companies use sentimental versioning rather then semver.
"sentimental versioning" rings so true!
(comment deleted)
Or they version by marketing
Sure, but even marketing versions are usually backed by the real version if you look for it.
Looks at Terraform ( ͡° ͜ʖ ͡°)
One day people will realize that Terraform is actually a disservice ;-)

And that IaaC is about the I rather than the C.

Could you expand on this?

HCL seems to be a language designed to avoid deadlocks, similar to eBPF, so the "code" of it is weird.. but I'm not sure I fully understand this comment otherwise. :S

What's "IaaC"? If that's what I think it is, shouldn't it be "IaC"?
For others who are confused: infrastructure as code.
Infrastructure as a Cervice.
(comment deleted)
Infrastructure as anemic Code.
Yeah, I just dealt with Terraform failing on a patchlevel change that created incompatible state files. Get it together, guys.
> In practice, these packages are already widely deployed and NOT tagging a major version completely invalidates the versioning mechanism all together.

It implies that all releases are potentially equivalent to semver-major releases, but that doesn't invalidate the mechanism. It means there is no such thing as a safe upgrade, though.

(I think it's a mistake that semver doesn't didn't keep patch versions as having the same semantics in pre-1.0 releases as post-1.0, though.)

Yeah exactly. All <1.0 indicates is "we're not really doing SemVer yet, but intend to do so at some point in the future, probably maybe".
Or "our tooling or the ecosystem forced us to use SemVer".

Or "the available tooling isn't good enough to make strong promises about API stability, and I'd prefer not to lie to you".

I'm actually OK with people following semver, and also refusing to tag a 1.0, as long as they also refrain from advertising the product as stable and production-ready. You can't have your cake and eat it, too.
If you refuse to tag a 1.0.0 and follow semver it means any change could be a breaking change. This has an impact on consumers of the software. As a consumer, I quite often value stability.
I think the idea is that as a consumer, you shouldn’t use pre-1.0 packages in the first place, since pre-1.0 means “not ready for use”.
I think it's great that projects hold off on calling a release "1.0.0"--they're signaling to users that value stability like you that they're not ready for you yet.
If stability is important to you, just don't use software tagged 0.x.y.

It's not like it's going to be more stable if the authors are forced to mark it 1.0.0.

At least authors are honest that their goal at the moment of release is not stability but their freedom to break it every release if they see a reason for it.

It's not really about whether the software is actually more stable, it's the signaling the project gives by choosing to hold their version numbers in semver's semantic no-man's-land where the version numbers aren't expected to give a clear indication of the severity of the change.

In particular, what the signal says is, "We aren't yet ready to think about or support the needs of someone who's trying to use this in production."

Which, if the project is also then pitching the software as production-ready, is a terrible mixed message. Maintainers really should pick one story and stick with it.

> In particular, what the signal says is, "We aren't yet ready to think about or support the needs of someone who's trying to use this in production."

> Which, if the project is also then pitching the software as production-ready, is a terrible mixed message. Maintainers really should pick one story and stick with it.

Yes, that's true, but you've got to realize that these messages are often coming from different teams. Versions are more likely to come from engineers, whereas pitches are more likely to come from sales. As an engineer, version numbers are one of the few tools I have for telling the truth when sales is lying. Engineers generally don't want to promise stability that isn't fully-formed, because if a valuable enough client starts using it, then we end up supporting some horrible half-baked interface until they decide to upgrade.

In that context, when you see version numbers disagreeing with marketing, don't point the finger at semantic versioning as "refusing to tag a 1.0.0 release". They're the ones telling you the truth. Point the finger at marketing.

In a larger sense, the sooner you start viewing all marketing as untrustworthy and basing your decisions on evidence instead, the happier you'll be. Version numbers are a small signal. Actual trial periods and getting to use the software should be the real deciding factor. And don't make the mistake of turning a trial period into a commitment without actually evaluating whether the software does what you want it to.

It's kind of hard to rest things like a huge chunk of the world's computing infrastructure basing itself on OpenSSL long before the product put out its 1.0 release on the usual "don't trust marketing" finger-tutting. Did OpenSSL even have a marketing team back in the early 2000s?
Sure, hype isn't necessarily based on marketing. That's why there's the word "often" in my previous post.

I think in the case of OpenSSL, there was a huge need for what OpenSSL was doing which justified a lot of people taking a calculated risk to use it. I'll concede in that case that the risk may have actually even been justified.

That same logic doesn't apply to blahblah.io's "revolutionary" 0.x.y packages on npm or pip, or the bleeding-edge SaaS of the week.

To a consumer a 1.0.0 may be more stable.

At a 1.0.0 you expect the authors to not change the API for what ever the API is for the thing. Your interactions with the software have an expected level of stability and authors don't need to take into account prior to that.

Stable means no changing or firmly established. Once you tag a 1.0.0 you say that some things will no longer change. Hence, an element of stability.

No, it means that anything within 1.0 won't change, but that 2.0.0 and above will include breaking changes from 1.0.0. That's the whole point. Semver versions don't imply general stability of the service, just stability of the interface within the major versions.

If you're depending on the software in your dependencies, you can mark >1.0.0,<2.0.0 and if people are correctly doing semver, your software won't break. When you need to move to another major version, you'll need to ensure your usage hasn't broken with the change.

If you import packages that aren't tagged 1.0.0 yet, you apparently don't value stability enough to listen to package managers saying their packages aren't stable yet.

If you're demanding that package managers tag a 1.0.0 version, you're either demanding that they make their product stable now, or you're demanding that they lie to you.

Cargo works around that by treating the first non-zero number as the major version, so 0.x version bumps are also considered breaking (and don't happen automatically).
The problem with that is that many of these projects are production-ready enough and end up being used in pretty big projects because the alternative is starting from scratch or using an inferior solution. Because they're eternally under v1.0, APIs break and so the package never gets updated in production.
> people refusing to tag a 1.0

Notable offenders listed here: 0ver.org

I'm guessing that website is satire, but if so, they don't make that very clear.
If you have to explain the joke, it becomes less funny.
Well, I have to agree with that. What I meant is that it's not even clear whether this is a joke or not. Or maybe I just don't know/care enough about software versioning to get it.
They link to https://calver.org/ which is about using the date, at least partly... somewhat similar to samver.

(Not 100% sure it isn't sarcasm, too.)

Offenders? But they say 0.x it's good, in the same ways as zero indexing arrays
I am also okay with following semver but being allowed to release major versions that dont necessarily warrant "breaking changes." Software has milestones. Lets make them obvious. If from 0.0.1 to 0.7.4 you had like 5 major changes but nothing was broken API wise those should of been 5 major releases...
Strong disagree. Incrementing the major version in cases like that is just going to confuse people.
Only people familiar with semver maybe, but the rest of the world lives on just fine. This is also what release notes are for, to clarify why the major version update. Every project has its own standards, semver is not a law just a sane suggestion.
> I am also okay with following semver but being allowed to release major versions that dont necessarily warrant "breaking changes."

Then, to be blunt, you are not following SemVer

> Software has milestones. Lets make them obvious.

SemVer is about signalling to clients what expectations they should have by being able to look at your version number changes. If you don't follow it then you are confusing your clients.

> If from 0.0.1 to 0.7.4 you had like 5 major changes but nothing was broken API wise those should of been 5 major releases...

Anything <1.0.0 is already, by definition, subject to any change so your example is a bit off.

However SemVer, in my opinion, is primarily used for libraries that are part of systems of dependency resolution.

If you have a desktop app used by end-users then I wouldn't recommend using SemVer, just do what marketing requires.

Refusing to tag a v1.0 at least signals your intentions of API instability, so I don't see how it's abuse. Semver without incrementing a 0 major version is just the status quo of arbitrary versioning where anything can break, and you have to scour patch notes to find out. But it's not abuse. It's what we live with with the vast majority of software.

The difference is that someone doing that on semver at least may cross the threshold into sanity one day, or is signalling that it's possibly in their roadmap. Seems like a notch better than the rest of software.

But I think your frustration makes more sense reserved for the 99% rest of software.

> In practice, these packages are already widely deployed

Other people's poor decisions do not confer any obligation on the person doing the versioning.

Generally agree with this: semver makes a lot of sense for code libraries, makes almost no sense for anything else. CSS, styles, and user-facing documents have no concept of compatibility without change, because they have no real implementation-hiding: they should probably just be incrementing integers.
For another perspective on “semver is a lie,” see the “Live at Head” talk from Titus Winters of the Google core C++ team.

https://m.youtube.com/watch?v=tISy7EJQPzI

TL;DW: By Hyrum’s law, at sufficient scale, there’s no such thing as a non-breaking change (including changing comments). Embrace that, and test sufficiently that you can always take the latest dependency but detect when it breaks.

> (including changing comments).

Huh? I didn't watch, but practically speaking, how do you break something by editing comments? Let's assume it's an actual comment and not a shebang line or something.

Because someone in your (presumably massive) org had some code somewhere that was parsing the source itself looking for... something. (The story Googlers tell is a program looking for a specific line number in a program’s output.)

More on Hyrum’s Law: https://www.hyrumslaw.com/

I didn't watch it either, but...

Does your language have exceptions? Do the exceptions support stack traces? Do those stack traces have line numbers? Well then...

    // old code: version 1.2.3
    if(problem1) throw new Exception();
    if(problem2) throw new Exception();
Meanwhile, we have client code that catches the exception and then inspects the line number to see whether it was caused by problem1 or by problem2.

(You may say that this is crazy and no one would do such a thing. Maybe so. But this at least proves that it's theoretically possible. Also, you'd be surprised at what some people will do.)

Anyway, we upgrade to the new code:

    // new code: version 1.2.4
    // Helpful comment here; no other changes.
    if(problem1) throw new Exception();
    if(problem2) throw new Exception();
And of course this comment messes up all the line numbers, at which point we can have anything from misleading error messages to system meltdown, depending on what the client code used the line number for.
I was pretty impressed with Microsoft's support system that would pull up the symbols from the release build that created a crash dump. It's not a good use of time to troubleshoot using some wrong version of the code that may or may not even have the same problem.
That's only if you consider line numbers as part of the public API, which would be crazy.
I'm guessing no one is crazy enough to consider line numbers part of the public API. And yet... I could imagine myself writing code like this if I were desperate enough, even though I would know perfectly well that my code would probably break the never next time I upgrade.
Sure, but that's because you know you've used a non-public API in which case semver has nothing to do with the conversation.
Source code level reflection. Things like __LINE__ can change with new comments above them. An example is something like a logging statement that makes use of something like a line macro and a brittle test that matches against an exact logging entry.
You can break things by editing comments, but those things aren't, usually, part of the public API.

SemVer is a claim about the public API, not the total of behavior you could potentially observe and react to.

Another classic was (is?) V8 using the textual size of a function as a heuristic when deciding whether to inline it in the jit. So changing a comment could have a large performance impact.
Wouldn't it be so smart to exclude comments in the size calculation?
Hard to say! It seems obvious, but given it is so obvious there might be a good reason to not do that. For example, if 99.9% of JavaScript in the wild weighted by popularity is minified, this could be the right tradeoff.
> at sufficient scale, there’s no such thing as a non-breaking change

I don't disagree with you that unexpected breakage can come from the strangest things, especially as systems get larger and more complicated.

That said, that's not what SemVer is about. SemVer is explicitly about public APIs. It's about communicating from upstream to downstream what level of changes they should expect from the new version. Breakage caused by unintentional bugs, badly designed clients making incorrect assumptions, and things that fiddle around with non-public interfaces is all out of scope.

If I have an API that returns a named array as part of the response in version 1.0, and I add a few new entries to that array, that's version 1.1 to me. It should not break anything. It certainly can break something, for example if a naive developer decided to reference parts of the array by index value or just iterate through and assume they're always in the same order, but that does not mean it should be called a breaking change.

From the perspective of SemVer, a non-breaking change means it will not break a client that correctly implements a public interface. If we have to care about incorrect implementations and non-public interfaces then it all becomes meaningless.

That's the point. It's meaningless. If I'm a client, and I've bumped my dependency from 1.2.3 to 1.3.0, it doesn't matter in that moment whether my deployment is on fire because there was an unannounced but intentional change, or a breakage because of a bug. Semver is supposed to give me confidence that that upgrade is safe, and it fundamentally can't.
But it does prepare you for the scale of potential breakage? I always just saw semver as a scale of the possibility something could break without any guarantees. Major change is highly likely, minor change is potentially and patch is shouldn't but give it a quick check. That doesn't make it meaningless, it just makes it not black and white.

No change no matter how small should give anyone confidence that something won't break. I don't think it reduces the value of semver though. It's just another attempt in a long line of attempts to categorise changes into something quickly digestible and it does that well enough.

> If I'm a client, and I've bumped my dependency from 1.2.3 to 1.3.0, it doesn't matter in that moment whether my deployment is on fire because there was an unannounced but intentional change, or a breakage because of a bug.

So in your mind because bugs exist there is no purpose in communicating whether an update intentionally changes things? I can not see your logic.

Bugs will always exist. No meaningfully complicated software will ever be bug free. By your logic we should just never upgrade anything? Except that there are time-based bugs all over the place, so not upgrading anything is also guaranteed to break things (2038 anyone?)

I switched to integer version numbers.
(comment deleted)
I'll abuse it all I like. At the end of the day it's a format standard for versions, whether you use it semantically or not. It has a well designed mechanism for comparing versions. It allows adding of meta data to the version, so you can build what commit, and what build run on your CI system built it, or any other things you want. Many tools understand it.

I really struggle to see any meaningful downside in using semver, even if you don't do it semantically, if all you really need is some ever increasing number that is trackable through your system, it does that job as well.

This is actually a very reasonable response, if only delivered in a somewhat dismissive way.

I believe the point is that even if you follow the recommendations to the letter, and do everything correctly and never even accidentally change API behaviour between minor versions, anyone using your product still cannot be sure that that is actually the case.

When I upgrade a piece of software, can I rely on the version number actually reflecting the compatibility? Of course not. As long as even a minority of software doesn't follow these recommendations (and to be realistic, they never will) you can never actually rely on these numbers for anything useful in the general case.

In the special case, sure. Some vendors have very strict compatibility guarantees and that is really useful. But to take advantage of these guarantees you need to read their documentation on the topic, and if you read the documentation it doesn't really matter what versioning scheme they follow.

> I really struggle to see any meaningful downside in using semver, even if you don't do it semantically,

It erodes the social contract of semver.

Without a common understanding and trust of semver you lose the ability to have an educated guess on whether 2.4.3->2.5.0 is an API-breaking change. Naturally, you cannot truly rely on this anyway (and dependency versions updates need to be tested), but being able to have a general idea about your dependencies' API breakage at a glance is a great help.

This becomes even more important in the case of transitive depdencies: something that might be drive-by bumped by your direct dependency (satisfied by the fact that it's only a minor release bump and _their_ use doesn't break) might actually cause your direct use of that transitive dependency to break.

What is the social contract of semver, as a library consumer? If either the patch or minor versions change, I should not care since everything is backwards compatible. But if the major version changes, all bets are off. This seems like almost no contract at all.

It seems to me that this same information could be communicated by combining the minor and patch versions into one number, and changing the name of the library rather than changing the major version number.

I like having the patch number set apart. I frequently run into bugs with dependencies where the first thing I check is if they've released a patch, so I can try that to fix the bug before digging any deeper.

Muddle that with minor revisions and that information is lost. Minor revisions, in addition to signaling new functionality, also signal the possibility of the introduction of new bugs.

So both patch versions and minor versions signal the possibility of introduction of new bugs - is there a point to separating them?
AFAIK, the core use-case for Semver is OS package management — specifically, it’s the ability to auto-update installed system packages, knowing that a new patchlevel version of a system library won’t break the OS from applications’ perspective.

Distro maintainers have always been doing something like Semver, with their own “compatibility versions” distinct from the upstream’s versions. Semver is basically just a push to get the upstream developers to do the distro maintainers’ work for them, by versioning things the way the maintainers would.

Note that this has little to do with language package ecosystems in the constraint-resolution / bundling / locking of deps sense. Rubygems et al get frozen at build time (and as such, don’t need Semver — see how long Golang got along without module versions.)

Semver is instead for version negotiation at runtime (or “ops time”, if you prefer): it’s for resolving symbols using dynamic library loading; or for resolving which version of a daemon to install, to be compatible with a given client library’s wire protocol ABI level.

And for those use-cases, Semver is pretty much essential. If you’re not doing it for your package, someone else downstream of you still has to.

We want the ability to automatically update our npm packages too. So that use-case is not limited to OS packages. It applies to anything that gets distributed by packages and you want to easily update or install it. Who would want otherwise?
There’s a difference between these two senses of “automatic”, though.

An “automatic” update of a language-ecosystem build-time dependency package, is still curated by your (not usually fully-automatic) release-management process, like any other codebase change. With full CI/CD automation, some bot (e.g. https://dependabot.com/) notices that a dep has updated, and “proposes” a PR that updates your project’s lockfile; your CI buildbot then notices the new PR, and tries building that branch to see whether it compiles and all the tests pass; and, if everything looks good, it merges the PR into the main development branch. Even then, it’s now just merged into the ‘develop’ branch, and is not necessarily going to be a part of a cut release yet. A CD bot may put it into a QA environment from there; divert a percentage of traffic to it (ala https://github.com/github/scientist); and then maybe eventually make the call that it’s not hurting your metrics, and so switch the traffic fully over to it. But more often, there are usually humans in charge of that final cut-over switch, whoget to make a final call, before this kind of “automatic” update fully hits prod.

Whereas, with OS packages, there’s no CI/CD pipeline — the version number on the package (coupled with your auto-update config) is the “last line of defense” standing between the package and your production system. You can set up a mirroring QA environment that gets updates first; but this is effectively just “smoke testing” — you don’t really get to “run all the tests” for your entire OS and application layer over again, in light of e.g. a new libc, to “prove”—at least in some minimal sense—that things will be fine in prod. You just deploy the configuration, and observe that it’s “working.” Maybe you will have metrics on OS log volume or something similar; but OS package updates often introduce new spurious logs that you don’t care about, so this is a bad metric to track.

(Of course, you can do the QA traffic-splitting thing for your OS-package updates as well... but this means slowing the cadence of OS updates / bundling updates together so that you can gather enough data to say something about whether each new update-bundle is “working.” And if you bundle enough updates together, you’re effectively taking the reins of the OS from its maintainers, with the OS becoming just another part of your release-management process, i.e. with you effectively cutting whole-VM-image “releases.” Which somewhat removes the key advantage of automatic updates — especially automatic security updates, and especially especially automatic kernel security updates [ala https://ubuntu.com/livepatch]. You make yourself vulnerable to these discovered attacks in the interrim, in the name of stability.)

When you think about it, as long as you’re operating in “full paranoia” mode in both cases (as described above), then for the “build-time dependency” packages, all the stuff the CI/CD pipeline does kind of obviates versioning. An incompatible new version of a dep that breaks your software, will just be caught during the regular CI process. You may as well use the sort of timestamp+gitsha versioning that the OP proposes; nothing would really change. It’s only in the OS package case, where semver is getting you a benefit you wouldn’t otherwise get just from good release hygiene.

>see how long Golang got along without module versions

What? What was the problem?

Humans determining the difference between a major minor and patch is never perfect. If we cannot rely on semver and can only trust a changelog, why suffer the manual step in our CD? Just autoincrement and maintain a decent changelog.
I've found a lot of complaints about SemVer can be fixed by tacking a vestigial "1." on the front of the version. You keep everything else the same, and that front "version" number is for "complete overhaul".
Using dates as release version names has its own problems. The key thing I find challenging with them is you don't know what the release name will be until it's out there (or you try to guess ahead of time and are often wrong).

This makes talking about and planning for a release unnecessarily verbose. You can only describe your releases in terms of relative time. Item A is going in the next release. Or the release after next. Or three releases from now. It's much easier to know the name of a release ahead of time.

I did away with semver for my company awhile ago, moved to dates for a little while, and now just do plain numbers. It's basically just a simplified version of semver without a distinction between MAJOR or MINOR patches. This works just fine when you're building a web app and not a library to be consumed by others.

I'm using semver with minor as date For example: 1.0.20200729
If skipping patch numbers I don't think this is actually SemVer. Rather it's a custom versioning whose major and minor convention is similar. (Which is fine just not SemVer.)
Why it's not a semver? The minor is present and still increasing.
The introductory paragraphs and the proposed Alternative are completely disjointed.

The stated problems are caused by breaking API changes. The author says it clearly: don't break your API.

Now, how does a different version naming convention change anything? You still have breaking changes, just with a different name. And I'm not sure at all that dates make it any easier to spot breaking changes.

There's this very illustrative talk explaining why breaking API changes are such a bad thing. https://github.com/matthiasn/talk-transcripts/blob/master/Hi...

nah the author addresses this:

> Make a new thing, name it something else, figure out the migration that makes sense for you and your users.

If you follow that advice, you don't have a broken API and the rest still follows.

It's also possible to follow the alternative and still keep breaking APIs.

It's much more effective to focus on a single thing (breaking change rejection) than to pretend that one thing can be magically accomplished through another.

yeah, the scheme doesn't somehow prevent you from breaking your API. that's still on us :)
That doesn't fix anything.

• Most often people break semver by accident, so they wouldn't know when a release should have been "a new thing" instead. You can't replace QA with a versioning scheme.

• Hyrum's Law means you can't really change anything without risk of breaking someone somewhere. It's hard to know what can you change without breaking someone. If you try to never break anything, it leads to absurd trade-offs (every release becomes a thing you support forever).

• And when APIs are broken intentionally, it may be unavoidable necessity. Sometimes some external dependency changes (OS, hardware, other APIs, 3rd party vendors, laws) and the breakage has to be propagated through. Sometimes APIs reach their scaling limits and can't support increased amounts of data (in library APIs you may have O(n^2) interface, in web APIs you may have exposed something that requires full DB sort or scan, in binaries you may run out of bits in field sizes).

Maybe use ISO8601 time instead so that timezones are accounted for (if team members are in different parts of the globe).
Date is extremely noisy and doesn't tell you anything that (pseudo-) semver wouldn't. If anything it tells you less. Why would I want to use 20 characters when 7 or 8 will do the job?

Make sure you have a git tag corresponding to your release versions. That's the main thing they're good for.

Semantic versioning is about the interface. If there's no change to the interface, there's no change to the semver.

You don't bump the semver because you've fixed internal server bugs, you don't bump it if you've added logging to your code, you don't bump it if you've refactored internally.

(comment deleted)
> You don't bump the semver because you've fixed internal server bugs, you don't bump it if you've added logging to your code, you don't bump it if you've refactored internally.

All of those would be patch-level changes in semver (assuming they were in an actual release.)

Not if they don't actually change the API. If you fix an internal server bug that relates to a visible change in the API, then yes, it's a patch in the semver.

If you change something internal that has nothing to do with a client's use of your API, then there's no change to the semver.

Your package/container/program may change version in deployment, but that has nothing to do with the API.

If I decide to move from redis to elasticache or mysql to postgres but my API hasn't changed, that's not a change in the semver.

> Not if they don't actually change the API.

If they don't effect behavior of the API, they'd be build-level, which is optional in SemVer.

> If you fix an internal server bug that relates to a visible change in the API, then yes, it's a patch in the semver.

Visible changes to the API are SemVer minor if they are backward compatible, SemVer major if they are not. The lower level fields of SemVer are for things that don't change the expected behavior of the public API, including (at the build level) those that don't effect visible behavior of the API.

SemVer is not API versioning, it's product versioning for a product that implements an API. (I agree that something like SemVer used to version independent APIs implemented by a product is in some respect a better idea than SemVer itself, except that package management systems don't support API identification and versioning in a way which would let it be useful in most cases; SemVer is product versioning for the world where your product version needs to both tell about product increments that don't effect the public API and those that do.)

I think the important line a lot of folks here are missing when discussing the "alternative" is this:

>Are you releasing software to other developers who are making concious choices on when and how to depend on your software, as part of a dependency tool (like ruby gems) across an organizational boundary? Then maybe use semver.

And earlier:

>Do your users even care what version of your software they're using? Or do they just want it to work?

Clearly semver has useful information imbedded into it, which a date+shortsha doesn't, but the big take away from Sam is that there's a good chance your version number doesn't matter to your users. Marco Arment (ATP, Overcast) spoke about this on Under The Radar[1] but, as an example, semver for an iOS app is meaningless for the typical iPhone user.

I personally use a date+sha versionish on a project because there's only one way to use the software. The date tells users when anything got added or broken, and is useful for communicating updates; the sha is mostly for me.

1: https://www.relay.fm/radar/164

There seems to be a confusion between versioning an API, which is the contract between a service and its clients; and versioning an implementation of that API.

I version the API with semver. If I add a new optional field, that's a bump in minor. If I add to a list of enumerated strings, that's a bump in minor.

But if I change my internal database schema, that's a change in my deployed service without any change in the API.

I use date-based release IDs for the service, because it marks a point in time of my development.

So release 2020-07-29 of MyService might support API versions 1.0, 1.1, 1.2 and 2.0. Clients using 1.2 are backward compatible with 1.0 and 1.1. Clients using 2.0 are not.

2020-07-29 is useful to SREs running MyService. It's irrelevant to the clients unless it changes that list of available semver'd API definitions.

> There seems to be a confusion between versioning an API, which is the contract between a service and its clients; and versioning an implementation of that API.

Semver does not take into account ABI compatibility, which might also have a role if you version a interface with it.

> I version the API with semver

SemVer is very particularly about versioning implementations of the API, not just the API itself. Only SemVer major and SemVer minor are about the abstract API, SemVer patch is about implementation changes that correct bugs in the implementation of the API, and the optional build part of SemVer (if it changes without any higher level change) is about implementation releases that have no impact on the behavior specified in the API.

You can obviously use SemVer-ish versioning for APIs, but a major reason for SemVer is for automated tooling that relies on product version numbers and doesn't separately understand identifying APIs and their version numbers as implemented in the products.

Dude! Nothing frustrates me more than people mistaking server version with api version. It means I can’t update one piece of infrastructure at a time or you can never break api compact. Your server needs to support multiple api versions for rational progression of a distributed system.
Semver is not about choice.

It's about conveiing when something might potentially have broken due to API changes.

And it's about doing so in a way which can be interpreted by an algorithm.

They speak like they own the X.Y.Z format. I use it but I don't conform to semver at all. Just leave me alone.
I believe the article is specifically about semver, not X.Y.Z format. You can have the format without the meaning. Linux uses X.Y.Z for example, but it's explicitly not semver.
> There is a way to programatically compare an interface and bump a semver string automatically, but down that path lies Swagger diffing

No, you cannot do this.

If the semantics of an API have changed in a backwards-incompatible way, even if the structure (name, parameters, types) did not, then you must do a semver major bump.

If you somehow are disciplined enough to never change semantics incompatibility for a defined API and you make a new endpoint, then you don't really need semver at the product level. You need a way of conveying what APIs exist, but a mechanical version like 1.200.0 would work for that, or just client discovery.

> Without outside information, when trying to communicate a feature or a bugfix, whats easier, "/foo was added in version 27" or "/foo was added in 2008.02.28" ?

Using the date does not work if you release version 26.1 on March 1st.

CalVer is great for the limited use cases where you never maintain stable branches and all your clients can use the latest version - but at that point, ideally you don't need versions at all, just use HEAD. If your clients cannot reliably and automatically use the latest HEAD, then that's a sign that stable branches are a problem you will soon have to think about.

CalVer where you pick some date of branching and add a point release (e.g., "Ubuntu 18.04.2 LTS," released well after April 2018 but part of a stable branch cut in April 2018) also works well, but cannot be automated in the fashion the author advocates, and certainly cannot answer "When was this feature added," because the feature could have been backported to the stable branch.

Microsoft did this right with a day counter and build number.

I often use git describe + tags + $(wc -l release history) + date + version as I thought appropriate. Then I expose these strings at key places. x.y.z is fine, do that, but we also don't need to be so precious with our bytes now. A 30 byte version string is totally fine.

From my perspective, things should be as nearly replicable as possible for bug tracking and fixing. The current version (as in what explicit almost reproducible build) some random person is using should be easily discoverable

When things break, I document it using these numbers. When things are added, I document it using these numbers.

When tests fix defects, that as well.

Defects should have a story. When they came in, when they went out, what was affected, etc.

I view them more as narrative checksums bringing sanity to the moving goalposts of any project.

A competent person should be able to glance at it, see things in order, consistent, and well described and not be confused.

That's the real purpose of this stuff. Anything less is failing.

The marketing number is just that. It's why slackware skipped a bunch. For that, Ubuntus YY.MM system is the clearest one I've seen. 14.04 tells you exactly when it was released, dates are kinda magical like that.

> A 30 byte version string is totally fine.

As long as it maintains total ordering. The primary job of a version number is so that you can compare two of them to tell whether one version is behind or ahead the other one (and, preferably, by roughly how much).

I've seen people using VCS commit hashes as version numbers, which defeats the purpose of having such a number.

But if you have access to the code repository, the VCS commit hashes do allow for total ordering. It's just that the ordering is only available to those with access to the repo
Most VCS are a tree, which implies a partial ordering, not necessarily a total one. Unless you meant to order by the commit date, in which case you could just include that in the version.
Sure I alluded to sequence numbering multiple times
There's one more situation where you should use semver: where you simultaneously develop the next version, which will not be ready for release in the next couple of weeks, and provide hotfixes to the public one. May be the end user won't be making a decision which version to use, but your qa will certainly experience both at the same time, so they should have some understanding of what's the magic version of the product, aside from release date.
Thanks! I've been hunting for this blog post, but couldn't remember the name "ComVer". I was hoping someone in this HN thread would mention this. :)
May I suggest my own invention, smver? It's MajorPatch.Minor, where Patch is lettered. For example, this spec itself, which I wrote today, is at v0a.1. The first stable version will be 1a.0.

Basically, it's the same principle as comver, where any breaking change must increment the first identifier, but with some added metadata to hint how big of a change the developer intended this to be. You can omit the patch if you want, in release announcements, so marketing can have the "6.0 released!" moment (I am considering omitting the patch letter for the first major release, so 4a.0 follows 4.0).

This is a very confused post.

The title is correct. It calls out that individuals are abusing semver, rather than calling out semver itself.

Then it goes on to list examples of such abuse. Fine.

Then, it concludes by proposing to REPLACE SEMVER!!!??

At no point has the article called out any problems with semver itself, other than people use it incorrectly.

Even if only 5% of people use semver correctly, that's still 5% of uses that give more beneficial information than this proposed alternative.

Follow up point: if something is used incorrectly more often than not, perhaps the conclusion one could draw is that the UX is wrong, and we should invent a versioning system that is harder to abuse.

This author has not done this.

You read over the part where the author recommends semver where appropriate.
My guess is that if it were a reasoned argument for when to use semver and when not to use it, it wouldn't be controversial enough to make the front page.

The first third of the article is insightful. The rest is mostly bait.

Where i work we abuse semver but I hope in a good way. Major is wire api break. Minor is effectively YYYYMMWW (we ship weekly) and patch is number of commits off stable branch.