Semver hasn't failed anyone--web developers and the bozos in the Node community (of which I cheerfully count myself as a member) are lacking in discipline.
You should be fast to get to 1.x.x. If your app breaks on a patch release (that was only a bugfix and provided that interfaces stayed consistent), maybe you shouldn't rely on buggy behavior and maybe you should fix your code.
This isn't a failing of semver--this is a failure of being responsible engineers.
EDIT:
The best example from the article is the bit talking about how people interpret 0.x vs 1.x vs 2.x. Folks, it's just a damned version number, and a number that only indicates you may have to refactor you application to handle changed interfaces. Any other psychological baggage you attach to it is just that, baggage. Aaaargh.
I don't disagree, but unfortunately believing that humans - no matter how logical CS people tend to be - will always think completely logical and rational is a flawed line of thinking.
Yes, people _should_ get over changes in version number, but in reality they don't. So library authors cater to reality, not how they wish things should be. That is the problem, and why semvar is broken. Not in principle it isn't, but in how it actually functions in the hands of humans.
We've used SemVer for Julia for a while now and it seems fine. However, versioning and compatibility are still hard problems and a perennial, unavoidable pain. The way I like to think of it is this:
- patch: forwards and backwards compatible – only bug fixes, no new features, no changing existing features.
- minor: backwards compatible – new features allowed, but old code should keep working.
- major: all bets are off.
This is pretty much what the SemVer spec says, and it's hard to imagine what else you could do. The significance of forwards compatibility for patch versions is that if you bump a patch version and find that it was problematic, you should be able to revert back to a lower patch version without breaking new code that's been written since the bump.
The thing is, it's still a judgement call what constitutes a bug fix. If you're fixing a segfault, that's a no brainer – no one (sane) was relying on something segfaulting. If you're changing a behavior from one potentially reasonable behavior to the one that was actually documented, then, yeah, that might not be "just a bug fix" – people might be relying on it. So we're super conservative about "fixing" even fairly bad behavior in patch releases. Still, the only way to make sure an application keeps working exactly the same is to use the same exact versions – down to the patch level.
The FerVer "spec" is barely coherent, I'm not sure how anyone could consider that something to follow. SlimVer basically just gets rid of things in SemVer that you don't have to use anyway. Pre-release versions – especially release candidates – are incredibly useful. You tag 1.2.3-rc1 and if no issues crop up in a week, then that becomes your 1.2.3 release (1.2.3-rc1 and 1.2.3 are exactly the same); if issues do crop up, then you fix them and tag 1.2.3-rc2 in a week and repeat.
I also think that pre-1.0 versions are useful: just treat the minor version like a major version. For 0.1.2 => 0.1.3 you only fix bugs but for 0.1.3 => 0.2.0 anything goes. There's no point in doing feature-only, backwards compatible releases while you're still working out the API for something, and 0.x releases are for precisely that kind of exploratory period.
The argument in the article seems strange to me: "downstream relied on undocumented behavior, and now that behavior has changed, so downstream breaks"
So what should upstream do? After all, the premise must be that the upstream does not control or even know about all the clients! The entire point is to separate the concerns, so how could upstream know if a bug fix is a breaking change if it passes the internal regression tests?
Of course they can't know without asking downstream, and hence the use of release candidates.
Any suggestions for improving the spec? I wrote that in like an hour and I have no experience writing specs.
I don't and wouldn't even really use it anyways. Easier to just follow semver and bump minor instead of patch if there's a remote possibility of anything breaking
Worse still, people can - and do - rely on behaviours that cause major security holes. So not only are some fixes backwards compatible for most but not all users, it can be critically important that those users get the version with the fix and not an older unpatched version.
SemVer is working fine, you just need to face up to the real impact of a change on clients, not what you wish was the impact of the change. If a change breaks clients who rely on buggy behavior, you need to bump a version, not a patch number.
This may be true, but in practice (as pointed out in the article), EVERYTHING can potentially be a breaking change (http://xkcd.com/1172/). Since this is the case, you either need to always bump the version number (rendering the patch number useless), or bump the patch number but then have to wait for the bug reports to come in... once you get one bug report stating your patch broke someone's code, you would bump the version number... this doesn't seem practical, however.
SemVer isn't broken, and you already outlined the obvious solution:
1. Don't depend on 0.x.x version modules unless you are willing to accept the pain of updating versions constantly.
2. Bump major versions liberally.
If enough people just get used to this reality, then everything will be fine. The problem is trying to impose artificially conceived notions of how things "ought" to work on top of this.
Until someone invents a viable system for checking invariants and interfaces on codes statically, semver is pretty much the only show in town for specifying compatibility between code modules.
Exactly, if you claim to be following semver, and your major version is 1, 2, 3; you're probably not following semver. People have to get used to the fact that bumping the major version is not a big deal. All that it means is that there was an API update or breaking change; it may have been tiny. Chrome is up to version 38; they're doing it right.
For example, IIRC, Rails 2.3.5 introduced some API changes. That should have bumped the major version.
Rails 3.0 was a "rewrite your code" type bump; that should have been signified with a name change or other similar type of marketing codename. Rails+, for example.
I was really disappointed that Linus went with "Linux 3.0" rather than "Linux 40" as the version following 2.6.39 when he finally acknowledged that the '2.6' no longer had any meaning.
Completely agree. For a long time, I've thought that humans shouldn't be in charge of version numbers at all. The main problem is that a human isn't reliable at determining "is this change breaking?".
Unfortunately our software isn't well-specified enough to allow the computer to determine version numbers automatically yet. To make this a reality, you'd need Haskell-level type safety, along with specifying other constraints, like "this fn is O(log(n)) or better".
>If you're pinning versions, you're reducing the package manager to a glorified CURL
I'm actually quite happy with this. I realized it when a few weeks ago, bower broke for me. Turns out, a grandchild dependency of bower failed to follow semver. This means that it's not enough for you to follow semver, every single dependency of all of your dependencies have to follow semver as well.
For anyone in the Haskell world, it's obvious that semantic versioning[1] doesn't work. Some non-trivial amount of that is likely due to how horribly broken Cabal is, a problem the community remains in staunch denial of. But for the rest, one problem is that because Haskell more thoroughly specifies types, individual functions can remain the same in behavior but change in type because a bug-fix, say, fixes a record type used somewhere. The external interface might be largely the same, or even identical, but the binary output is non-backwards compatible. Dynamic libraries and strong, descriptive type systems aren't very compatible. The result is that a lot of bug fixes become minor version bumps.
These minor version bumps scare library developers, especially people that produce libraries that depend on libraries that depend on... and so on. So packages fail to build too often because of constraints. So the irony of this thorough process for package building is that humans have mucked it up and made it break builds. In almost every case in which semantic versions blocked some update from occurring and caused my work to stop, it was because I needed to bump the constraints on some third-party package. It's a boring process that I've only become too accustomed to:
1. try to build with updated libA 1.1.0-foobar
2. libB depended on libA < 1.1
3. download source for libB
4. update constraint and repackage libB locally
5. rebuild main application
This happens just about every time there's a new version of any major piece of Haskell software.
If we really want to move into The Future, we need to start versioning individual modules or functions, instead of whole suites of software. But the cognitive burden of doing this is very high, and the software to do it hasn't been written yet.
[1] The Haskell community doesn't technically use semver, but rather a related policy called the package versioning policy. That's a nitpick though, and doesn't affect my argument.
> Unfortunately our software isn't well-specified enough to allow the computer to determine version numbers automatically yet. To make this a reality, you'd need Haskell-level type safety, along with specifying other constraints, like "this fn is O(log(n)) or better".
Isn't this going to be impossible for the same reason the Halting Problem is unsolvable?
A core problem is that the current system conflates two subtly different notions of version: the low-level package manager version (is it backwards compatible? will it build?) and the high-level person version (is this the same library with some changes, or is it a new generation or significant redesign?).
The first notion is needed for keeping our code stable but keeping up with bufixes and security patches. The second one is needed to deal with design issues: will this change require a lot of reworking? Will it change the underlying model or abstraction in the library?
Both of these are important. But because Semver requires bumping for any breaking changes—even if they are semantically minor—it can only address the first, low-level notion of versioning, not the second.
A possible solution? We can have two minor version numbers:
4.1.0.2
Patches and backwards compatible changes work as always. But if you are making a breaking change, you have two options. If it's not too big, just increment the first one:
4.2.0.0
But if you're really changing things up and creating a new generation of your library, go for
5.0.0.0
Of course, the difference between the two major numbers is not well-specified. But that's fine! From the more technical, low-level perspective, it's no different from normal Semver: any change in the major version (either of the two numbers) can be breaking. It just also lets library authors send an additional signal to developers about the scope of the change.
For example, this scheme gives more information on how "stable" a library is. If the second major number is high, it means there are a lot of minor breaking changes; it would take a bit of active maintenance to keep up, but as long as the first major number is not changing, these steps won't be radical.
Personally, this approach makes a lot of sense to me. It's also more or less what Haskell packages use according to the "package versioning policy", and it seems to work pretty well. (There are some other pervasive issues with Haskell version numbers, but not related to how they treat major versions.)
I think the root problem is that SemVer works great, and was designed for, larger projects with many developers and a relatively stable API.
For smaller projects and libraries, it works very poorly, because these are usually "itch-scratching" efforts that don't necessarily have a clear endpoint or goal. For these kinds of packages, I wish people would just use a commit number. I really hate having to agonize over SemVer for a 500 LOC miniproject.
That kind of project, incidentally, is the most likely to be in "0.X.X" limbo forever. See for example the Clojure ecosystem where 80% of packages are this way.
His new versioning scheme ("ferver") is built on the premise that "people don't like it when major versions change often." Seems like a fourth version number is the solution, not breaking the semantics of SemVer.
I propose 4Ver. Just add a marketing version to the beginning. Update that one whenever you want (may include breaking changes). Since you have 4 numbers, you can remove the exceptions for the 0th version as well, and make version 0 follow the same rules as everything else. This cleanly separates the marketing/naming aspect of versions from the semantic aspects.
I want to agree with the sentiment about the human side of versioning being unreliable, but when the author "pins" the argument on the fact that pinning all versions is "annoying" s/he loses me. Pinning (and vendoring) everything is the only way to know that exactly what you expect to be deployed will be deployed when you deploy it. Anything less is inappropriate for production environments.
You're assuming that development and production version ranges will always be the same, which is false. This is what npm shrinkwrap is for, and not pinning during production is stupid. But pinning everything in development is unnecessary until you stage/deploy.
Semver is busted because it makes builds non-deterministic. Building the same code tomorrow may fail if a third party dependency publishes a change violating semver.
Huh? Which ecosystem advocates automatic updates and doesn't allow locking the version numbers of the dependencies until a developer manually updates them?
As someone who hasn't been paying attention to semantic versioning orthodoxy, the author's suggested alternative of ferver[1] is what I thought semantic versioning already was. So yeah, do that. All I care about is breakage, not whether new features were added.
It's basically just semver, with a little more indication given to what sorts of breakages are considered. The problem I have with "minor" vs "major" breakage is that if I only use one function from your library, and it breaks and screws up my entire app, that ain't "minor".
This is just more of the same marking of territory that Node is kinda famous for.
SemVer makes sense as a system for versioning exposed interfaces, and for versioning products where the product is just an implementation of single, coherent interface.
When a product is not principally an implementation of a single interface, including notably the case where it exposes multiple interfaces that are somewhat orthogonal (as might be the case, e.g., with an RDBMS where the SQL language support, the TCP/IP interface, etc., are all interfaces which might sensibly be versioned), it probably makes sense to use separate semver-like versions for the individual interfaces and something else for the product.
Language implementations naively seem like really good SemVer candidates by that criteria, but the sibling comment [1] addressing binary vs. source compatibility, etc., reveals how such implementations can present multiple different perspectives that are problematic.
The bigger problem has always been when the version number is marketing.
I worked in one team, and there was a contractual requirement that v 1.16 was the last version we would ship. So, where before we would increase the number at certain steps, we stopped at 1.16 and shifted to a third digit. Since we were a continuous build shop, we ended up with 1.16.110 very quickly. At that point, we shifted from 3 digit versions to a version number which was marketing controlled and a build number which was controlled by engineering (hashed from the tag+date).
This pattern repeated over and over. The version number would be communicated to the customer, a new version would be needed and so the version number would grow a digit. It ended up at X.Y.Z.A.B rev C. Yes, it was silly.
So, no matter what you use, make sure you keep your externally visible version numbers separate from the numbers you use to talk about them internally. :) Internal numbers should remain completely opaque to the customer.
Personally, I would far prefer a strong contracts system over semantic versioning. The ability to say "I rely on these N interfaces to present accept these argument types and return these types, and here are some test arguments and the values I expect returned" to be a much better way of describing what you rely on. Even better is that by defining things in terms of contracts, you should be able to replace dependencies with any module that matches the contract you require.
In general, it's always better to have a computer enforced standard over a human enforced one. Semantic versioning can just become human readable sugar over a set of contractual expectations.
Unless you checked every possible result for every possible function call you use then the 3rd party library could have been changed in such a way which would break your program. There's no way to prevent such situation in general, e.g when the input value space is non-finite.
Other kinds of specification would seem very hard to test, for example rendering a 3D scene, if we used a bitmap as test data then we'd get false positives when the rendering is improved in some minor way, and the spec will fail, we'd need to use some perceptual metric for image similarity instead.
Version numbers aren't so bad, given their ease of use.
Versioning schemes like semver don't work in practice because change is breaking by definition. I'm increasingly convinced that the only version people should use is "latest." Security patches are the only things that should be retroactively added (maybe even as out-of-band patchfiles clearly marked with "to be used only until you get up and upgrade your software.") If you've amassed such tech debt that an upgrade is infeasible, you've forked the software and are responsible for its upgrades.
You had me until "Duplicate dependencies are bad". The reason every package manager except npm and Apple's bundles are a piece of shit is because they attempt to install random dependent garbage globally and by default.
You're assuming "no duplicate dependencies" is equivalent to "install globally", which is false. In the article's context, the argument is against duplicate dependencies per project
I (along with Natalie Weizenbaum) wrote the package manager that Dart uses[1]. It's based on semver. It's good linkbait to characterize semver as broken, but not at all true. Semver works fine when you realize what it is.
Semver is a way to communicate between people about compatibility expectations they should have between two versions of a package. When library maintainer says "this version changes from 1.2.3 to 1.3.0" that tells something useful to a human consumer of that package.
It absolutely does not communicate anything precise in the language of machines. It's not a formal system, and compatibility isn't formally defined. If you want it to be that, you're setting yourself up for hearthbreak.
Code reuse is a social process. Understand that semver is a tool at that level and you'll be pretty happy with it. In my mind, the most important feature of semver is that by associating pre-arranged semantics to versions, consumers can specify version ranges before those versions exist.
If I'm using foo 1.2.3, I can depend on foo >=1.2.3 <1.3.0 with decent confidence even though 1.2.4 may not even exist yet. That sort of forward compatibility makes the package ecosystem a lot more resilient to changes over time.
Part of the major issues with semver is that patchs could
break. A feature could be "fixed", but a consumer could
have relied on that very buggy feature, and patching it
would break their app.
This is true. One man's bug is another man's feature. If you want to be fully strict, all of the behavior of a program is significant which means you can never release anything that isn't a breaking change.
Being that rigorous isn't useful. Instead, realize that most patches don't break most users. For the ones that do, that's why you pin. You are pinning, aren't you?
Versions < 1.0.0 do not have semantic versions according
to semver. These are considered libraries "in development"
and developers could version however they see fit.
This part is straight up annoying especially as the web moves to a world where damn near everything is <1.0.0. On the Dart team, we've adopted a policy that for sub 1.0.0 packages, we do semantically version them. We just shift the numbers one to the right.
So, going from 0.1.0 to 0.2.0 is a breaking change, 0.1.0 to 0.1.1 is an API addition, and 0.1.0 to 0.1.0+1 is a patch.
It seems to work really well for us.
The current solution to the above problems is to pin all
dependencies. However, this is absolutely stupid and
annoying to me. ... When every library pins, you're going
to have a lot of duplicate dependencies, even if they're
the same version!
No no no no no.
You don't pin versions in libraries, you pin them in applications.
Almost every package in the dependency graph should be using version ranges. That avoids version lock and makes it easier to satisfy shared dependencies. It also means libraries don't all have to bump patches that just change dependency versions.
But, in your application, in the root of the dependency graph, you pin everything that you're using right now. You check that in to ensure that everyone on your team and every machine gets the same versions of all of the dependencies.
You get good code reuse and deterministic builds.
This is exactly why Bundler separates out the lockfile from the Gemspec. It's unintuitive at first, but it works better than any other system I've seen once you grok it.
There is one think in semver that I think is totally inane. The latest version of the spec says build suffixes have no relative ordering. We don't do that in pub because it makes no sense. If a user has uploaded foo 1.0.0+a and foo 1.0.0+b, which version should an application get that depends on foo? Pick randomly?
npm init should really use 1.0.0 as default, and I agree 0.x.x should be dropped. It causes a lot of problems since most people publishing packages don't know what 0.x.x means, and they just end up leaving their (stable) modules at 0.0.0.
I am not sure I agree with all of OP's argument, and I don't really care for the whole "version numbers as a marketing message" thing, but for me SemVer fails miserably when it comes to binary compatibility versus source compatibility (and don't even start on semantic compatibility...).
In languages where that difference matters, let's say Java/Scala [1], there are way too many possibilities:
- bug fix: forward and backward binary compatible; basically a drop-in replacement. Package managers are supposed to resolve conflicts between those automatically,
- new feature, but backward binary compatible; just some new code alongside the old. Package managers should also be able to resolve a conflict between dependencies that depend on different versions of those.
- bug fix: forward and backward source compatible; here we have a problem: we can guarantee developers that they won't have to edit a single line of code to migrate, but we can't allow (binary) package managers to do anything. They have to rebuild their library, which means bumping their own version number. And does that means that their new version becomes binary-incompatible with the previous one, or just their dependencies?... who knows? Maybe it depends on if the consumer of that library calls into the conflicting dependency directly or only uses it indirectly? Maybe not? Can we tell?
- new feature, backward source compatibility, but no binary compatibility; same as above: package managers cannot do anything, and conflicting transitive dependencies on the new and the old may or may not be solved by recompiling one's library. The only safe bet is to wait for the dependencies to upgrade.
- all bets are off (possibly split in minor and major): here at least we know there is nothing to be hoped. But in practice after upgrading to the new API, you are faced with the same questions concerning that thing's dependencies...
And if every dependency manager within the same packaging eco-system doesn't enforce a single global versioning scheme, how can they handle any dependency version conflict anyway? They could if packages could auto-describe their semantic versioning, so basically they can't, so they don't, and we're back to source dependencies, with recompile and bump in version number for every dependency change (ok, sometimes they do try and we get JAR hell...).
And since a single versioning scheme isn't enforced, people use hundreds of different ones (or just none), and the maintainer of a library has to make all of those decisions, recursively (except that below 1 level of dependency, he/she might not be able to do anything as long as even a single dependency keeps an old conflicting dependency around, but they still have to check, in case they are lucky and can do something), taking into account the different versioning semantics of each dependency. That's madness.
So in the end, if we get a conflict between dependencies far down in the dependency tree, it becomes something pretty ugly:
- if guaranteed binary compatibility, everything is fine,
- otherwise:
0) look at that dependency's official versioning scheme, usually find nothing (except for big famous libraries)
1) pick the highest common-one, try to recompile your library
2) if it works, try the tests (your library's tests, not your dependencies' tests, mind you. Even though those should probably be run, considering they directly depend on it, and use (and hopefully test) more stuff from that library)
3a) it it works, ship it and pray.
3b) it will probably fail somewhere since the dependencies in between have not be recompiled; then you can only wait for them to upgrade.
I know in the end this doesn't work so bad (well, everything is relative...), but it still makes me sad that upgrading dependencies is still human-based trial and error.
Oh. I guess this is a rant.
[1] that's just what I am the most familiar with, but you could say the same with C packages in a distro's package repo, for example.
[+] [-] angersock|11 years ago|reply
You should be fast to get to 1.x.x. If your app breaks on a patch release (that was only a bugfix and provided that interfaces stayed consistent), maybe you shouldn't rely on buggy behavior and maybe you should fix your code.
This isn't a failing of semver--this is a failure of being responsible engineers.
EDIT:
The best example from the article is the bit talking about how people interpret 0.x vs 1.x vs 2.x. Folks, it's just a damned version number, and a number that only indicates you may have to refactor you application to handle changed interfaces. Any other psychological baggage you attach to it is just that, baggage. Aaaargh.
[+] [-] nmjohn|11 years ago|reply
Yes, people _should_ get over changes in version number, but in reality they don't. So library authors cater to reality, not how they wish things should be. That is the problem, and why semvar is broken. Not in principle it isn't, but in how it actually functions in the hands of humans.
[+] [-] StefanKarpinski|11 years ago|reply
- patch: forwards and backwards compatible – only bug fixes, no new features, no changing existing features.
- minor: backwards compatible – new features allowed, but old code should keep working.
- major: all bets are off.
This is pretty much what the SemVer spec says, and it's hard to imagine what else you could do. The significance of forwards compatibility for patch versions is that if you bump a patch version and find that it was problematic, you should be able to revert back to a lower patch version without breaking new code that's been written since the bump.
The thing is, it's still a judgement call what constitutes a bug fix. If you're fixing a segfault, that's a no brainer – no one (sane) was relying on something segfaulting. If you're changing a behavior from one potentially reasonable behavior to the one that was actually documented, then, yeah, that might not be "just a bug fix" – people might be relying on it. So we're super conservative about "fixing" even fairly bad behavior in patch releases. Still, the only way to make sure an application keeps working exactly the same is to use the same exact versions – down to the patch level.
The FerVer "spec" is barely coherent, I'm not sure how anyone could consider that something to follow. SlimVer basically just gets rid of things in SemVer that you don't have to use anyway. Pre-release versions – especially release candidates – are incredibly useful. You tag 1.2.3-rc1 and if no issues crop up in a week, then that becomes your 1.2.3 release (1.2.3-rc1 and 1.2.3 are exactly the same); if issues do crop up, then you fix them and tag 1.2.3-rc2 in a week and repeat.
I also think that pre-1.0 versions are useful: just treat the minor version like a major version. For 0.1.2 => 0.1.3 you only fix bugs but for 0.1.3 => 0.2.0 anything goes. There's no point in doing feature-only, backwards compatible releases while you're still working out the API for something, and 0.x releases are for precisely that kind of exploratory period.
[+] [-] Trombone5|11 years ago|reply
So what should upstream do? After all, the premise must be that the upstream does not control or even know about all the clients! The entire point is to separate the concerns, so how could upstream know if a bug fix is a breaking change if it passes the internal regression tests?
Of course they can't know without asking downstream, and hence the use of release candidates.
[+] [-] jongleberry|11 years ago|reply
I don't and wouldn't even really use it anyways. Easier to just follow semver and bump minor instead of patch if there's a remote possibility of anything breaking
[+] [-] makomk|11 years ago|reply
[+] [-] jameshart|11 years ago|reply
[+] [-] cortesoft|11 years ago|reply
[+] [-] 33a|11 years ago|reply
1. Don't depend on 0.x.x version modules unless you are willing to accept the pain of updating versions constantly.
2. Bump major versions liberally.
If enough people just get used to this reality, then everything will be fine. The problem is trying to impose artificially conceived notions of how things "ought" to work on top of this.
Until someone invents a viable system for checking invariants and interfaces on codes statically, semver is pretty much the only show in town for specifying compatibility between code modules.
[+] [-] bryanlarsen|11 years ago|reply
For example, IIRC, Rails 2.3.5 introduced some API changes. That should have bumped the major version.
Rails 3.0 was a "rewrite your code" type bump; that should have been signified with a name change or other similar type of marketing codename. Rails+, for example.
I was really disappointed that Linus went with "Linux 3.0" rather than "Linux 40" as the version following 2.6.39 when he finally acknowledged that the '2.6' no longer had any meaning.
[+] [-] arohner|11 years ago|reply
Unfortunately our software isn't well-specified enough to allow the computer to determine version numbers automatically yet. To make this a reality, you'd need Haskell-level type safety, along with specifying other constraints, like "this fn is O(log(n)) or better".
>If you're pinning versions, you're reducing the package manager to a glorified CURL
I'm actually quite happy with this. I realized it when a few weeks ago, bower broke for me. Turns out, a grandchild dependency of bower failed to follow semver. This means that it's not enough for you to follow semver, every single dependency of all of your dependencies have to follow semver as well.
[+] [-] AaronFriel|11 years ago|reply
These minor version bumps scare library developers, especially people that produce libraries that depend on libraries that depend on... and so on. So packages fail to build too often because of constraints. So the irony of this thorough process for package building is that humans have mucked it up and made it break builds. In almost every case in which semantic versions blocked some update from occurring and caused my work to stop, it was because I needed to bump the constraints on some third-party package. It's a boring process that I've only become too accustomed to:
1. try to build with updated libA 1.1.0-foobar
2. libB depended on libA < 1.1
3. download source for libB
4. update constraint and repackage libB locally
5. rebuild main application
This happens just about every time there's a new version of any major piece of Haskell software.
If we really want to move into The Future, we need to start versioning individual modules or functions, instead of whole suites of software. But the cognitive burden of doing this is very high, and the software to do it hasn't been written yet.
[1] The Haskell community doesn't technically use semver, but rather a related policy called the package versioning policy. That's a nitpick though, and doesn't affect my argument.
[+] [-] ZitchDog|11 years ago|reply
Isn't this going to be impossible for the same reason the Halting Problem is unsolvable?
[+] [-] tikhonj|11 years ago|reply
The first notion is needed for keeping our code stable but keeping up with bufixes and security patches. The second one is needed to deal with design issues: will this change require a lot of reworking? Will it change the underlying model or abstraction in the library?
Both of these are important. But because Semver requires bumping for any breaking changes—even if they are semantically minor—it can only address the first, low-level notion of versioning, not the second.
A possible solution? We can have two minor version numbers:
Patches and backwards compatible changes work as always. But if you are making a breaking change, you have two options. If it's not too big, just increment the first one: But if you're really changing things up and creating a new generation of your library, go for Of course, the difference between the two major numbers is not well-specified. But that's fine! From the more technical, low-level perspective, it's no different from normal Semver: any change in the major version (either of the two numbers) can be breaking. It just also lets library authors send an additional signal to developers about the scope of the change.For example, this scheme gives more information on how "stable" a library is. If the second major number is high, it means there are a lot of minor breaking changes; it would take a bit of active maintenance to keep up, but as long as the first major number is not changing, these steps won't be radical.
Personally, this approach makes a lot of sense to me. It's also more or less what Haskell packages use according to the "package versioning policy", and it seems to work pretty well. (There are some other pervasive issues with Haskell version numbers, but not related to how they treat major versions.)
[+] [-] xaa|11 years ago|reply
For smaller projects and libraries, it works very poorly, because these are usually "itch-scratching" efforts that don't necessarily have a clear endpoint or goal. For these kinds of packages, I wish people would just use a commit number. I really hate having to agonize over SemVer for a 500 LOC miniproject.
That kind of project, incidentally, is the most likely to be in "0.X.X" limbo forever. See for example the Clojure ecosystem where 80% of packages are this way.
[+] [-] nardi|11 years ago|reply
I propose 4Ver. Just add a marketing version to the beginning. Update that one whenever you want (may include breaking changes). Since you have 4 numbers, you can remove the exceptions for the 0th version as well, and make version 0 follow the same rules as everything else. This cleanly separates the marketing/naming aspect of versions from the semantic aspects.
[+] [-] jongleberry|11 years ago|reply
[+] [-] thedaniel|11 years ago|reply
[+] [-] jongleberry|11 years ago|reply
[+] [-] dustingetz|11 years ago|reply
For example: https://github.com/bower/bower/issues/1404#issuecomment-4876...
I unfortunately can only complain, I have no solution to offer.
[+] [-] bhaak|11 years ago|reply
[+] [-] natrius|11 years ago|reply
[1] https://github.com/jonathanong/ferver
[+] [-] angersock|11 years ago|reply
This is just more of the same marking of territory that Node is kinda famous for.
[+] [-] dragonwriter|11 years ago|reply
When a product is not principally an implementation of a single interface, including notably the case where it exposes multiple interfaces that are somewhat orthogonal (as might be the case, e.g., with an RDBMS where the SQL language support, the TCP/IP interface, etc., are all interfaces which might sensibly be versioned), it probably makes sense to use separate semver-like versions for the individual interfaces and something else for the product.
Language implementations naively seem like really good SemVer candidates by that criteria, but the sibling comment [1] addressing binary vs. source compatibility, etc., reveals how such implementations can present multiple different perspectives that are problematic.
[1] https://news.ycombinator.com/item?id=8155501
[+] [-] jpollock|11 years ago|reply
I worked in one team, and there was a contractual requirement that v 1.16 was the last version we would ship. So, where before we would increase the number at certain steps, we stopped at 1.16 and shifted to a third digit. Since we were a continuous build shop, we ended up with 1.16.110 very quickly. At that point, we shifted from 3 digit versions to a version number which was marketing controlled and a build number which was controlled by engineering (hashed from the tag+date).
This pattern repeated over and over. The version number would be communicated to the customer, a new version would be needed and so the version number would grow a digit. It ended up at X.Y.Z.A.B rev C. Yes, it was silly.
So, no matter what you use, make sure you keep your externally visible version numbers separate from the numbers you use to talk about them internally. :) Internal numbers should remain completely opaque to the customer.
[+] [-] malandrew|11 years ago|reply
In general, it's always better to have a computer enforced standard over a human enforced one. Semantic versioning can just become human readable sugar over a set of contractual expectations.
[+] [-] jahewson|11 years ago|reply
Other kinds of specification would seem very hard to test, for example rendering a 3D scene, if we used a bitmap as test data then we'd get false positives when the rendering is improved in some minor way, and the spec will fail, we'd need to use some perceptual metric for image similarity instead.
Version numbers aren't so bad, given their ease of use.
[+] [-] nilved|11 years ago|reply
[+] [-] bcoates|11 years ago|reply
[+] [-] jongleberry|11 years ago|reply
[+] [-] munificent|11 years ago|reply
[1]: http://pub.dartlang.org/
Semver is a way to communicate between people about compatibility expectations they should have between two versions of a package. When library maintainer says "this version changes from 1.2.3 to 1.3.0" that tells something useful to a human consumer of that package.
It absolutely does not communicate anything precise in the language of machines. It's not a formal system, and compatibility isn't formally defined. If you want it to be that, you're setting yourself up for hearthbreak.
Code reuse is a social process. Understand that semver is a tool at that level and you'll be pretty happy with it. In my mind, the most important feature of semver is that by associating pre-arranged semantics to versions, consumers can specify version ranges before those versions exist.
If I'm using foo 1.2.3, I can depend on foo >=1.2.3 <1.3.0 with decent confidence even though 1.2.4 may not even exist yet. That sort of forward compatibility makes the package ecosystem a lot more resilient to changes over time.
This is true. One man's bug is another man's feature. If you want to be fully strict, all of the behavior of a program is significant which means you can never release anything that isn't a breaking change.Being that rigorous isn't useful. Instead, realize that most patches don't break most users. For the ones that do, that's why you pin. You are pinning, aren't you?
This part is straight up annoying especially as the web moves to a world where damn near everything is <1.0.0. On the Dart team, we've adopted a policy that for sub 1.0.0 packages, we do semantically version them. We just shift the numbers one to the right.So, going from 0.1.0 to 0.2.0 is a breaking change, 0.1.0 to 0.1.1 is an API addition, and 0.1.0 to 0.1.0+1 is a patch.
It seems to work really well for us.
No no no no no.You don't pin versions in libraries, you pin them in applications.
Almost every package in the dependency graph should be using version ranges. That avoids version lock and makes it easier to satisfy shared dependencies. It also means libraries don't all have to bump patches that just change dependency versions.
But, in your application, in the root of the dependency graph, you pin everything that you're using right now. You check that in to ensure that everyone on your team and every machine gets the same versions of all of the dependencies.
You get good code reuse and deterministic builds.
This is exactly why Bundler separates out the lockfile from the Gemspec. It's unintuitive at first, but it works better than any other system I've seen once you grok it.
There is one think in semver that I think is totally inane. The latest version of the spec says build suffixes have no relative ordering. We don't do that in pub because it makes no sense. If a user has uploaded foo 1.0.0+a and foo 1.0.0+b, which version should an application get that depends on foo? Pick randomly?
[+] [-] mattdesl|11 years ago|reply
[+] [-] gourlaysama|11 years ago|reply
In languages where that difference matters, let's say Java/Scala [1], there are way too many possibilities:
- bug fix: forward and backward binary compatible; basically a drop-in replacement. Package managers are supposed to resolve conflicts between those automatically,
- new feature, but backward binary compatible; just some new code alongside the old. Package managers should also be able to resolve a conflict between dependencies that depend on different versions of those.
- bug fix: forward and backward source compatible; here we have a problem: we can guarantee developers that they won't have to edit a single line of code to migrate, but we can't allow (binary) package managers to do anything. They have to rebuild their library, which means bumping their own version number. And does that means that their new version becomes binary-incompatible with the previous one, or just their dependencies?... who knows? Maybe it depends on if the consumer of that library calls into the conflicting dependency directly or only uses it indirectly? Maybe not? Can we tell?
- new feature, backward source compatibility, but no binary compatibility; same as above: package managers cannot do anything, and conflicting transitive dependencies on the new and the old may or may not be solved by recompiling one's library. The only safe bet is to wait for the dependencies to upgrade.
- all bets are off (possibly split in minor and major): here at least we know there is nothing to be hoped. But in practice after upgrading to the new API, you are faced with the same questions concerning that thing's dependencies...
And if every dependency manager within the same packaging eco-system doesn't enforce a single global versioning scheme, how can they handle any dependency version conflict anyway? They could if packages could auto-describe their semantic versioning, so basically they can't, so they don't, and we're back to source dependencies, with recompile and bump in version number for every dependency change (ok, sometimes they do try and we get JAR hell...). And since a single versioning scheme isn't enforced, people use hundreds of different ones (or just none), and the maintainer of a library has to make all of those decisions, recursively (except that below 1 level of dependency, he/she might not be able to do anything as long as even a single dependency keeps an old conflicting dependency around, but they still have to check, in case they are lucky and can do something), taking into account the different versioning semantics of each dependency. That's madness.
So in the end, if we get a conflict between dependencies far down in the dependency tree, it becomes something pretty ugly:
- if guaranteed binary compatibility, everything is fine,
- otherwise:
0) look at that dependency's official versioning scheme, usually find nothing (except for big famous libraries)
1) pick the highest common-one, try to recompile your library
2) if it works, try the tests (your library's tests, not your dependencies' tests, mind you. Even though those should probably be run, considering they directly depend on it, and use (and hopefully test) more stuff from that library)
3a) it it works, ship it and pray.
3b) it will probably fail somewhere since the dependencies in between have not be recompiled; then you can only wait for them to upgrade.
I know in the end this doesn't work so bad (well, everything is relative...), but it still makes me sad that upgrading dependencies is still human-based trial and error.
Oh. I guess this is a rant.
[1] that's just what I am the most familiar with, but you could say the same with C packages in a distro's package repo, for example.