name: Build Windows # Windows-only build for the self-hosted org runner labelled "windows-latest". # Produces dist/macropad.exe as a build artifact, and on a version tag (v*) # creates/updates a Gitea release and attaches the exe. # # Note: actions/setup-python is intentionally NOT used — its Windows # tool-cache install script hangs on self-hosted runners. Instead we use an # existing Python 3.11 if the VM has one, otherwise silently install it # per-user (no elevation, no prompts). on: workflow_dispatch: push: tags: - 'v*' jobs: build-windows: runs-on: windows-latest timeout-minutes: 30 steps: - name: Checkout uses: actions/checkout@v4 - name: Ensure Python 3.11 shell: pwsh run: | $ErrorActionPreference = 'Stop' $python = $null # 1) Existing "py -3.11" launcher try { $exe = & py -3.11 -c "import sys; print(sys.executable)" 2>$null if ($LASTEXITCODE -eq 0 -and $exe) { $python = $exe.Trim() } } catch {} # 2) A python on PATH that is 3.11.x if (-not $python) { try { $v = & python --version 2>&1 if ($LASTEXITCODE -eq 0 -and "$v" -match '3\.11') { $python = (& python -c "import sys; print(sys.executable)").Trim() } } catch {} } # 3) Install per-user, silent (no elevation, cannot prompt) if (-not $python) { Write-Host "Python 3.11 not found - installing per-user..." $tmp = if ($env:RUNNER_TEMP) { $env:RUNNER_TEMP } else { $env:TEMP } $installer = Join-Path $tmp "python-3.11.9-amd64.exe" Invoke-WebRequest -Uri "https://www.python.org/ftp/python/3.11.9/python-3.11.9-amd64.exe" -OutFile $installer $dir = Join-Path $tmp "Python311" $proc = Start-Process -FilePath $installer -Wait -PassThru -NoNewWindow -ArgumentList @( "/quiet","InstallAllUsers=0","PrependPath=0","Include_launcher=0","Include_test=0","TargetDir=$dir" ) if ($proc.ExitCode -ne 0) { throw "Python installer exited with $($proc.ExitCode)" } $python = Join-Path $dir "python.exe" } if (-not (Test-Path $python)) { throw "Python not found at '$python'" } & $python --version "PYTHON=$python" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 - name: Install dependencies shell: pwsh run: | $ErrorActionPreference = 'Stop' & $env:PYTHON -m pip install --upgrade pip & $env:PYTHON -m pip install -e . & $env:PYTHON -m pip install pyinstaller - name: Build Windows executable shell: pwsh run: | $ErrorActionPreference = 'Stop' & $env:PYTHON -m PyInstaller --noconfirm macropad.spec - name: Verify build output shell: pwsh run: | if (-not (Test-Path dist/macropad.exe)) { throw "Build failed: dist/macropad.exe not found" } Get-Item dist/macropad.exe | Format-List Name, Length, LastWriteTime - name: Upload build artifact uses: actions/upload-artifact@v4 with: name: macropad-windows path: dist/macropad.exe if-no-files-found: error - name: Publish release asset (tags only) if: startsWith(github.ref, 'refs/tags/') shell: pwsh env: GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | $ErrorActionPreference = 'Stop' $api = $env:GITHUB_API_URL $repo = $env:GITHUB_REPOSITORY $tag = $env:GITHUB_REF_NAME $headers = @{ Authorization = "token $env:GITEA_TOKEN" } # Create the release for this tag, or fetch it if it already exists. $body = @{ tag_name = $tag; name = $tag; draft = $false; prerelease = $false } | ConvertTo-Json try { $rel = Invoke-RestMethod -Method Post -Uri "$api/repos/$repo/releases" ` -Headers $headers -ContentType 'application/json' -Body $body } catch { $rel = Invoke-RestMethod -Method Get -Uri "$api/repos/$repo/releases/tags/$tag" -Headers $headers } # Attach the executable (replace a prior asset of the same name). $existing = $rel.assets | Where-Object { $_.name -eq 'macropad.exe' } if ($existing) { Invoke-RestMethod -Method Delete -Headers $headers ` -Uri "$api/repos/$repo/releases/$($rel.id)/assets/$($existing.id)" } Invoke-RestMethod -Method Post -Headers $headers ` -Uri "$api/repos/$repo/releases/$($rel.id)/assets?name=macropad.exe" ` -InFile dist/macropad.exe -ContentType 'application/octet-stream' Write-Host "Attached macropad.exe to release $tag"