Skip to content

Commit 2107b54

Browse files
authored
Fixes CI Version string and file version creation
This fixes the bug where CI version strings are ignoring the Patch+1 design * Removed ignored ShortCSemVer support - Not used, not tested and therefore fragile and likely incorrect * Added IsCIBuild property to task to make it simpler to centralize the evaluation of that state. * Added automatic conversion of "prerelease" to "pre" as a CSemVer pre-release name. * Added Patch+1 support to creation of the version string for CI builds * Added clarifying docs * Added Build scripting to correctly form a CI build string AND FileVersion for a CI build. Co-authored-by: smaillet <[email protected]>
1 parent 058b476 commit 2107b54

File tree

12 files changed

+458
-147
lines changed

12 files changed

+458
-147
lines changed

Directory.Packages.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
2323
<PackageVersion Include="MSTest.TestAdapter" Version="3.9.1" />
2424
<PackageVersion Include="MSTest.TestFramework" Version="3.9.1" />
25-
<PackageVersion Include="Polyfill" Version="7.31.0" />
25+
<PackageVersion Include="PolySharp" Version="1.15.0" />
2626
<PackageVersion Include="Sprache" Version="2.3.1" />
2727
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
2828
</ItemGroup>
29-
</Project>
29+
</Project>

IgnoredWords.dic

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ paren
114114
perf
115115
plugin
116116
pointee
117+
poly
117118
Pre
118119
prerelease
119120
proj

New-GeneratedVersionProps.ps1

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,17 +199,49 @@ class CSemVer
199199
throw 'CiBuildName is required if CiBuildIndex is provided';
200200
}
201201

202+
# For a CI build the OrderedVersion value is that of the BASE build
202203
$this.OrderedVersion = [CSemVer]::GetOrderedVersion($this.Major, $this.Minor, $this.Patch, $this.PreReleaseVersion)
203204
$fileVer64 = $this.OrderedVersion -shl 1
205+
204206
# If this is a CI build include that in the file version
207+
# AND increment the ordered version so that it is patch+1
205208
if($this.CiBuildIndex -and $this.CiBuildName)
206209
{
210+
$this.OrderedVersion = [CSemVer]::MakePatchPlus1($this.OrderedVersion)
211+
$this.UpdateFromOrderedVersion($this.OrderedVersion)
207212
$fileVer64 += 1;
208213
}
209214

210215
$this.FileVersion = [CSemVer]::ConvertToVersion($fileVer64)
211216
}
212217

218+
hidden UpdateFromOrderedVersion([long]$orderedVersion)
219+
{
220+
[ulong] $MulNum = 100;
221+
[ulong] $MulName = $MulNum * 100;
222+
[ulong] $MulPatch = ($MulName * 8) + 1;
223+
[ulong] $MulMinor = $MulPatch * 10000;
224+
[ulong] $MulMajor = $MulMinor * 50000;
225+
226+
# This effectively reverses the math used in computing the ordered version.
227+
$accumulator = [UInt64]$orderedVersion;
228+
$preRelPart = $accumulator % $MulPatch;
229+
230+
# skipping pre-release info as it is used AS-IS
231+
if($preRelPart -eq 0)
232+
{
233+
$accumulator -= $MulPatch;
234+
}
235+
236+
$this.Major = [Int32]($accumulator / $MulMajor);
237+
$accumulator %= $MulMajor;
238+
239+
$this.Minor = [Int32]($accumulator / $MulMinor);
240+
$accumulator %= $MulMinor;
241+
242+
$this.Patch = [Int32]($accumulator / $MulPatch);
243+
}
244+
213245
[string] ToString([bool] $includeMetadata)
214246
{
215247
$bldr = [System.Text.StringBuilder]::new()
@@ -239,6 +271,14 @@ class CSemVer
239271
return $this.ToString($true, $false);
240272
}
241273

274+
hidden static [ulong] MakePatchPlus1($orderedVersion)
275+
{
276+
[ulong] $MulNum = 100;
277+
[ulong] $MulName = $MulNum * 100;
278+
[ulong] $MulPatch = ($MulName * 8) + 1;
279+
return $orderedVersion + $MulPatch;
280+
}
281+
242282
hidden static [ulong] GetOrderedVersion($Major, $Minor, $Patch, [PreReleaseVersion] $PreReleaseVersion)
243283
{
244284
[ulong] $MulNum = 100;
@@ -388,5 +428,3 @@ finally
388428
Pop-Location
389429
$env:Path = $oldPath
390430
}
391-
392-
Write-Information 'Done build'

docfx/Attributions.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ Like almost any OSS project, this project relies on the work of others.
88
Copyright :copyright: 2020 Twitter, Inc and other contributors. The graphics are licensed under
99
[CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/).
1010
* MSBUILD task support from the [MSBUILD](https://github.com/dotnet/msbuild) project
11+
* [PolySharp](https://github.com/Sergio0694/PolySharp) To support poly-filling for down-level runtimes
12+
13+
And, of course, the [SemVer](https://SemVer.org) and [CSemVer](https://csemver.org) specs as this project
14+
implements support for them.
1115

1216
### Non-Shipping
1317
While not included in the shipping packages the following components are used in producing this
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
---
2+
uid: understanding-ci-builds
3+
---
4+
# Understanding Continuous Integration (CI) Builds
5+
The use of a version specific to CI builds is unique to the Constrained Semantic
6+
Version spec, in particular the CSemVer-CI support. These versions are NOT compatible
7+
with a CSemVer but are compatible with a SemVer. If that's not confusing enough the
8+
way in which they are constrained makes things a LOT more complicated. This task and
9+
the [Ubiquity.NET.Versioning](https://www.nuget.org/packages/Ubiquity.NET.Versioning)
10+
library BOTH got the behavior wrong initially! This article will hopefully make sense
11+
out of things...
12+
13+
## Examples
14+
Hopefully examples will make things more clear:
15+
16+
1) `v5.0.4--ci.123456.ZZZ`
17+
* This is a CI build based on a release `v5.0.3`
18+
* That is, it is ordered AFTER a release of `v5.0.3`
19+
* It is also a SemVer 'pre-release' and therefore ordered BEFORE
20+
`v5.0.4`.
21+
* Thus this CI build is ordered BETWEEN the previously released `v5.0.3` and
22+
the as of yet unreleased `v5.0.4`.
23+
* Using this task the `ZZZ` indicates this is a developer local build. No
24+
guarantees of uniqueness are provided for such versions across machines.
25+
* It is possible for two developers to achieve the same build version for two
26+
completely distinct builds.
27+
* Though in practical terms this is a very unlikely statistical
28+
probability.
29+
2) 'v5.0.4-beta.0.1.ci.123456.ZZZ`
30+
* This is a CI Build based on a previously released "pre-release" version
31+
`v5.0.3-alpha.0.1`
32+
* As with the previous example this is ordered AFTER the release it is based on
33+
and BEFORE the Patch+1 version (`v5.0.4-beta.0.1`).
34+
35+
## lifetime scope of a CI Build
36+
The lifetime of a CI build is generally very short and once a version is released
37+
all CIs that led up to that release are essentially moot.
38+
39+
## CI versions are POST-RELEASE based
40+
CI versioning falls into one of two categories:
41+
1) Never had a release to base anything on
42+
1) Intermediate builds while developing the very first release of a product.
43+
2) A build that occurs ***AFTER*** something was released, but ***BEFORE*** the
44+
next formal release. Such builds usually include:
45+
1) Local Developer Builds
46+
2) Pull Request builds (Automated "buddy" builds)
47+
3) "CI" builds
48+
1) Formal builds of the code base either from any PR or on a schedule (usually
49+
nightly builds)
50+
51+
For CSemVer-CI, which are based on SemVer, the pre-release syntax is all that is
52+
available to indicate the status of a CI build. Additionally, a CSemVer[-CI] supports
53+
representation as an "ordered" version number OR as a File Version Quad. The file
54+
version quad keeps things 'interesting'...
55+
56+
### String forms of versions
57+
As a string a CI build is represented with pre-release component of 'ci' or '-ci'. The
58+
latter is used if the base version already includes pre-release data (The 'double dash'
59+
trick)
60+
61+
`5.0.5--ci.BuildIndex.BuildName'
62+
63+
#### As a SemVer
64+
65+
| Value | Description
66+
|-------|------------|
67+
| 5 | Major portion of the version |
68+
| 0 | Minor portion of the version |
69+
| 5 | Patch portion of the version |
70+
| 'ci.BuildIndex.BuildName' | pre-release data indicating a CI build where the version is patch+1 |
71+
72+
Since this is a pre-release it is ordered BEFORE an actual release of (5.0.5) and
73+
AFTER any release of (5.0.4).
74+
75+
#### As a CSemVer-CI
76+
As a CSemVer-CI that is interpreted a bit differently than you might expect, in
77+
particular the 'Major.Minor.Patch' are what is known as the "Patch+1" form. That is
78+
the `base build` version for this CSemVer-CI version is v5.0.4! That is what is used
79+
as the defining value of a build as it isn't known what the actual release version
80+
will be (it hasn't released yet!).
81+
82+
SemVer has rules regarding compatibilities with respect to changes in elements of a
83+
version. Thus, any versioning strategy that follows those rules does NOT have a fixed
84+
(next version). So CSemVer-CI uses a POST release strategy where the
85+
`<major>.<minor>.<patch>` is actually `<major>.<minor>.<patch+1>` to guarantee it is
86+
ordered AFTER the release it is based on but BEFORE the next possible release. The
87+
actual release value may use an even greater value, but CSemVer-CI doesn't care.
88+
89+
The CSemVer-CI spec is silent on the point of what happens if the base build
90+
version patch is already `9999` (The max allowed value). Does it roll over to
91+
0 and +1 to minor? While that does maintain ordering it pushes the boundaries
92+
of the meaning of the version numbers. Though it is a rather common scenario for
93+
a build to require a major version update when only some small portion of things
94+
is actually incompatible and the rest is largely backwards compatible. It just doesn't
95+
guarantee it anymore.
96+
97+
This task assumes it is proper to roll over into the next form. (In fact it relies
98+
on the ordered integral form of the version and increments that, until it reaches the
99+
maximum)
100+
101+
### Ordered Version
102+
Ordered versions are a concept unique to Constrained Semantic versions. The constraints
103+
applied to a SemVer allow creation of an integral value for all versions, except CI
104+
builds. Ignoring CI builds for the moment, the ordered number is computed from the
105+
values of the various parts of a version as they are constrained by the CSemVer spec.
106+
The math involved is not important for this discussion. Just that each Constrained
107+
Version is representable as a distinct integral value (63 bits actually). A CSemVer-CI
108+
build has two elements the base build and the additional 'BuildIndex' and 'BuildName'
109+
components. This means the string, File version and ordered version numbers are
110+
confusingly different for a CI build. The ordered version number does NOT account for
111+
CI in any way. It is ONLY able to convert to/from a CSemVer. Thus, a CSemVer-CI has
112+
an ambiguous conversion. Should it convert the Patch+1 form in a string or the
113+
base build number?.
114+
115+
### File Version Quad and UINT64
116+
A file Version quad is a data structure that is blittable as an unsigned 64 bit value.
117+
Each such value is broken down into four fields (thus the 'quad' in the common naming)
118+
that are 16 bits each (unsigned). These versions are common in Windows platforms as
119+
that is the form used in resource file versioning. (Though this form is used in other
120+
cases on other platforms as well.) Such a value is really the ordered version number
121+
shifted left by one bit and the LSB used to indicate a CI build with odd numbered
122+
values to represent a CI build. Thus, a File Version of a CI build is derived from
123+
the base version of the CSemVer-CI. The ordering of such values is the same as an
124+
integral conversion. (or, most likely, a simple re-interpret style cast to an unsigned
125+
64 bit value). The LSB reserved to indicate a CI ensures that a CI file Version is
126+
ordered AFTER a non-CI File version for the same base build. This is the most
127+
confusing and subtle aspect of the versioning and where this task an related library
128+
went wrong in early releases.
129+
130+
>[!IMPORTANT]
131+
> To be crystal clear, the FILEVERSION for a CI build comes from the ***base build***
132+
> version as it includes a bit that, when set, indicates a CI build, but also a
133+
> greater integral value. Thus, ordering for a CI build as a POST release version is
134+
> built into this representation already.
135+
>
136+
> The ***string*** form of a version uses the `Patch+1` version to maintain proper
137+
> ordering following the rules defined by SemVer. That is the FileVersion and string
138+
> forms will use different versions at first glance. The string form requires some
139+
> interpretation to fully understand the meaning, while still retaining the desired
140+
> ordering based on SemVer defined rules.
141+
142+
File Version QUAD (Bit 63 is the MSB and bit 0 is the LSB)<sup>[1](#footnote_1)</sup>
143+
144+
|Field | Name | Description |
145+
|------|------|-------------|
146+
|Bits 48-63 | Major | Major portion of a build |
147+
|Bits 32-47 | Minor | Minor portion of a build |
148+
|Bits 16-31| Build | Build number for a build |
149+
|Bits 1-15 | Revision | Revision of the build |
150+
|Bit 0 | CI Build | normally incorporated into the "revision" (1 == CI Build, 0 == release build)|
151+
152+
Bits 1-63 are the same as the ordered version of the base build for a CI build and
153+
the same as the ordered version of a release build.
154+
155+
------
156+
<sup><a id="footnote_1">1</a></sup> Endianess of the platform does not matter as the bits are numbered as MSB->LSB
157+
and the actual byte layout is dependent on the target platform even though the bits
158+
are not. It is NOT safe to transfer a FileVersion (or Ordered version) as in integral
159+
value without considering the endianess of the source, target and transport mechanism,
160+
all of which are out of scope for this library and the CSemVer spec in general.
161+
162+

0 commit comments

Comments
 (0)