A colleague recently pointed out that version 1.2.b.1 was invalid Semantic Versioning as referenced in the Iūdex developers guide. I’ll show that SemVer has been a moving target, and the tools involved don’t entirely support it. But at minimum, the intent and meaning of 1.2.b.1 may not be as clear as I had hoped. I’ll document the pitfalls found along the way, and suggest a clearer strategy moving forward.

tl/dr: Given the complexities, incompatibilities and pitfalls involved, don’t bother with pre-release version notations. See conclusion.

Ελληνικά

The b in 1.2.b.1 was for beta. The Greek letter names alpha and beta have long been used to designate pre-release software. I would define this further and pragmatically as software made available with either known problems or without sufficient testing for confidence that it will meet the same quality levels as current or future release versions.1 I had wanted a short form comparable with the decimal versions, but perhaps 1.2.α.0 and 1.2.β.0 would have been more clear? I joke, but it offers a fitting segue to the underlying issue here: tool compatibility and interoperability. I simply don’t have the fortitude to use ~> 1.2.β in a Rubygems dependency or the equivalent Maven version range [1.2.β,1.3). Fortunately in all specifications and tools discussed below beta and b; alpha and a are interchangable.

Semantic Versioning

The version 1.2.b.1 is invalid, with different possible corrections, with three different versions of SemVer. Since commit 05a00df0 last year, a pre-release modifier/component is required to be delimited with a - hyphen instead of the . dot used in prior versions. The following table shows corrected examples based on each:

SemVer Version Example Corrections
before 05a00df0 1.2.0.b1
1.2.0.b-1
1.0.0 1.2.0-b1
1.2.0-b-1
2.0.0-rc.1 1.2.0-b.1

Note that only SemVer 2.0.0-rc.1 offers numeric sort order for the 1 following the b. All other versions have the potential pitfall of lexicographic sorting, e.g. b10 < b9 and b-10 < b-9.

But returning to the practical: No release version of SemVer is compatible with Rubygems.

Rubygems

The 1.2.b.1 version is compliant with the rubygems version notation which currently supports the following rule set:

My intent with 1.2.b.1 was to release a 1.2 beta series followed by a 1.2.0 release. The third position would sort as the progression a b 0 1 … The problem with this is that the rubygems authors had a different progression in mind, given the behavior of “pessimistic” or “spermy” version specifiers with pre-releases. The table shows the intended progression and what some specifiers match:

Version ~> 1.1.0 ~> 1.2.b ~> 1.2.0
1.1.0    
1.1.1    
1.2.a.0      
1.2.b.0    
1.2.b.1    
1.2.0  
1.2.1  
1.3.b.0   ¤  
1.3.0   ¤  
1.4.0   ¤  

In my own usage, I had expected ~> 1.2.b to accept the beta series and subsequent 1.2.x releases but not 1.3.b.0, 1.3.0 and beyond (¤ above).

The actual implemented behavior of pessimistic specifiers with pre-release tokens is obscure and undocumented. Through my own testing, ~> 1.2.b is effectively equivalent to [ '~> 1.2', '>= 1.2.b' ]. The closest alternative specification for my original intent is [ '>= 1.2.b', '< 1.3.a' ] which is awkward at best.

The reason for this seamingly over-complicated behavior is that the rubygems authors opted to support an alternative progression for beta releases, as shown in the following table:

Version ~> 1.1.0 ~> 1.2.0.b ~> 1.2.0
1.1.0    
1.1.1    
1.2.0.a.0      
1.2.0.b.0    
1.2.0.b.1    
1.2.0  
1.2.1  
1.3.0.b.0      
1.3.0      

So for example, if you intend to put out a pre-release for what will become a final release of 1.4.0, then you had better version it like 1.4.0.rc.0 (replace rc as desired) and not 1.4.rc.0. As this is rather obscure and confusing, several prominent projects have done it incorrectly, for example, the 1.1.pre and 1.1.rc series of bundler, and similar versions of i18n, pg, and mongo.

Maven

If you’re not yet acquainted with my use of Java for performance and Ruby for elegant brevity: I’m using Maven only as a javac with dependencies tool and packing jars in gems. But in some cases there are combined java/ruby dependencies which must be expressed in a compatible way between the two systems.

In a similar fashion, 1.2.b.1 is accepted by Maven but non-standard and has its own set of pitfalls. The most significant of these is that Maven, according to documentation, orders such versions as follows:

1.2.0 < 1.2.1 < 1.2.2 < 1.2.a.1 < 1.2.b.1 < 1.2.b.2

Yet in practice Maven appears, at least in certain contexts to order as:

1.2.0 < 1.2.a.1 < 1.2.b.1 < 1.2.b.2 < 1.2.1 < 1.2.2

So don’t make the mistake of using versions like this in Maven. Fortunately, a Maven version range specifier of [1.2,1.2.9999) filters out the pre-releases despite the above ordering. I will be moving on from this particular issue by making 1.2.1 the next (non-pre) release of Iudex (which appears to avoid issues.)

There is a somewhat comparable mapping of pre-release versions available across the SemVer v2, Rubygems and Maven:

SemVer 2 Rubygems Maven
1.2.0-b.2 1.2.0.b.2 1.2.0-b.2
1.3.0 1.3.0 1.3.0

However, b.2 is still compared lexicographical in Maven, so with this scheme, you would need to avoid b.10. Furthermore, range specifiers aren’t particularly pre-release aware, so the Maven range equivalent to Rubygems specifier ~> 1.2.0.b.2 would presumably need to be [1.2.0-b.2, 1.2.999) though this isn’t documented and I haven’t tested it.

It may also be worth noting that Maven dependency mediation allows users to avoid version ranges while muddling the vary intent of library authors in specifying compatible dependencies.

Did you notice how relatively simple that 1.3.0 version is looking in the above table?

Conclusion

Despite best intentions by spec. and tool authors, pre-release notations and behaviors are obscure, often conflicting, and with many potential pitfalls.

Consider also that many pre-release progressions end with a production release with no functional change from a prior candidate. Extra release work to support an often arbitrary distinction. Release beauty is ultimately in the eye of the beholder. Perhaps in general, and most certainly with this particular set of tools, we would be better served to dispense with the notion that the highest major.minor release number is necessarily the most stable. And as a corollary, continue to release patches to the previous major.minor branch.

The next Iūdex preview/candidate/beta release will be versioned: 1.3.0 and will be experimental by comparision to 1.2.2.

  1. I’m not sure how this notion of pre-release relates to test-driven development concepts, where any new feature would of course be sufficiently unit tested.

  2. What locale you might ask? Greek?