Skip to content
Merged
87 changes: 87 additions & 0 deletions .github/scripts/apply-version.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
param(
[Parameter(Mandatory = $false)]
[string]$BuildNumber = "local",

[Parameter(Mandatory = $false)]
[string]$VersionFile = "version.cmake",

[Parameter(Mandatory = $false)]
[string]$HeaderPath = "src/Elemental/Elemental.h"
)

$ErrorActionPreference = "Stop"

function Get-CMakeValue
{
param(
[string]$Content,
[string]$Name
)

$pattern = '(?m)^\s*set\(\s*' + [Regex]::Escape($Name) + '\s+"(?<value>[^"]*)"\s*\)\s*$'
$match = [Regex]::Match($Content, $pattern)

if (!$match.Success)
{
throw "Unable to read $Name from $VersionFile."
}

return $match.Groups["value"].Value
}

$versionContent = Get-Content -Path $VersionFile -Raw
$major = Get-CMakeValue $versionContent "ELEM_VERSION_MAJOR"
$minor = Get-CMakeValue $versionContent "ELEM_VERSION_MINOR"
$patch = Get-CMakeValue $versionContent "ELEM_VERSION_PATCH"
$stage = Get-CMakeValue $versionContent "ELEM_VERSION_STAGE"

if ($major -notmatch '^\d+$' -or $minor -notmatch '^\d+$' -or $patch -notmatch '^\d+$')
{
throw "Elemental semantic version components must be numeric."
}

if (![string]::IsNullOrWhiteSpace($stage) -and $stage -notmatch '^(dev|alpha|beta|rc)$')
{
throw "Unsupported Elemental release stage: $stage"
}

$productVersion = "$major.$minor.$patch"

if ([string]::IsNullOrWhiteSpace($stage))
{
$version = $productVersion
}
else
{
if ($BuildNumber -notmatch '^(local|\d+)$')
{
throw "Prerelease build number must be 'local' or numeric: $BuildNumber"
}

$version = "$productVersion-$stage.$BuildNumber"
}

$header = Get-Content -Path $HeaderPath -Raw
$versionCommentPattern = '(?m)^// Version: [^\r\n]*(?=\r?$)'
$versionMacroPattern = '(?m)^#define ELEM_VERSION_LABEL "[^"\r\n]*"(?=\r?$)'

if ([Regex]::Matches($header, $versionCommentPattern).Count -ne 1)
{
throw "Expected exactly one version comment in $HeaderPath."
}

if ([Regex]::Matches($header, $versionMacroPattern).Count -ne 1)
{
throw "Expected exactly one ELEM_VERSION_LABEL definition in $HeaderPath."
}

$header = [Regex]::Replace($header, $versionCommentPattern, "// Version: $version")
$header = [Regex]::Replace($header, $versionMacroPattern, "#define ELEM_VERSION_LABEL `"$version`"")

[System.IO.File]::WriteAllText(
$HeaderPath,
$header,
[System.Text.UTF8Encoding]::new($false)
)

Write-Host "Elemental build version: $version"
205 changes: 205 additions & 0 deletions .github/scripts/release-notes.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
param(
[Parameter(Mandatory = $true)]
[string]$Version,

[Parameter(Mandatory = $true)]
[string]$Repository,

[Parameter(Mandatory = $true)]
[string]$OutputPath
)

$ErrorActionPreference = "Stop"

if ($Version -notmatch '^(?<product>\d+\.\d+\.\d+)(?:-(?<stage>dev|alpha|beta|rc)\.(?<build>\d+))?$')
{
throw "Unsupported release version: $Version"
}

$productVersion = $Matches["product"]
$stage = $Matches["stage"]

function Get-PreviousTag
{
param(
[string]$Revision
)

$tag = & git describe --tags --abbrev=0 $Revision 2>$null

if ($LASTEXITCODE -ne 0)
{
return $null
}

return ($tag | Select-Object -First 1).Trim()
}

function Get-LatestStableTag
{
$tags = @(
& git tag --merged HEAD --sort=-version:refname --list "v*" |
Where-Object { $_ -match '^v\d+\.\d+\.\d+$' }
)

if ($tags.Count -eq 0)
{
return $null
}

return $tags[0].Trim()
}

$previousTag = Get-PreviousTag "HEAD^"
$baseTag = $null
$rangeDescription = $null
$sameStageAsPreviousRelease = $false

if (![string]::IsNullOrWhiteSpace($stage) -and $null -ne $previousTag -and
$previousTag -match '^v(?<product>\d+\.\d+\.\d+)-(?<stage>dev|alpha|beta|rc)\.\d+$')
{
$sameStageAsPreviousRelease =
$Matches["product"] -eq $productVersion -and
$Matches["stage"] -eq $stage
}

if ($sameStageAsPreviousRelease)
{
$baseTag = $previousTag
$rangeDescription = "Includes changes since $baseTag."
}
else
{
$baseTag = Get-LatestStableTag

$rangeDescription = if ($null -eq $baseTag)
{
"Includes all changes in the $productVersion release cycle since the beginning of the project."
}
else
{
"Includes all changes in the $productVersion release cycle since $baseTag."
}
}

$commitArguments = if ($null -eq $baseTag)
{
@("rev-list", "--reverse", "HEAD")
}
else
{
@("rev-list", "--reverse", "$baseTag..HEAD")
}

$commits = @(& git @commitArguments)

if ($LASTEXITCODE -ne 0)
{
throw "Unable to determine commits for the release notes."
}

$pullRequests = @{}
$directCommits = [System.Collections.Generic.List[object]]::new()

foreach ($commit in $commits)
{
$sha = $commit.Trim()

if ([string]::IsNullOrWhiteSpace($sha))
{
continue
}

$json = (& gh api "repos/$Repository/commits/$sha/pulls" | Out-String)

if ($LASTEXITCODE -ne 0)
{
throw "Unable to find pull requests associated with commit $sha."
}

$associatedPullRequests = @(
$json | ConvertFrom-Json |
Where-Object { $null -ne $_.merged_at -and $_.base.ref -eq "main" }
)

if ($associatedPullRequests.Count -gt 0)
{
foreach ($pullRequest in $associatedPullRequests)
{
$pullRequests[$pullRequest.number.ToString()] = $pullRequest
}

continue
}

$subject = (& git show -s --format=%s $sha | Out-String).Trim()
$shortSha = (& git rev-parse --short $sha | Out-String).Trim()

$directCommits.Add([pscustomobject]@{
Sha = $shortSha
Subject = $subject
})
}

$orderedPullRequests = @(
$pullRequests.Values |
Sort-Object @{ Expression = { [DateTime]$_.merged_at } }, @{ Expression = { [int]$_.number } }
)

$releaseUrl = "https://github.com/$Repository/releases/tag/v$Version"
$notes = [System.Collections.Generic.List[string]]::new()
$notes.Add("# Elemental [**$Version**]($releaseUrl)")
$notes.Add("")
$notes.Add($rangeDescription)
$notes.Add("")

if ($orderedPullRequests.Count -gt 0)
{
$notes.Add("## Pull requests")
$notes.Add("")

foreach ($pullRequest in $orderedPullRequests)
{
$notes.Add("### [#$($pullRequest.number)]($($pullRequest.html_url)) — $($pullRequest.title)")
$notes.Add("")

if ([string]::IsNullOrWhiteSpace($pullRequest.body))
{
$notes.Add("_No description provided._")
}
else
{
$notes.Add($pullRequest.body.Trim())
}

$notes.Add("")
}
}

if ($directCommits.Count -gt 0)
{
$notes.Add("## Direct commits")
$notes.Add("")

foreach ($commit in $directCommits)
{
$notes.Add("- ``$($commit.Sha)`` $($commit.Subject)")
}

$notes.Add("")
}

if ($orderedPullRequests.Count -eq 0 -and $directCommits.Count -eq 0)
{
$notes.Add("No changes were found for this release range.")
$notes.Add("")
}

$parentDirectory = Split-Path -Parent $OutputPath

if (![string]::IsNullOrWhiteSpace($parentDirectory))
{
New-Item -ItemType Directory -Path $parentDirectory -Force | Out-Null
}

$notes | Set-Content -Path $OutputPath -Encoding utf8
Loading
Loading