name: Cross Build Windows Release on Linux on: push: branches: - main tags: - 'v*' pull_request: branches: - main workflow_dispatch: jobs: cross-build-windows: # 使用可访问 GitHub 的 Gitea act_runner(runner-mihomo),可直接拉取 actions/checkout、actions/upload-artifact 等 GitHub Action。 runs-on: ubuntu-latest container: image: fedora:latest steps: - name: Prepare action runtime 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 dnf install -y git nodejs - name: Checkout uses: actions/checkout@v4 - name: Install cross build dependencies run: | set -euo pipefail dnf install -y \ cmake \ curl \ ninja-build \ p7zip \ p7zip-plugins \ python3 \ mingw64-gcc-c++ \ mingw64-qt6-qtbase \ mingw64-opencv - 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 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" test -f "$EXE" rm -rf dist-windows-cross mkdir -p "$PACKAGE_DIR" cp "$EXE" "$PACKAGE_DIR/" # 收集 MinGW 运行时、Qt、OpenCV 依赖 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 交叉编译发布包 运行方式: 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 artifact uses: actions/upload-artifact@v3 with: name: chengnan-windows-x86_64-cross path: | dist-windows-cross/chengnan-windows-x86_64-cross.zip dist-windows-cross/chengnan-windows-x86_64-cross.zip.sha256 if-no-files-found: error - name: Upload package to repository release if: ${{ github.event_name != 'pull_request' }} 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()))' } 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")" 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,sys; 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 } 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}"