Version numbers should be boring

Reading time: 14 minutes

In a perfect world, I would never blog about version numbers in Perl.

Version numbers should be boring. No one should have to think about how they specify or check a version number. Perl programming makes things easy, not hard, right?

Unfortunately, version numbers in Perl aren’t boring and easy. Instead, they are complicated and confusing. Every Perl programmer needs to understand at least some of this complexity. Otherwise, you can make life difficult for yourself or others without realizing it.

In this article, I’m going to explain what I think are ‘good practices’ for dealing with version numbers in Perl. I’m going to point out what I think are ‘bad practices’. ((N.B., the Perl Best Practices book is wrong)) In the end, I’m going to make some recommendations that I hope people will follow for their own sake and the sake of others who work with their code.

For the impatient, the disinterested or those who just want to follow a recipe, my advice for all modules is this:

our $VERSION = "0.001"; # or "0.001_001" for a dev release
$VERSION = eval $VERSION;

If you already use decimal versions that aren’t in the 3-digit decimal format, that’s fine, too. It’s not worth switching for code you’ve already published.

If you disagree with me or would like to see alternatives or want to know the ugly details behind this recommendation, then the rest of this article is for you.

Can we skip the history lesson? 🔗︎

It will be helpful to review at least a little history, then we can ignore how we got here and just focus on what to do. There are three big historical accidents worth noting: ((They were actually intentional, but had unforeseen consequences))

Accident #1: Perl expects a module to put its version number in a package variable called $VERSION

The first accident means that a module version number can be anything that can be represented by a Perl scalar: a number, a string, an object, etc. There are no constraints, only conventions. This is a blessing and a curse; it’s the main reason why version numbers in Perl aren’t boring.

Accident #2: The Perl 5.6 interpreter changed from a ‘decimal’ version number (5.005_03) to a ‘dotted-integer’ version number (5.6.0)

The second accident got people thinking that ‘version numbers’ for modules shouldn’t be decimals either. It also introduced a three-digit convention for translating between the two approaches: 5.6.2 is equivalent to 5.006002 and 5.005_03 is equivalent to 5.5.30. Finally, it introduced new syntax for ‘v-string’ literals of the form ‘v1.2.3.4’ to help represent dotted-integer version numbers.

A third historical accident was actually an attempt to fix the second:

Accident #3: The Perl 5.10 interpreter made $^V a version object instead of a v-string

Version objects are an attempt to overcome numerous limitations and challenges using v-strings for module version numbers. The UNIVERSAL::VERSION() method also changed, to better accomodate version objects and v-strings.

Here is a very contrived example of the change to VERSION(), where 120, 100, 103 are just the ASCII codes of the letters of my IRC nickname:

$ perl5.8.8 -e '$VERSION=v120.100.103; print main->VERSION'
xdg

$ perl5.10.0 -e '$VERSION=v120.100.103; print main->VERSION'
v120.100.103

The version.pm module provides an interface to version objects and, for older versions of Perl, overrides UNIVERSAL::VERSION() globally to match the behavior of Perl 5.10. The version.pm module is one more thing that keeps version numbers from being as boring as I’d like.

$ perl5.8.8 -Mversion -e '$VERSION=v120.100.103; print main->VERSION'
v120.100.103

Think about the implications of that for a module called ‘Foo’. Even if Foo doesn’t use version.pm, if version.pm is loaded anywhere then Foo->VERSION acts differently and gives a different value than is in $Foo::VERSION.

Version numbers are for machines, not people 🔗︎

For the most part, version numbers are used to answer just two questions:

  • What version of Module X does Module Y require in order to work properly?
  • Is there a more up-to-date release of Module Z available?

Neither question is one that we, as people, particularly want to waste our time on if a computer can answer the questions for us. That means that version numbers need to be specified in a way that makes it easy for a computer to answer these questions.

Recommendation: Never use alphabetical characters in a version

Don’t use ‘1.20alpha’ or ‘2.34beta2’ or ‘2.00R3’. There are no standard conventions for alphanumerics, and you just make life hard for the machines, which means no help for the humans.

Decimals and dotted-integers 🔗︎

After following the first recommendation and excluding alphabetical characters for module versions, we are left with two numeric conventions, just like Perl itself:

  • decimal numbers
  • dotted-integers (also called dotted-decimals or tuples)

A decimal version is just what it seems: an ordinary number that is either an integer (12) or decimal fraction (12.345). Decimal versions are compared in the ordinary, numerical way.

Dotted-integer versions are vectors of (positive) integers, so called because they are usually shown separated by full-stop characters (12.3.45). Dotted-integer versions are compared by pairwise numerical comparison of the first element of each version, then the second, the third, and so on.

There are two ways to represent dotted-integer versions in Perl:

  • v-strings
  • version objects

I already described v-strings, but you can read more about them in the perldata documentation page. Despite what it says, v-strings are not deprecated. (This warning will be removed in a future version of Perl.)

Version objects are created using the version.pm module. There are several ways to do this. Here is one example (and it must be kept on one line):

use version; our $VERSION = version->new("v1.2.3");

The version.pm documentation has been updated in 0.77 to better explain the options and potential pitfalls creating version objects, so I won’t repeat them here.

John Peacock, the author of version.pm, recently suggested that it is probably enough to use version.pm and give a quoted $VERSION string without the need to explictly construct an object, as UNIVERSAL::VERSION will convert it to a version object internally anyway. ((The ‘use version;’ part isn’t even required if the code is limited to Perl 5.10 or later.))

use version; our $VERSION = "v1.2.3";

This approach is new and untested, but may eventually be regarded as the best way to use version.pm.

Three-digit convention 🔗︎

Perl decided upon on a three-digit convention to convert between the older decimal style and the newer dotted-integer style so that older Perls could give a useful error message when checking the Perl version number.

# on Perls before 5.6.0
use 5.6.2; # syntax error
use 5.006002; # Perl version error

The three-digit convention takes each dotted-integer component after the first, pads them with leading zeroes if less than 3 digits, and concatenates them together. To go the other way, the fraction portion of a decimal version is padded with trailing zeroes until the number of characters is a multiple of three, then each group of three digits is turned into an integer.

This convention has a subtle complexity: note that the conversion does not round-trip if a dotted-integer element is greater than 999. This might happen if a version number were constructed from a VCS revision number or a timestamp.

v1.2.34567 -> 1.00234567
1.00234567 -> v1.2.345.670

The introduction of the three-digit convention confused people in two ways: how to convert modules from decimal versions to dotted-integers and how to specify version number limits to use().

Many CPAN modules don’t use three-digit decimals, partly because most module boilerplate tools create modules with a two-digit decimal starting version:

$VERSION = "0.01";

These modules usually increment their versions as 0.02, 0.03 and so on. Some authors have been surprised trying to convert their decimal versions into dotted-integers and running afoul of the three-digit convention. In the following example, a naive conversion results in a number that is less than the previous one:

0.01
0.02
0.03
v0.4.0 # WRONG: this is 0.004
v0.40.0 # RIGHT: this is 0.040

The other area of confusion is providing a version number requirement to the use() keyword. With the introduction of v-strings, but prior to Perl 5.10.0, use() internally converts “use Foo v1.2.3” to “use Foo 1.002003”. So this works as expected:

# in Foo.pm
our $VERSION = "0.001002";

# in foo.pl
use Foo v0.1.2; # WORKS

However, since Perl recommends specifying “use v5.6.0” as “use 5.006”, some people think the same should apply to loading modules with use(). But, prior to Perl 5.10.0 (and the change to UNIVERSAL::VERSION), the inverse case might not work at all! Consider this example:

# in Foo.pm
our $VERSION = v0.1.2;

# in foo.pl
use Foo 0.001002;

On a Perl compiled with support for long doubles, the extra precision in converting with the three-digit convention causes the comparison to fail with this incredibly confusing error message:

$ perl-5.8.9-64bit -e 'use Foo 0.001002'
Foo version 0.001002 required--this is only version 0.001002

Recommendation: always use() in the same form as $VERSION in a module

When $VERSION is a v-string, it should only be requested as a v-string (“use Foo v0.1.2”), except on Perl 5.10 or when using version.pm. There is a corollary:

Recommendation: don’t switch version number schemes for a published module

If you do switch, then users won’t know in advance the right format to request.

Distribution version numbers 🔗︎

Distributions on CPAN also have version numbers. These are specified as part of the filename. (See a prior article for a formal definition of modules and distributions.)

DAGOLDEN/File-Marker-0.13.tar.gz

Recommendation: Set the distribution version from a module

Usually, distribution versions are set automatically from a primary module within the distribution. This is good – we let the machine take care of it for us. It’s one less thing for us to do, and helps to ensure machine-friendly distribution versions. That’s good for binary packages like .rpm and .deb.

For example, using Module::Build, the ‘module_name’ parameter in Build.PL specifies the name of a module to examine for the distribution name and version.

use Module::Build;
Module::Build->new(
 module_name => 'Foo::Bar',
 license => 'perl',
)->create_build_script;

Or, using Module::Install, you can do the same thing with a Makefile.PL like this:

use inc::Module::Install;

name 'Foo-Bar';
all_from 'lib/Foo/Bar.pm';

WriteAll;

The underscore convention 🔗︎

It has been a long-standing CPAN convention that distribution version numbers containing an underscore are ‘development’ or ‘alpha’ versions, and the corresponding distribution files do not get indexed by PAUSE as a ‘release’ version.

DAGOLDEN/Test-Reporter-1.53_03.tar.gz

The three main build tools, ExtUtils::MakeMaker, Module::Build and

Module::Install will all attempt to parse a version number for a

distribution from a primary module file using the MM->parse_version() function provided by ExtUtils::MakeMaker. The parse_version() method looks for the first line in the file that appears to set $VERSION, and then calls eval() on that entire line.

The following sections show how to specify an alpha version in each of the three version number styles and some things to consider for each.

Decimal alpha version 🔗︎

our $VERSION = "0.001_001";
$VERSION = eval $VERSION;

For a decimal alpha version, the definition is split into two parts. The

first part provides the version in quotes, which is what gets returned by

MM->parse_version(). It has to be in quotes so that the underscore is preserved in the eval() call within parse_version(). Without quotes, parse_version() returns it as an ordinary decimal.

our $VERSION = 0.001_001; # WRONG: parse_version() gives 0.001001

The second line is required to make $VERSION an ordinary number at runtime. Without it, Perl would convert $VERSION to a number by truncating at the underscore, resulting in the wrong version number. ((Perl converts strings to numbers differently at runtime than how it parses numeric literals during compilation.))

Dotted-integer alpha version with v-strings 🔗︎

our $VERSION = v0.1_1;

For some versions of Perl, it’s possible to specify an ‘alpha v-string’, with a final decimal point replaced with an underscore. ((This is an undocumented syntax feature/bug)) Consider a ‘Foo module with a $VERSION line like the one above. Here’s how different versions of Perl handle a request for a higher version number:

$ perl5.10.0 -e 'use Foo v0.1.2'
Foo version v0.1.2 required--this is only version v0.1_1 at -e line 1.
BEGIN failed--compilation aborted at -e line 1.

$ perl5.8.9 -e 'use Foo v0.1.2'

$ perl5.8.0 -e 'use Foo v0.1.2'
Foo v0.1.2 required--this is only v0.1.1 at -e line 1.
BEGIN failed--compilation aborted at -e line 1.

$ perl5.6.2 -e 'use Foo v0.1.2'

Note how Perl 5.8.9 and Perl 5.6.2 both succeed, even though a higher version is requested. One potential solution is to require the version.pm module. This ‘fixes’ 5.8.9, but fails in a different way for 5.6.2.

perl5.8.9 -Mversion -e 'use Foo v0.1.2'
Foo version v0.1.2 required--this is only version v0.1_1 at -e line 1.
BEGIN failed--compilation aborted at -e line 1.

perl5.6.2 -Mversion -e 'use Foo v0.1.2'
Foo version v0.1.2 required--this is only version v0.0.0 at -e line 1.
BEGIN failed--compilation aborted at -e line 1.

Dotted-integer alpha version with version objects 🔗︎

use version; our $VERSION = version->new("v0.1_1");

This form is visually similar to an alpha v-string, but by putting it in quotes and by passing it the version object constructor, the version is protected from idiosyncracies of different versions of the Perl interpreter.

On the surface, it seems like this is a good approach, but it still has problems if one tries to use() the same version on 5.8.0 and 5.6.2.

$ perl5.8.0 -e 'use Foo v0.1_1'
Foo version v0.1.1 required--this is only version v0.1_1 at -e line 1.
BEGIN failed--compilation aborted at -e line 1.

$ perl5.6.2 -e 'use Foo v0.1_1'
Foo version 0.011 required--this is only version v0.1_1 at -e line 1.
BEGIN failed--compilation aborted at -e line 1.

Requiring an alpha version as a decimal 🔗︎

The counter-examples in the previous sections assume a situation where an alpha version of a module is a prerequisite and show how an alpha v-string argument to use() means different things depending on the version of Perl and whether version.pm has been loaded.

The other alternative is to specify the version as a decimal.

use Foo 0.001_001;

While this is in the form of a decimal alpha version, the Perl parser sees this numeric literal as 0.001001. This works perfectly with the decimal alpha version example above that has the “$VERSION = eval $VERSION” line. Both are just numbers and they compare as equal.

That isn’t the case for version objects. An alpha version object is not equal to a non-alpha version object, even if their numeric components are the same. If Foo has this version line:

use version; our $VERSION = version->new("v0.1_1");

then “use Foo 0.001_001” fails, even on a recent Perl.

$ perl5.10.0 -e 'use Foo 0.001_001'
Foo version 0.001001 required--this is only version v0.1_1

Recommendation: Don’t use v-strings or version objects as alpha versions

There are just too many ways for alpha v-strings and version objects to be used incorrectly. Even if requiring Perl 5.10 or with version.pm, which a module author can control, if a user follows the three-digit convention and uses a decimal version in the call to use(), it can fail.

Other issues in the toolchain 🔗︎

There are several other ways in which version numbers refuse to be boring. Each could be an entire mini-essay, so I will only highlight a few of the issues I’ve found:

  • Module::Build can’t handle v-string prerequisites unless they are quoted
  • ExtUtils::MakeMaker and Module::Install have trouble with v-string module versions and prerequisites, even if quoted or given as version objects
  • In at least one recent bug I’ve studied, an XS module failed to load with a version object created using the form “1.2.3” (without a leading-v)

Conclusion 🔗︎

I think version numbers should be boring. I don’t want to have to think about how to write them and I don’t want to make users think about how to format a version prerequisite. And I don’t want to get bug reports about it when a user gets it wrong.

For me, a ‘boring’ version number must:

  • be expressed the same way in its definition, in use() checks, in prerequisites, in *.PL files and in META.yml
  • support the underscore convention in the same format
  • have similar behavior across different Perl versions
  • have similar behavior in use() when converted from X.YYYZZZ to vX.Y.Z format or vice-versa ((Remember that users might write a dotted-integer as a decimal, just like they are taught for Perl itself.))

Given these criteria, my recommendation is to use decimal version numbers, put them in quotes, and add a string eval:

our $VERSION = "0.001";
$VERSION = eval $VERSION;

This is safe and effective and always works. By putting any $VERSION in quotes, even if it isn’t an alpha, you don’t have to remember to add them if you ever change to an alpha version. (And numbers with trailing zeroes are nicely formatted when parsed for distribution versions.)

If you really want to have a dotted-integer module version, then I strongly recommend that you limit your module to Perl 5.10 (or require version.pm and at least Perl 5.8.1) and that you never use an alpha version number. Always quote your dotted integer version when you define it and always use a leading-v to guide your users towards proper usage.

It’s unfortunate that version numbers are so complicated in Perl, but if you follow the recommendations in this article, your version numbers will be as boring as possible. And if you’ve read this all the way to the end, I hope I’ve convinced you that ‘boring’ version numbers are exactly what you want.

I would like to thank Curtis Poe, Ricardo Signes and Eric Wilhelm for reviewing a draft of this article and providing excellent suggestions for improving clarity and correctness. I would also like to thank the particpants in the #toolchain and #corehackers channels on IRC for being a sounding board as the article was developed. Thanks also go to John Peacock for his graceful acceptance of my criticisms and suggestions about version.pm.

•      â€¢      â€¢

If you enjoyed this or have feedback, please let me know by or