Understanding Continuous Integration (CI) Builds
The use of a version specific to CI builds is unique to the Constrained Semantic Version spec, in particular the CSemVer-CI support. These versions are NOT compatible with a CSemVer but are compatible with a SemVer. If that's not confusing enough the way in which they are constrained makes things a LOT more complicated. This task and the Ubiquity.NET.Versioning library BOTH got the behavior wrong initially! This article will hopefully make sense out of things...
Examples
Hopefully examples will make things more clear:
v5.0.4--ci.123456.ZZZ
- This is a CI build based on a release
v5.0.3
- That is, it is ordered AFTER a release of
v5.0.3
- That is, it is ordered AFTER a release of
- It is also a SemVer 'pre-release' and therefore ordered BEFORE
v5.0.4
.- Thus this CI build is ordered BETWEEN the previously released
v5.0.3
and the as of yet unreleasedv5.0.4
.
- Thus this CI build is ordered BETWEEN the previously released
- Using this task the
ZZZ
indicates this is a developer local build. No guarantees of uniqueness are provided for such versions across machines.- It is possible for two developers to achieve the same build version for two
completely distinct builds.
- Though in practical terms this is a very unlikely statistical probability.
- It is possible for two developers to achieve the same build version for two
completely distinct builds.
- This is a CI build based on a release
- 'v5.0.4-beta.0.1.ci.123456.ZZZ`
- This is a CI Build based on a previously released "pre-release" version
v5.0.3-alpha.0.1
- As with the previous example this is ordered AFTER the release it is based on
and BEFORE the Patch+1 version (
v5.0.4-beta.0.1
).
- This is a CI Build based on a previously released "pre-release" version
Note
As [BUG] - CI version string formatting does not ALWAYS include pre-release information for a pre-release
points out the formatting of pre-release version numbers is different in CSemVer-CI. In a CI
version the Number
and Fix
values are ALWAYS included, even if 0
. In a CSemVer
things are more complex. In such a case, the Number
and Fix
are NOT shown if 0.
Unless the Number
is 0
AND Fix > 0
in that case the Number
is shown
as a zero and the Fix
is shown as it's non-zero value.
lifetime scope of a CI Build
The lifetime of a CI build is generally very short and once a version is released all CIs that led up to that release are essentially moot.
CI versions are POST-RELEASE based
CI versioning falls into one of two categories:
- Never had a release to base anything on
- Intermediate builds while developing the very first release of a product.
- A build that occurs AFTER something was released, but BEFORE the
next formal release. Such builds usually include:
- Local Developer Builds
- Pull Request builds (Automated "buddy" builds)
- "CI" builds
- Formal builds of the code base either from any PR or on a schedule (usually nightly builds)
For CSemVer-CI, which are based on SemVer, the pre-release syntax is all that is available to indicate the status of a CI build. Additionally, a CSemVer[-CI] supports representation as an "ordered" version number OR as a File Version Quad. The file version quad keeps things 'interesting'...
String forms of versions
As a string a CI build is represented with pre-release component of 'ci' or '-ci'. The latter is used if the base version already includes pre-release data (The 'double dash' trick)
`5.0.5--ci.BuildIndex.BuildName'
As a SemVer
Value | Description |
---|---|
5 | Major portion of the version |
0 | Minor portion of the version |
5 | Patch portion of the version |
'ci.BuildIndex.BuildName' | pre-release data indicating a CI build where the version is patch+1 |
Since this is a pre-release it is ordered BEFORE an actual release of (5.0.5) and AFTER any release of (5.0.4).
As a CSemVer-CI
As a CSemVer-CI that is interpreted a bit differently than you might expect, in
particular the 'Major.Minor.Patch' are what is known as the "Patch+1" form. That is
the base build
version for this CSemVer-CI version is v5.0.4! That is what is used
as the defining value of a build as it isn't known what the actual release version
will be (it hasn't released yet!).
SemVer has rules regarding compatibilities with respect to changes in elements of a
version. Thus, any versioning strategy that follows those rules does NOT have a fixed
(next version). So CSemVer-CI uses a POST release strategy where the
<major>.<minor>.<patch>
is actually <major>.<minor>.<patch+1>
to guarantee it is
ordered AFTER the release it is based on but BEFORE the next possible release. The
actual release value may use an even greater value, but CSemVer-CI doesn't care.
The CSemVer-CI spec is silent on the point of what happens if the base build
version patch is already 9999
(The max allowed value). Does it roll over to
0 and +1 to minor? While that does maintain ordering it pushes the boundaries
of the meaning of the version numbers. Though it is a rather common scenario for
a build to require a major version update when only some small portion of things
is actually incompatible and the rest is largely backwards compatible. It just doesn't
guarantee it anymore.
This task assumes it is proper to roll over into the next form. (In fact it relies on the ordered integral form of the version and increments that, until it reaches the maximum)
Ordered Version
Ordered versions are a concept unique to Constrained Semantic versions. The constraints applied to a SemVer allow creation of an integral value for all versions, except CI builds. Ignoring CI builds for the moment, the ordered number is computed from the values of the various parts of a version as they are constrained by the CSemVer spec. The math involved is not important for this discussion. Just that each Constrained Version is representable as a distinct integral value (63 bits actually). A CSemVer-CI build has two elements the base build and the additional 'BuildIndex' and 'BuildName' components. This means the string, File version and ordered version numbers are confusingly different for a CI build. The ordered version number does NOT account for CI in any way. It is ONLY able to convert to/from a CSemVer. Thus, a CSemVer-CI has an ambiguous conversion. Should it convert the Patch+1 form in a string or the base build number?
File Version Quad and UINT64
A file Version quad is a data structure that is blittable as an unsigned 64 bit value. Each such value is broken down into four fields (thus the 'quad' in the common naming) that are 16 bits each (unsigned). These versions are common in Windows platforms as that is the form used in resource file versioning. (Though this form is used in other cases on other platforms as well.) Such a value is really the ordered version number shifted left by one bit and the LSB used to indicate a CI build with odd numbered values to represent a CI build. Thus, a File Version of a CI build is derived from the base version of the CSemVer-CI. The ordering of such values is the same as an integral conversion. (or, most likely, a simple re-interpret style cast to an unsigned 64 bit value). The LSB reserved to indicate a CI ensures that a CI file Version is ordered AFTER a non-CI File version for the same base build. This is the most confusing and subtle aspect of the versioning and where this task an related library went wrong in early releases.
Important
To be crystal clear, the FILEVERSION for a CI build comes from the base build version as it includes a bit that, when set, indicates a CI build, but also a greater integral value. Thus, ordering for a CI build as a POST release version is built into this representation already.
The string form of a version uses the Patch+1
version to maintain proper
ordering following the rules defined by SemVer. That is the FileVersion and string
forms will use different versions at first glance. The string form requires some
interpretation to fully understand the meaning, while still retaining the desired
ordering based on SemVer defined rules.
File Version QUAD (Bit 63 is the MSB and bit 0 is the LSB)1
Field | Name | Description |
---|---|---|
Bits 48-63 | Major | Major portion of a build |
Bits 32-47 | Minor | Minor portion of a build |
Bits 16-31 | Build | Build number for a build |
Bits 1-15 | Revision | Revision of the build |
Bit 0 | CI Build | normally incorporated into the "revision" (1 == CI Build, 0 == release build) |
Bits 1-63 are the same as the ordered version of the base build for a CI build and the same as the ordered version of a release build.
1 Endianess of the platform does not matter for the purposes of discussion or this library, as the bits are numbered as MSB->LSB and the actual byte layout is dependent on the target platform even though the bits are not. It is NOT safe to transfer a FileVersion (or Ordered version) as in integral value without considering the endianess of the source, target and transport mechanism, all of which are out of scope for this library and the CSemVer spec in general.