I don't understand why -- and do educate me on why I'm wrong -- software don't just use an internal and external version number. Version numbers may have started as a technical label with precise meanings, but they've been a part of branding and marketing for a while now. It's futile to fight it[0].
Let the former be a monotonically increasing (say 64-bt) integer, and the latter be a free-form string. This way, marketing folks are free to call one version "macOS 10.16 Big Sur", followed by "macOS 11.0 Big Sur Pro Max", "macOS 10.32 Pro SE", etc. w/o developers pondering over whether "Pro SE" > "Pro Max". As for API, maybe something like getProductVersion() for the internal version, and getProductName() for the external version. Heck, be facetious and let the latter return a string like "!!! DO NOT USE FOR VERSION COMPARISON USE getProductVersion() INSTEAD !!!\07\07\07macOS 10.16 Big Sur".
Yes, lazy developers will get it wrong, similar to how they use gettimeofday()[1] instead of clock_gettime(CLOCK_MONOTONIC, ...)[2]. In their defense, often software switch between versioning conventions such that what's the "right" thing to do is unclear.
[0] Such appropriation is everywhere. Don't get me started on how Porsche Taycan, an electric car, has a "Turbo" model. It probably doesn't even come with blinker fluid standard.
CFBundleVersion: The version of the build that identifies an iteration of the bundle. This key is a machine-readable string composed of one to three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods.
CFBundleShortVersionString: The release or version number of the bundle. This key is a user-visible string for the version of the bundle. The required format is three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods.
Android similarly has versionName and versionCode.
The problem is that no matter what, someone somewhere is going to parse something that was never intended for non-human consumption. Or code ends up making assumptions that it shouldn't about a machine-readable version.
I have code which parses CFBundleVersion. It assumes the version follows Apple's docs. But guess what? CFBundleVersion is just a string and nothing enforces Apple's guidance so I was surprised (I shouldn't have been) to have to deal with an internally distributed iOS app that had the word "debug" tacked on to the end of its CFBundleVersion.
Android's "versionCode" is an ever-increasing number, you must submit a higher versionCode than your previous build.
CFBundleVersion can be set back to 0 with each CFBundleShortVersionString change.
Normally I just set both to a build number on whatever CI I use (for something like a cordova cross-platform app) and then increase CFBundleShortVersionString & versionName using the tag name (or a custom version name I give a release).
CFBundleShortVersionString must increment too though. So if you join “${CFBundleShortVersionString}.${CFBundleVersion}” the whole thing must increment, and this explains why CFBundleVersion can revert when CFBundleShortVersionString increments.
Thanks for the references. I think Android's versionName / versionCode are closest to what I have in mind. The problem IMHO with CFBundleVersion, CFBundleShortVersionString, et al. is that they need to be parsed before making comparisons, and this is where developers make mistakes or take shortcuts. Whereas with plain integers, direct comparisons are simple and usually correct as well. Take the story in question: I can imagine being lazy and skip over the major version part of the version string. But with integers, I wouldn't go out of the way to mask out the MSBs, no?
Guess what happens with something as simple as versionCode?
Hacks like this, to allow a single "version" of an app to have multiple APKs, but wait, versionCode has to be unique. I know, we'll do this:
> In the following example, the APK for the x86 ABI would get a versionCode of 2004 and the x86_64 ABI would get 3004. Assigning version codes in large increments, such as 1000, allows you to later assign unique version codes if you need to update your app. For example, if defaultConfig.versionCode iterates to 5 in a subsequent update, Gradle would assign a versionCode of 2005 to the x86 APK and 3005 to the x86_64 APK.
Sigh. Nothing is ever easy. We make assumptions when we design our systems without realizing it and then paint ourselves into these silly corners to not break things.
The format is really [major version][train][audience]?[build number][mastering version]?, where major version is numbers, train is a capital letter, audience is a digit, build number is numbers, and mastering version is a lowercase letter (and things with question marks are optional, and of course Apple does its own thing from time to time). So the current beta for Big Sur, which is 20A4300b, has
20: Major version (happens to match the XNU kernel version)
A: Train (First “point” release, although that is not how marketing does it)
4: Audience (Developer seed)
300: Build number (300 builds since the train started)
b: Mastering version (forked from mainline two builds ago for polishing)
It is how you want it to be. The marketing name is Big Sur. The version is 11.0.0.
There is meaning to the separate numbers in the version, there’s a guarantee they aren’t going to remove api if the third number increases but they might if the second one increases. The first one only increases with truly big changes such as this move to ARM.
Using an integer doesn’t work because point releases where the third number increases are not monotonous, 10.15.20 might be released while 11.0.0 is already out. Apple does maintain the previous release with security updates while there already is a newer current release.
Note that Windows is a big mess with Windows 10 build 2004 (because they want to avoid confusion with Server 2003) which is version 19041.329, or perhaps 10.0.19041.329. It’s almost the same scheme but with large numbers you can’t remember and are hard to find in the documentation.
At a previous company, for a very complicated web app with some offline caching aspects, we used the latest git commit hash of each build as a public-facing unique version id in the help menu. This made it dead simple to use proper semver versioning internally without needing to correct user expectations about version numbers, while still having easily tracked unique IDs in bug reports.
This is somewhat true, but Windows is a perfect example of this not working. There was no Windows 9, because of fear of people using the Windows string name to detect Windows 95/98 (or at least that is the prominent theory).
This is also why the Windows 10 May 2020 update is v2004 instead of v2003 to avoid issues with old software thinking it is running on Windows Server 2003. [1]
My manager at work came across this version number on a document properties screen (possibly in Word?), thinking it referred to the version of software the document was written in. We bullshitted a bit about how we're still iterating on 16-yo documents, until I googled and found that it was a new MS version.
(I often find that a document I am editing at work was created by "Valued Gateway 2000 Customer")
When safari 2.0.4 was first released huge numbers of websites broke simply because they detected Netscape 4 by doing navigator.useragent.indexif(“4”)>0
Never underestimate the ability of developer hacks to screw things for years.
Many releases after had a lot of paranoia about including the character 4 in the version number
I thought it was because they wanted to go to 10, like Mac OS X. Same as the Xbox 360 was called that because they didn’t want to bring out the Xbox 2 when Sony was bringing out the PS3.
Some software used to check that the version name string started with "Windows 9" to determine if it was running on a "Windows 98" or "Windows 95" system as opposed to 3.x, NT, XP, etc. Yes, there are internal major and minor version numbers available, however that didn't stop people. Ergo, jumping straight to "Windows 10" which would cause software that did this to find "Windows 1" in the string triggering a message like "This program requires at least Windows 95..." or whatever instead of immediately crashing because it made a system call to an API that hasn't existed for 10 years. Or worse, silently corrupting data because of other OS API changes.
Windows doesn't provide API to get OS product name IIRC but Java provides "os.name" property that returns like "Windows 95". That's bad api to check version but some Java apps use it.
That's because developers have written and still write incorrect software that relies on the external version number. It's also the reason Windows lies about its build number unless you add magic (read: not deterministic) GUIDs to your apps' manifest. Raymond Chen's blog[0] has tons of stories about this kind of bugs.
I think Apple's "marketing folks" already do a reasonable job of this with macos - most non-dev people and most (all?) of Apple's consumer facing marketing refer to the OS as "Catalina" or "High Sierra" or "Big Sur". Outside of the sort of people who visit HN, I wonder how many Mac users could tell you what the version number "High Sierra" was?
The problem with this is most tech people and mere mortals alike can’t keep the sequence straight. I gave up on trying to even map names to versions and just go to “about” every week or three and provide whichever detail is most useful.
While I generally agree with your comment, I also find it ironic that one of the POSIX APIs you chose to use as an example of API misusage (clock_gettime) is ... not available on macOS.
It's been added since macOS 10.12 (Sierra). According to my man page. Couldn't find any authoritative sources online on that, though. However, there are several secondary references[0][1].
A couple of decades back, our product was always internally <codename>.<buildnumber>, where parallel codenames would exist when multiple versions were supported or in development (think "1.x" and "2.x").
What was displayed to the user was hex-edited into the CD image after it passed QA, according to the whims of marketing, and we treated it as pure text.
You got the answer right. Software typically does work this way but then people inevitably make the mistake of using the wrong version and then hacks are needed to not break those apps.
Let the former be a monotonically increasing (say 64-bt) integer, and the latter be a free-form string. This way, marketing folks are free to call one version "macOS 10.16 Big Sur", followed by "macOS 11.0 Big Sur Pro Max", "macOS 10.32 Pro SE", etc. w/o developers pondering over whether "Pro SE" > "Pro Max". As for API, maybe something like getProductVersion() for the internal version, and getProductName() for the external version. Heck, be facetious and let the latter return a string like "!!! DO NOT USE FOR VERSION COMPARISON USE getProductVersion() INSTEAD !!!\07\07\07macOS 10.16 Big Sur".
Yes, lazy developers will get it wrong, similar to how they use gettimeofday()[1] instead of clock_gettime(CLOCK_MONOTONIC, ...)[2]. In their defense, often software switch between versioning conventions such that what's the "right" thing to do is unclear.
[0] Such appropriation is everywhere. Don't get me started on how Porsche Taycan, an electric car, has a "Turbo" model. It probably doesn't even come with blinker fluid standard.
[1] https://pubs.opengroup.org/onlinepubs/9699919799/functions/g...
[2] https://pubs.opengroup.org/onlinepubs/9699919799/functions/c...