ci: build release on Windows runner
Some checks failed
Build Windows Release / Build Windows x64 Release (push) Has been cancelled

This commit is contained in:
luochen570
2026-05-30 20:50:34 +08:00
parent 83ac7ada50
commit b0f8695a25

View File

@@ -12,217 +12,179 @@ permissions:
contents: write
jobs:
cross-build-windows:
name: Cross build Windows x86_64 Release
# Gitea act_runner 标签为 9950x该工作流在 Linux Runner 中交叉编译 Windows x86_64 包
runs-on: 9950x
container:
image: fedora:latest
build-windows-release:
name: Build Windows x64 Release
# 需要 Gitea act_runner 中存在并在线的 Windows runner且带有 windows 标签
runs-on: windows
env:
BUILD_TYPE: Release
QT_VERSION: '6.7.3'
QT_ARCH: win64_msvc2019_64
QT_DIR_NAME: msvc2019_64
DIST_DIR: dist-windows\chengnan
PACKAGE_BASENAME: chengnan-windows-x64
steps:
- name: Configure Fedora TUNA mirror
run: |
set -euo pipefail
sed -e 's|^metalink=|#metalink=|g' \
-e 's|^#baseurl=http://download.example/pub/fedora/linux|baseurl=https://mirrors.tuna.tsinghua.edu.cn/fedora|g' \
-i /etc/yum.repos.d/fedora*.repo
if [ -f /etc/yum.repos.d/fedora-cisco-openh264.repo ]; then
sed -i 's|^enabled=1|enabled=0|g' /etc/yum.repos.d/fedora-cisco-openh264.repo
fi
dnf makecache --refresh -y
- name: Checkout
run: |
set -euo pipefail
dnf install -y git rsync
WORKDIR="$(pwd)"
SRC_DIR="$(mktemp -d /tmp/chengnan-source.XXXXXX)"
REPO_URL="${GITHUB_SERVER_URL:-https://git.luochen570.cn}/${GITHUB_REPOSITORY}.git"
if [ -n "${GITHUB_TOKEN:-}" ]; then
git -c http.extraHeader="Authorization: token ${GITHUB_TOKEN}" \
clone --depth 1 --branch "${GITHUB_REF_NAME:-main}" "$REPO_URL" "$SRC_DIR"
else
git clone --depth 1 --branch "${GITHUB_REF_NAME:-main}" "$REPO_URL" "$SRC_DIR"
fi
rsync -a --delete "$SRC_DIR/" "$WORKDIR/"
rm -rf "$SRC_DIR"
uses: actions/checkout@v4
- name: Install cross build dependencies
run: |
set -euo pipefail
dnf install -y \
cmake \
curl \
ninja-build \
p7zip \
p7zip-plugins \
mingw64-gcc-c++ \
mingw64-qt6-qtbase
- name: Setup MSVC developer command prompt
uses: ilammy/msvc-dev-cmd@v1
with:
arch: x64
- name: Configure
run: |
set -euo pipefail
cmake -S . -B build-windows-cross -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_TOOLCHAIN_FILE=/usr/share/mingw/toolchain-mingw64.cmake
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Build
- name: Install Qt
shell: powershell
run: |
set -euo pipefail
cmake --build build-windows-cross --parallel "$(nproc)"
$ErrorActionPreference = 'Stop'
python -m pip install --upgrade pip
python -m pip install aqtinstall
python -m aqt install-qt windows desktop $env:QT_VERSION $env:QT_ARCH --outputdir C:\Qt --modules qtimageformats
$qtPrefix = "C:\Qt\$env:QT_VERSION\$env:QT_DIR_NAME"
$windeployqt = Join-Path $qtPrefix 'bin\windeployqt.exe'
if (-not (Test-Path $windeployqt)) {
throw "Qt 安装不完整,找不到 windeployqt$windeployqt"
}
"QT_PREFIX=$qtPrefix" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
- name: Build and deploy Qt runtime
shell: powershell
run: |
$ErrorActionPreference = 'Stop'
powershell -ExecutionPolicy Bypass -File .\build-windows.ps1 -QtPrefix "$env:QT_PREFIX" -Config $env:BUILD_TYPE
if (-not (Test-Path $env:DIST_DIR)) {
throw "发布目录不存在:$env:DIST_DIR"
}
if (-not (Test-Path "$env:DIST_DIR\chengnan.exe")) {
throw "构建产物不存在:$env:DIST_DIR\chengnan.exe"
}
- name: Package
shell: powershell
run: |
set -euo pipefail
APP_NAME="chengnan"
PACKAGE_NAME="chengnan-windows-x86_64-cross"
PACKAGE_DIR="dist-windows-cross/${PACKAGE_NAME}"
EXE="build-windows-cross/${APP_NAME}.exe"
$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"
test -f "$EXE"
rm -rf dist-windows-cross
mkdir -p "$PACKAGE_DIR"
cp "$EXE" "$PACKAGE_DIR/"
# 递归收集 MinGW 运行时和 Qt 依赖 DLL。
for round in 1 2 3; do
while IFS= read -r binary; do
while IFS= read -r dll; do
[ -f "$dll" ] || continue
cp -n "$dll" "$PACKAGE_DIR/" || true
done < <(x86_64-w64-mingw32-objdump -p "$binary" \
| awk '/DLL Name:/ {print $3}' \
| while read -r name; do \
find /usr/x86_64-w64-mingw32/sys-root/mingw/bin -iname "$name" -print -quit; \
done)
done < <(find "$PACKAGE_DIR" -maxdepth 1 -type f \( -name '*.exe' -o -name '*.dll' \) | sort)
done
# Qt 平台插件是 Windows Qt GUI 程序启动所必需的。
QT_PLUGIN_ROOT="/usr/x86_64-w64-mingw32/sys-root/mingw/lib/qt6/plugins"
if [ -d "$QT_PLUGIN_ROOT/platforms" ]; then
mkdir -p "$PACKAGE_DIR/platforms"
cp -n "$QT_PLUGIN_ROOT/platforms"/*.dll "$PACKAGE_DIR/platforms/"
fi
cat > "$PACKAGE_DIR/README.txt" <<'EOF'
chengnan Windows x86_64 交叉编译发布包
@"
chengnan Windows x64 发布包
运行方式:
1. 解压整个目录。
2. 双击 chengnan.exe 运行。
说明:
- 本目录由 Linux Runner 使用 MinGW-w64 交叉编译生成。
- 请不要只复制 chengnan.exe需保留同目录 DLL 和 platforms 子目录。
EOF
- 本目录由 Windows Runner 使用 MSVC + Qt 构建生成。
- 请不要只复制 chengnan.exe需保留同目录 DLL 和 Qt 插件目录。
"@ | Out-File -FilePath "$env:DIST_DIR\README.txt" -Encoding utf8
(cd dist-windows-cross && 7z a -tzip "${PACKAGE_NAME}.zip" "${PACKAGE_NAME}" >/dev/null)
(cd dist-windows-cross && sha256sum "${PACKAGE_NAME}.zip" > "${PACKAGE_NAME}.zip.sha256")
du -h "dist-windows-cross/${PACKAGE_NAME}.zip"
if (Test-Path $zipName) {
Remove-Item -Force $zipName
}
Compress-Archive -Path "$env:DIST_DIR\*" -DestinationPath $zipName -Force
$hash = (Get-FileHash $zipName -Algorithm SHA256).Hash.ToLowerInvariant()
"$hash $zipName" | Out-File -FilePath "$zipName.sha256" -Encoding ascii
"PACKAGE_ZIP=$zipName" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
"RELEASE_TAG=$version" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
Write-Host "Package: $zipName"
Write-Host "SHA256: $hash"
- name: Verify package
shell: powershell
run: |
set -euo pipefail
test -s dist-windows-cross/chengnan-windows-x86_64-cross.zip
test -s dist-windows-cross/chengnan-windows-x86_64-cross.zip.sha256
(cd dist-windows-cross && sha256sum -c chengnan-windows-x86_64-cross.zip.sha256)
7z l dist-windows-cross/chengnan-windows-x86_64-cross.zip | grep 'chengnan.exe'
7z l dist-windows-cross/chengnan-windows-x86_64-cross.zip | grep 'platforms/'
$ErrorActionPreference = 'Stop'
if (-not (Test-Path $env:PACKAGE_ZIP)) {
throw "zip 不存在:$env:PACKAGE_ZIP"
}
if (-not (Test-Path "$env:PACKAGE_ZIP.sha256")) {
throw "sha256 不存在:$env:PACKAGE_ZIP.sha256"
}
$items = Get-ChildItem -Path $env:PACKAGE_ZIP | Select-Object -ExpandProperty FullName
if (-not $items) {
throw "zip 文件为空或不可读:$env:PACKAGE_ZIP"
}
- name: Upload build artifact
uses: actions/upload-artifact@v3
with:
name: chengnan-windows-x86_64-cross
name: chengnan-windows-x64
path: |
dist-windows-cross/chengnan-windows-x86_64-cross.zip
dist-windows-cross/chengnan-windows-x86_64-cross.zip.sha256
${{ env.PACKAGE_ZIP }}
${{ env.PACKAGE_ZIP }}.sha256
if-no-files-found: error
- name: Upload package to repository release
shell: powershell
env:
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
set -euo pipefail
PACKAGE_NAME="chengnan-windows-x86_64-cross"
SERVER_URL="${GITHUB_SERVER_URL:-https://git.luochen570.cn}"
REPOSITORY="${GITHUB_REPOSITORY}"
API_BASE="${SERVER_URL}/api/v1/repos/${REPOSITORY}"
TOKEN="${RELEASE_TOKEN:-${GITHUB_TOKEN:-}}"
test -n "$TOKEN"
if [ "${GITHUB_REF_TYPE:-}" = "tag" ]; then
TAG_NAME="${GITHUB_REF_NAME}"
RELEASE_NAME="${GITHUB_REF_NAME}"
PRERELEASE=false
else
TAG_NAME="build-${GITHUB_SHA:0:10}"
RELEASE_NAME="chengnan ${GITHUB_SHA:0:10}"
PRERELEASE=true
fi
json_escape() {
python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))'
$ErrorActionPreference = 'Stop'
$token = $env:RELEASE_TOKEN
if ([string]::IsNullOrWhiteSpace($token)) {
$token = $env:GITHUB_TOKEN
}
if ([string]::IsNullOrWhiteSpace($token)) {
throw '缺少发布 Token请在仓库 Secrets 中配置 RELEASE_TOKEN或确保 Gitea Actions 提供 GITHUB_TOKEN。'
}
release_payload="$(printf '{"tag_name":%s,"target_commitish":%s,"name":%s,"body":%s,"draft":false,"prerelease":%s}' \
"$(printf '%s' "$TAG_NAME" | json_escape)" \
"$(printf '%s' "${GITHUB_SHA}" | json_escape)" \
"$(printf '%s' "$RELEASE_NAME" | json_escape)" \
"$(printf 'Automated Windows x86_64 cross build from Gitea Actions.\n' | json_escape)" \
"$PRERELEASE")"
$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 = $env:RELEASE_TAG
$releaseName = if ($env:GITHUB_REF_TYPE -eq 'tag') { $env:GITHUB_REF_NAME } else { "chengnan $($env:GITHUB_SHA.Substring(0, 10))" }
$isPrerelease = if ($env:GITHUB_REF_TYPE -eq 'tag') { $false } else { $true }
$headers = @{ Authorization = "token $token"; Accept = 'application/json' }
status="$(curl --show-error --silent --location \
--header "Authorization: token ${TOKEN}" \
--header 'Content-Type: application/json' \
--data "$release_payload" \
--write-out '%{http_code}' \
--output /tmp/gitea-release-response.json \
"${API_BASE}/releases")"
$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
if [ "$status" = "409" ] || [ "$status" = "422" ]; then
RELEASE_ID="$(curl --show-error --silent --location \
--header "Authorization: token ${TOKEN}" \
"${API_BASE}/releases/tags/${TAG_NAME}" \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')"
elif [ "$status" = "201" ] || [ "$status" = "200" ]; then
RELEASE_ID="$(python3 -c 'import json; print(json.load(open("/tmp/gitea-release-response.json"))["id"])')"
else
echo "Create release failed with HTTP ${status}" >&2
cat /tmp/gitea-release-response.json >&2
exit 1
fi
upload_asset() {
local src="$1"
local name
local existing_id
local status
name="$(basename "$src")"
existing_id="$(curl --show-error --silent --location \
--header "Authorization: token ${TOKEN}" \
"${API_BASE}/releases/${RELEASE_ID}/assets" \
| ASSET_NAME="$name" python3 -c 'import json,os,sys; assets=json.load(sys.stdin); print(next((str(a["id"]) for a in assets if a.get("name")==os.environ["ASSET_NAME"]), ""))')"
if [ -n "$existing_id" ]; then
curl --show-error --silent --location \
--request DELETE \
--header "Authorization: token ${TOKEN}" \
--output /tmp/gitea-release-asset-delete-response.json \
"${API_BASE}/releases/${RELEASE_ID}/assets/${existing_id}"
fi
status="$(curl --show-error --silent --location \
--header "Authorization: token ${TOKEN}" \
--form "attachment=@${src};filename=${name}" \
--write-out '%{http_code}' \
--output /tmp/gitea-release-asset-response.json \
"${API_BASE}/releases/${RELEASE_ID}/assets?name=${name}")"
if [ "$status" != "201" ] && [ "$status" != "200" ]; then
echo "Upload release asset failed with HTTP ${status}: ${name}" >&2
cat /tmp/gitea-release-asset-response.json >&2
exit 1
fi
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
}
upload_asset "dist-windows-cross/${PACKAGE_NAME}.zip"
upload_asset "dist-windows-cross/${PACKAGE_NAME}.zip.sha256"
echo "Release assets uploaded to ${SERVER_URL}/${REPOSITORY}/releases/tag/${TAG_NAME}"
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"
$status = & curl.exe --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 "上传 Release 附件失败HTTP $status$name"
if (Test-Path $responseFile) { Get-Content $responseFile }
exit 1
}
Write-Host "Uploaded release asset: $name"
}
Upload-Asset $env:PACKAGE_ZIP
Upload-Asset "$env:PACKAGE_ZIP.sha256"
Write-Host "Release assets uploaded to $serverUrl/$repo/releases/tag/$tagName"