microsoft/typespec

Public

mirrored from https://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
efeedc42a9fb908c64751d97fea2ae3d7dfeb4ee

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

eng/emitters/scripts/SemVer.ps1

368lines · modecode

1# cSpell:ignore prenumsep
2# cSpell:ignore pbeta
3
4<#
5.DESCRIPTION
6Parses a semver version string into its components and supports operations around it that we use for versioning our packages.
7
8See https://azure.github.io/azure-sdk/policies_releases.html#package-versioning
9
10Example: 1.2.3-beta.4
11Components: Major.Minor.Patch-PrereleaseLabel.PrereleaseNumber
12
13Example: 1.2.3-alpha.20200828.4
14Components: Major.Minor.Patch-PrereleaseLabel.PrereleaseNumber.BuildNumber
15
16Note: A builtin Powershell version of SemVer exists in 'System.Management.Automation'. At this time, it does not parsing of PrereleaseNumber. It's name is also type accelerated to 'SemVer'.
17#>
18
19class AzureEngSemanticVersion : IComparable {
20 [int] $Major
21 [int] $Minor
22 [int] $Patch
23 [string] $PrereleaseLabelSeparator
24 [string] $PrereleaseLabel
25 [string] $PrereleaseNumberSeparator
26 [string] $BuildNumberSeparator
27 # BuildNumber is string to preserve zero-padding where applicable
28 [string] $BuildNumber
29 [int] $PrereleaseNumber
30 [bool] $IsPrerelease
31 [string] $VersionType
32 [string] $RawVersion
33 [bool] $IsSemVerFormat
34 [string] $DefaultPrereleaseLabel
35 [string] $DefaultAlphaReleaseLabel
36
37 # Regex inspired but simplified from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
38 # Validation: https://regex101.com/r/vkijKf/426
39 static [string] $SEMVER_REGEX = "(?<major>0|[1-9]\d*)\.(?<minor>0|[1-9]\d*)\.(?<patch>0|[1-9]\d*)(?:(?<presep>-?)(?<prelabel>[a-zA-Z]+)(?:(?<prenumsep>\.?)(?<prenumber>[0-9]{1,8})(?:(?<buildnumsep>\.?)(?<buildnumber>\d{1,3}))?)?)?"
40
41 static [AzureEngSemanticVersion] ParseVersionString([string] $versionString)
42 {
43 $version = [AzureEngSemanticVersion]::new($versionString)
44
45 if (!$version.IsSemVerFormat) {
46 return $null
47 }
48 return $version
49 }
50
51 static [AzureEngSemanticVersion] ParsePythonVersionString([string] $versionString)
52 {
53 $version = [AzureEngSemanticVersion]::ParseVersionString($versionString)
54
55 if (!$version) {
56 return $null
57 }
58
59 $version.SetupPythonConventions()
60 return $version
61 }
62
63 AzureEngSemanticVersion([string] $versionString)
64 {
65 if ($versionString -match "^$([AzureEngSemanticVersion]::SEMVER_REGEX)$")
66 {
67 $this.IsSemVerFormat = $true
68 $this.RawVersion = $versionString
69 $this.Major = [int]$matches.Major
70 $this.Minor = [int]$matches.Minor
71 $this.Patch = [int]$matches.Patch
72
73 # If Language exists and is set to python setup the python conventions.
74 $parseLanguage = (Get-Variable -Name "Language" -ValueOnly -ErrorAction "Ignore")
75 if ($parseLanguage -eq "python") {
76 $this.SetupPythonConventions()
77 }
78 else {
79 $this.SetupDefaultConventions()
80 }
81
82 if ($null -eq $matches['prelabel'])
83 {
84 # artificially provide these values for non-prereleases to enable easy sorting of them later than prereleases.
85 $this.PrereleaseLabel = "zzz"
86 $this.PrereleaseNumber = 99999999
87 $this.IsPrerelease = $false
88 $this.VersionType = "GA"
89 if ($this.Major -eq 0) {
90 # Treat initial 0 versions as a prerelease beta's
91 $this.VersionType = "Beta"
92 $this.IsPrerelease = $true
93 }
94 elseif ($this.Patch -ne 0) {
95 $this.VersionType = "Patch"
96 }
97 }
98 else
99 {
100 $this.PrereleaseLabel = $matches["prelabel"]
101 $this.PrereleaseLabelSeparator = $matches["presep"]
102 $this.PrereleaseNumber = [int]$matches["prenumber"]
103 $this.PrereleaseNumberSeparator = $matches["prenumsep"]
104 $this.IsPrerelease = $true
105 $this.VersionType = "Beta"
106
107 $this.BuildNumberSeparator = $matches["buildnumsep"]
108 $this.BuildNumber = $matches["buildnumber"]
109 }
110 }
111 else
112 {
113 $this.RawVersion = $versionString
114 $this.IsSemVerFormat = $false
115 }
116 }
117
118 # If a prerelease label exists, it must be 'beta', and similar semantics used in our release guidelines
119 # See https://azure.github.io/azure-sdk/policies_releases.html#package-versioning
120 [bool] HasValidPrereleaseLabel()
121 {
122 if ($this.IsPrerelease -eq $true) {
123 if ($this.PrereleaseLabel -ne $this.DefaultPrereleaseLabel -and $this.PrereleaseLabel -ne $this.DefaultAlphaReleaseLabel) {
124 Write-Host "Unexpected pre-release identifier '$($this.PrereleaseLabel)', "`
125 "should be '$($this.DefaultPrereleaseLabel)' or '$($this.DefaultAlphaReleaseLabel)'"
126 return $false;
127 }
128 if ($this.PrereleaseNumber -lt 1)
129 {
130 Write-Host "Unexpected pre-release version '$($this.PrereleaseNumber)', should be >= '1'"
131 return $false;
132 }
133 }
134
135 return $true;
136 }
137
138 [string] ToString()
139 {
140 $versionString = "{0}.{1}.{2}" -F $this.Major, $this.Minor, $this.Patch
141
142 if ($this.IsPrerelease -and $this.PrereleaseLabel -ne "zzz")
143 {
144 $versionString += $this.PrereleaseLabelSeparator + $this.PrereleaseLabel + `
145 $this.PrereleaseNumberSeparator + $this.PrereleaseNumber
146 if ($this.BuildNumber) {
147 $versionString += $this.BuildNumberSeparator + $this.BuildNumber
148 }
149 }
150 return $versionString;
151 }
152
153 [void] IncrementAndSetToPrerelease() {
154 if ($this.IsPrerelease -eq $false)
155 {
156 $this.PrereleaseLabel = $this.DefaultPrereleaseLabel
157 $this.PrereleaseNumber = 1
158 $this.Minor++
159 $this.Patch = 0
160 $this.IsPrerelease = $true
161 }
162 else
163 {
164 if ($this.BuildNumber) {
165 throw "Cannot increment releases tagged with azure pipelines build numbers"
166 }
167 $this.PrereleaseNumber++
168 }
169 }
170
171 [void] SetupPythonConventions()
172 {
173 # Python uses no separators and "b" for beta so this sets up the the object to work with those conventions
174 $this.PrereleaseLabelSeparator = $this.PrereleaseNumberSeparator = $this.BuildNumberSeparator = ""
175 $this.DefaultPrereleaseLabel = "b"
176 $this.DefaultAlphaReleaseLabel = "a"
177 }
178
179 [void] SetupDefaultConventions()
180 {
181 # Use the default common conventions
182 $this.PrereleaseLabelSeparator = "-"
183 $this.PrereleaseNumberSeparator = "."
184 $this.BuildNumberSeparator = "."
185 $this.DefaultPrereleaseLabel = "beta"
186 $this.DefaultAlphaReleaseLabel = "alpha"
187 }
188
189 [int] CompareTo($other)
190 {
191 if ($other -isnot [AzureEngSemanticVersion]) {
192 throw "Cannot compare $other with $this"
193 }
194
195 $ret = $this.Major.CompareTo($other.Major)
196 if ($ret) { return $ret }
197
198 $ret = $this.Minor.CompareTo($other.Minor)
199 if ($ret) { return $ret }
200
201 $ret = $this.Patch.CompareTo($other.Patch)
202 if ($ret) { return $ret }
203
204 # Mimic PowerShell that uses case-insensitive comparisons by default.
205 $ret = [string]::Compare($this.PrereleaseLabel, $other.PrereleaseLabel, $true)
206 if ($ret) { return $ret }
207
208 $ret = $this.PrereleaseNumber.CompareTo($other.PrereleaseNumber)
209 if ($ret) { return $ret }
210
211 return ([int] $this.BuildNumber).CompareTo([int] $other.BuildNumber)
212 }
213
214 static [string[]] SortVersionStrings([string[]] $versionStrings)
215 {
216 $versions = $versionStrings | ForEach-Object { [AzureEngSemanticVersion]::ParseVersionString($_) }
217 $sortedVersions = [AzureEngSemanticVersion]::SortVersions($versions)
218 return ($sortedVersions | ForEach-Object { $_.RawVersion })
219 }
220
221 static [AzureEngSemanticVersion[]] SortVersions([AzureEngSemanticVersion[]] $versions)
222 {
223 return $versions | Sort-Object -Descending
224 }
225
226 static [void] QuickTests()
227 {
228 $global:Language = ""
229 $versions = @(
230 "1.0.1",
231 "2.0.0",
232 "2.0.0-alpha.20200920",
233 "2.0.0-alpha.20200920.1",
234 "2.0.0-beta.2",
235 "1.0.10",
236 "2.0.0-alpha.20201221.03",
237 "2.0.0-alpha.20201221.1",
238 "2.0.0-alpha.20201221.5",
239 "2.0.0-alpha.20201221.2",
240 "2.0.0-alpha.20201221.10",
241 "2.0.0-beta.1",
242 "2.0.0-beta.10",
243 "1.0.0",
244 "1.0.0b2",
245 "1.0.2")
246
247 $expectedSort = @(
248 "2.0.0",
249 "2.0.0-beta.10",
250 "2.0.0-beta.2",
251 "2.0.0-beta.1",
252 "2.0.0-alpha.20201221.10",
253 "2.0.0-alpha.20201221.5",
254 "2.0.0-alpha.20201221.03",
255 "2.0.0-alpha.20201221.2",
256 "2.0.0-alpha.20201221.1",
257 "2.0.0-alpha.20200920.1",
258 "2.0.0-alpha.20200920",
259 "1.0.10",
260 "1.0.2",
261 "1.0.1",
262 "1.0.0",
263 "1.0.0b2")
264
265 $sort = [AzureEngSemanticVersion]::SortVersionStrings($versions)
266
267 for ($i = 0; $i -lt $expectedSort.Count; $i++)
268 {
269 if ($sort[$i] -ne $expectedSort[$i]) {
270 Write-Host "Error: Incorrect version sort:"
271 Write-Host "Expected: "
272 Write-Host $expectedSort
273 Write-Host "Actual:"
274 Write-Host $sort
275 break
276 }
277 }
278
279 $alphaVerString = "1.2.3-alpha.20200828.9"
280 $alphaVer = [AzureEngSemanticVersion]::new($alphaVerString)
281 if (!$alphaVer.IsPrerelease) {
282 Write-Host "Expected alpha version to be marked as prerelease"
283 }
284 if ($alphaVer.Major -ne 1 -or $alphaVer.Minor -ne 2 -or $alphaVer.Patch -ne 3 -or `
285 $alphaVer.PrereleaseLabel -ne "alpha" -or $alphaVer.PrereleaseNumber -ne 20200828 -or $alphaVer.BuildNumber -ne 9) {
286 Write-Host "Error: Didn't correctly parse alpha version string $alphaVerString"
287 }
288 if ($alphaVerString -ne $alphaVer.ToString()) {
289 Write-Host "Error: alpha string did not correctly round trip with ToString. Expected: $($alphaVerString), Actual: $($alphaVer)"
290 }
291
292 $global:Language = "python"
293 $pythonAlphaVerString = "1.2.3a20200828009"
294 $pythonAlphaVer = [AzureEngSemanticVersion]::new($pythonAlphaVerString)
295 if (!$pythonAlphaVer.IsPrerelease) {
296 Write-Host "Expected python alpha version to be marked as prerelease"
297 }
298 # Note: For python we lump build number into prerelease number, since it simplifies the code and regex, and is behaviorally the same
299 if ($pythonAlphaVer.Major -ne 1 -or $pythonAlphaVer.Minor -ne 2 -or $pythonAlphaVer.Patch -ne 3 `
300 -or $pythonAlphaVer.PrereleaseLabel -ne "a" -or $pythonAlphaVer.PrereleaseNumber -ne 20200828 `
301 -or $pythonAlphaVer.BuildNumber -ne "009") {
302 Write-Host "Error: Didn't correctly parse python alpha version string $pythonAlphaVerString"
303 }
304 if ($pythonAlphaVerString -ne $pythonAlphaVer.ToString()) {
305 Write-Host "Error: python alpha string did not correctly round trip with ToString. Expected: $($pythonAlphaVerString), Actual: $($pythonAlphaVer)"
306 }
307
308 $versions = @("1.0.1", "2.0.0", "2.0.0a20201208001", "2.0.0a20201105020", "2.0.0a20201208012", `
309 "2.0.0b2", "1.0.10", "2.0.0b1", "2.0.0b10", "1.0.0", "1.0.0b2", "1.0.2")
310 $expectedSort = @("2.0.0", "2.0.0b10", "2.0.0b2", "2.0.0b1", "2.0.0a20201208012", "2.0.0a20201208001", `
311 "2.0.0a20201105020", "1.0.10", "1.0.2", "1.0.1", "1.0.0", "1.0.0b2")
312 $sort = [AzureEngSemanticVersion]::SortVersionStrings($versions)
313 for ($i = 0; $i -lt $expectedSort.Count; $i++)
314 {
315 if ($sort[$i] -ne $expectedSort[$i]) {
316 Write-Host "Error: Incorrect python version sort:"
317 Write-Host "Expected: "
318 Write-Host $expectedSort
319 Write-Host "Actual:"
320 Write-Host $sort
321 break
322 }
323 }
324
325 $global:Language = ""
326
327 $gaVerString = "1.2.3"
328 $gaVer = [AzureEngSemanticVersion]::ParseVersionString($gaVerString)
329 if ($gaVer.Major -ne 1 -or $gaVer.Minor -ne 2 -or $gaVer.Patch -ne 3) {
330 Write-Host "Error: Didn't correctly parse ga version string $gaVerString"
331 }
332 if ($gaVerString -ne $gaVer.ToString()) {
333 Write-Host "Error: Ga string did not correctly round trip with ToString. Expected: $($gaVerString), Actual: $($gaVer)"
334 }
335 $gaVer.IncrementAndSetToPrerelease()
336 if ("1.3.0-beta.1" -ne $gaVer.ToString()) {
337 Write-Host "Error: Ga string did not correctly increment"
338 }
339
340 $betaVerString = "1.2.3-beta.4"
341 $betaVer = [AzureEngSemanticVersion]::ParseVersionString($betaVerString)
342 if ($betaVer.Major -ne 1 -or $betaVer.Minor -ne 2 -or $betaVer.Patch -ne 3 -or $betaVer.PrereleaseLabel -ne "beta" -or $betaVer.PrereleaseNumber -ne 4) {
343 Write-Host "Error: Didn't correctly parse beta version string $betaVerString"
344 }
345 if ($betaVerString -ne $betaVer.ToString()) {
346 Write-Host "Error: beta string did not correctly round trip with ToString. Expected: $($betaVerString), Actual: $($betaVer)"
347 }
348 $betaVer.IncrementAndSetToPrerelease()
349 if ("1.2.3-beta.5" -ne $betaVer.ToString()) {
350 Write-Host "Error: Beta string did not correctly increment"
351 }
352
353 $pythonBetaVerString = "1.2.3b4"
354 $pbetaVer = [AzureEngSemanticVersion]::ParsePythonVersionString($pythonBetaVerString)
355 if ($pbetaVer.Major -ne 1 -or $pbetaVer.Minor -ne 2 -or $pbetaVer.Patch -ne 3 -or $pbetaVer.PrereleaseLabel -ne "b" -or $pbetaVer.PrereleaseNumber -ne 4) {
356 Write-Host "Error: Didn't correctly parse python beta string $pythonBetaVerString"
357 }
358 if ($pythonBetaVerString -ne $pbetaVer.ToString()) {
359 Write-Host "Error: python beta string did not correctly round trip with ToString"
360 }
361 $pbetaVer.IncrementAndSetToPrerelease()
362 if ("1.2.3b5" -ne $pbetaVer.ToString()) {
363 Write-Host "Error: Python beta string did not correctly increment"
364 }
365
366 Write-Host "QuickTests done"
367 }
368}
369