OMG IS THIS WORK NO #17
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 | ||
| actions: write | ||
| checks: write | ||
| jobs: | ||
| prepare: | ||
| name: Prepare release (check files, version, notes) | ||
| runs-on: ubuntu-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: | | ||
| Write-Output "DEBUG: Starting check_files" | ||
| Write-Output "DEBUG: PR Event Data - Base: ${{ github.event.pull_request.base.sha }} - Head: ${{ github.event.pull_request.head.sha }}" | ||
| $changedFiles = git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | ||
| Write-Output "DEBUG: Changed files found:" | ||
| Write-Output $changedFiles | ||
| $sourceChanged = $false | ||
| foreach ($file in $changedFiles) { | ||
| if ($file -match '\.(cpp|h|hpp|c|cc|cxx)$') { | ||
| $sourceChanged = $true | ||
| Write-Output "DEBUG: Source file changed: $file" | ||
| break | ||
| } | ||
| } | ||
| Write-Output "DEBUG: sourceChanged is $sourceChanged" | ||
| if ($sourceChanged) { | ||
| Write-Output "should_release=true" >> $env:GITHUB_OUTPUT | ||
| Write-Output "DEBUG: Setting should_release=true" | ||
| } else { | ||
| Write-Output "should_release=false" >> $env:GITHUB_OUTPUT | ||
| Write-Output "DEBUG: Setting should_release=false" | ||
| } | ||
| - 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: | | ||
| Write-Output "DEBUG: Starting get_latest_tag" | ||
| $repo = "${{ github.repository }}" | ||
| Write-Output "DEBUG: Repo is $repo" | ||
| $headers = @{"Authorization" = "token $env:GITHUB_TOKEN"; "Accept" = "application/vnd.github.v3+json"} | ||
| # Try GitHub Releases API first (includes prereleases) | ||
| try { | ||
| Write-Output "DEBUG: Fetching releases from API..." | ||
| $releases = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/releases?per_page=100" -Headers $headers -UseBasicParsing | ||
| Write-Output "DEBUG: Fetched $($releases.Count) releases" | ||
| } catch { | ||
| Write-Output "DEBUG: Failed to fetch releases from API: $_" | ||
| $releases = @() | ||
| } | ||
| $candidates = @() | ||
| foreach ($r in $releases) { | ||
| Write-Output "DEBUG: Checking release tag: $($r.tag_name)" | ||
| 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) { | ||
| Write-Output "DEBUG: Found valid candidates in releases." | ||
| $sorted = $candidates | Sort-Object -Property @{Expression={$_.Major};Descending=$true}, @{Expression={$_.Minor};Descending=$true}, @{Expression={$_.Patch};Descending=$true} | ||
| $latestTag = $sorted[0].Tag | ||
| Write-Output "DEBUG: Selected latest tag from API: $latestTag" | ||
| Write-Output "latest_tag=$latestTag" >> $env:GITHUB_OUTPUT | ||
| exit 0 | ||
| } | ||
| # Fallback: ensure tags are fetched and inspect local tags | ||
| Write-Output "DEBUG: Fallback to local git tags" | ||
| git fetch --tags --prune || true | ||
| $local = git tag -l "v[0-9]*.[0-9]*.[0-9]*" | ||
| Write-Output "DEBUG: Local tags found: $local" | ||
| 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 "DEBUG: Selected latest tag from local tags: $latestTag" | ||
| Write-Output "latest_tag=$latestTag" >> $env:GITHUB_OUTPUT | ||
| } else { | ||
| Write-Output "DEBUG: No valid semantic version tags found locally. Defaulting to v0.0.0" | ||
| Write-Output "latest_tag=v0.0.0" >> $env:GITHUB_OUTPUT | ||
| } | ||
| } else { | ||
| Write-Output "DEBUG: No local tags found. Defaulting to v0.0.0" | ||
| 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 }}" | ||
| Write-Output "DEBUG: Starting next_version. Input latestTag: $latestTag" | ||
| 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 "DEBUG: Calculated new version: $newVersion" | ||
| 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: | | ||
| Write-Output "DEBUG: Starting parse_commit" | ||
| $commitMsg = git log -1 --pretty=%B | ||
| Write-Output "DEBUG: Commit message: $commitMsg" | ||
| $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" | ||
| } | ||
| Write-Output "DEBUG: releaseNotes: $releaseNotes" | ||
| $commitSha = git rev-parse --short HEAD | ||
| $releaseNotes += "`n---`n*Commit: $commitSha*" | ||
| $delimiter = "EOF_$(Get-Random)" | ||
| Write-Output "DEBUG: Writing notes to output using delimiter $delimiter" | ||
| 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: [prepare, 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 | ||
| echo "DEBUG: Ensure Release Tag Exists" | ||
| echo "DEBUG: received version: '${{ needs.prepare.outputs.version }}'" | ||
| echo "DEBUG: received sha: $(git rev-parse HEAD)" | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "github-actions[bot]@users.noreply.github.com" | ||
| 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 "DEBUG: Tag $TAG already exists locally" | ||
| else | ||
| # Explicitly tag the HEAD sha to match the checked out state | ||
| HEAD_SHA=$(git rev-parse HEAD) | ||
| echo "DEBUG: Creating tag $TAG on $HEAD_SHA" | ||
| git tag -a "$TAG" -m "Release $TAG" "$HEAD_SHA" | ||
| echo "DEBUG: Created local tag $TAG" | ||
| fi | ||
| # Push tag (skip errors if it already exists on remote) | ||
| echo "DEBUG: Pushing tag $TAG" | ||
| 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 }} | ||