diff --git a/.gitea/workflows/build-windows-msys2-release.yml b/.gitea/workflows/build-windows-msys2-release.yml new file mode 100644 index 0000000..8c8f6b1 --- /dev/null +++ b/.gitea/workflows/build-windows-msys2-release.yml @@ -0,0 +1,180 @@ +name: Build Windows Release with MSYS2 + +on: + push: + branches: + - main + tags: + - 'v*' + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + build-windows-msys2: + # 需要 Gitea act_runner 运行在 Windows,并且能访问 GitHub 以拉取 actions/checkout、msys2/setup-msys2、actions/upload-artifact。 + # 如果你的 runner 使用了自定义标签,可以在 Gitea runner 配置中给它同时加上 windows 标签。 + runs-on: + - windows + + defaults: + run: + shell: msys2 {0} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup MSYS2 build environment + uses: msys2/setup-msys2@v2 + with: + msystem: UCRT64 + update: true + install: >- + git + curl + zip + 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-opencv + + - name: Configure + run: | + set -euo pipefail + cmake -S . -B build-windows-msys2 -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH=/ucrt64 + + - name: Build + run: | + set -euo pipefail + cmake --build build-windows-msys2 --parallel "$(nproc)" + + - name: Package + run: | + set -euo pipefail + APP_NAME="chengnan" + PACKAGE_NAME="chengnan-windows-x86_64-msys2" + PACKAGE_DIR="dist-windows-msys2/${PACKAGE_NAME}" + EXE="build-windows-msys2/${APP_NAME}.exe" + + test -f "$EXE" + rm -rf dist-windows-msys2 + mkdir -p "$PACKAGE_DIR" + cp "$EXE" "$PACKAGE_DIR/" + + # 部署 Qt 运行时和插件。 + if command -v windeployqt >/dev/null 2>&1; then + windeployqt --release --no-translations "$PACKAGE_DIR/${APP_NAME}.exe" + elif command -v windeployqt6 >/dev/null 2>&1; then + windeployqt6 --release --no-translations "$PACKAGE_DIR/${APP_NAME}.exe" + else + echo "windeployqt not found" >&2 + exit 1 + fi + + # 收集 MinGW/MSYS2/OpenCV 运行时 DLL。ldd 会列出 exe/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 < <(ldd "$binary" \ + | awk '/=> \/ucrt64\/bin\// {print $3} /^\/ucrt64\/bin\// {print $1}' \ + | sort -u) + done < <(find "$PACKAGE_DIR" -maxdepth 2 -type f \( -name '*.exe' -o -name '*.dll' \) | sort) + done + + cat > "$PACKAGE_DIR/README.txt" <<'EOF' + chengnan Windows x86_64 MSYS2 发布包 + + 运行方式: + 1. 解压整个目录。 + 2. 双击 chengnan.exe 运行。 + + 说明: + - 本目录由 Windows Runner 使用 MSYS2 UCRT64 构建生成。 + - 本目录已包含 Qt、OpenCV 和 MinGW 运行时 DLL。 + - 请不要只复制 chengnan.exe,需保留同目录 DLL 和 Qt plugins 子目录。 + EOF + + (cd dist-windows-msys2 && 7z a -tzip "${PACKAGE_NAME}.zip" "${PACKAGE_NAME}" >/dev/null) + (cd dist-windows-msys2 && sha256sum "${PACKAGE_NAME}.zip" > "${PACKAGE_NAME}.zip.sha256") + + # 当前 Gitea 上传入口约 60 秒会返回 502;大文件按 15 MiB 分片上传。 + ZIP="dist-windows-msys2/${PACKAGE_NAME}.zip" + split -b 15M -d -a 3 "$ZIP" "${ZIP}.part-" + (cd dist-windows-msys2 && sha256sum "${PACKAGE_NAME}.zip".part-* > "${PACKAGE_NAME}.zip.parts.sha256") + du -h "$ZIP" "${ZIP}".part-* + + - name: Verify package + run: | + set -euo pipefail + test -s dist-windows-msys2/chengnan-windows-x86_64-msys2.zip + test -s dist-windows-msys2/chengnan-windows-x86_64-msys2.zip.part-000 + test -s dist-windows-msys2/chengnan-windows-x86_64-msys2.zip.sha256 + test -s dist-windows-msys2/chengnan-windows-x86_64-msys2.zip.parts.sha256 + (cd dist-windows-msys2 && sha256sum -c chengnan-windows-x86_64-msys2.zip.sha256) + (cd dist-windows-msys2 && sha256sum -c chengnan-windows-x86_64-msys2.zip.parts.sha256) + 7z l dist-windows-msys2/chengnan-windows-x86_64-msys2.zip | grep 'chengnan.exe' + limit=$((20 * 1024 * 1024)) + for part in dist-windows-msys2/chengnan-windows-x86_64-msys2.zip.part-*; do + size="$(wc -c < "$part")" + echo "$(basename "$part") $size" + if [ "$size" -gt "$limit" ]; then + echo "$part is too large for the current Gitea upload path" >&2 + exit 1 + fi + done + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: chengnan-windows-x86_64-msys2 + path: | + dist-windows-msys2/chengnan-windows-x86_64-msys2.zip + dist-windows-msys2/chengnan-windows-x86_64-msys2.zip.sha256 + dist-windows-msys2/chengnan-windows-x86_64-msys2.zip.part-* + dist-windows-msys2/chengnan-windows-x86_64-msys2.zip.parts.sha256 + if-no-files-found: error + + - name: Upload package to Gitea Packages + if: ${{ github.event_name != 'pull_request' }} + env: + PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }} + run: | + set -euo pipefail + PACKAGE_NAME="chengnan-windows-x86_64-msys2" + VERSION="${GITHUB_SHA:-manual}" + VERSION="${VERSION:0:10}" + OWNER="${GITHUB_REPOSITORY%%/*}" + API_BASE="${GITHUB_SERVER_URL:-https://git.luochen570.cn}/api/packages/${OWNER}/generic/chengnan/${VERSION}" + TOKEN="${PACKAGE_TOKEN:-${GITHUB_TOKEN:-}}" + test -n "$TOKEN" + upload_file() { + local src="$1" + local url="$2" + local status + status="$(curl --show-error --silent --location \ + --header "Authorization: token ${TOKEN}" \ + --upload-file "$src" \ + --write-out '%{http_code}' \ + --output /tmp/gitea-upload-response.txt \ + "$url")" + if [ "$status" != "201" ] && [ "$status" != "200" ]; then + echo "Upload failed with HTTP ${status}: ${url}" >&2 + cat /tmp/gitea-upload-response.txt >&2 + exit 1 + fi + } + for part in dist-windows-msys2/${PACKAGE_NAME}.zip.part-*; do + upload_file "$part" "${API_BASE}/$(basename "$part")" + done + upload_file "dist-windows-msys2/${PACKAGE_NAME}.zip.sha256" "${API_BASE}/${PACKAGE_NAME}.zip.sha256" + upload_file "dist-windows-msys2/${PACKAGE_NAME}.zip.parts.sha256" "${API_BASE}/${PACKAGE_NAME}.zip.parts.sha256" + echo "Package uploaded to: ${API_BASE}/" + echo "Download all ${PACKAGE_NAME}.zip.part-* files, then reconstruct with: cat ${PACKAGE_NAME}.zip.part-* > ${PACKAGE_NAME}.zip"