Project:Perl/Version-Scheme

From Gentoo Wiki
Jump to: navigation, search

The Gentoo Perl Versioning Scheme

A common observation/confusion people have is that the versions used on Perl related things in Gentoo don't directly correspond to upstream versions.

This is because Upstream uses 2 different schemes, and one of those schemes is fundamentally incompatible with Gentoos.

The Problem

In most people's mind, this is how version numbers sort:

1.0
1.1
1.5
1.10
1.15
1.20
1.45

This is because you read . as a delimiter for multiple integers.

However, in Perl, those same versions sort as follows:

1.0
1.1  1.10
1.15
1.20
1.45
1.5

Because these versions are all treated as floating point, and yes, 1.1 and 1.10 are the same version.

perl -E 'say version->parse( q[1.1] ) <=> version->parse( q[1.10] )' 
# 0

To make matters interesting, a leading v on a Perl version, or the presence of 2 or more .'s imply a sort order closer to Gentoos. And so, the net result is this mess:

perl -E'say for sort map { version->parse($_) } qw(  1.0 1.10 1.1 1.10 1.15 1.20 1.45 v1.2 v1.3 v1.4 v1.1 v1.100 )'
# 1.0
# v1.1
# v1.2
# v1.3
# v1.4
# 1.10
# 1.1
# 1.10
# v1.100
# 1.15
# 1.20
# 1.45

Yes, if you realised v1.100 == 1.1 == 1.10 but v1.1 != 1.1 you were paying enough attention.

Confused? Good. So is everyone else who touches Perl versions, and the whole host of related historical nightmares

Our Solution

The solution we employ is to decode upstreams version scheme and translate it into a consistent scheme that, after being translated, preserves upstreams sorting order when translated to gentoo versions, and we use this practically everywhere:

  • On Package Versions
  • In dependency specifications

This means as long as you're familiar with the translation process, you can get highly predictable results instead of a random fractal of confusion.

Gentoo::PerlMod::Version is a reference implementation of this translation, and it can be used effectively blindly to translate upstream versions to downstream versions.

And we then hard-code the "real" upstream version inside the package (because its impossible to convert Gentoo versions generically to upstream ones, because you don't know which of the many formats it will take)

Conversion of v-Versions

Upstream does have some version schemes that we can use verbatim:

  • Any version with more than one . can be treated as an integer sequence like Gentoo Versions
  • Any version with a leading v can be treated an integer sequence like Gentoo Versions (after stripping the leading v)

Conversion of "Floating" versions

First, to understand how this works, you have to first understand how "v-Versions" are converted and compared with "Floating" version upstream:

  • Each digit group after the first is interpreted as 3 digits in the range 000-999
  • The values are concatenated to create the tail

So, for example, upstream: 1.1.2 means 1.001002, 1.20.30 means 1.020030, etc.

Our conversion to Gentoo versions basically applies the opposite of this, to convert a "Floating" version into a "v-Version" (without the "v")

  1. Take the floating point number
  2. Pad its tail to a multiple of 3 digits with trailing zeros
  3. Split the tail into groups of 3 digits
  4. Strip any leading 0's from each group
  5. Ensure there is a minimum of 3 groups of digits (with each at having at least one digit per group)

So for example:

1.1    ->   1.100 -> 1.100.0 
1.001  ->   1.1.0
1.0001 ->   1.000100 -> 1.000.100 -> 1.0.100

And you can find a large table of other examples in the tests for Gentoo::PerlMod::Version

The End Result

You can thus, pump Gentoo versions for Perl packages into Perl's version comparison, and it will still treat the normalized verions the same as the pre-normalized ones.

perl -MGentoo::PerlMod::Version=gentooize_version -E'say gentooize_version($_) for sort map { version->parse($_) } qw( 1.0 1.10 1.1 1.10 1.15 1.20 1.45 v1.2 v1.3 v1.4 v1.100 )'
# 1.0.0
# 1.2.0
# 1.3.0
# 1.4.0
# 1.100.0
# 1.100.0
# 1.100.0
# 1.100.0
# 1.150.0
# 1.200.0
# 1.450.0

perl -E'say q[GOOD] if version->parse(q[1.0001]) == version->parse(q[1.0.100]) ' # GOOD

Competing Normalisation Schemes

Some competing normalisations have issues in that they don't retain their comparison positions when fed back into Perl comparison functions, and are thus simply less desirable because it requires much more effort to reason about how they work.

They can also risk colliding with alternative existing forms upstream so the risk would occur that upstream could legally ship a new version such that a new version and an old version normalised to the same version gentoo side, and this is something we wish to avoid.

(This problem has already happened, but it happened in a way that was illegal on CPAN as well, because our version scheme ensures that its only illegal when its illegal on CPAN)

eg:

# 2.02 -> 2.1002  
perl -E'say q[BAD] unless version->parse(q[2.02]) == version->parse(q[2.1002]) '  # BAD

# 2.02 -> 2.0.2.0
perl -E'say q[BAD] unless version->parse(q[2.02]) == version->parse(q[2.0.2.0]) ' # BAD

Resources