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.
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.
The 1.2.b.1
version is compliant with the rubygems version notation
which currently supports the following rule set:
0
tokens. Thus 1.2.b
is less than 1.2
because it is less than 1.2.0.0…
gem --prerelease
is
specified.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.
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?
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
.