Add binary size check workflow #1
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Binary Size Check | |
| on: | |
| pull_request: | |
| branches: [master] | |
| concurrency: | |
| group: size-check-${{ github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| jobs: | |
| size-check: | |
| runs-on: windows-latest | |
| timeout-minutes: 90 | |
| permissions: | |
| pull-requests: write | |
| contents: read | |
| steps: | |
| - name: Checkout base ref | |
| uses: actions/checkout@v5 | |
| with: | |
| ref: ${{ github.event.pull_request.base.sha }} | |
| path: base | |
| - name: Checkout head ref | |
| uses: actions/checkout@v5 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha }} | |
| path: head | |
| - name: Configure base (Win32 x64 D3D11, matches CI flags) | |
| shell: cmd | |
| working-directory: base | |
| run: | | |
| cmake -G "Visual Studio 17 2022" ^ | |
| -B build\Win32_x64 ^ | |
| -A x64 ^ | |
| -D BX_CONFIG_DEBUG=ON ^ | |
| -D GRAPHICS_API=D3D11 ^ | |
| -D BGFX_CONFIG_MAX_FRAME_BUFFERS=256 ^ | |
| -D BABYLON_DEBUG_TRACE=ON | |
| - name: Build base Playground | |
| shell: cmd | |
| working-directory: base | |
| run: cmake --build build\Win32_x64 --config RelWithDebInfo --target Playground -- /m | |
| - name: Configure head (same flags) | |
| shell: cmd | |
| working-directory: head | |
| run: | | |
| cmake -G "Visual Studio 17 2022" ^ | |
| -B build\Win32_x64 ^ | |
| -A x64 ^ | |
| -D BX_CONFIG_DEBUG=ON ^ | |
| -D GRAPHICS_API=D3D11 ^ | |
| -D BGFX_CONFIG_MAX_FRAME_BUFFERS=256 ^ | |
| -D BABYLON_DEBUG_TRACE=ON | |
| - name: Build head Playground | |
| shell: cmd | |
| working-directory: head | |
| run: cmake --build build\Win32_x64 --config RelWithDebInfo --target Playground -- /m | |
| - name: Measure sizes and format comment | |
| id: measure | |
| shell: pwsh | |
| run: | | |
| $marker = '<!-- binary-size-check -->' | |
| $config = 'RelWithDebInfo' | |
| $platform = 'Win32 x64 D3D11 RelWithDebInfo' | |
| $baseBuild = Join-Path $env:GITHUB_WORKSPACE 'base\build\Win32_x64' | |
| $headBuild = Join-Path $env:GITHUB_WORKSPACE 'head\build\Win32_x64' | |
| $baseRef = '${{ github.event.pull_request.base.sha }}' | |
| $headRef = '${{ github.event.pull_request.head.sha }}' | |
| $playgroundRel = "Apps\Playground\$config\Playground.exe" | |
| function Get-Artifacts($root, $cfg) { | |
| if (-not (Test-Path $root)) { return @() } | |
| Get-ChildItem $root -Recurse -File -Include *.exe,*.dll,*.lib -ErrorAction SilentlyContinue | | |
| Where-Object { $_.FullName -match "\\$cfg\\" } | | |
| ForEach-Object { | |
| [PSCustomObject]@{ | |
| Rel = $_.FullName.Substring($root.Length + 1) | |
| Size = $_.Length | |
| } | |
| } | |
| } | |
| $baseArts = Get-Artifacts $baseBuild $config | |
| $headArts = Get-Artifacts $headBuild $config | |
| if ($baseArts.Count -eq 0 -or $headArts.Count -eq 0) { | |
| throw "No artifacts found. base=$($baseArts.Count) head=$($headArts.Count). Build likely failed." | |
| } | |
| function FormatBytes([long]$n) { | |
| $sign = if ($n -lt 0) { '-' } else { '+' } | |
| $abs = [math]::Abs($n) | |
| if ($abs -ge 1MB) { return "$sign$([math]::Round($abs/1MB, 2)) MB" } | |
| if ($abs -ge 1KB) { return "$sign$([math]::Round($abs/1KB, 1)) KB" } | |
| return "$sign$abs B" | |
| } | |
| function FormatCount([long]$n) { '{0:N0}' -f $n } | |
| # Headline: Playground.exe | |
| $basePg = $baseArts | Where-Object { $_.Rel -match 'Playground\.exe$' } | Select-Object -First 1 | |
| $headPg = $headArts | Where-Object { $_.Rel -match 'Playground\.exe$' } | Select-Object -First 1 | |
| if (-not $basePg -or -not $headPg) { | |
| throw "Playground.exe missing. base=$($basePg -ne $null) head=$($headPg -ne $null)" | |
| } | |
| $pgDelta = $headPg.Size - $basePg.Size | |
| $pgPct = if ($basePg.Size -gt 0) { [math]::Round(100 * $pgDelta / $basePg.Size, 3) } else { 0 } | |
| # Build a name->sizes map for delta comparison | |
| $pairs = @{} | |
| foreach ($a in $baseArts) { $pairs[$a.Rel] = @{ Base = $a.Size; Head = 0 } } | |
| foreach ($a in $headArts) { | |
| if (-not $pairs.ContainsKey($a.Rel)) { $pairs[$a.Rel] = @{ Base = 0; Head = 0 } } | |
| $pairs[$a.Rel].Head = $a.Size | |
| } | |
| $rows = $pairs.Keys | ForEach-Object { | |
| $p = $pairs[$_] | |
| [PSCustomObject]@{ | |
| Rel = $_ | |
| Base = $p.Base | |
| Head = $p.Head | |
| Delta = $p.Head - $p.Base | |
| } | |
| } | |
| # Top movers (absolute delta, excluding Playground.exe which we already feature) | |
| $top = $rows | | |
| Where-Object { $_.Rel -notmatch 'Playground\.exe$' -and [math]::Abs($_.Delta) -gt 256 } | | |
| Sort-Object { [math]::Abs($_.Delta) } -Descending | | |
| Select-Object -First 10 | |
| $baseTotal = ($baseArts | Measure-Object Size -Sum).Sum | |
| $headTotal = ($headArts | Measure-Object Size -Sum).Sum | |
| $totalDelta = $headTotal - $baseTotal | |
| $totalPct = if ($baseTotal -gt 0) { [math]::Round(100 * $totalDelta / $baseTotal, 3) } else { 0 } | |
| # Build markdown | |
| $sb = [System.Text.StringBuilder]::new() | |
| [void]$sb.AppendLine($marker) | |
| [void]$sb.AppendLine('## Binary size impact') | |
| [void]$sb.AppendLine('') | |
| [void]$sb.AppendLine("Platform: ``$platform`` | base ``$($baseRef.Substring(0,7))`` | head ``$($headRef.Substring(0,7))``") | |
| [void]$sb.AppendLine('') | |
| [void]$sb.AppendLine('### Final linked binary') | |
| [void]$sb.AppendLine('') | |
| [void]$sb.AppendLine('| Artifact | Base | Head | Δ bytes | Δ% |') | |
| [void]$sb.AppendLine('|---|---:|---:|---:|---:|') | |
| [void]$sb.AppendLine("| **Playground.exe** | $(FormatCount $basePg.Size) | $(FormatCount $headPg.Size) | **$(FormatBytes $pgDelta)** | **$pgPct %** |") | |
| [void]$sb.AppendLine("| Aggregate (.exe/.dll/.lib) | $(FormatCount $baseTotal) | $(FormatCount $headTotal) | $(FormatBytes $totalDelta) | $totalPct % |") | |
| [void]$sb.AppendLine('') | |
| if ($top -and $top.Count -gt 0) { | |
| [void]$sb.AppendLine('### Top movers (other static libs / DLLs, |Δ| > 256 B)') | |
| [void]$sb.AppendLine('') | |
| [void]$sb.AppendLine('| Artifact | Base | Head | Δ bytes |') | |
| [void]$sb.AppendLine('|---|---:|---:|---:|') | |
| foreach ($r in $top) { | |
| $shortRel = $r.Rel -replace '\\', '/' | |
| [void]$sb.AppendLine("| ``$shortRel`` | $(FormatCount $r.Base) | $(FormatCount $r.Head) | $(FormatBytes $r.Delta) |") | |
| } | |
| [void]$sb.AppendLine('') | |
| } else { | |
| [void]$sb.AppendLine('_No other artifact moved by more than 256 B._') | |
| [void]$sb.AppendLine('') | |
| } | |
| [void]$sb.AppendLine('---') | |
| [void]$sb.AppendLine('<sub>Comparison is informational only — no threshold enforcement. Configuration matches the Win32_x64_D3D11 CI job. Two sequential Release-with-debug-info builds per run. Small deltas on unchanged libs are typically COFF timestamps.</sub>') | |
| $body = $sb.ToString() | |
| $bodyFile = Join-Path $env:RUNNER_TEMP 'size-comment.md' | |
| $body | Set-Content -Path $bodyFile -Encoding utf8 | |
| Write-Host "=== comment body ===" | |
| Write-Host $body | |
| Write-Host "====================" | |
| # Surface in run summary too | |
| $body | Add-Content -Path $env:GITHUB_STEP_SUMMARY -Encoding utf8 | |
| "body_file=$bodyFile" | Add-Content -Path $env:GITHUB_OUTPUT -Encoding utf8 | |
| "marker=$marker" | Add-Content -Path $env:GITHUB_OUTPUT -Encoding utf8 | |
| - name: Post or update sticky PR comment | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| $marker = '${{ steps.measure.outputs.marker }}' | |
| $bodyFile = '${{ steps.measure.outputs.body_file }}' | |
| $pr = ${{ github.event.pull_request.number }} | |
| $repo = '${{ github.repository }}' | |
| try { | |
| $comments = gh api "repos/$repo/issues/$pr/comments" --paginate | ConvertFrom-Json | |
| if ($LASTEXITCODE -ne 0) { throw "gh api list comments exited $LASTEXITCODE" } | |
| $existing = $comments | Where-Object { $_.body -like "*$marker*" } | Select-Object -First 1 | |
| if ($existing) { | |
| Write-Host "Updating existing comment $($existing.id)" | |
| gh api "repos/$repo/issues/comments/$($existing.id)" --method PATCH -F "body=@$bodyFile" | Out-Null | |
| } else { | |
| Write-Host "Creating new comment" | |
| gh api "repos/$repo/issues/$pr/comments" --method POST -F "body=@$bodyFile" | Out-Null | |
| } | |
| if ($LASTEXITCODE -ne 0) { throw "gh api comment write exited $LASTEXITCODE" } | |
| Write-Host "Posted comment OK." | |
| } catch { | |
| Write-Warning "Could not post PR comment (likely no write token on fork PR): $_" | |
| Write-Host "Size details are still available in the run summary above." | |
| } |