diff --git a/eng/common/scripts/ChangeLog-Operations.ps1 b/eng/common/scripts/ChangeLog-Operations.ps1 index d644d022658a..43dba5d74e6f 100644 --- a/eng/common/scripts/ChangeLog-Operations.ps1 +++ b/eng/common/scripts/ChangeLog-Operations.ps1 @@ -1,6 +1,10 @@ # Common Changelog Operations +. "${PSScriptRoot}\logging.ps1" +. "${PSScriptRoot}\SemVer.ps1" -$RELEASE_TITLE_REGEX = "(?^\#+.*(?\b\d+\.\d+\.\d+([^0-9\s][^\s:]+)?)(\s(?\(Unreleased\)|\(\d{4}-\d{2}-\d{2}\)))?)" +$RELEASE_TITLE_REGEX = "(?^\#+.*(?\b\d+\.\d+\.\d+([^0-9\s][^\s:]+)?)(\s+(?\(Unreleased\)|\(\d{4}-\d{2}-\d{2}\)))?)" +$CHANGELOG_UNRELEASED_STATUS = "(Unreleased)" +$CHANGELOG_DATE_FORMAT = "yyyy-MM-dd" # Returns a Collection of changeLogEntry object containing changelog info for all version present in the gived CHANGELOG function Get-ChangeLogEntries { @@ -11,7 +15,7 @@ function Get-ChangeLogEntries { $changeLogEntries = @{} if (!(Test-Path $ChangeLogLocation)) { - Write-Error "ChangeLog[${ChangeLogLocation}] does not exist" + LogError "ChangeLog[${ChangeLogLocation}] does not exist" return $null } @@ -23,9 +27,9 @@ function Get-ChangeLogEntries { if ($line -match $RELEASE_TITLE_REGEX) { $changeLogEntry = [pscustomobject]@{ ReleaseVersion = $matches["version"] - ReleaseStatus = $matches["releaseStatus"] - ReleaseTitle = $line - ReleaseContent = @() # Release content without the version title + ReleaseStatus = $matches["releaseStatus"] + ReleaseTitle = "## {0} {1}" -f $matches["version"], $matches["releaseStatus"] + ReleaseContent = @() } $changeLogEntries[$changeLogEntry.ReleaseVersion] = $changeLogEntry } @@ -72,6 +76,7 @@ function Get-ChangeLogEntryAsString { return ChangeLogEntryAsString $changeLogEntry } + function ChangeLogEntryAsString($changeLogEntry) { if (!$changeLogEntry) { return "[Missing change log entry]" @@ -93,7 +98,7 @@ function Confirm-ChangeLogEntry { $changeLogEntry = Get-ChangeLogEntry -ChangeLogLocation $ChangeLogLocation -VersionString $VersionString if (!$changeLogEntry) { - Write-Error "ChangeLog[${ChangeLogLocation}] does not have an entry for version ${VersionString}." + LogError "ChangeLog[${ChangeLogLocation}] does not have an entry for version ${VersionString}." return $false } @@ -103,28 +108,108 @@ function Confirm-ChangeLogEntry { Write-Host "-----" if ([System.String]::IsNullOrEmpty($changeLogEntry.ReleaseStatus)) { - Write-Error "Entry does not have a correct release status. Please ensure the status is set to a date '(yyyy-MM-dd)' or '(Unreleased)' if not yet released." + LogError "Entry does not have a correct release status. Please ensure the status is set to a date '($CHANGELOG_DATE_FORMAT)' or '$CHANGELOG_UNRELEASED_STATUS' if not yet released." return $false } if ($ForRelease -eq $True) { - if ($changeLogEntry.ReleaseStatus -eq "(Unreleased)") { - Write-Error "Entry has no release date set. Please ensure to set a release date with format 'yyyy-MM-dd'." + if ($changeLogEntry.ReleaseStatus -eq $CHANGELOG_UNRELEASED_STATUS) { + LogError "Entry has no release date set. Please ensure to set a release date with format '$CHANGELOG_DATE_FORMAT'." return $false } + else { + $status = $changeLogEntry.ReleaseStatus.Trim().Trim("()") + try { + [DateTime]$status + } + catch { + LogError "Invalid date [ $status ] passed as status for Version [$($changeLogEntry.ReleaseVersion)]." + return $false + } + } if ([System.String]::IsNullOrWhiteSpace($changeLogEntry.ReleaseContent)) { - Write-Error "Entry has no content. Please ensure to provide some content of what changed in this version." + LogError "Entry has no content. Please ensure to provide some content of what changed in this version." return $false } } return $true } -function Set-TestChangeLog($TestVersion, $changeLogFile, $ReleaseEntry) { - Set-Content -Path $changeLogFile -Value @" -# Release History -## $TestVersion ($(Get-Date -f "yyyy-MM-dd")) -- $ReleaseEntry -"@ +function New-ChangeLogEntry { + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String]$Version, + [String]$Status=$CHANGELOG_UNRELEASED_STATUS, + [String[]]$Content + ) + + # Validate RelaseStatus + $Status = $Status.Trim().Trim("()") + if ($Status -ne "Unreleased") { + try { + $Status = ([DateTime]$Status).ToString($CHANGELOG_DATE_FORMAT) + } + catch { + LogWarning "Invalid date [ $Status ] passed as status for Version [$Version]. Please use a valid date in the format '$CHANGELOG_DATE_FORMAT' or use '$CHANGELOG_UNRELEASED_STATUS'" + return $null + } + } + $Status = "($Status)" + + # Validate Version + try { + $Version = ([AzureEngSemanticVersion]::ParseVersionString($Version)).ToString() + } + catch { + LogWarning "Invalid version [ $Version ]." + return $null + } + + if (!$Content) { $Content = @() } + + $newChangeLogEntry = [pscustomobject]@{ + ReleaseVersion = $Version + ReleaseStatus = $Status + ReleaseTitle = "## $Version $Status" + ReleaseContent = $Content + } + + return $newChangeLogEntry +} + +function Set-ChangeLogContent { + param ( + [Parameter(Mandatory = $true)] + [String]$ChangeLogLocation, + [Parameter(Mandatory = $true)] + $ChangeLogEntries + ) + + $changeLogContent = @() + $changeLogContent += "# Release History" + $changeLogContent += "" + + try + { + $VersionsSorted = [AzureEngSemanticVersion]::SortVersionStrings($ChangeLogEntries.Keys) + } + catch { + LogError "Problem sorting version in ChangeLogEntries" + return + } + + foreach ($version in $VersionsSorted) { + $changeLogEntry = $ChangeLogEntries[$version] + $changeLogContent += $changeLogEntry.ReleaseTitle + if ($changeLogEntry.ReleaseContent.Count -eq 0) { + $changeLogContent += @("","") + } + else { + $changeLogContent += $changeLogEntry.ReleaseContent + } + } + + Set-Content -Path $ChangeLogLocation -Value $changeLogContent } \ No newline at end of file diff --git a/eng/common/scripts/Package-Properties.ps1 b/eng/common/scripts/Package-Properties.ps1 index f17b244f3f22..d75b897f28c6 100644 --- a/eng/common/scripts/Package-Properties.ps1 +++ b/eng/common/scripts/Package-Properties.ps1 @@ -1,4 +1,6 @@ # Helper functions for retireving useful information from azure-sdk-for-* repo +. "${PSScriptRoot}\logging.ps1" + class PackageProps { [string]$Name @@ -81,7 +83,7 @@ function Get-PkgProperties $serviceDirectoryPath = Join-Path $RepoRoot "sdk" $ServiceDirectory if (!(Test-Path $serviceDirectoryPath)) { - Write-Error "Service Directory $ServiceDirectory does not exist" + LogError "Service Directory $ServiceDirectory does not exist" exit 1 } @@ -97,7 +99,7 @@ function Get-PkgProperties } else { - Write-Error "The function '$GetPackageInfoFromRepoFn' was not found." + LogError "The function '$GetPackageInfoFromRepoFn' was not found." } if ($pkgProps -ne $null) @@ -105,7 +107,7 @@ function Get-PkgProperties return $pkgProps } } - Write-Error "Failed to retrive Properties for $PackageName" + LogError "Failed to retrive Properties for $PackageName" } # Takes ServiceName and Repo Root Directory @@ -175,7 +177,7 @@ function Get-PkgListFromYml ($ciYmlPath) } if ($artifactsInCI -eq $null) { - Write-Error "Failed to retrive package names in ci $ciYmlPath" + LogError "Failed to retrive package names in ci $ciYmlPath" } return $artifactsInCI } \ No newline at end of file diff --git a/eng/common/scripts/SemVer.ps1 b/eng/common/scripts/SemVer.ps1 index 0efdfae9cd42..ab4eef38052b 100644 --- a/eng/common/scripts/SemVer.ps1 +++ b/eng/common/scripts/SemVer.ps1 @@ -22,8 +22,10 @@ class AzureEngSemanticVersion { [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 [string] $ParseLanguage = $Language static [AzureEngSemanticVersion] ParseVersionString([string] $versionString) { @@ -57,7 +59,12 @@ class AzureEngSemanticVersion { $this.Minor = [int]$matches.Minor $this.Patch = [int]$matches.Patch - $this.SetupDefaultConventions() + if ([AzureEngSemanticVersion]::ParseLanguage -eq "python") { + $this.SetupPythonConventions() + } + else { + $this.SetupDefaultConventions() + } if ($null -eq $matches['prelabel']) { diff --git a/eng/common/scripts/Update-ChangeLog.ps1 b/eng/common/scripts/Update-ChangeLog.ps1 new file mode 100644 index 000000000000..eb506076c62b --- /dev/null +++ b/eng/common/scripts/Update-ChangeLog.ps1 @@ -0,0 +1,119 @@ +# Note: This script will add or replace version title in change log + +# Parameter description +# Version : Version to add or replace in change log +# Unreleased: Default is true. If it is set to false, then today's date will be set in verion title. If it is True then title will show "Unreleased" +# ReplaceLatestEntry: Replaces the latest changelog entry, including its content. + +param ( + [Parameter(Mandatory = $true)] + [String]$Version, + [Parameter(Mandatory = $true)] + [String]$ServiceDirectory, + [Parameter(Mandatory = $true)] + [String]$PackageName, + [boolean]$Unreleased=$True, + [boolean]$ReplaceLatestEntry = $False, + [String]$ReleaseDate +) + +if ($ReleaseDate -and ($Unreleased -eq $True)) { + LogError "Do not pass 'ReleaseDate' arguement when 'Unreleased' is true" + exit 1 +} + +. "${PSScriptRoot}\common.ps1" + +if ($ReleaseDate) +{ + try { + $ReleaseStatus = ([DateTime]$ReleaseDate).ToString($CHANGELOG_DATE_FORMAT) + $ReleaseStatus = "($ReleaseStatus)" + } + catch { + LogError "Invalid 'ReleaseDate'. Please use a valid date in the format '$CHANGELOG_DATE_FORMAT'" + exit 1 + } +} +elseif ($Unreleased) { + $ReleaseStatus = $CHANGELOG_UNRELEASED_STATUS +} +else { + $ReleaseStatus = "$(Get-Date -Format $CHANGELOG_DATE_FORMAT)" + $ReleaseStatus = "($ReleaseStatus)" +} + +if ($null -eq [AzureEngSemanticVersion]::ParseVersionString($Version)) +{ + LogError "Version [$Version] is invalid. Please use a valid SemVer" + exit(0) +} + +$PkgProperties = Get-PkgProperties -PackageName $PackageName -ServiceDirectory $ServiceDirectory +$ChangeLogEntries = Get-ChangeLogEntries -ChangeLogLocation $PkgProperties.ChangeLogPath + + +if ($ChangeLogEntries.Contains($Version)) +{ + if ($ChangeLogEntries[$Version].ReleaseStatus -eq $ReleaseStatus) + { + LogWarning "Version is already present in change log with specificed ReleaseStatus [$ReleaseStatus]" + exit(0) + } + + if ($Unreleased -and ($ChangeLogEntries[$Version].ReleaseStatus -ne $ReleaseStatus)) + { + LogWarning "Version is already present in change log with a release date. Please review [$($PkgProperties.ChangeLogPath)]" + exit(0) + } + + if (!$Unreleased -and ($ChangeLogEntries[$Version].ReleaseStatus -ne $CHANGELOG_UNRELEASED_STATUS)) + { + if ((Get-Date ($ChangeLogEntries[$Version].ReleaseStatus).Trim("()")) -gt (Get-Date $ReleaseStatus.Trim("()"))) + { + LogWarning "New ReleaseDate for version [$Version] is older than existing release date in changelog. Please review [$($PkgProperties.ChangeLogPath)]" + exit(0) + } + } +} + +$PresentVersionsSorted = [AzureEngSemanticVersion]::SortVersionStrings($ChangeLogEntries.Keys) +$LatestVersion = $PresentVersionsSorted[0] + +$LatestsSorted = [AzureEngSemanticVersion]::SortVersionStrings(@($LatestVersion, $Version)) +if ($LatestsSorted[0] -ne $Version) { + LogWarning "Passed Version [$Version] is older than the latestversion [$LatestVersion] in the changelog. Please use a more recent version." + exit(0) +} + +if ($ReplaceLatestEntry) +{ + $ChangeLogEntries.Remove($LatestVersion) + $newChangeLogEntry = New-ChangeLogEntry -Version $Version -Status $ReleaseStatus + if ($newChangeLogEntry) { + $ChangeLogEntries[$Version] = $newChangeLogEntry + } + else { + LogError "Failed to create new changelog entry" + } +} +elseif ($ChangeLogEntries.Contains($Version)) +{ + $ChangeLogEntries[$Version].ReleaseVersion = $Version + $ChangeLogEntries[$Version].ReleaseStatus = $ReleaseStatus + $ChangeLogEntries[$Version].ReleaseTitle = "## $Version $ReleaseStatus" +} +else +{ + $newChangeLogEntry = New-ChangeLogEntry -Version $Version -Status $ReleaseStatus + if ($newChangeLogEntry) { + $ChangeLogEntries[$Version] = $newChangeLogEntry + } + else { + LogError "Failed to create new changelog entry" + } +} + +Set-ChangeLogContent -ChangeLogLocation $PkgProperties.ChangeLogPath -ChangeLogEntries $ChangeLogEntries + +