ci: use Linux runner for Windows release
Some checks failed
Build Windows Release / Cross build Windows x86_64 Release (push) Has been cancelled
Some checks failed
Build Windows Release / Cross build Windows x86_64 Release (push) Has been cancelled
This commit is contained in:
@@ -12,118 +12,217 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-windows-release:
|
cross-build-windows:
|
||||||
name: Build Windows x64 Release
|
name: Cross build Windows x86_64 Release
|
||||||
runs-on:
|
# Gitea act_runner 标签为 9950x;该工作流在 Linux Runner 中交叉编译 Windows x86_64 包。
|
||||||
- windows
|
runs-on: 9950x
|
||||||
|
container:
|
||||||
env:
|
image: fedora:latest
|
||||||
BUILD_TYPE: Release
|
|
||||||
QT_VERSION: '6.7.3'
|
|
||||||
QT_ARCH: win64_msvc2019_64
|
|
||||||
QT_MODULES: qtimageformats
|
|
||||||
DIST_DIR: dist-windows\chengnan
|
|
||||||
|
|
||||||
steps:
|
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
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup MSVC developer command prompt
|
|
||||||
uses: ilammy/msvc-dev-cmd@v1
|
|
||||||
with:
|
|
||||||
arch: x64
|
|
||||||
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.x'
|
|
||||||
|
|
||||||
- name: Install Qt with aqtinstall
|
|
||||||
shell: powershell
|
|
||||||
run: |
|
run: |
|
||||||
$ErrorActionPreference = 'Stop'
|
set -euo pipefail
|
||||||
python -m pip install --upgrade pip
|
dnf install -y git rsync
|
||||||
python -m pip install aqtinstall
|
WORKDIR="$(pwd)"
|
||||||
python -m aqt install-qt windows desktop $env:QT_VERSION win64_msvc2019_64 --outputdir C:\Qt --modules $env:QT_MODULES
|
SRC_DIR="$(mktemp -d /tmp/chengnan-source.XXXXXX)"
|
||||||
$qtPrefix = "C:\Qt\$env:QT_VERSION\msvc2019_64"
|
REPO_URL="${GITHUB_SERVER_URL:-https://git.luochen570.cn}/${GITHUB_REPOSITORY}.git"
|
||||||
if (-not (Test-Path "$qtPrefix\bin\windeployqt.exe")) {
|
if [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||||
throw "Qt 安装不完整,找不到 windeployqt:$qtPrefix\bin\windeployqt.exe"
|
git -c http.extraHeader="Authorization: token ${GITHUB_TOKEN}" \
|
||||||
}
|
clone --depth 1 --branch "${GITHUB_REF_NAME:-main}" "$REPO_URL" "$SRC_DIR"
|
||||||
"QT_PREFIX=$qtPrefix" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
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"
|
||||||
|
|
||||||
- name: Configure and build
|
- name: Install cross build dependencies
|
||||||
shell: powershell
|
|
||||||
run: |
|
run: |
|
||||||
$ErrorActionPreference = 'Stop'
|
set -euo pipefail
|
||||||
powershell -ExecutionPolicy Bypass -File .\build-windows.ps1 -QtPrefix "$env:QT_PREFIX" -Config $env:BUILD_TYPE
|
dnf install -y \
|
||||||
|
cmake \
|
||||||
|
curl \
|
||||||
|
ninja-build \
|
||||||
|
p7zip \
|
||||||
|
p7zip-plugins \
|
||||||
|
mingw64-gcc-c++ \
|
||||||
|
mingw64-qt6-qtbase
|
||||||
|
|
||||||
|
- 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: Build
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
cmake --build build-windows-cross --parallel "$(nproc)"
|
||||||
|
|
||||||
- name: Package
|
- name: Package
|
||||||
shell: powershell
|
|
||||||
run: |
|
run: |
|
||||||
$ErrorActionPreference = 'Stop'
|
set -euo pipefail
|
||||||
$shortSha = $env:GITHUB_SHA.Substring(0, 10)
|
APP_NAME="chengnan"
|
||||||
$version = if ($env:GITHUB_REF_TYPE -eq 'tag') { $env:GITHUB_REF_NAME } else { "build-$shortSha" }
|
PACKAGE_NAME="chengnan-windows-x86_64-cross"
|
||||||
$zipName = "chengnan-windows-x64-$version.zip"
|
PACKAGE_DIR="dist-windows-cross/${PACKAGE_NAME}"
|
||||||
if (-not (Test-Path $env:DIST_DIR)) {
|
EXE="build-windows-cross/${APP_NAME}.exe"
|
||||||
throw "发布目录不存在:$env:DIST_DIR"
|
|
||||||
}
|
test -f "$EXE"
|
||||||
Compress-Archive -Path "$env:DIST_DIR\*" -DestinationPath $zipName -Force
|
rm -rf dist-windows-cross
|
||||||
$hash = (Get-FileHash $zipName -Algorithm SHA256).Hash.ToLowerInvariant()
|
mkdir -p "$PACKAGE_DIR"
|
||||||
"$hash $zipName" | Out-File -FilePath "$zipName.sha256" -Encoding ascii
|
cp "$EXE" "$PACKAGE_DIR/"
|
||||||
"PACKAGE_ZIP=$zipName" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
|
||||||
"RELEASE_TAG=$version" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
# 递归收集 MinGW 运行时和 Qt 依赖 DLL。
|
||||||
Write-Host "Package: $zipName"
|
for round in 1 2 3; do
|
||||||
Write-Host "SHA256: $hash"
|
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 交叉编译发布包
|
||||||
|
|
||||||
|
运行方式:
|
||||||
|
1. 解压整个目录。
|
||||||
|
2. 双击 chengnan.exe 运行。
|
||||||
|
|
||||||
|
说明:
|
||||||
|
- 本目录由 Linux Runner 使用 MinGW-w64 交叉编译生成。
|
||||||
|
- 请不要只复制 chengnan.exe,需保留同目录 DLL 和 platforms 子目录。
|
||||||
|
EOF
|
||||||
|
|
||||||
|
(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"
|
||||||
|
|
||||||
|
- name: Verify package
|
||||||
|
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/'
|
||||||
|
|
||||||
- name: Upload build artifact
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: chengnan-windows-x64
|
name: chengnan-windows-x86_64-cross
|
||||||
path: |
|
path: |
|
||||||
${{ env.PACKAGE_ZIP }}
|
dist-windows-cross/chengnan-windows-x86_64-cross.zip
|
||||||
${{ env.PACKAGE_ZIP }}.sha256
|
dist-windows-cross/chengnan-windows-x86_64-cross.zip.sha256
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Publish to Gitea Release
|
- name: Upload package to repository release
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
shell: powershell
|
|
||||||
env:
|
env:
|
||||||
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}
|
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
$ErrorActionPreference = 'Stop'
|
set -euo pipefail
|
||||||
if ([string]::IsNullOrWhiteSpace($env:GITEA_TOKEN)) {
|
PACKAGE_NAME="chengnan-windows-x86_64-cross"
|
||||||
throw '缺少发布 Token。请在仓库 Secrets 中配置 RELEASE_TOKEN,或确保 Gitea 提供 GITHUB_TOKEN。'
|
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()))'
|
||||||
}
|
}
|
||||||
|
|
||||||
$api = $env:GITHUB_API_URL.TrimEnd('/')
|
release_payload="$(printf '{"tag_name":%s,"target_commitish":%s,"name":%s,"body":%s,"draft":false,"prerelease":%s}' \
|
||||||
$repo = $env:GITHUB_REPOSITORY
|
"$(printf '%s' "$TAG_NAME" | json_escape)" \
|
||||||
$tag = $env:RELEASE_TAG
|
"$(printf '%s' "${GITHUB_SHA}" | json_escape)" \
|
||||||
$zip = $env:PACKAGE_ZIP
|
"$(printf '%s' "$RELEASE_NAME" | json_escape)" \
|
||||||
$headers = @{
|
"$(printf 'Automated Windows x86_64 cross build from Gitea Actions.\n' | json_escape)" \
|
||||||
Authorization = "token $env:GITEA_TOKEN"
|
"$PRERELEASE")"
|
||||||
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")"
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
$releaseUrl = "$api/repos/$repo/releases/tags/$tag"
|
upload_asset "dist-windows-cross/${PACKAGE_NAME}.zip"
|
||||||
try {
|
upload_asset "dist-windows-cross/${PACKAGE_NAME}.zip.sha256"
|
||||||
$release = Invoke-RestMethod -Method Get -Uri $releaseUrl -Headers $headers
|
echo "Release assets uploaded to ${SERVER_URL}/${REPOSITORY}/releases/tag/${TAG_NAME}"
|
||||||
} catch {
|
|
||||||
$body = @{
|
|
||||||
tag_name = $tag
|
|
||||||
target_commitish = $env:GITHUB_SHA
|
|
||||||
name = "chengnan $tag"
|
|
||||||
body = "Windows x64 Release 自动构建产物。`n`n提交:$env:GITHUB_SHA"
|
|
||||||
draft = $false
|
|
||||||
prerelease = ($env:GITHUB_REF_TYPE -ne 'tag')
|
|
||||||
} | ConvertTo-Json -Depth 5
|
|
||||||
$release = Invoke-RestMethod -Method Post -Uri "$api/repos/$repo/releases" -Headers $headers -ContentType 'application/json' -Body $body
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($asset in @($zip, "$zip.sha256")) {
|
|
||||||
$existing = @($release.assets | Where-Object { $_.name -eq $asset })
|
|
||||||
foreach ($item in $existing) {
|
|
||||||
Invoke-RestMethod -Method Delete -Uri "$api/repos/$repo/releases/assets/$($item.id)" -Headers $headers | Out-Null
|
|
||||||
}
|
|
||||||
$uploadUrl = "$api/repos/$repo/releases/$($release.id)/assets?name=$([uri]::EscapeDataString($asset))"
|
|
||||||
Invoke-RestMethod -Method Post -Uri $uploadUrl -Headers $headers -ContentType 'multipart/form-data' -Form @{ attachment = Get-Item $asset } | Out-Null
|
|
||||||
Write-Host "Uploaded release asset: $asset"
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user