Revert "Revert "Revert "Revert "Main"""" #16
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: Release Build | |
| on: | |
| pull_request: | |
| types: [closed] | |
| branches: | |
| - main | |
| permissions: | |
| contents: write | |
| jobs: | |
| prepare: | |
| name: Prepare release (check files, version, notes) | |
| runs-on: windows-latest | |
| outputs: | |
| should_release: ${{ steps.check_files.outputs.should_release }} | |
| version: ${{ steps.next_version.outputs.version }} | |
| version_number: ${{ steps.next_version.outputs.version_number }} | |
| notes: ${{ steps.parse_commit.outputs.notes }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check if source files changed | |
| id: check_files | |
| shell: pwsh | |
| run: | | |
| $changedFiles = git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | |
| Write-Output "Changed files:" | |
| Write-Output $changedFiles | |
| $sourceChanged = $false | |
| foreach ($file in $changedFiles) { | |
| if ($file -match '\.(cpp|h|hpp|c|cc|cxx)$') { | |
| $sourceChanged = $true | |
| Write-Output "Source file changed: $file" | |
| break | |
| } | |
| } | |
| if ($sourceChanged) { | |
| Write-Output "should_release=true" >> $env:GITHUB_OUTPUT | |
| Write-Output "Source files changed - will proceed with release" | |
| } else { | |
| Write-Output "should_release=false" >> $env:GITHUB_OUTPUT | |
| Write-Output "No source files changed - skipping release" | |
| } | |
| - name: Get latest release tag | |
| if: steps.check_files.outputs.should_release == 'true' | |
| id: get_latest_tag | |
| shell: pwsh | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| $repo = "${{ github.repository }}" | |
| $headers = @{"Authorization" = "token $env:GITHUB_TOKEN"; "Accept" = "application/vnd.github.v3+json"} | |
| # Try GitHub Releases API first (includes prereleases) | |
| try { | |
| $releases = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/releases?per_page=100" -Headers $headers -UseBasicParsing | |
| } catch { | |
| $releases = @() | |
| } | |
| $candidates = @() | |
| foreach ($r in $releases) { | |
| if ($r.tag_name -match 'v?(\d+)\.(\d+)\.(\d+)') { | |
| $candidates += [PSCustomObject]@{ Tag = $r.tag_name; Major = [int]$Matches[1]; Minor = [int]$Matches[2]; Patch = [int]$Matches[3] } | |
| } | |
| } | |
| if ($candidates.Count -gt 0) { | |
| $sorted = $candidates | Sort-Object -Property @{Expression={$_.Major};Descending=$true}, @{Expression={$_.Minor};Descending=$true}, @{Expression={$_.Patch};Descending=$true} | |
| $latestTag = $sorted[0].Tag | |
| Write-Output "latest_tag=$latestTag" >> $env:GITHUB_OUTPUT | |
| exit 0 | |
| } | |
| # Fallback: ensure tags are fetched and inspect local tags | |
| git fetch --tags --prune || true | |
| $local = git tag -l "v[0-9]*.[0-9]*.[0-9]*" | |
| if ($local) { | |
| $sortedTags = $local | ForEach-Object { | |
| if ($_ -match 'v?(\d+)\.(\d+)\.(\d+)') { | |
| [PSCustomObject]@{ Tag = $_; Major = [int]$Matches[1]; Minor = [int]$Matches[2]; Patch = [int]$Matches[3] } | |
| } | |
| } | Sort-Object -Property @{Expression={$_.Major};Descending=$true}, @{Expression={$_.Minor};Descending=$true}, @{Expression={$_.Patch};Descending=$true} | |
| if ($sortedTags.Count -gt 0) { | |
| $latestTag = $sortedTags[0].Tag | |
| Write-Output "latest_tag=$latestTag" >> $env:GITHUB_OUTPUT | |
| } else { | |
| Write-Output "latest_tag=v0.0.0" >> $env:GITHUB_OUTPUT | |
| } | |
| } else { | |
| Write-Output "latest_tag=v0.0.0" >> $env:GITHUB_OUTPUT | |
| } | |
| - name: Determine next version | |
| if: steps.check_files.outputs.should_release == 'true' | |
| id: next_version | |
| shell: pwsh | |
| run: | | |
| $latestTag = "${{ steps.get_latest_tag.outputs.latest_tag }}" | |
| if ($latestTag -match 'v?(\d+)\.(\d+)\.(\d+)') { | |
| $major = [int]$Matches[1] | |
| $minor = [int]$Matches[2] | |
| $patch = [int]$Matches[3] | |
| $patch = $patch + 1 | |
| $newVersion = "v$major.$minor.$patch" | |
| Write-Output "version=$newVersion" >> $env:GITHUB_OUTPUT | |
| Write-Output "version_number=$major.$minor.$patch" >> $env:GITHUB_OUTPUT | |
| } else { | |
| Write-Error "Could not parse version from tag: $latestTag" | |
| exit 1 | |
| } | |
| - name: Parse commit message for release notes | |
| if: steps.check_files.outputs.should_release == 'true' | |
| id: parse_commit | |
| shell: pwsh | |
| run: | | |
| $commitMsg = git log -1 --pretty=%B | |
| $commitSubject = $commitMsg.Split("`n")[0] | |
| $commitBodyLines = $commitMsg.Split("`n") | Select-Object -Skip 1 | |
| $commitBody = ($commitBodyLines -join "`n").Trim() | |
| $releaseNotes = "## Changes`n`n" | |
| if ($commitSubject -match '^(fix|feat|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?:\s*(.+)$') { | |
| $type = $Matches[1] | |
| $description = $Matches[3] | |
| $typeLabel = switch ($type) { | |
| 'fix' { '🐛 Fix' } | |
| 'feat' { '✨ Feature' } | |
| 'refactor' { '♻️ Refactor' } | |
| 'test' { '✅ Test' } | |
| 'chore' { '🔧 Chore' } | |
| 'perf' { '⚡ Performance' } | |
| 'build' { '📦 Build' } | |
| 'revert' { '⏪ Revert' } | |
| default { '📝 Update' } | |
| } | |
| $releaseNotes += "**$typeLabel**: $description`n" | |
| } else { | |
| $releaseNotes += "$commitSubject`n" | |
| } | |
| if ($commitBody -ne "") { | |
| $maxBodyLength = 1000 | |
| if ($commitBody.Length -gt $maxBodyLength) { | |
| $commitBody = $commitBody.Substring(0, $maxBodyLength) + "..." | |
| } | |
| $releaseNotes += "`n$commitBody`n" | |
| } | |
| $commitSha = git rev-parse --short HEAD | |
| $releaseNotes += "`n---`n*Commit: $commitSha*" | |
| $delimiter = "EOF_$(Get-Random)" | |
| Write-Output "notes<<$delimiter" >> $env:GITHUB_OUTPUT | |
| Write-Output $releaseNotes >> $env:GITHUB_OUTPUT | |
| Write-Output $delimiter >> $env:GITHUB_OUTPUT | |
| build: | |
| name: Build matrix for multiple Windows architectures | |
| needs: prepare | |
| if: needs.prepare.outputs.should_release == 'true' | |
| runs-on: windows-latest | |
| strategy: | |
| matrix: | |
| arch: [x64, x86, arm64] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Compile for ${{ matrix.arch }} | |
| shell: cmd | |
| run: | | |
| @echo off | |
| REM Find vcvarsall.bat dynamically | |
| for /f "usebackq tokens=*" %%i in (`"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do ( | |
| set "VS_PATH=%%i" | |
| ) | |
| if not exist "%VS_PATH%\VC\Auxiliary\Build\vcvarsall.bat" ( | |
| echo Error: vcvarsall.bat not found. | |
| exit /b 1 | |
| ) | |
| REM Map architecture for cross-compilation (host_target) | |
| REM GitHub Actions windows-latest runners are x64, so we need x64_<target> for cross-compilation | |
| set "TARGET_ARCH=${{ matrix.arch }}" | |
| set "VCVARS_ARCH=%TARGET_ARCH%" | |
| if "%TARGET_ARCH%"=="x86" set "VCVARS_ARCH=x64_x86" | |
| if "%TARGET_ARCH%"=="arm64" set "VCVARS_ARCH=x64_arm64" | |
| REM Initialize environment for the target architecture | |
| call "%VS_PATH%\VC\Auxiliary\Build\vcvarsall.bat" %VCVARS_ARCH% | |
| set outName=win-witr-${{ matrix.arch }}.exe | |
| echo Compiling %outName%... | |
| cl /O2 /Ot /GL /std:c++20 /EHsc main.cpp /DUNICODE /D_UNICODE /Fe:%outName% | |
| if errorlevel 1 exit /b 1 | |
| - name: Upload build artifact for ${{ matrix.arch }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: win-witr-${{ matrix.arch }} | |
| path: win-witr-${{ matrix.arch }}.exe | |
| create-release: | |
| name: Create GitHub Release with all artifacts | |
| needs: build | |
| if: needs.prepare.outputs.should_release == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download all build artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| - name: List artifacts | |
| run: ls -R artifacts | |
| - name: Checkout repository for tagging | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Ensure release tag exists and push | |
| if: needs.prepare.outputs.version != '' | |
| shell: bash | |
| run: | | |
| set -e | |
| TAG="${{ needs.prepare.outputs.version }}" | |
| echo "Preparing tag: $TAG" | |
| if [ -z "$TAG" ]; then | |
| echo "No tag provided; cannot create release" | |
| exit 1 | |
| fi | |
| if git rev-parse "$TAG" >/dev/null 2>&1; then | |
| echo "Tag $TAG already exists locally" | |
| else | |
| git tag -a "$TAG" -m "Release $TAG" | |
| echo "Created local tag $TAG" | |
| fi | |
| # Push tag (skip errors if it already exists on remote) | |
| git push origin "$TAG" || echo "Tag push failed or already exists on remote" | |
| - name: Create Release and Upload Assets | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.prepare.outputs.version }} | |
| name: win-witr ${{ needs.prepare.outputs.version }} | |
| body: ${{ needs.prepare.outputs.notes }} | |
| files: artifacts/*/*.exe | |
| draft: false | |
| prerelease: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |