Hacker News new | past | comments | ask | show | jobs | submit login
Pragmatic Versioning – An Alternative to Semver (github.com/seveibar)
102 points by seveibar 11 months ago | hide | past | favorite | 108 comments



> In Semantic Versioning, there is no way to release or communicate a LTS/"long term support" version of a package. In Pragmatic Versioning, this is solved with a BIGRELEASE version, which has meaning that can be controlled by the package author.

That doesn't make sense to me.

With semantic versioning the way you communicate a LTS version of a package is you declare that in your documentation. Django (which doesn't fully use SemVer, but is still a good example here) does that on this page: https://www.djangoproject.com/download/#supported-versions

"In Pragmatic Versioning, this is solved with a BIGRELEASE version, which has meaning that can be controlled by the package author" - that doesn't appear to address the need for LTS communication at all. It feels like the situation is exactly the same as SemVer: if you want to do LTS you have to represent that outside of the version number.


At first I was confused because of how similar this sounds to semver, but it's really just changing the granularity from (major, minor, patch) to (release, major, minor), where "major" versions break compatibility in both schemes.


(release, major, minor) is honestly how versions often get used in the wild. Especially in CD contexts where every change is versioned and deployed and the meaning between patch and minor gets a little lost.


I would still like this proposal to formalise having an optional PATCH in there though:

    BIGRELEASE.MAJOR.MINOR[.PATCH]
That way one can lock the version to get only actual bugfixes, with no other change:

    gem 'foo', '~> 2.3.4.5'
would get 2.3.4.5 and up but none† of 2.3.5, and it's up to the project to decide if they want to do any release like that.

† excluding any 2.3.5 prerelease as well


This is approximately how most Haskell libraries are versioned

  -- PVP summary:     +-+------- breaking API changes
  --                  | | +----- non-breaking API additions
  --                  | | | +--- code changes with no API change
  version:            0.0.0.1

[0]: https://pvp.haskell.org/

[1]: https://pvp.haskell.org/faq/


At Bond we’ve used pragmatic versioning with the names:

vMAJOR.MINOR.PATCH[.TWEAK]

Indeed the TWEAK is only for hotfixes, though due to pushback from other devs using semver parsers, we've had to hold back on the tweaks. (Though it’s awkward in a hot fix situation.)

But I like the names the repo author proposes.


Yep, originally I wrote BIGRELEASE.MAJOR.MINOR but I didn't want to confuse the terms. (The first commit shows this: https://github.com/seveibar/pragmaticversioning/commit/70b76...) Good read :)


“There’s no way to…” is maybe imprecise phrasing but this is a real problem.

Marketing departments really want to use that first number for Big Important Releases and only Big Important Releases.

I have seen products get their version incremented from 2.x.x 3.0.0 to indicate that an entirely different product (that could be deployed alongside the first but was otherwise unrelated) was being launched.


So just tack on a number - marketing.breaking.feature.patch - or market on the feature number (Solaris 11 uname will happily tell you its real version is 5.11)


Okay I now very strongly support renaming the numbers to be more explicit whether we add more of them or not.

Literally calling the numbers 'breaking' and 'feature' would have made things more clear.


Yes, “just stop using semver” is basically the solution to enterprise versioning


I've also seen the opposite where an organization night be hesitant to release a new major because it comes with long contractual support obligations.


I've also seen too much of marketing is watching the project iterate too closely, it's a Ship of Theseus, and they never know when to declare a "Big Release" anyway. In six months all of the planks are different, but it happened so slowly for marketing they still think it is the same ship. Projects just get stuck in 1.x marketing version because marketing has no idea where major releases should go. They want always up to date SaaS but then are confused they can't market it like the big Waterfall adventures of yesterday.

At least with semver they might be forced to say something. I've done that a few times where the marketing version number displayed in the app is effectively just 1.{semver major}.{semver minor} and "just tell us when you want to bump that 1 to a 2 or something" getting the response "well, it hasn't really changed all that much from 1.0, has it?" and then pulling out the receipts of something like 30+ semver breaking differences in X months.


Yep.

Same project regularly shipped breaking changes in “feature” updates.


I am not sure we are talking about the same project. However, the one I am thinking of had a interesting challenge where a security issue required making the default configuration requiring a allow-list for a certain long-existing feature. This ended up going out in a patch, even though it's a breaking change. I am still uncertain what the ideal solution would have been. Following SemVer 100% would required this to be a major. However, all supported versions needed this change. Forcing users from really old majors and minors to jump to a new major with all kinds of new stuff is less than ideal. The alternative would have been to ship multiple new majors that are really just upgrades to the old minor that they are patching. WHile technically correct, also crazy.


That doesn't sound like an issue with semver (or pragmatic). There is no versioning convention that will prevent people from being people. A rigid, automated (magical) versioning _system_ might do that, but not a convention/standard.

I find semver extremely helpful, both as an author and consumer. It may make me think a little for some packages about where I want to pin things, but at least it makes the mechanics of pinning them sensible.

Edit: packets -> packages


Server is very helpful in many contexts, especially libraries and packages, and that don’t have associated commercial support obligation.

It has real drawbacks for on-premise software with commercial support obligations, and shouldn’t be used automatically just because it works well for libraries.


You could also have separate marketing and compatibility release numbers.


>That doesn't make sense to me. With semantic versioning the way you communicate a LTS version of a package is you declare that in your documentation

So, TFA's statement that "In Semantic Versioning, there is no way to release or communicate a LTS/"long term support" version of a package" does make sense to you after all.

You can't do it in the versioning, you need to do it in the docuementation.


What didn't make sense to me is that it says "there's no way to indicate LTS in SemVer" (which I agree with)... and then fails to make the case that this new versioning system solves that problem.


Yea I think more specificity is needed, SemVer is an actual spec with well-defined terms and this is more of an early concept. The tricky thing to maneuver is many package authors will not want to commit to an LTS, so what do you do? In pragmatic versioning, there's an available mechanism in the versioning scheme available to package authors, but not a guarantee to package consumers.


> SemVer is an actual spec with well-defined terms [...]

I dissent, it is a pseudo-specification that looks like a proper specification but isn't. Many proper specification starts with a glossary because you can't get everyone to agree on common words that don't need a definition. Let me give some examples.

• SemVer never defines what the "increment" means---is an increment by more than one allowed or not? Can one release a minor version of given major version (say, 1.2.0) when a higher major version (say, 2.0.0) has been already released? If it's allowed, how should later versions (say, 2.1.0) be related to aforementioned versions?

• SemVer says the "contents" of once released version must not be modified, but unlike "API" which is a variable at the software's choice, it is never mentioned elsewhere---does the contents include a documentation or not, say?

• SemVer explains how to compare pre-release and build identifiers separated by dots, by dividing them into "identifiers consisting of only digits" and "identifiers with letters or hyphens". And then it mentions "numeric identifiers" and "non-numeric identifiers"---which one is which? The former should be numeric, okay, but the latter may still contain digits so is it "non-numeric" after all?


Agreed - and just to drive home a point you touched on: the foundation of SemVer is an arbitrarily defined 'public API'.

Imagine a project which consists of a library, a CLI tool, and perhaps a GUI.

* Is the library API part of the public API? That's for you to decide according to SemVer.

* Is removing/renaming a CLI flag a breaking change? Also for you to decide.

* Is removing major functionality from the GUI a breaking change?

..you get the picture. Every project defines 'their public API' for themselves.

This isn't a made-up example, many projects have a library and a CLI, with one considered to be 'the main project' while the other is something most people are not expected to use.

Heck, even following SemVer to the exact letter often doesn't communicate information how people expect. For example, say your library exposes a piece of metadata from the underlying OS: a function returning a `string`. But in Windows 10 -> Windows 11, they changed the API to return an `[]string` instead of a single `string`. If you want your software to run on Windows 11, you have no choice but to change your public API in a breaking, non-backwards-compatible way.

Cool, no problem, according to SemVer we bump our public API version from v1.0 to v2.0 now, right? But wait! 99% of our users do not even make use of this one-off weird function! In fact, we think we could remove it without anyone complaining but it's technically part of our public API..

SemVer has no way to express 'we had to make a breaking change that doesn't affect 99% of users', it just has a way to express 'breaking change'. If you follow SemVer, then that means your project needs to go from v1.0 -> v2.0 in that case, yes - and now you get to explain to your users why v1.0 -> v2.0 is not, in fact, a 'major change' despite it being defined as such semantically.

What most people think of as SemVer, in practice, is often _romantic versioning_ or _sentimental versioning_ and not _semantic versioning_.


Also

> BIGRELEASE version, which has meaning that can be controlled by the package author

So BIGRELEASE doesn't actually convey LTS at all. Meaning, Pragmatic Versioning does not solve LTS any more than Semver does.


Don't expect too much from version numbering schemes; they can follow sufficiently objective conventions (breaking changes for typical usage or not, worth announcing according to the authors or not) but not convey complex information.

There is no way to tell which ones in a set of equally major versions are LTS or not using version numbers because there are often too many; for example Java has major version numbers for major language features and standard library additions and removals, with reasonably relevant versions currently spanning from 8 (1.8 in the old numbering) to 21, three or four of them LTS and the others transitional.

There is also no way to tell obsolete LTS versions from current ones using version numbers, without explicit documentation committing to dates or making EOL announcement.


This is a great critique, and something important to address if "pragmatic versioning" actually becomes a thing. I think one solution is to have "LTS Pragmatic Versioning" as a subtype e.g. LTS.ANNOUNCE.INCREMENT could be "LTS Pragmatic Versioning", if you're not committing to LTS you can just use the more vague base "Pragmatic Versioning" until/unless you're willing to commit to more as a package author. The main tension here is some package authors will want to indicate LTS, and others won't (I might only commit to LTS for 10-20% of my packages, since every change needs a tiny bit of extra evaluation)


How about the following scenario:

Package A is SemVer 1.2.3, package B is PragVer 1.2.3. both A and B have big release with breaking changes and marketing hype, so now they're both at 2.0.0. It is later found that in version 1 of both packages there is a bug requiring a breaking change in order to fix. B (pragmatic) can apply the fix to version 1 branch and release it as 1.3.0. How does A (semantic) make a breaking change on version 1 and version it correctly?

TL;DR Pragmatic Versioning enables breaking changes on LTS versions.


The point of LTS versions is that they don't force me to make changes. This sounds like a bug more than a feature.


Not all breaking changes require consumers to make changes. For example, in react-router v6, the useSearchParams hook returns a setter which lacks referential stability (for no good reason). There is an open PR to fix this, but the maintainers haven't addressed it. My theory is that it would technically be a breaking change since it would cause fewer rerenders. Theoretically an app might rely on this, but in practice that would be an extremely brittle and unusual behavior to rely on, and the vast majority of users would prefer the reduced rerenders. Under pragmatic versioning they could bump v6.x, and note that the change should be tested but likely doesn't require any code changes. With SemVer they'd be forced to bump to 7.0.0, but this is not a marketable change for a major release.


If you follow SemVer strictly, the version bump is not necessary unless the number of rerenders is part of the documented API.


Yeah it doesn’t grok for me either.

If I do a big release, but API hasn’t changed, am I going from:

10.50.1

To

A) 11.50.1

B) 11.0.0

?


The biggest issue with semver is that it encourages compatibility breakages under the false promise that increased major version number saves the users from the dependency hell.

https://github.com/semver/semver/issues/771

SemVer is a product of Ruby community.

In 2000s Ruby library authors were breaking compatibility left and right, neglecting elementary compatibility practices. If you were working on an application, every time when you update dependencies, the application would break.

So (in 2011 ?) they came out with this "manifesto" (Why such a big name? This scheme of versioning was well established in linkers and sonames of Unix-like systems for decades - it goes back to at least 1987 paper "Shared Libraries in SunOS").

It's a good thing SemVer acknowledged finally that compatibility is a serious matter. Only that it's better to discourage compatibility breakages. An in cases when it's really needed (I agree such cases exists), there are things to take care of in addition to simply increasing major version num.


From your issue link:

> Then commons-logging changes its API incompatibly and is released as commons-logging 2.0.1. Authentication adopts commons-logging 2.0.1 while other libraries still depend on 1.1.1

> Now my-application is broken, because the dependency tree includes two versions of commons-logging which share packages, class / functions names, and thus can not be loaded simultaneously.

I absolutely don't see how this is a problem with semver, it is not the responsibility of semver to tell a language how packages should be isolated and loaded. That is a problem of a) the language and b) dependency resolution in the package manager.

> SemVer is a product of Ruby community.

Bundler, by design, does not allow the above, instead having a flat, consistent vision of dependencies.

NPM though, allows that, allowing nested dependencies, by virtue of the ES6 module system importing to a variable in a lexical scope. Go also allows that, by virtue of its imports being scoped to a package (or file, I can't recall).

Ruby has side-effectful load/require decoupled from the open class system - which is what bundler is designed around -, but can do that kind of isolation too! In fact, I've done it: https://github.com/lloeki/pak

Unless packages leak to globals each version is oblivious to the one next to it. Unless package dependents communicate with one another using objects from the packages they can happily live in their own little isolated world. Now if they do, then it's like hitting a HTTP /api/v1 with an HTTP /api/v2 client and somehow wishing things will work. Either the package (which should not leak globals / disallow cross-version communication) or the language (which should not allow leaking globals / detect incompatible communication).

None of this is the responsibility of semver. In fact, semver would help the language provide tooling to detect that kind of "hey this instance is from foo-1.0 but you're trying to consume it in foo-2.0".


> I absolutely don't see how this is a problem with semver,

Strange to not see it. SemVer promises to solve dependency hell. In the example everyone correctly followed the sevmver and the app is broken by a dependency hell issue.

> it is not the responsibility of semver to tell a language how packages should be isolated and loaded. That is a problem of a) the language and b) dependency resolution in the package manager.

So semver only works for "good" languages?

> Bundler, by design, does not allow the above, instead having a flat, consistent vision of dependencies.

Ok, so what happens with the app when packages managed by Bundler get fragmented into sets depending on incompatible versions of sub-dependency (commons-logging 1.1.1 vs 2.0.1 as in the example)?

Also note, even for languages and tooling supporting multiple library versions loaded side by side, there are scenarios where things break.

For example, the "libc apocalypse" situation in Rust https://github.com/rust-lang/libc/issues/547

Here after the "libc" module released a major version, the definition for the `void` C type in two versions of the lib are considered by the compiler as two different types, resulting in breakages everywhere around the library ecosystem.

There are also scenarios for dynamic languages / runtime errors.

> None of this is the responsibility of semver. In fact, semver would help the language provide tooling to detect that kind of "hey this instance is from foo-1.0 but you're trying to consume it in foo-2.0".

And what's next after it detected the dependency hell? It's too late and the person suffering is not in the position to fix it. You have to upgrade to "authentication 1.1.2" for security compliance, because the version 1.1.1 has known vulnerabilities. But that breaks the application, because the maintainer of the lower level dependency "commons-logging" happily breaks compatibility, proud of himself following the engineering best practice SemVer and increasing the major version number. What would you do? Leave the vulnerable lib in the app? Patch the libraries?

The promise was to prevent dependency hell, not to detect it.

Quoting the ticket and reiterating the point of my first comment above:

> Once again, the point of this ticket is to:

> 1. Remove the false promise that SemVer solves dependency hell by simply increasing major version.

> 2. Discourage unnecessary compatibility breakages, when it's trivial to maintain compatibility.

> 3. Several sentences of advice for the cases when comparability breakage is really needed.


> Ok, so what happens with the app when packages managed by Bundler get fragmented into sets depending on incompatible versions of sub-dependency (commons-logging 1.1.1 vs 2.0.1 as in the example)?

Bundler immediately yells at you, pointing out which transient dependencies are in conflict as where these dependencies come from, preventing the issue altogether.

> But that breaks the application, because the maintainer of the lower level dependency "commons-logging" happily breaks compatibility, proud of himself following the engineering best practice SemVer and increasing the major version number. What would you do? Leave the vulnerable lib in the app? Patch the libraries?

Either commons-logging backports the vulnerability on a 1.x-stable branch and does a release or it tells people to move to 2.x through and through - because many FOSS maintainers do maintain stuff on their spare time out of pure good will and cannot maintain stuff endlessly til the end of times; the dependency that continues depending on 1.x is a liability and should update to 2.x.

> Strange to not see it. SemVer promises to solve dependency hell

I would argue that it does when appropriately wielded by a competent dependency manager, I have not suffered dependency hell in Ruby for a long time. Sure there are occasional "oh there's this lib that needs an update oh wait I need to update this one oh wait it's depending on an older one oh wait this one is not compatible with ruby x.y" but I've not been hitting those for a long time, bundler doing a very good job at resolving things to compatible versions and people being largely diligent with semver (or variants and approximations thereof), and then the alternatives are:

- either you have a huge monolith dependency that does everything, IOW give up modularity

- or once a version of anything is released then breaking changes are forbidden forever, IOW give up major changes

SemVer does not solve dependency hell by itself, but it sure helps a ton in smoothing communication between library providers and consumers in a very synthetic way.


This sounds similar to how TypeScript versions are named and it is a huge pain to me. Can I upgrade from 5.2.2 to 5.3.2 without everything breaking? Who knows. I thought I could, but I was wrong. Semver (when followed correctly, of course) would've conveyed this information instantly.

I don't care about the branding, I just want my code to work. (Disclaimer: I am obviously not in marketing.)


To be fair, Semver also falls apart relatively regularly, where a major version upgrade is easy and fine, but a minor upgrade trips you up.

Even if an API stays exactly the same, something about what it returns could subtly change, so there's really no replacement for extensive testing.


> Even if an API stays exactly the same, something about what it returns could subtly change

Where I work, we call that a breaking change. If a consumer of our API codes for a particular response, and we change that response, then we could break their application.

No one wants to have to validate deeply nested objects to test if they do indeed return the thing we promised to return in our contract...


So in your world, almost every bugfix is a breaking change, right? Either that or all of your bugs affect only side effects. That seems impractical to me.


It's all about the distinction between contract and implementation.

The difference with bugfixes is that pre-bugfix the intended API contract - including behaviour, not just signature - was not respected by the implementation and post-bugfix the implementation becomes (more) correct WRT the intended contract.

IOW:

- On a semver patch bump the API contract does not change but the implementation of that contract was incorrect.

- On a semver minor bump the API contract receives an addendum, the previous contract is still in effect as a subset of the new contract.

- On a semver major bump the preexisting API contract is voided and is replaced by a new contract. The new contract may or may not include all or part of the old contract as a subset. Breaking change => major bump is an implication, not an equivalence: one is completely free to bump major purely for marketing reasons.

Of course, the difference between theory and practice is that they're the same in theory: the contract is an ideal, the implementation may come close but will always fall short in some way.

I feel like many of both semver detractors and advocates get lost in theoreticals, reality is fuzzy, just use the damn thing. Or don't and pick up another scheme, but be abundantly clear about how your versioning scheme works so that users can make sense of it and automate version requirement declarations in their dependency manager.


Great explanation.

I think the confusion over what constitutes a breaking change stems from the fact that, in practice, contracts are often very underspecified, sometimes to the point of being omitted entirely ("This function just does the obviously correct thing"). A classic example would be whether an API that munges filesystem paths should resolve symlinks or not -- if this is unspecified, then changing that behaviour is technically not a breaking change, but is likely to upset some fraction of your users, who may have even done some experiments to find out what the code does, and assumed that that undocumented behaviour won't change.

(This is not an argument against semver at all -- just an observation that contracts are hard, and mostly need to be much more detailed than they typically are, and this "detail gap" seems unlikely to ever completely go away.)


> A classic example would be whether an API that munges filesystem paths should resolve symlinks or not -- if this is unspecified, then changing that behaviour is technically not a breaking change

Another example would be Ruby 2.4 changing String#upcase & friends from handling ASCII only to being unicode aware with a new argument to override and changing the default. Technically a breaking change but actually silently fixes a ton of bugs.


> Even if an API stays exactly the same, something about what it returns could subtly change

This means the API did in fact change. But you're right of course, sometimes we pick the wrong number to bump (major vs minor vs patch).


It depends what you consider your "API" to be. One developer's bug is another's contract.

For Typescript in particular, which is nothing _but_ a type system, every change is going to be a breaking change.


> One developer's bug is another's contract.

That is why you have major.minor.patch semantic versions. The documented and official contract is according to equal to major versions, and greater or equal to minor versions (and possibly the patch number as well); if you depend on a specific bug or internal function then you should use the exact full version number only, in order to avoid such a problem.

A bug would be one example, but another example might be the sqlite3_test_control function in SQLite.


Exactly - if your API makes a mistake somewhere, someone is depending on that mistake. So even if the API says “returns the number of chickens” and now correctly returns 5 when there are 5 instead of saying 4; that may break something.


In other words, Hyrums Law[0] in action.

[0]: With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody.


And remember “we” is often you, the maintainers of the packages you rely on, the maintainers of the packages that they rely on, the maintainers of the packages that THEY rely on… All need to have lock-step agreement about what a “breaking” change is, and all need to have communicated it clearly.

The result though is the same, semantic versioning or no: If something changed you must retest. This is why all that’s needed is a “do I need to retest?” flag. Major/minor/patch is irrelevant if you need to keep things working.


Which is why I would give this versionong scheme the name "honest versioning". It's not following semver and it's honest about it. If all semver projects took it seriously, we'd see far more v12.0.0's and the likes. But we don't.


If a minor version only adds features then it shouldn't trip one up. That's the idea anyway.


100%

The best thing by far about semver, if accurately followed, is that it encourages strict and accurate versioning based on what changed.

I've seen teams use both approaches, side-by-side, the willy-nilly pick a version as you please group offered a significantly less stable product.


"Democracy is the worst form of government, except all those other forms that have been tried from time to time." - attributed (but likely not accurately) to Winston Churchill

Looks to me like Semantic Versioning is the worst form of versioning except for everything else we've tried.


Perhaps, but a lot of people misuse it. It's easy to accidentally get breaking changes when some part of the chain is misused.


Hum, no. Semver has lots of problems and many other standards work better. The ones that look like this one on the article tend to fare the best for libraries.


Which standards work better?


Anything that has a human-decided number overruling all the others and no indefinite timespan when no rules apply.

Personally, I do like the Haskell standard a lot. People tend to not use the extra number a lot, but just because it exists the other ones are much cleaner.

Or you could use the one on the article.


The problem is that semver is versioning meant to make sense to package managers and downstream dependency resolution, but not to actual endusers of software. Great for libraries, less great for say, a text editor.

Most people are nowadays used to versioning that's something like BIG.SMALL.IRRELEVANT, where those words basically indicate how much has been changed in a release. To the layman, "version 3 has released" usually is accompanied by the question of "and what's the new stuff that made them make a version 3". BIG doesn't necessarily have to be the first number; OS X for example does all major changes on the second number, so when that goes up, new features are expected, while the last number is reserved for bugfixes.

In semver, this question is very boring - you have to bump MAJOR every time the public API changes, so most MAJOR changes don't mean much. To try and solve this, most projects have decided to just forgoing MINOR and PATCH entirely (or going for YEAR.MONTH versioning, and trying to keep breaking changes to the next year) or excessively holding back new features to then merge them all at once.

This tends to result in semver software feeling... rather stagnant (or on the other side, impossible to say anything about due to constant major changes) and is why for non-libraries, I don't think semver is all that useful. Pragmatic versioning seems like a closer way for how end-user software should be versioned.

There's also the fun situation going on with cargo where almost every package is still at 0.x because rust programmers love semantic versioning but want the escape hatch to do major breakage without bumping MAJOR. Which isn't necessarily indicative of the actual state of the project (most 0.x rust projects are afaict actually well maintained and ready for production use), but it does show how semver can really mess with expectations.


I like this rationale. Acknowledging that the first number is very much the product brand is wise. Release numbers can be quite emotional things.

Version numbers are indeed for communicating with your users, and in particular where that communication is a one way broadcast. In my career I’ve found that, when version broadcasting is used inside a team, it’s a sign of dysfunctional communication. Even at FAANG scale I encourage groups of people to colocate their code and collaborate on trunk in preference to putting up hard boundaries between projects — namely having to cross repositories and integrate with each others versioned releases.

There’s no point hampering yourself with the one-way broadcast mode of communication unless you absolutely have to. When you have multiple teams one-way broadcasting at each other then, well, maybe it’s time we sat down for a chat instead?


Why not encode further information into the number, if that's what you want?

1004.0.0 to 1005.0.0 is an api-major business-minor version bump (incompatible API). Semver-compatible.

1004.0.0 to 2000.0.0 is an api-major business-major version bump. There, the first number has changed, if that's important to you. Semver-compatible.

1004.0.0 to 1004.1.0 can be the "INCREMENT" thing they also mention.

As a bonus, your version number is soon going to be much higher than competitors. That's sure to impress clients. You can refer to the 9000-line easy enough.


Needs more bitmasking


Only if a machine wants to read the business number, which it doesn't.


Why not just have a four part version number?

* 1.4.0.0 to 1.5.0.0

* 1.4.0.0 to 2.0.0.0

* 1.4.0.0 to 1.4.1.0


Not semver-compatible.


Neither are breaking changes in a minor-according-to-semver bump.


My point is that you don't have to do that. You still do a major version bump for breaking changes, you just have another number before that, for your business communication concerns.


Versions are a communication tool.

There is no best tool for all jobs.

Write down your communication objectives and pick a versioning scheme which fulfills them. Want to communicate api compatibility? Use server. Want to communicate a new version worth at least checking out? Bump that major version.


For my rest apis I define a fixed endpoint /versions that returns an array of json objects that looks like:

    {
      “version”: “1.2.3”,
      “href”: “https://host/1.2.3”,
      “not_before”: “2000-01–01T00:00Z”
      “not_after”: “2025-01-01T00:00Z”
    }

The array lists the available versions of the api and the endpoint to use. Not before/after can be null to indicate infinite past or future.

The contract for /versions is that the client must check it once every 24 hours for changes.

Allows deprecation of previous versions by changing a not after of null to a timestamp etc


This is great. Especially for products that need long term support, which is basically not compatible with semantic versioning. (Since LTS versions occasionally must have security fixes that are also breaking changes backported.)


I think "BIGRELEASE" is not accurate representation of how semver is used. I always understood it as something that introduces a non-backwards compatible change in the API.

This is so that you can specify ranges of api versions that your program could be built with. For reference, here's where I took my understanding: https://semver.org/


An early draft of an alternate system to semantic versioning (semver) that I think is more useful in practice for both package maintainers (who don't want to evaluate every change for meaning, and want to be able to maintain large releases) and package contributors (who want to know what the maintenance period is)

Pragmatic versioning reflects more closely what many mature projects do in practice. Feedback welcome!


It basically just says that version numbers are meaningless and purely marketing. "Pragmatic" yes, but in need of standardization, no. The project comes across almost as a satire, so its odd for you to treat it as serious here.


Honestly, this is all too often how versions actually get used - that's what makes it feel like satire. But there's nothing wrong with admitting that the ideal of Semantic misses the mark, looking at real world usage, and saying "Okay, maybe we should formalize that in some way." I can see that being useful, especially if people are able to say "We use PragVersion rather than Semantic" to communicate the reality, rather than saying "We use Semantic" when they are, in fact, using Prag.


It's the old way of versioning things, somewhat formalized.

With semver you live in this world where you are promised the versions to follow semantics, but human nature creeps in and we introduce breaking changes (like bugs!) without setting the version correctly.

With pragmatic versioning you give expectations but also convey that to upgrade you need to test that your software still works.


I love this! Looks like this would solve a common problem where product managers and marketers are running this "pragmatic" versioning scheme (release, major, minor), and developers are running some other "internal" versioning scheme that is closer to semver (major, minor, patch).

Wouldn't Pragmatic Versioning work for both?


Seems like where semver focuses slightly more on the state and process of development, this thing focuses slightly more on marketing.

For instance, semver increments field values when the state of the API or level of compatibility with previous versions changes, or maybe just when something about the build has changed.

This thing appears to aim to express changes in features and functionality. Less about the mechanics of software assembly and more about reasons the vendor would want users to update.

It describes issues with semver similarly in terms of marketing, which might explain what seems to be some cognitive disconnect among the different ways people seem to be interpreting both systems. Semver speaks developer language, while this thing seems to speak product manager language.

Not sure how it’s differentiated pragmatically, except perhaps for the practical purposes of a specific role in a vendor’s organization.


Yes, and maybe that means marketing shouldn't be using dev version numbers.

Marketing-Driven Development? Yuck.


I experience the problem Pragmatic Versioning is trying to solve. For a commercial software product there are often two root causes to change the "major" number in a software product version.

1. There was a breaking change to the API.

2. The company wants to signal great new features.

With pure Semantic Versioning the second reason can not be used alone. And breaking changes in the API are actually not good news for the users.

I think semantic versioning is adopted broadly because great new features often also introduce breaking API changes. So that conflict does not need to be resolved.

Pragmatic versioning helps solving this conflict by splitting um the "major" version number into two new ones for breaking changes and great new features.

The main advantage Semantic Versioning has is that it is the default. So seeing a version tag with three numbers means Semantic Versioning today.


No need for marketing version and dev version to be the same. Just have a mapping


Our ad hoc system on the last big product I worked on was roughly: A.B.C

A is a big major release, a significant step forward in terms of how you interact with the product - the difference between 1.b.c and 2.b.c should be fairly apparent.

B is for Breaking changes within a release - if it used to work one way in a.1.c, you shouldn’t rely on it working the same way in a.2.c

C is for non-breaking new features and bug fixes - if you’ve been using a.b.1, it should be totally safe to switch to a.b.2

Not sure if that was the best approach, but it’s sort of what we settled on organically and nobody complained so

It helped that our users were mostly our client’s counterpart developers, so they were pretty looped into our release cycle anyway.


I liked the ideas behind another semver alternative called "imver" or "Immutable Versioning" (https://github.com/imver/imver/blob/master/imver.md)

that never quite caught on. It's basically just a timestamp. It requires full backwards compatibility of public APIs and behaviors (side effects) for the lifetime of the package. Read about it on an interesting 2019 blog post about monorepos and versioning: https://hub.packtpub.com/why-dont-you-have-a-monorepo


That just pushed the "major" into the package name.

You've released package foo version iv2019.01.01 and now need to make an incompatible change. You mark foo as deprecated and release foo2 version iv2023.12.06.

That is not an improvement.


I've seen and used marketing-led versioning for internal systems a few times. The major version is used purely for promoting team progress (e.g. Product 2.0).

The purist in me balks, but the pragmatic cynic is happy to have a tool in the belt.


Ever since Windows 95, I've had the rule that marketing does marketing and products need internal names & versioning to avoid trouble with marketing making last minute changes.


My approach to semver:

Breaking.Planned.Hotfix.Publish

Breaking is for breaking changes.

Planned is for planned changes.

HotFix is for emergency unplanned changes.

Publish is internal, in the event getting the release out the door took multiple kicks at the build server and you still want to give those distinct builds their own tags. There's a lot of tools that are very opinionated that anything that hits public must have a distinct ID, and sometimes stuff happens and the pipeline doesn't go right on the first try.


I've always been a bit baffled by these types of discussions. Doesn't any of you have multiple versioning systems, say "internal" and "external" at the minimum?

One project I'm on has an internal version number of around 28-dot-something-something. This is the number for techies, and it conveys some information that end users are fully ignorant of. End users see "Edition 2" in stead (soon to become 3)


SemVer is intended primarily for libraries; techies are the end users.


I think that "major.minor" is a useful feature of semantic versioning, that you can specify breaking changes vs non-breaking changes. "Announcement" version numbers may also be useful, although perhaps not as much, it seems to me. However, one thing I think would be useful is numbering of versions of "configuration-safe" upgrades.


In my original commit I wrote "BIGRELEASE.MAJOR.MINOR" but I realized in practice many packages start to bundle multiple changes into their MAJOR and sometimes even MINOR releases. To me this started to really confuse SemVer, ANNOUNCE/INCREMENT is comparatively a bit cleaner/more like what they were doing in practice (see the NextJS example)


Whatever you call it, there needs to be some distinction between breaking changes and other changes. It doesn't need to be as prominent as the marketing-oriented big release, but it needs to be in there somewhere.

Modern software projects have too many dependencies, and too much noise coming from each dependency. Your project is likely just one of hundreds of libraries that got pulled into my dependency graph for some reason. I want to know if, and only if, there's a change that I need to be aware of. As for that shiny new feature that you're really proud of, I shouldn't even notice it unless it breaks an existing API.


Semver and this are orthogonal. Semantic versioning is about the semantics of an interface (rest or otherwise).

This is about the version of the underlying implementation.

“LTS” of a semver api is driven by backwards compatibility, so it’s the second section (1.2 vs 1.3).

Declare the “not before” and “not after” of the semver and you have effectively defined lifetime.


A lot of the communications regarding End of Life for Support is done very effectively here: https://endoflife.date/


Needs examples.


I think that how people feel about versioning tends to correlate with their attitude towards making breaking changes to their interfaces. Some people DGAF and want to move fast and break things. They want a versioning scheme that prevents pesky end users from complaining about stuff that is now broken. Some people will go to unreasonable lengths to avoid incompatible changes. They want a versioning scheme that makes it hard for the first group of people to break stuff. These two groups will never agree on a versioning scheme.



The way I understood both makes it seem the goals are different, as PragVer is more marketing oriented. The ANNOUNCE is also a bit different than MINOR because it can also include breaking changes, as opposed to just feature additions


I am surprised more software libraries don't simply use date versioning. What is the reason?


It doesn't convey useful information like Semver versioning does.

(Now, why things that don't do Semver don't just do DateVer in lieu of that… IDK, really. Marketing likes to take advantage of it? …& then you get stuff like the OP here.)


At lot of projects honestly should just do something simple like date versioning, approximate pi, just increment a single number, ...

Especially those which currently use the major.minor.patch format but don't care at all about what it usually means, that in itself can be fine but using such a version scheme is then annoyingly misleading.


Dates are one dimensional, monotonic, totally non-strictly ordered. There’s no room for branching. Branching is necessary anytime you want to have multiple “latest”s. This is usually major releases with ongoing bugfixes and security updates but minimal breaking changes.

What I’ve never seen, but seems useful, is to throw the date on the version number as the third or last number. The nice thing is you get the ordering back. Something like 4.3.20231204


As I recall, Maven tooling lets you say 4.3-SNAPSHOT and creates 4.3-2023.12.04-23.27. When you release 4.3 it will be considered newer than all the snapshot versions (and other qualified versions like 4.3-beta).


Nice. That makes me realize I’ve seen it as part of nightly builds. Seems like it should just be standard though.


That's a pre-release and is covered by Semver.


With semantic versioning, it's easy to go back and provide a security update (for instance) for an earlier version of a library (which matters if the newer version is API compatible or has different dependencies or a different license or has stopped supporting the platform the user is on). If you try to do the same with date versioning, your versioning suddenly because very misleading and confusing.


The "sane" way of combining those is that the date takes the role of the major version, and this mostly applies to big projects that are pretty much guaranteed to have at least one breaking change in their twice a year release. Then you can say 2023-06-01.2 for a maintenance release on top of 2023-06-01.


Because software versioning is non-linear. A security patch to a previous LTS version can be newer than a new version of the software.


You've made it pretty hard for anything downstream to declare a dependency on a version range. They can depend on a date range, but it's not clear what basis there is for picking any particular upper bound other than "last known working version."


That's exactly what I do on my OSS work [1]: MAJOR.date. it works great for my use case (not a lib) but I don't count how many discussions I've had with people telling me: "do you know semver? You should use it"

[1]: https://demo.filestash.app/about


We use YEAR.SPRINT.PATCH scheme, for example 23.10.1. But this is an internal SaaS version.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: