...

Text file src/github.com/Azure/azure-sdk-for-go/eng/common/scripts/SemVer.ps1

Documentation: github.com/Azure/azure-sdk-for-go/eng/common/scripts

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

View as plain text