Build and Draft Release #48
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: Build and Draft Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Release version (e.g. 1.2.3.4, v1.2.3.4, 1.0.0.1-beta, v2.0.0.0-rc1)" | |
| required: true | |
| default: "1.0.0.0" | |
| type: string | |
| jobs: | |
| prepare: | |
| name: Prepare Release Version | |
| runs-on: ubuntu-latest | |
| outputs: | |
| release_version: ${{ steps.version.outputs.release_version }} | |
| numeric_version: ${{ steps.version.outputs.numeric_version }} | |
| release_tag: ${{ steps.version.outputs.release_tag }} | |
| steps: | |
| - name: Normalize and Validate Version | |
| id: version | |
| shell: pwsh | |
| env: | |
| VERSION_INPUT: ${{ github.event.inputs.version }} | |
| run: | | |
| $raw = "$env:VERSION_INPUT".Trim() | |
| if ([string]::IsNullOrWhiteSpace($raw)) { | |
| throw "Version input cannot be empty." | |
| } | |
| # Strip optional 'v' prefix | |
| $normalized = if ($raw.StartsWith('v', [System.StringComparison]::OrdinalIgnoreCase)) { | |
| $raw.Substring(1) | |
| } else { | |
| $raw | |
| } | |
| # Split into numeric part and optional pre-release suffix (e.g. "1.0.0.1-beta" -> "1.0.0.1" + "-beta") | |
| if ($normalized -match '^([\d\.]+)(-[A-Za-z0-9\.\-]+)?$') { | |
| $numericPart = $Matches[1] | |
| # $Matches[2] is the suffix including the dash, or $null | |
| } else { | |
| throw "Invalid version '$raw'. Expected format: Major.Minor.Build.Revision[-suffix] (e.g. 1.0.0.1, 1.0.0.1-beta, 1.0.0.1-rc1)." | |
| } | |
| $parsed = $null | |
| if (-not [System.Version]::TryParse($numericPart, [ref]$parsed)) { | |
| throw "Invalid numeric version '$numericPart' from input '$raw'. The numeric portion must be a valid version like 1.2.3.4." | |
| } | |
| # release_version = full version with suffix (e.g. "1.0.0.1-beta") | |
| # numeric_version = just the numeric part (e.g. "1.0.0.1") for AssemblyVersion/FileVersion | |
| "release_version=$normalized" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| "numeric_version=$numericPart" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| "release_tag=v$normalized" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| build: | |
| name: Build ${{ matrix.arch }} | |
| needs: prepare | |
| runs-on: windows-latest | |
| strategy: | |
| matrix: | |
| arch: [x64, x86, arm64] | |
| env: | |
| RELEASE_VERSION: ${{ needs.prepare.outputs.release_version }} | |
| NUMERIC_VERSION: ${{ needs.prepare.outputs.numeric_version }} | |
| RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }} | |
| ARCH: ${{ matrix.arch }} | |
| steps: | |
| - name: Checkout Code | |
| uses: actions/checkout@v7 | |
| with: | |
| submodules: recursive | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v6 | |
| with: | |
| dotnet-version: "10.0.x" | |
| - name: Ensure VC++ ARM64 Tools | |
| if: matrix.arch == 'arm64' | |
| shell: pwsh | |
| run: | | |
| $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" | |
| $vsPath = & $vswhere -latest -property installationPath | |
| function Test-Arm64Tools { | |
| $msvcRoot = Join-Path $vsPath 'VC\Tools\MSVC' | |
| if (-not (Test-Path $msvcRoot)) { return $false } | |
| return [bool](Get-ChildItem $msvcRoot -Directory | | |
| Where-Object { Test-Path (Join-Path $_.FullName 'bin\Hostx64\arm64\link.exe') } | | |
| Select-Object -First 1) | |
| } | |
| if (Test-Arm64Tools) { | |
| Write-Host "VC++ ARM64 tools already present." | |
| } else { | |
| Write-Host "Installing VC++ ARM64 tools into '$vsPath'..." | |
| $installer = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vs_installer.exe" | |
| $proc = Start-Process -FilePath $installer -Wait -PassThru -ArgumentList @( | |
| 'modify', '--installPath', "`"$vsPath`"", | |
| '--add', 'Microsoft.VisualStudio.Component.VC.Tools.ARM64', | |
| '--quiet', '--norestart', '--force', '--nocache' | |
| ) | |
| Write-Host "vs_installer exit code: $($proc.ExitCode)" | |
| # 0 = success, 3010 = success/reboot-required. | |
| if ($proc.ExitCode -notin 0, 3010) { | |
| throw "VS installer failed with exit code $($proc.ExitCode)." | |
| } | |
| if (-not (Test-Arm64Tools)) { | |
| throw "VC++ ARM64 tools still missing after install." | |
| } | |
| Write-Host "VC++ ARM64 tools installed." | |
| } | |
| - name: Setup MSVC Toolchain (for NativeAOT) | |
| # NativeAOT links a native binary, so it needs the MSVC linker and the | |
| # target-architecture libraries on PATH/LIB. The runner is x64, so x86 and | |
| # arm64 are cross-compiled (amd64_x86 / amd64_arm64). The AOT publish below | |
| # consumes this environment via -p:IlcUseEnvironmentalTools=true. | |
| uses: ilammy/msvc-dev-cmd@v1 | |
| with: | |
| arch: ${{ matrix.arch == 'arm64' && 'amd64_arm64' || matrix.arch == 'x86' && 'amd64_x86' || 'amd64' }} | |
| - name: Cache NuGet Packages | |
| uses: actions/cache@v6 | |
| with: | |
| path: ~/.nuget/packages | |
| key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/packages.lock.json') }} | |
| restore-keys: nuget-${{ runner.os }}- | |
| - name: Publish WinUI 3 App (Framework-Dependent) | |
| shell: pwsh | |
| run: > | |
| dotnet publish Raven/Raven.csproj | |
| -c Release | |
| -r win-$env:ARCH | |
| --self-contained false | |
| -o ./publish_output | |
| -p:Version=$env:NUMERIC_VERSION | |
| -p:AssemblyVersion=$env:NUMERIC_VERSION | |
| -p:FileVersion=$env:NUMERIC_VERSION | |
| -p:InformationalVersion=$env:RELEASE_TAG | |
| - name: Clean Intermediates Before Self-Contained Publish | |
| shell: pwsh | |
| run: | | |
| # The framework-dependent publish leaves a thin resources.pri in obj/. | |
| # MSBuild's incremental PRI step does not treat WindowsAppSDKSelfContained | |
| # as a changed input, so without this clean the self-contained publish | |
| # reuses that thin PRI and ships without the bundled WinUI framework | |
| # resources, crashing at startup with a missing themeresources.xaml. | |
| Remove-Item -Path Raven/obj, Raven/bin -Recurse -Force -ErrorAction SilentlyContinue | |
| - name: Publish WinUI 3 App (Self-Contained) | |
| shell: pwsh | |
| run: > | |
| dotnet publish Raven/Raven.csproj | |
| -c Release | |
| -r win-$env:ARCH | |
| --self-contained true | |
| -p:WindowsAppSDKSelfContained=true | |
| -o ./publish_output_full | |
| -p:Version=$env:NUMERIC_VERSION | |
| -p:AssemblyVersion=$env:NUMERIC_VERSION | |
| -p:FileVersion=$env:NUMERIC_VERSION | |
| -p:InformationalVersion=$env:RELEASE_TAG | |
| - name: Publish Updater (NativeAOT) | |
| shell: pwsh | |
| run: > | |
| dotnet publish Raven.Updater/Raven.Updater.csproj | |
| -c Release | |
| -r win-$env:ARCH | |
| -p:PublishAot=true | |
| -p:IlcUseEnvironmentalTools=true | |
| -o ./updater_aot | |
| -p:Version=$env:NUMERIC_VERSION | |
| -p:AssemblyVersion=$env:NUMERIC_VERSION | |
| -p:FileVersion=$env:NUMERIC_VERSION | |
| - name: Replace Updater With NativeAOT Build | |
| shell: pwsh | |
| run: | | |
| $aotUpdater = "./updater_aot/Raven.Updater.exe" | |
| if (-not (Test-Path $aotUpdater)) { | |
| throw "NativeAOT updater was not produced at $aotUpdater." | |
| } | |
| foreach ($dir in @('./publish_output', './publish_output_full')) { | |
| # Drop the framework-dependent updater payload copied in via ProjectReference | |
| # (.exe/.dll/.deps.json/.runtimeconfig.json/.pdb), then drop in the self-sufficient AOT exe. | |
| Get-ChildItem -Path $dir -Filter 'Raven.Updater.*' -File | Remove-Item -Force | |
| Copy-Item -Path $aotUpdater -Destination (Join-Path $dir 'Raven.Updater.exe') -Force | |
| } | |
| - name: Decode Certificate | |
| shell: pwsh | |
| env: | |
| CERT_BASE64: ${{ secrets.CERT_BASE64 }} | |
| run: | | |
| $pfxCertBytes = [System.Convert]::FromBase64String($env:CERT_BASE64) | |
| [System.IO.File]::WriteAllBytes("RavenCert.pfx", $pfxCertBytes) | |
| - name: Sign App Files and Export Public Certificate | |
| shell: pwsh | |
| env: | |
| CERT_PASSWORD: ${{ secrets.CERT_PASSWORD }} | |
| run: | | |
| $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new( | |
| "RavenCert.pfx", | |
| $env:CERT_PASSWORD | |
| ) | |
| Get-ChildItem -Path ./publish_output, ./publish_output_full -Include *.exe,*.dll -Recurse | ForEach-Object { | |
| Set-AuthenticodeSignature -FilePath $_.FullName -Certificate $cert -TimestampServer "http://timestamp.digicert.com" | Out-Null | |
| } | |
| $certBytes = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) | |
| [System.IO.File]::WriteAllBytes("RavenCert.cer", $certBytes) | |
| - name: Create Zip Archives | |
| shell: pwsh | |
| run: | | |
| Get-ChildItem -Path ./publish_output, ./publish_output_full -Include *.cer,*.pfx -Recurse -File -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue | |
| Compress-Archive -Path ./publish_output/* -DestinationPath "Raven-$($env:RELEASE_TAG)-win-$($env:ARCH).zip" | |
| Compress-Archive -Path ./publish_output_full/* -DestinationPath "Raven-$($env:RELEASE_TAG)-win-$($env:ARCH)-self-contained.zip" | |
| - name: Create Auto Install Certificate Zip | |
| if: matrix.arch == 'x64' | |
| shell: pwsh | |
| run: | | |
| $bundleDir = Join-Path $PWD "auto_install_cert" | |
| New-Item -ItemType Directory -Path $bundleDir -Force | Out-Null | |
| Copy-Item -Path "RavenCert.cer" -Destination (Join-Path $bundleDir "raven.cer") -Force | |
| Copy-Item -Path ".github/scripts/Install-RavenCert.bat" -Destination (Join-Path $bundleDir "install_raven_cert.bat") -Force | |
| Compress-Archive -Path (Join-Path $bundleDir "*") -DestinationPath "raven_cert.zip" -Force | |
| - name: Upload App Artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: Raven-${{ matrix.arch }} | |
| path: | | |
| Raven-${{ needs.prepare.outputs.release_tag }}-win-${{ matrix.arch }}.zip | |
| Raven-${{ needs.prepare.outputs.release_tag }}-win-${{ matrix.arch }}-self-contained.zip | |
| - name: Upload Auto Install Certificate Artifact | |
| if: matrix.arch == 'x64' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: Auto-Install-Cert | |
| path: raven_cert.zip | |
| release: | |
| name: Create Draft Release | |
| needs: [prepare, build] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Download Artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| path: ./artifacts | |
| merge-multiple: true | |
| - name: Create Draft GitHub Release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| tag_name: ${{ needs.prepare.outputs.release_tag }} | |
| name: Raven ${{ needs.prepare.outputs.release_tag }} | |
| draft: true | |
| prerelease: ${{ needs.prepare.outputs.release_version != needs.prepare.outputs.numeric_version }} | |
| generate_release_notes: true | |
| files: | | |
| ./artifacts/Raven-*-win-x64.zip | |
| ./artifacts/Raven-*-win-x86.zip | |
| ./artifacts/Raven-*-win-arm64.zip | |
| ./artifacts/*-self-contained.zip | |
| ./artifacts/raven_cert.zip |