diff --git a/eng/common/scripts/SemVer.ps1 b/eng/common/scripts/SemVer.ps1 index 402c9fb1f57da..0efdfae9cd425 100644 --- a/eng/common/scripts/SemVer.ps1 +++ b/eng/common/scripts/SemVer.ps1 @@ -4,102 +4,244 @@ Parses a semver version string into its components and supports operations aroun See https://azure.github.io/azure-sdk/policies_releases.html#package-versioning -Example: 1.2.3-preview.4 +Example: 1.2.3-beta.4 Components: Major.Minor.Patch-PrereleaseLabel.PrereleaseNumber Note: 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'. #> class AzureEngSemanticVersion { - [int] $Major - [int] $Minor - [int] $Patch - [string] $PrereleaseLabel - [int] $PrereleaseNumber - [bool] $IsPrerelease - [string] $RawVersion - # Regex inspired but simplified from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string - static [string] $SEMVER_REGEX = "(?0|[1-9]\d*)\.(?0|[1-9]\d*)\.(?0|[1-9]\d*)(?:-?(?[a-zA-Z-]*)(?:\.?(?0|[1-9]\d*)))?" - - static [AzureEngSemanticVersion] ParseVersionString([string] $versionString) + [int] $Major + [int] $Minor + [int] $Patch + [string] $PrereleaseLabelSeparator + [string] $PrereleaseLabel + [string] $PrereleaseNumberSeparator + [int] $PrereleaseNumber + [bool] $IsPrerelease + [string] $RawVersion + [bool] $IsSemVerFormat + [string] $DefaultPrereleaseLabel + # Regex inspired but simplified from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + static [string] $SEMVER_REGEX = "(?0|[1-9]\d*)\.(?0|[1-9]\d*)\.(?0|[1-9]\d*)(?:(?-?)(?[a-zA-Z-]*)(?\.?)(?0|[1-9]\d*))?" + + static [AzureEngSemanticVersion] ParseVersionString([string] $versionString) + { + $version = [AzureEngSemanticVersion]::new($versionString) + + if (!$version.IsSemVerFormat) { + return $null + } + return $version + } + + static [AzureEngSemanticVersion] ParsePythonVersionString([string] $versionString) + { + $version = [AzureEngSemanticVersion]::ParseVersionString($versionString) + + if (!$version) { + return $null + } + + $version.SetupPythonConventions() + return $version + } + + AzureEngSemanticVersion([string] $versionString) + { + if ($versionString -match "^$([AzureEngSemanticVersion]::SEMVER_REGEX)$") + { + $this.IsSemVerFormat = $true + $this.RawVersion = $versionString + $this.Major = [int]$matches.Major + $this.Minor = [int]$matches.Minor + $this.Patch = [int]$matches.Patch + + $this.SetupDefaultConventions() + + if ($null -eq $matches['prelabel']) + { + # artifically provide these values for non-prereleases to enable easy sorting of them later than prereleases. + $this.PrereleaseLabel = "zzz" + $this.PrereleaseNumber = 999 + $this.IsPrerelease = $false + } + else + { + $this.PrereleaseLabel = $matches["prelabel"] + $this.PrereleaseLabelSeparator = $matches["presep"] + $this.PrereleaseNumber = [int]$matches["prenumber"] + $this.PrereleaseNumberSeparator = $matches["prenumsep"] + $this.IsPrerelease = $true + } + } + else + { + $this.RawVersion = $versionString + $this.IsSemVerFormat = $false + } + } + + # If a prerelease label exists, it must be 'beta', and similar semantics used in our release guidelines + # See https://azure.github.io/azure-sdk/policies_releases.html#package-versioning + [bool] HasValidPrereleaseLabel() + { + if ($this.IsPrerelease -eq $true) { + if ($this.PrereleaseLabel -ne $this.DefaultPrereleaseLabel) { + Write-Host "Unexpected pre-release identifier '$($this.PrereleaseLabel)', should be '$($this.DefaultPrereleaseLabel)'" + return $false; + } + if ($this.PrereleaseNumber -lt 1) + { + Write-Host "Unexpected pre-release version '$($this.PrereleaseNumber)', should be >= '1'" + return $false; + } + } + return $true; + } + + [string] ToString() + { + $versionString = "{0}.{1}.{2}" -F $this.Major, $this.Minor, $this.Patch + + if ($this.IsPrerelease) + { + $versionString += $this.PrereleaseLabelSeparator + $this.PrereleaseLabel + $this.PrereleaseNumberSeparator + $this.PrereleaseNumber + } + return $versionString; + } + + [void] IncrementAndSetToPrerelease() { + if ($this.IsPrerelease -eq $false) + { + $this.PrereleaseLabel = $this.DefaultPrereleaseLabel + $this.PrereleaseNumber = 1 + $this.Minor++ + $this.Patch = 0 + $this.IsPrerelease = $true + } + else + { + $this.PrereleaseNumber++ + } + } + + [void] SetupPythonConventions() + { + # Python uses no separators and "b" for beta so this sets up the the object to work with those conventions + $this.PrereleaseLabelSeparator = $this.PrereleaseNumberSeparator = "" + $this.DefaultPrereleaseLabel = "b" + } + + [void] SetupDefaultConventions() + { + # Use the default common conventions + $this.PrereleaseLabelSeparator = "-" + $this.PrereleaseNumberSeparator = "." + $this.DefaultPrereleaseLabel = "beta" + } + + static [string[]] SortVersionStrings([string[]] $versionStrings) + { + $versions = $versionStrings | ForEach-Object { [AzureEngSemanticVersion]::ParseVersionString($_) } + $sortedVersions = [AzureEngSemanticVersion]::SortVersions($versions) + return ($sortedVersions | ForEach-Object { $_.ToString() }) + } + + static [AzureEngSemanticVersion[]] SortVersions([AzureEngSemanticVersion[]] $versions) + { + return ($versions | Sort-Object -Property Major, Minor, Patch, PrereleaseLabel, PrereleaseNumber -Descending) + } + + static [void] QuickTests() + { + $versions = @( + "1.0.1", + "2.0.0", + "2.0.0-alpha.20200920", + "2.0.0-beta.2", + "1.0.10", + "2.0.0-beta.1", + "2.0.0-beta.10", + "1.0.0", + "1.0.0b2", + "1.0.2") + + $expectedSort = @( + "2.0.0", + "2.0.0-beta.10", + "2.0.0-beta.2", + "2.0.0-beta.1", + "2.0.0-alpha.20200920", + "1.0.10", + "1.0.2", + "1.0.1", + "1.0.0", + "1.0.0b2") + + $sort = [AzureEngSemanticVersion]::SortVersionStrings($versions) + + for ($i = 0; $i -lt $expectedSort.Count; $i++) { - try { - return [AzureEngSemanticVersion]::new($versionString) - } - catch { - return $null - } - } - - AzureEngSemanticVersion([string] $versionString){ - if ($versionString -match "^$([AzureEngSemanticVersion]::SEMVER_REGEX)$") { - if ($null -eq $matches['prelabel']) { - # artifically provide these values for non-prereleases to enable easy sorting of them later than prereleases. - $prelabel = "zzz" - $prenumber = 999; - $isPre = $false; - } - else { - $prelabel = $matches["prelabel"] - $prenumber = [int]$matches["prenumber"] - $isPre = $true; - } - - $this.Major = [int]$matches.Major - $this.Minor = [int]$matches.Minor - $this.Patch = [int]$matches.Patch - $this.PrereleaseLabel = $prelabel - $this.PrereleaseNumber = $prenumber - $this.IsPrerelease = $isPre - $this.RawVersion = $versionString - } - else - { - throw "Invalid version string: '$versionString'" - } - } - - # If a prerelease label exists, it must be 'preview', and similar semantics used in our release guidelines - # See https://azure.github.io/azure-sdk/policies_releases.html#package-versioning - [bool] HasValidPrereleaseLabel(){ - if ($this.IsPrerelease -eq $true) { - if ($this.PrereleaseLabel -ne 'preview') { - Write-Error "Unexpected pre-release identifier '$this.PrereleaseLabel', should be 'preview'" - return $false; - } - if ($this.PrereleaseNumber -lt 1) - { - Write-Error "Unexpected pre-release version '$this.PrereleaseNumber', should be >= '1'" - return $false; - } - } - return $true; - } - - [string] ToString(){ - if ($this.IsPrerelease -eq $false) - { - $versionString = "{0}.{1}.{2}" -F $this.Major, $this.Minor, $this.Patch - } - else - { - $versionString = "{0}.{1}.{2}-{3}.{4}" -F $this.Major, $this.Minor, $this.Patch, $this.PrereleaseLabel, $this.PrereleaseNumber - } - return $versionString; - } - - [void] IncrementAndSetToPrerelease(){ - if ($this.IsPrerelease -eq $false) - { - $this.PrereleaseLabel = 'preview' - $this.PrereleaseNumber = 1 - $this.Minor++ - $this.Patch = 0 - $this.IsPrerelease = $true - } - else - { - $this.PrereleaseNumber++ - } + if ($sort[$i] -ne $expectedSort[$i]) { + Write-Host "Error: Incorrect sort:" + Write-Host "Expected: " + Write-Host $expectedSort + Write-Host "Actual:" + Write-Host $sort + break + } + } + + $devVerString = "1.2.3-alpha.20200828.1" + $devVerNew = [AzureEngSemanticVersion]::new($devVerString) + if (!$devVerNew -or $devVerNew.IsSemVerFormat -ne $false) { + Write-Host "Error: Didn't expect daily dev version to match our semver regex because of the extra .r" + } + $devVerparse = [AzureEngSemanticVersion]::ParseVersionString($devVerString) + if ($devVerparse) { + Write-Host "Error: Didn't expect daily dev version to parse because of the extra .r" + } + + $gaVerString = "1.2.3" + $gaVer = [AzureEngSemanticVersion]::ParseVersionString($gaVerString) + if ($gaVer.Major -ne 1 -or $gaVer.Minor -ne 2 -or $gaVer.Patch -ne 3) { + Write-Host "Error: Didn't correctly parse ga version string $gaVerString" + } + if ($gaVerString -ne $gaVer.ToString()) { + Write-Host "Error: Ga string did not correctly round trip with ToString" + } + $gaVer.IncrementAndSetToPrerelease() + if ("1.3.0-beta.1" -ne $gaVer.ToString()) { + Write-Host "Error: Ga string did not correctly increment" + } + + $betaVerString = "1.2.3-beta.4" + $betaVer = [AzureEngSemanticVersion]::ParseVersionString($betaVerString) + 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) { + Write-Host "Error: Didn't correctly parse beta version string $betaVerString" + } + if ($betaVerString -ne $betaVer.ToString()) { + Write-Host "Error: beta string did not correctly round trip with ToString" } + $betaVer.IncrementAndSetToPrerelease() + if ("1.2.3-beta.5" -ne $betaVer.ToString()) { + Write-Host "Error: Beta string did not correctly increment" + } + + $pythonBetaVerString = "1.2.3b4" + $pbetaVer = [AzureEngSemanticVersion]::ParsePythonVersionString($pythonBetaVerString) + 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) { + Write-Host "Error: Didn't correctly parse python beta string $pythonBetaVerString" + } + if ($pythonBetaVerString -ne $pbetaVer.ToString()) { + Write-Host "Error: python beta string did not correctly round trip with ToString" + } + $pbetaVer.IncrementAndSetToPrerelease() + if ("1.2.3b5" -ne $pbetaVer.ToString()) { + Write-Host "Error: Python beta string did not correctly increment" + } + + Write-Host "QuickTests done" + } }