name: Build Windows Release on: push: branches: - main tags: - 'v*' workflow_dispatch: permissions: contents: write jobs: build-windows-release: name: Build Windows x64 Release (MSVC + Qt) # Windows runner 已注册标签:windows-latest、windows-2022、self-hosted;均为 host 模式。 runs-on: windows-2022 env: BUILD_TYPE: Release QT_VERSION: 6.7.3 QT_ARCH: win64_msvc2022_64 QT_DIR_NAME: msvc2022_64 PACKAGE_BASENAME: chengnan-windows-x64-msvc PACKAGE_NAME: chengnan-windows-x64-msvc 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 official Qt for MSVC shell: powershell run: | $ErrorActionPreference = 'Stop' $qtRoot = Join-Path $env:RUNNER_TEMP 'qt-msvc' $qtPrefix = Join-Path $qtRoot "$env:QT_VERSION\$env:QT_DIR_NAME" $windeployqt = Join-Path $qtPrefix 'bin\windeployqt.exe' if (Test-Path $windeployqt) { Write-Host "Qt already installed: $qtPrefix" exit 0 } $pyLauncher = Get-Command py -ErrorAction SilentlyContinue if ($pyLauncher) { $pythonExe = $pyLauncher.Source $pythonArgs = @('-3') } else { $python = Get-Command python -ErrorAction SilentlyContinue if ($python) { $pythonExe = $python.Source $pythonArgs = @() } else { $pythonRoot = Join-Path $env:RUNNER_TEMP 'python312' $pythonExe = Join-Path $pythonRoot 'python.exe' if (-not (Test-Path $pythonExe)) { $installer = Join-Path $env:RUNNER_TEMP 'python-installer.exe' $pythonUrl = 'https://www.python.org/ftp/python/3.12.10/python-3.12.10-amd64.exe' Write-Host "Downloading Python from $pythonUrl" Invoke-WebRequest -Uri $pythonUrl -OutFile $installer $installArgs = @('/quiet', 'InstallAllUsers=0', "TargetDir=$pythonRoot", 'Include_pip=1', 'Include_test=0', 'PrependPath=0') $proc = Start-Process -FilePath $installer -ArgumentList $installArgs -Wait -PassThru if ($proc.ExitCode -ne 0) { throw "Python installer failed, exit code: $($proc.ExitCode)" } } if (-not (Test-Path $pythonExe)) { throw "Python installation finished but python.exe was not found: $pythonExe" } $pythonArgs = @() } } & $pythonExe @pythonArgs -m pip install --user --upgrade aqtinstall if ($LASTEXITCODE -ne 0) { throw "pip install aqtinstall failed, exit code: $LASTEXITCODE" } & $pythonExe @pythonArgs -m aqt install-qt windows desktop $env:QT_VERSION $env:QT_ARCH -O $qtRoot -m qtimageformats if ($LASTEXITCODE -ne 0) { throw "aqt Qt installation failed, exit code: $LASTEXITCODE" } if (-not (Test-Path $windeployqt)) { throw "Qt installation finished but windeployqt was not found: $windeployqt" } Write-Host "Qt installed: $qtPrefix" - name: Configure, build, and deploy with MSVC shell: powershell run: | $ErrorActionPreference = 'Stop' $src = Join-Path $env:RUNNER_TEMP 'chengnan-work' $qtRoot = Join-Path $env:RUNNER_TEMP 'qt-msvc' $qtPrefix = Join-Path $qtRoot "$env:QT_VERSION\$env:QT_DIR_NAME" $vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe' if (-not (Test-Path $vswhere)) { throw "vswhere not found: $vswhere. Install Visual Studio 2022 Build Tools with Desktop development with C++." } $vsPath = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath if ([string]::IsNullOrWhiteSpace($vsPath)) { throw 'Visual Studio 2022 C++ build tools were not found. Install Desktop development with C++.' } $vsDevCmd = Join-Path $vsPath 'Common7\Tools\VsDevCmd.bat' if (-not (Test-Path $vsDevCmd)) { throw "VsDevCmd.bat not found: $vsDevCmd" } $vsCmake = Join-Path $vsPath 'Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin' if (Test-Path (Join-Path $vsCmake 'cmake.exe')) { $env:PATH = "$vsCmake;$env:PATH" } if (-not (Get-Command cmake -ErrorAction SilentlyContinue)) { throw 'cmake was not found on PATH or in Visual Studio bundled CMake.' } $buildDir = Join-Path $src 'build-windows' $distDir = Join-Path $src 'dist-windows\chengnan' $script = Join-Path $src 'build-windows.ps1' $cmd = "`"$vsDevCmd`" -arch=x64 -host_arch=x64 && powershell -NoProfile -ExecutionPolicy Bypass -File `"$script`" -QtPrefix `"$qtPrefix`" -BuildDir `"$buildDir`" -DistDir `"$distDir`" -Generator `"Visual Studio 17 2022`" -Arch x64 -Config Release" Write-Host "Building with Visual Studio at: $vsPath" Write-Host "Using Qt: $qtPrefix" & cmd.exe /c $cmd if ($LASTEXITCODE -ne 0) { throw "MSVC build failed, exit code: $LASTEXITCODE" } $readme = Join-Path $distDir 'README.txt' @( '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 MSVC + official Qt.', '- Do not copy chengnan.exe alone; keep DLLs and Qt plugin directories together.' ) | Out-File -FilePath $readme -Encoding ascii - 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 MSVC 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"