LLVM's New Versioning Scheme
blog.llvm.org
blog.llvm.org
Basically, any code that uses LLVM bitrots very quickly because the API is (potentially) incompatible on each release. The way that large projects (like Rust [0]) generally deal with this is by sticking close to the bleeding edge of development, and have an established process for integrating changes as they come. But smaller projects just don't have the resources to do this.
KLEE (a symbolic execution engine) appears to be 5 versions behind the newest release [1]. Terra (a language focusing on metaprogramming for high performance) [2] is only one version behind, but the maintainer has since graduated and I'm worried the community is too small to keep it from bitrotting.
Nominally the C API is supposed to solve this by being more stable, but it achieves this by exposing a smaller surface area, which means you can't do a lot of what you might need to do with this API.
I'd like a stable, full-featured API for LLVM. It sounds like they're going this way with the bitcode format, and I think they could start to move in this direction with the API as well. When the project was young, the argument was that avoiding stability allowed the developers the flexibility to explore design decisions and make different tradeoffs over time necessary for the long-term health of the code base. I understand that. But this instability makes it essentially impossible to maintain client projects without ongoing effort, and means that small projects almost always bitrot.
I think we know enough now to develop a pretty decent stable API for LLVM. And I'd be a lot more confident in trusting smaller projects that use LLVM if such an API existed.
[0]: https://www.rust-lang.org/ [1]: https://klee.github.io/getting-started/ [2]: http://terralang.org/
And for being spot on.
LLVM is an incredible piece of software, but it's the lowest part in the stack.
Sure, clang, rust or swift have enough manpower to always follow, but smaller languages / compilers can't possibly hope to deal with breaking changes all the time.
The constant breaking changes also make it very hard to support multiple LLVM versions simultaneously.
Which makes development and deployment harder, because you can usually only install one LLVM version via distro packages.
So if you want to compile something that depends on an older / newer LLVM version, you have to compile LLVM, which takes a loooong time and is not something you can bring many people to do.
Rust supports LLVM 3.7, 3.8, and 3.9, and it isn't particularly hard to do so. Very annoying, yes, hard, no. It does need a bit of manpower though.
Some time after the major version hits 3, the madness seems to begin. Am I running Firefox 73 or 85 or was that last years version? I honestly don't know anymore. Are the stable versions even, odd, Fibonacci or prime?
Why not be brave about it and use date-based numberings such as 16.12!
Thinking on it now, I wish instead of major.minor.patch the format was major.breaking.minor.patch, where the first component is for "significant" releases, and the second component is what you use when you're simply introducing breaking changes (but doesn't qualify as a "significant" release).
That's the same thing as projectName.major.minor.patch in some respects. You could just rebrand the project if it's a completely different direction. There's not a technical reason to keep the name, just a marketing/political/organizational reason.
When you're doing backend/API things (where breaking changes matter), I sure hope you think a million times before making breaking changes.
Can you imagine if someone had to go through millions of lines of his code to make sure nothing broke, then, a week later, you broke his code again?
But if I go and drastically change things in a way likely to impact many users, that gets a brand new version number too. So v1.0.0 -> v2.0.0 only really communicates "something might break".
The scheme proposed by the parent would be able to communicate "expect many things to break because I refactored the heck out of stuff to fix some long-standing design deficiencies" -- though admittedly when to bump that first version number is likely to be a subjective topic. :)
Perhaps this isn't as valuable as it seems at a first glance, but if anybody's tried something like this I for one would be interested to hear about it.
The main reason semver is done the way it is is so you can do things like have package managers automatically pick the latest compatible version, since incompatibilities are denoted by the major version number. That's why I'd prefer major.breaking.minor.patch, because you can still have the package manager automatically detect compatible versions, but you don't end up in the crazy land of releasing a library at v27.
So let's say you have Compiler 5.3.2
It means that the important thing is compiler #5. Upgrading from 4 to 5 is a _Big Deal_. You may have to rewrite all your code.
Within 5, you have a version 3. 3 has features A,B,C which 2 doesn't have. Most additions go there. So it should be safe to upgrade.
Within that, you have bugfix #2. That _should_ always be upgraded, unless you rely on undocumented features.
So it's easy for me to tell if I should upgrade.
So upgrading from Apache 1 to Apache 2 may brake config scripts and .htaccess files. Don't upgrade on production build.
Upgrading Apache 1.1 to 1.2, See README, Should be fine, do a small test on your testing machine.
Upgrading Apache 1.1.2 to 1.1.3. Probably a security fix. Do so. Immediately.
---------
The OP's numbering system doesn't tell me anything. should I upgrade 5.4.3.2 to 5.5.0.0? Will it be safe? Probably not. You may have to schedule a full testing load just to be sure.
What about from 5.4.3.2 to 6.4.0.0? Same thing. You have to do a full testing.
And if you _really_ break old code, do everyone a favor and rename your project (So, no, please don't call Go C++ V.13 or something)
I expect to audit dependencies I use when they break API compatibility in any way. That's a feature, not a bug. Having a "well, the maintainer think this is a bigger break" number does nothing. It's still a break. It's still a major version change.
1. What if it's a dll, .so? You upgrade and find out that your program is broken.
2. Sometimes the API stays the same but the code behind the API changes a result (for example, secure_hash goes from MD5 to bcrypt)?
3. What about non-type safe languages (like HTML or JS, so things like Firefox or Chrome)?
The point is that you should avoid breaking other people's code if you can. What happened if that removal of one function in that one module costs me a full years of work?
Sometimes you can't help yourself. PHP had register_globals. Some people were able to use it safely (initialize all variables before use), but PHP rightfully realized the security implications and disabled it. However, it broke code, and a lot of it.
These are things you should think about and heavily before breaking code. It may be one line for you, but for all the millions of people who use your library it could be thousands of man-years of work.
If it's not backwards-compatible then it needs to bump the appropriate version number (in my proposal, that would be the second dotted component). So I'm not sure what you're trying to say here.
> * Sometimes the API stays the same but the code behind the API changes a result (for example, secure_hash goes from MD5 to bcrypt)?*
If it's a non-backwards-compatible behavioral change then maybe you need to design your API better such that this kind of change is expressed in the API. After all, if you expect anybody to ever upgrade to your new bcrypt version, you need to provide some path for people to still work with their older MD5 hashes anyway.
> What about non-type safe languages (like HTML or JS, so things like Firefox or Chrome)?
Not something I particularly care about. Though it doesn't really matter anyway; even if you think the breaking change number is too "subtle", anyone who's manually upgrading to a new version instead of letting their package manager do it should already be prepared to deal with breaking changes, because if there aren't breaking changes then their package manager should have been happy to upgrade without any manual intervention.
> The point is that you should avoid breaking other people's code if you can. What happened if that removal of one function in that one module costs me a full years of work?
I have no idea what point you're trying to make here. My suggestion was just about changing the format of the version number, and has no bearing whatsoever on the actual breaking changes you do or don't introduce. I'm certainly not advocating for removing functionality.
But with my proposal, users can see that the breaking change is a "minor" one, and therefore they don't need to be prepared to learn about a bunch of big changes in order to upgrade.
The Arch number represents a "generation" of the software that represents a significant overhaul where the architecture changes and input files that were generated for previous versions are not likely to work.
Major represents breaking API compatibility, so users who write plugins for the software will need to recompile and possibly change their code (but maybe not depending on what changed).
Minor and Patch are what you'd expect from semver.
This works extremely well from my experience.
I can't picture this. Breaking changes are always significant, aren't they?
Got an example?
The particular case that I was referring to when I said I decided to put off changes because I didn't want to bump the version number was actually a stylistic change, renaming a method from `parse(with:)` to `parse(using:)`, in order to better match the Swift 3 naming conventions. Normally I would have just marked the old method as deprecated, except a compiler bug means that if I do so, any code using trailing-closure syntax fails to compile (https://bugs.swift.org/browse/SR-3227). So it's literally impossible for me to rename this method in this manner without deleting the old method entirely (which is a breaking change). But I just bumped the major version number recently when Swift 3 was released, and I didn't want to bump it again shortly afterwards just because I didn't consider this method name during the Swift 2->3 migration.
The case I was talking about was the OpenVR library, which changed the names for some of its enum values in the upgrade from 1.0.4 to 1.0.5. The change was documented in the release notes and it was straightforward to fix our code, but now we have to document that we require at least v1.0.5 and everyone building our code has to update their copy of OpenVR and so on. There are knock-on effects, is what I'm trying to say.
Also, we decided that "stealing" major version numbers for speculative code was a bad idea. We'd have a lot of false starts that get abandoned and it got really confusing when the next major version got released (you're either re-using that version number or skipping a bunch).
Personally, I like semver, but think versioning mechanisms can differ (going from major OSes to small libraries I write at work) based on the needs. One thing I've noticed when working on artwork for clients, they get scared and confused by large revision numbers, so we tend to keep them fairly low by keeping internal version control numbers separate from review versions.
That's kind of the point though, right. As an API consumer/library user breaking changes are a pain in the ass. A library that rapidly issues breaking changes is one that is just plain difficult to use. Part of the goal with semantic versioning is to push authors to slow down on that and respect the commitments of their users.
Also, another thought here is that if you're hitting that many breaking changes that quickly, you possibly went 1.0 too early.
> where the first component is for "significant" releases, and the second component is what you use when you're simply introducing breaking changes
From a user's point of view, a breaking change is a significant release. It means I can't just hit update and get bug fixes (hopefully) for no effort. I have to investigate changes and test everything to make sure my software will still work.
Major would be for rewrites: (almost) a new product, but with the same name for brand recognition.
You can do that as much as you want in version 0.x.x
Get to v1 when you figured out the API and it is stable.
In my case, the library that I avoided making breaking changes to because I didn't want to bump the major version was already on v2, and I didn't want to make it v3 after just a few weeks at v2.
This allows for new APIs to bake before having the experimental flag removed and stability guarantees introduced.
Introduce the new APIs in a new namespace, class, module, new function name, whatever.
Either you write one new function yourself, or you force all API users to rewrite their functions to accommodate you.
The choice will be a show of what you value more: yourself or your users.
In RxJava [1], parts of the API are marked with @Experimental or @Beta. @Experimental provides no guarantees, while @Beta only guarantees no breakage in the same minor version.
What bothers me about the current trends in release management is that there is way too much emphasis on iterating quickly, even for libraries where that's completely inappropriate.
It's much better to just put all your breaking changes into an unstable branch and only merge to stable (and change version numbers) infrequently, when you're sure the dust has settled. Anyone that really needs the latest updates can pull the unstable branch at their own risk, but everyone else doesn't have their build broken every other day. This is a really serious problem in the JavaScript ecosystem right now (even disregarding the left-pad debacle).
Get over it? It's a number. If you want to base it off a consistent semantic reasoning then do so. Otherwise don't do it at all.
From your point of view, the problem is something inherent to semantic versioning. From my point of view, the problem is inherent to your behaviour – if you really want to do “several breaking releases one after another”, why hide it from users?
Well how about stopping to think about your API instead of firing off several breaking changes in short succession?
The problem here is lack of commitment to a stable API, not the versioning scheme.
In case of Firefox every seventh (v%7=3) receives extended support releases.
Just imagine the headache if Angular had bumped the most significant number for every change pre-Angular2. All the we difference between 1.x and 2.x would be perfectly obscured.
Like IntelliJ IDEA? The version I'm running right now is 2016.3.1
I think the attachment around "saving" major releases are really just attachments to marketing messages of yesteryear. It still feels good to announce "Library Version 3!", but from a technical perspective, semver is far more consumable and sensible. I'd rather be confident Lib v27.3.0 -> v27.4.3 won't break my build than having v2.9.0 -> v2.10.11 break my build, but look nicer.
I'd like to provide some examples but I can't find my calculator :(
Or avoid it entirely and use a 4 digit year.