name: Build Windows Release on: push: branches: - main tags: - 'v*' workflow_dispatch: permissions: contents: write jobs: build-windows-release: name: Build Windows x64 Release # Windows runner 已注册标签:windows-latest、windows-2022、self-hosted;均为 host 模式。 runs-on: windows-2022 env: BUILD_TYPE: Release MSYS2_ROOT: C:\msys64 MSYSTEM: UCRT64 CHERE_INVOKING: 1 PACKAGE_BASENAME: chengnan-windows-x64 PACKAGE_NAME: chengnan-windows-x64 steps: - name: Prepare source shell: powershell run: | $ErrorActionPreference = 'Stop' $work = Join-Path $env:RUNNER_TEMP 'chengnan-src' $srcFixed = Join-Path $env:RUNNER_TEMP 'chengnan-work' $zip = Join-Path $env:RUNNER_TEMP 'chengnan-src.zip' if (Test-Path $work) { Remove-Item -Recurse -Force $work } if (Test-Path $srcFixed) { Remove-Item -Recurse -Force $srcFixed } if (Test-Path $zip) { Remove-Item -Force $zip } New-Item -ItemType Directory -Force -Path $work | Out-Null $serverUrl = if ($env:GITHUB_SERVER_URL) { $env:GITHUB_SERVER_URL.TrimEnd('/') } else { 'https://git.luochen570.cn' } $archiveUrl = "$serverUrl/$env:GITHUB_REPOSITORY/archive/$env:GITHUB_SHA.zip" Write-Host "Downloading source archive from $archiveUrl" Invoke-WebRequest -Uri $archiveUrl -OutFile $zip Expand-Archive -Path $zip -DestinationPath $work -Force $src = Get-ChildItem -Path $work -Directory | Select-Object -First 1 if (-not $src) { throw 'Source archive extraction produced no directory' } Copy-Item -Path $src.FullName -Destination $srcFixed -Recurse -Force Write-Host "Source dir: $srcFixed" - name: Install MSYS2 and build dependencies shell: powershell run: | $ErrorActionPreference = 'Stop' $msysRoot = $env:MSYS2_ROOT if (-not (Test-Path "$msysRoot\usr\bin\bash.exe")) { $installer = Join-Path $env:RUNNER_TEMP 'msys2-installer.exe' $installerUrl = 'https://github.com/msys2/msys2-installer/releases/latest/download/msys2-x86_64-latest.exe' Write-Host "Downloading MSYS2 installer from $installerUrl" Invoke-WebRequest -Uri $installerUrl -OutFile $installer & $installer in --confirm-command --accept-messages --root $msysRoot } $bash = "$msysRoot\usr\bin\bash.exe" & $bash -lc "pacman --noconfirm -Syuu" if ($LASTEXITCODE -ne 0) { Write-Host 'First pacman system update returned non-zero; continuing with second update.' } & $bash -lc "pacman --noconfirm -Syuu" if ($LASTEXITCODE -ne 0) { throw "pacman system update failed, exit code: $LASTEXITCODE" } & $bash -lc "pacman --noconfirm -S --needed git zip unzip p7zip mingw-w64-ucrt-x86_64-toolchain mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-ninja mingw-w64-ucrt-x86_64-qt6-base mingw-w64-ucrt-x86_64-qt6-imageformats" if ($LASTEXITCODE -ne 0) { throw "pacman dependency installation failed, exit code: $LASTEXITCODE" } - name: Configure and build shell: powershell run: | $ErrorActionPreference = 'Stop' $bash = "$env:MSYS2_ROOT\usr\bin\bash.exe" $srcWin = Join-Path $env:RUNNER_TEMP 'chengnan-work' $src = $srcWin -replace '\\','/' $src = $src -replace '^([A-Za-z]):','/$1' $src = $src.ToLower() $script = @" set -euo pipefail cd '$src' export PATH=/ucrt64/bin:/usr/bin:`$PATH cmake -S . -B build-windows -G Ninja -DCMAKE_BUILD_TYPE=Release cmake --build build-windows --parallel `$(nproc) rm -rf dist-windows mkdir -p dist-windows/chengnan cp build-windows/chengnan.exe dist-windows/chengnan/ /ucrt64/bin/windeployqt6.exe --release --no-translations dist-windows/chengnan/chengnan.exe || /ucrt64/bin/windeployqt.exe --release --no-translations dist-windows/chengnan/chengnan.exe printf '%s\n' 'chengnan Windows x64 package' '' 'Usage:' '1. Extract the whole directory.' '2. Double-click chengnan.exe to run.' '' 'Note:' '- This package is built by Windows Runner with MSYS2 UCRT64 + Qt.' '- Do not copy chengnan.exe alone; keep DLLs and Qt plugin directories together.' > dist-windows/chengnan/README.txt "@ & $bash -lc $script - name: Package shell: powershell run: | $ErrorActionPreference = 'Stop' $shortSha = $env:GITHUB_SHA.Substring(0, 10) $version = if ($env:GITHUB_REF_TYPE -eq 'tag') { $env:GITHUB_REF_NAME } else { "build-$shortSha" } $zipName = "$env:PACKAGE_BASENAME-$version.zip" $sourceDir = Join-Path $env:RUNNER_TEMP 'chengnan-work' $dist = Join-Path $sourceDir 'dist-windows\chengnan' if (-not (Test-Path "$dist\chengnan.exe")) { throw "Missing build output: $dist\chengnan.exe" } Push-Location (Split-Path $dist -Parent) if (Test-Path (Join-Path $sourceDir $zipName)) { Remove-Item -Force (Join-Path $sourceDir $zipName) } Compress-Archive -Path 'chengnan\*' -DestinationPath (Join-Path $sourceDir $zipName) -Force Pop-Location $zipPath = Join-Path $sourceDir $zipName $hash = (Get-FileHash $zipPath -Algorithm SHA256).Hash.ToLowerInvariant() "$hash $zipName" | Out-File -FilePath "$zipPath.sha256" -Encoding ascii Write-Host "Package: $zipPath" Write-Host "SHA256: $hash" - name: Verify package shell: powershell run: | $ErrorActionPreference = 'Stop' $shortSha = $env:GITHUB_SHA.Substring(0, 10) $version = if ($env:GITHUB_REF_TYPE -eq 'tag') { $env:GITHUB_REF_NAME } else { "build-$shortSha" } $zipName = "$env:PACKAGE_BASENAME-$version.zip" $sourceDir = Join-Path $env:RUNNER_TEMP 'chengnan-work' $zipPath = Join-Path $sourceDir $zipName $shaPath = "$zipPath.sha256" if (-not (Test-Path $zipPath)) { throw "zip does not exist: $zipPath" } if (-not (Test-Path $shaPath)) { throw "sha256 does not exist: $shaPath" } if ((Get-Item $zipPath).Length -le 0) { throw "zip file is empty: $zipPath" } Write-Host "Verified package: $zipPath" - name: Upload package to repository release shell: powershell env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | $ErrorActionPreference = 'Stop' $token = $env:RELEASE_TOKEN if ([string]::IsNullOrWhiteSpace($token)) { $token = $env:GITHUB_TOKEN } if ([string]::IsNullOrWhiteSpace($token)) { throw 'Missing release token. Configure RELEASE_TOKEN in repository secrets or ensure GITHUB_TOKEN is available.' } $shortSha = $env:GITHUB_SHA.Substring(0, 10) $version = if ($env:GITHUB_REF_TYPE -eq 'tag') { $env:GITHUB_REF_NAME } else { "build-$shortSha" } $zipName = "$env:PACKAGE_BASENAME-$version.zip" $sourceDir = Join-Path $env:RUNNER_TEMP 'chengnan-work' $zipPath = Join-Path $sourceDir $zipName $shaPath = "$zipPath.sha256" $serverUrl = if ($env:GITHUB_SERVER_URL) { $env:GITHUB_SERVER_URL.TrimEnd('/') } else { 'https://git.luochen570.cn' } $repo = $env:GITHUB_REPOSITORY $apiBase = "$serverUrl/api/v1/repos/$repo" $tagName = $version $releaseName = if ($env:GITHUB_REF_TYPE -eq 'tag') { $env:GITHUB_REF_NAME } else { "chengnan $shortSha" } $isPrerelease = if ($env:GITHUB_REF_TYPE -eq 'tag') { $false } else { $true } $headers = @{ Authorization = "token $token"; Accept = 'application/json' } $releasePayload = @{ tag_name = $tagName target_commitish = $env:GITHUB_SHA name = $releaseName body = "Automated Windows x64 build from Gitea Actions.`n" draft = $false prerelease = $isPrerelease } | ConvertTo-Json -Depth 5 try { $release = Invoke-RestMethod -Method Post -Uri "$apiBase/releases" -Headers $headers -ContentType 'application/json' -Body $releasePayload } catch { $release = Invoke-RestMethod -Method Get -Uri "$apiBase/releases/tags/$tagName" -Headers $headers } function Remove-ExistingAsset([string]$assetName) { $assets = Invoke-RestMethod -Method Get -Uri "$apiBase/releases/$($release.id)/assets" -Headers $headers foreach ($asset in @($assets)) { if ($asset.name -eq $assetName) { Invoke-RestMethod -Method Delete -Uri "$apiBase/releases/assets/$($asset.id)" -Headers $headers | Out-Null } } } function Upload-Asset([string]$path) { $name = Split-Path $path -Leaf Remove-ExistingAsset $name $uploadUrl = "$apiBase/releases/$($release.id)/assets?name=$([uri]::EscapeDataString($name))" $responseFile = Join-Path $env:RUNNER_TEMP "gitea-upload-response.json" $curl = Join-Path $env:SystemRoot 'System32\curl.exe' $status = & $curl --silent --show-error --location ` --header "Authorization: token $token" ` --form "attachment=@$path;filename=$name" ` --write-out '%{http_code}' ` --output $responseFile ` $uploadUrl if ($status -ne '200' -and $status -ne '201') { Write-Error "Upload release asset failed, HTTP ${status}: $name" if (Test-Path $responseFile) { Get-Content $responseFile } exit 1 } Write-Host "Uploaded release asset: $name" } Upload-Asset $zipPath Upload-Asset $shaPath Write-Host "Release assets uploaded to $serverUrl/$repo/releases/tag/$tagName"