Use VS2026 runner for release build #6
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
| # (260405Ch) Build ReciProSetup on v* tag push and publish a GitHub Release using the latest Version.cs history line | |
| name: Build And Release Installer | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| permissions: | |
| contents: write | |
| jobs: | |
| release: | |
| runs-on: windows-2025-vs2026 # (260405Ch) net10.0-windows + WinForms setup build needs Visual Studio 18 on GitHub-hosted runners. | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| - name: Setup .NET 10 | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: "10.0.x" | |
| - name: Restore managed dependencies | |
| shell: pwsh | |
| run: dotnet restore ReciPro\ReciPro.csproj | |
| - name: Confirm installer project sources | |
| shell: pwsh | |
| run: | | |
| # (260405Ch) Fail early with a clear message if installer project sources were not committed to GitHub. | |
| $requiredSources = @( | |
| "ReciProSetup\ReciProSetup.vdproj", | |
| "ReciProSetup\EnableLaunchApplication.js", | |
| "LICENSE.rtf", | |
| "REQUIREMENT.rtf" | |
| ) | |
| $missingSources = $requiredSources | Where-Object { -not (Test-Path $_) } | |
| if ($missingSources.Count -gt 0) { | |
| throw "Installer source files are missing from the checkout: $($missingSources -join ', ')" | |
| } | |
| - name: Read release info from Version.cs | |
| id: version_info | |
| shell: pwsh | |
| run: | | |
| $versionFile = "ReciPro\Version.cs" | |
| $content = Get-Content $versionFile -Raw | |
| $historyRegex = [regex]::Matches($content, 'ver[^\r\n"]+') | |
| if ($historyRegex.Count -eq 0) { | |
| throw "Could not find a release history line in $versionFile." | |
| } | |
| $latestHistory = $historyRegex[0].Value.Trim() | |
| if ($latestHistory -notmatch '^ver(?<Version>[0-9][0-9A-Za-z\.\-_]*)') { | |
| throw "Could not parse version number from history line: $latestHistory" | |
| } | |
| $historyTag = "v$($Matches.Version)" | |
| $tagName = "${{ github.ref_name }}" | |
| if ($historyTag -ne $tagName) { | |
| throw "Tag '$tagName' does not match Version.cs latest history '$historyTag'." | |
| } | |
| $releaseTitle = "ReciPro $tagName" | |
| $notesPath = Join-Path $env:RUNNER_TEMP "release-notes.txt" | |
| @( | |
| $latestHistory | |
| "" | |
| "Built automatically from tag $tagName." | |
| ) | Set-Content -Path $notesPath -Encoding UTF8 | |
| "latest_history=$latestHistory" >> $env:GITHUB_OUTPUT | |
| "release_title=$releaseTitle" >> $env:GITHUB_OUTPUT | |
| "notes_path=$notesPath" >> $env:GITHUB_OUTPUT | |
| - name: Disable out-of-proc setup project build | |
| shell: pwsh | |
| run: | | |
| # (260405Ch) Force installer builds to run in-proc so devenv does not return before MSI generation completes. | |
| $visualStudioRoot = "HKCU:\Software\Microsoft\VisualStudio" | |
| $configKeys = Get-ChildItem $visualStudioRoot -ErrorAction SilentlyContinue | | |
| Where-Object { $_.PSChildName -like '17.0*_Config' -or $_.PSChildName -like '18.0*_Config' } | |
| if ($configKeys.Count -eq 0) { | |
| Write-Warning "No Visual Studio *_Config registry keys were found under $visualStudioRoot." | |
| } | |
| foreach ($configKey in $configKeys) { | |
| $msbuildKey = Join-Path $configKey.PSPath "MSBuild" | |
| if (-not (Test-Path $msbuildKey)) { | |
| New-Item -Path $msbuildKey -Force | Out-Null | |
| } | |
| New-ItemProperty -Path $msbuildKey -Name DisableOutOfProcBuild -PropertyType DWord -Value 1 -Force | Out-Null | |
| Write-Host "Set DisableOutOfProcBuild=1 at $msbuildKey" | |
| } | |
| - name: Build installer with Visual Studio | |
| id: build_installer | |
| shell: pwsh | |
| run: | | |
| # (260405Ch) Use devenv.com for command-line builds so the workflow waits for vdproj build completion and captures logs. | |
| $devenvExe = (& "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -property productPath).Trim() | |
| if (-not $devenvExe) { | |
| throw "Visual Studio was not found." | |
| } | |
| $devenvCli = [System.IO.Path]::ChangeExtension($devenvExe, ".com") | |
| if (-not (Test-Path $devenvCli)) { | |
| throw "devenv.com was not found next to $devenvExe." | |
| } | |
| $buildLogPath = Join-Path $env:RUNNER_TEMP "devenv-build.log" | |
| & $devenvCli "ReciPro.sln" /Build "Release|x64" /Project "ReciProSetup" /Out $buildLogPath | |
| $buildExitCode = $LASTEXITCODE | |
| Write-Host "devenv exit code: $buildExitCode" | |
| if (Test-Path $buildLogPath) { | |
| Get-Content $buildLogPath | |
| } | |
| "build_exit_code=$buildExitCode" >> $env:GITHUB_OUTPUT | |
| "build_log_path=$buildLogPath" >> $env:GITHUB_OUTPUT | |
| - name: Confirm installer outputs | |
| id: installer_outputs | |
| shell: pwsh | |
| run: | | |
| # (260405Ch) Some environments materialize setup project outputs slightly after devenv returns, so wait and locate them dynamically. | |
| $requiredNames = @("ReciProSetup.msi") | |
| $foundFiles = @{} | |
| $deadline = (Get-Date).AddMinutes(2) | |
| do { | |
| Get-ChildItem -Path . -Recurse -File -ErrorAction SilentlyContinue | | |
| Where-Object { $requiredNames -contains $_.Name } | | |
| ForEach-Object { $foundFiles[$_.Name] = $_.FullName } | |
| $missingFiles = $requiredNames | Where-Object { -not $foundFiles.ContainsKey($_) } | |
| if ($missingFiles.Count -eq 0) { | |
| break | |
| } | |
| Start-Sleep -Seconds 2 | |
| } while ((Get-Date) -lt $deadline) | |
| $missingFiles = $requiredNames | Where-Object { -not $foundFiles.ContainsKey($_) } | |
| if ($missingFiles.Count -gt 0) { | |
| Write-Host "Located installer-like files:" | |
| Get-ChildItem -Path . -Recurse -File -ErrorAction SilentlyContinue | | |
| Where-Object { $_.Extension -eq ".msi" -or $_.Name -eq "setup.exe" } | | |
| Select-Object FullName, Length, LastWriteTime | |
| if (Test-Path "${{ steps.build_installer.outputs.build_log_path }}") { | |
| Write-Host "devenv build log:" | |
| Get-Content "${{ steps.build_installer.outputs.build_log_path }}" | |
| } | |
| throw "Required output was not found: $($missingFiles -join ', ')" | |
| } | |
| if ("${{ steps.build_installer.outputs.build_exit_code }}" -ne "0") { | |
| Write-Warning "devenv returned exit code ${{ steps.build_installer.outputs.build_exit_code }}, but the MSI output was found. Continuing with the generated file." | |
| } | |
| "msi_path=$($foundFiles['ReciProSetup.msi'])" >> $env:GITHUB_OUTPUT | |
| - name: Upload workflow artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ReciPro-installer-${{ github.ref_name }} | |
| path: | | |
| ${{ steps.installer_outputs.outputs.msi_path }} | |
| - name: Create GitHub Release | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh release create "${{ github.ref_name }}" ` | |
| "${{ steps.installer_outputs.outputs.msi_path }}" ` | |
| --verify-tag ` | |
| --title "${{ steps.version_info.outputs.release_title }}" ` | |
| --notes-file "${{ steps.version_info.outputs.notes_path }}" |