Last week I mentioned how I had uploaded a new version of Symbol::Approx::Sub. Because there were pretty major changes to the inner workings of the module (although the interface still looked the same) I decided that I would move it from version 2.07 to version 3. At the same time, I decided that I would switch to a semantic versioning scheme.
Later in the week, I released minor updates to a few more of my modules. And I decided to apply semantic versioning to those as well. But as I was only making minor packaging fixes to these modules, I didn’t increment the major version number. For example, Array::Compare went from 12.2 to 12.2.1.
It turns out that was a mistake.
Well, I don’t really think it was a mistake. I think it was the right thing to do. But it appears that my opinion is at odds with what some parts of the Perl toolchain think.
Last night I got this bug report. It seems that by switching to three-part semantic versions, the version number can (in some quite common circumstances) appear to decrease.
To my mind, a version number is a dot-separated sequence of numbers. So 12.2 is smaller than 12.2.1. Any sane version number comparison will separate the two strings on dots and compare the individual components. Any missing components (12.2 is, for example, one component shorter than 12.2.1) should be assumed to be zero.
But that’s not what the Perl toolchain does. Observe:
1 2 3 4 5 6 |
$ perl -Mversion -E"say version->parse('2.12')->normal" v2.120.0 $ perl -Mversion -E"say version->parse('2.12.1')->normal" v2.12.1 $ perl -Mversion -E"say version->parse('2.12') <=> version->parse('2.12.1')" 1 |
When the version number with two components (2.12) is split into components, the second component is bizarrely treated as a three-digit number so it becomes 120 instead of 12 and when it is compared with the second component of the three-component version, 120 is obviously larger than 12 and any tool which relies on this behaviour to work out which version of a module is the most recent will get the wrong answer.
This leads to other “interesting” effects. In my head, versions 1.1, 1.01 and 1.001 are all the same version. The leading zeroes mean nothing. But under this scheme, they are very different version numbers.
I know that versioning isn’t as easy as it should be and I know that some people use bizarre versioning systems. And I’m pretty sure that no matter how bizarre a versioning system is, you’ll almost certainly find an example of it on CPAN. So I suppose that this behaviour was a “least worse” scenario that was chosen to make the most sense given CPAN’s wide range of versioning schemes.
Personally, I see it as a bug in version.pm. But I’m not going to report it as such as I’m sure the Perl toolchain gang know what they’re doing and have very good reasons for adopting this seemingly broken behaviour.
I just need to remember to be more careful when switching my modules to semantic versioning. Using a minor or patch level version change when switching to semantic versioning is likely to lead to confusion and bug reports. Only a major level change (as I did with Symbol::Approx::Sub) is guaranteed to work.
And, I suppose, I’ll need to release Array::Compare 3.0.0 to CPAN pretty soon.
As you suspect, there is a good reason for this behavior in version.pm. Classically, perl version numbers were just decimal numbers. The translation between dotted-integer versions and decimal versions was established at least as far back as perl 3, with the $] variable. $] still uses this numeric form, with perl 5.22.1 represented in $] as 5.022001. $VERSION followed along in this, initially just being used for numeric comparisons. version.pm and proper conversions between the two forms didn’t come along until perl 5.10. Having version.pm (and perl core’s version comparisons) treat the existing numeric forms as dotted-integers would have broken modules, so they had to continue being treated as decimal numbers.
Without the second dot, version.pm assumes it is using a decimal form version. Using a leading v (“v2.12”) will allow specifying a dotted-integer style version without needing a second dot.
For my own code, I generally try to follow semver-style major, minor, and patch versions, but formatting them as perl decimal style versions. So Moo’s version is 2.003000, representing v2.3.0. This is because various parts of the toolchain have treated dotted-integer style versions badly, especially in older versions, and I’m usually writing modules with extensive backwards compatibility in mind.
I use the same version number scheme for my modules, mainly darkpan, too and only had one issue with it:
for example 2.003000 isn’t picked up by CPANPLUS if 2.002003 exists because of the order of filenames in package.txt in my CPANPLUS custom-source directory.
Sorry Dave, but I have to disagree with you here. You refused (for whatever reason) to go from 2.12 to 2.13. Doing so would make life so easy. That was the 2nd mistake. Your 1st 🙂 was to forget the complexity of Perl’s version numbering mess, err, methodology. Give your vast knowledge of Perl you /must/ have known about the ‘odd’ interpretation of the middle value of the 3 part system. And changing mid-stream from a 2-part to a 3-part system? Oh, dear.
And another problem. I clicked g+ but gave up waiting for a response. And I /know/ I’m logged into g+ because I logged in just 5 mins ago. Another mystery.
Cheers
Ron.
Well, as I said, I wanted to switch to semantic versioning. And going from 2.12 (which I, wrongly, assumed would be treated as 2.12.0) to 2.12.1 correctly reflected the level of change in the release.
And going from 2.12 to 2.13.0 wouldn’t have worked either as 2.12 is interpreted as 2.120 and 2.13.0 is interpreted as 2.013000.
Saying that I “/must/ have known” this doesn’t really help. Clearly I didn’t, or I wouldn’t have written this post. I’ve never changed version number schemes before, so I’ve never needed to know this.
I’ll have a look at your G+ issues.
As a second comment, I believe the proper way to change versioning schemes mid-stream, when not using a new major, is just to read the old 2.12 as if it had been 2.120, and then the next “minor” version would be 2.121000; I have done this myself.
But I didn’t want to increment the minor version, I wanted to increment the patch version. So that would have been 2.120001 (or 2.120.1). But that “120” offends my sense of aesthetics. 120 is far too large for a version number!
No, I’m pretty sure that 2.12.1 was the version number that I wanted. But because of how Perl interprets version numbers (which I still don’t agree with) I was forced to go to 3.0.0.
I would argue that 120 isn’t too large necessarily. What that means is you managed to have 120 releases without backwards-incompatible changes, which can be done. But the only time I would argue for that is that SEMANTICS are correct relative to the times when you weren’t using semantic versioning. This all being said, I fully agree that simply going to version 3.0.0 was the best course of action here. See, I believe that one should be allowed to increase the major for marketing reasons even if semantics wouldn’t require it. The only time I see the major being required is if a backwards-incompatible change was made. But the 3.0.0 is a clean break, and nothing wrong with it.
I did say that my objections were aesthetic 🙂
But it’s not what I’ve done. I’ve had twelve releases without backwards-incompatible changes. So 2.120.1 is a lie.
And (putting aside my aesthetic objections) if 120 isn’t too large for a version, why draw the line at 999? Surely it’s possible to consider a version number like 2.1234.5678. The three-digit constrain imposed by Perl can’t support this.
But this is all academic. I’m unlikely to go to version numbers that large. And I have a scheme that works within the constraints of the Perl versioning system. So everyone is happy. I think.
Dave Cross: For those wanting to do semantic versioning, which includes me, I have found from experience the best way in Perl 5 is strictly within the classic scheme, for example write 12.002001 everywhere, period. Do NOT use version.pm or any other added complexity, at all; plain Perl should be all that is used for this. The semantics are more important than the syntax, just look at X.YYYZZZ as being the Perl 5 syntax. Also, the legacy scheme does leave room to add extra positions, say X.YYYZZZ_W for example if one wants to indicate a non-production version without messing up your numbering scheme.
I didn’t use version.pm. Or, at least, not explicitly. I assume that’s what Perl is using internally to deal with semantic version numbers.
12.002001 is not (as far as I’m concerned) a semantic version number. Oh, sure you can say that there’s a semantic version number hidden within it. But part of the point of semantic versioning is to make the level of changes as clear as possible. 12.2.1 does that, 12.002001 doesn’t.
I would say that 12.002001 is as valid a semantic version number as 20161215 is valid as a date. Sure, there are no separator characters, but when fields are fixed width and there are leading zeroes its not too hard to follow. The key thing is that the correct SEMANTICS are preserved. Perl 5 can’t do X.Y.Z properly due to legacy, so we accept that and do the next best thing under the circumstances. Here’s also an opinion question for you, does it have to be periods? Would “12,2,1” be just as reasonable of a semantic version as “12.2.1”? They mean the same, a sequence of integers where each left-side integer is a namespace for interpreting the next one to its right, but the format is like what is more common for specifying a list, such as in Perl would be “[12,2,1]”.
The definition of semantic versioning is very clear on this point:
So, yes. It does have to be periods.
I should clarify that my proposal is strictly concerning the $VERSION that Perl 5 sees and any Perl code that does direct comparisons with it. In contrast, the version “12.002001” that is the declared $VERSION can reasonably be derived to the form “12.2.1” externally where packagers such as RHEL or whatever use. But the conversion should be at the fringes, while the Perl 5 native form internally would be the simple rational number that doesn’t require version.pm or anything complicated to understand for Perl 5’s internal logic that simply compares less than or greater than.
I’m curious about the bit where you said “In my head, versions 1.1, 1.01 and 1.001 are all the same version”. Really? But as floating point numbers, those are different, so I’m wondering what thought process led to this? I’m guessing it was something along the lines of “leading zeroes before integers don’t count, and dotted triples aren’t floating point numbers, so let’s say that each number in the triple is an independent integer, so zeroes don’t count there. Roughly right?
Yeah. That’s exactly right. I see version numbers as a dot-separated sequence of integers. So leading zeroes are meaningless.
(Sorry for the delay in replying – I was on holiday and then my server crashed!)