From 315cf79708e78401fae1a143d3e49c4df642b931 Mon Sep 17 00:00:00 2001 From: zijunchiang Date: Mon, 14 Sep 2026 20:04:54 +0800 Subject: [PATCH] =?UTF-8?q?feat(win):=20T-WIN-011=20Windows=20=E6=89=93?= =?UTF-8?q?=E5=8C=85=E4=B8=8E=E4=B8=80=E9=94=AE=E9=AA=8C=E8=AF=81=E8=84=9A?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/export-windows.ps1 | 112 +++++++++++++++++++++++++++++++++++++ build/export-windows.sh | 116 +++++++++++++++++++++++++++++++++++++++ build/package-release.sh | 55 +++++++++++++++++++ build/verify.sh | 89 ++++++++++++++++++++++++++++++ export_presets.cfg | 61 ++++++++++++++++++++ 5 files changed, 433 insertions(+) create mode 100644 build/export-windows.ps1 create mode 100755 build/export-windows.sh create mode 100755 build/package-release.sh create mode 100755 build/verify.sh create mode 100644 export_presets.cfg diff --git a/build/export-windows.ps1 b/build/export-windows.ps1 new file mode 100644 index 0000000..d4e7c43 --- /dev/null +++ b/build/export-windows.ps1 @@ -0,0 +1,112 @@ +<# +.SYNOPSIS + OpenDance Studio —— Windows 客户端导出(在 Windows 上执行) + +.DESCRIPTION + 文档 §5.3 方案 B(兜底):若因插件或引擎限制无法在 Linux 导出, + 由用户在 Windows 端执行本脚本。 + + 前置: + 1. 已安装 Godot 4 并将其加入 PATH,或用 -GodotBin 指定完整路径 + 2. 已安装对应版本的 export templates + (Godot 编辑器 → 编辑器 → 管理导出模板 → 下载并安装) + +.PARAMETER GodotBin + Godot 可执行文件路径,默认 "godot" + +.PARAMETER OutputDir + 产物输出目录,默认 ".\build\windows" + +.PARAMETER Mode + release 或 debug,默认 release + +.EXAMPLE + powershell -ExecutionPolicy Bypass -File build\export-windows.ps1 + powershell -ExecutionPolicy Bypass -File build\export-windows.ps1 -GodotBin "C:\Godot\godot.exe" +#> +param( + [string]$GodotBin = "godot", + [string]$OutputDir = ".\build\windows", + [ValidateSet("release", "debug")] + [string]$Mode = "release" +) + +$ErrorActionPreference = "Stop" + +$ProjectDir = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) +Set-Location $ProjectDir + +Write-Host "[export] 项目:$ProjectDir" +Write-Host "[export] Godot:$GodotBin" +Write-Host "[export] 模式:$Mode" + +# ---- 定位 Godot ---- +$godotCmd = Get-Command $GodotBin -ErrorAction SilentlyContinue +if (-not $godotCmd -and -not (Test-Path $GodotBin)) { + Write-Error "未找到 Godot 可执行文件「$GodotBin」。请安装 Godot 4 或用 -GodotBin 指定完整路径。" + exit 1 +} +$GodotExe = if ($godotCmd) { $godotCmd.Source } else { $GodotBin } + +$version = & $GodotExe --version 2>$null | Select-Object -First 1 +Write-Host "[export] 版本:$version" + +# ---- 导出预设必须位于项目根目录 ---- +if (-not (Test-Path (Join-Path $ProjectDir "export_presets.cfg"))) { + $fallback = Join-Path $ProjectDir "build\export_presets.cfg" + if (Test-Path $fallback) { + Copy-Item $fallback (Join-Path $ProjectDir "export_presets.cfg") + Write-Host "[export] 已从 build\ 复制导出预设到项目根目录" + } else { + Write-Error "缺少 export_presets.cfg(Godot 要求放在项目根目录)。" + exit 1 + } +} + +# ---- 检查导出模板 ---- +$templateDir = Join-Path $env:APPDATA "Godot\export_templates" +if (-not (Test-Path $templateDir) -or (Get-ChildItem $templateDir -ErrorAction SilentlyContinue).Count -eq 0) { + Write-Error "未找到导出模板($templateDir)。请在 Godot 编辑器中选择「编辑器 → 管理导出模板」下载安装。" + exit 1 +} +Write-Host "[export] 导出模板:$((Get-ChildItem $templateDir | Select-Object -ExpandProperty Name) -join ', ')" + +if (-not (Test-Path $OutputDir)) { + New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null +} + +# ---- 导入资源 ---- +Write-Host "[export] 导入资源…" +& $GodotExe --headless --path $ProjectDir --import 2>&1 | Out-Null + +# ---- 导出 ---- +Write-Host "[export] 导出中…" +$exePath = Join-Path $OutputDir "OpenDance.exe" +& $GodotExe --headless --path $ProjectDir --export-$Mode "Windows Desktop" $exePath +if ($LASTEXITCODE -ne 0) { + Write-Host "[export] 导出命令返回 $LASTEXITCODE,检查产物是否存在…" +} + +# ---- 校验产物 ---- +$pckPath = Join-Path $OutputDir "OpenDance.pck" +$missing = $false +if (-not (Test-Path $exePath)) { Write-Error "未生成 $exePath"; $missing = $true } +if (-not (Test-Path $pckPath)) { Write-Error "未生成 $pckPath"; $missing = $true } +if ($missing) { exit 1 } + +# 校验 PE 头(MZ) +$bytes = [System.IO.File]::ReadAllBytes($exePath)[0..1] +if ($bytes[0] -ne 0x4D -or $bytes[1] -ne 0x5A) { + Write-Error "$exePath 不是合法的 Windows PE 文件。" + exit 1 +} + +$exeSize = [math]::Round((Get-Item $exePath).Length / 1KB) +$pckSize = [math]::Round((Get-Item $pckPath).Length / 1KB) +Write-Host "" +Write-Host "[export] 完成:" -ForegroundColor Green +Write-Host " $exePath ($exeSize KiB, PE 头校验通过)" +Write-Host " $pckPath ($pckSize KiB)" +Write-Host "" +Write-Host "在 Windows 10/11 上运行 OpenDance.exe 即可(.pck 必须与 .exe 同目录)。" +exit 0 diff --git a/build/export-windows.sh b/build/export-windows.sh new file mode 100755 index 0000000..cb30dbb --- /dev/null +++ b/build/export-windows.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# ============================================================ +# OpenDance Studio —— Windows 客户端导出(在 Linux 上执行) +# +# 文档 §5.3 方案 A:Godot 4 支持在 Linux 上导出 Windows 可执行文件。 +# +# 前置: +# 1. Godot 4 可执行文件(GODOT_BIN 指定,或已在 PATH 中) +# 2. 已安装对应版本的 export templates +# 下载:https://github.com/godotengine/godot/releases +# Godot_v<版本>-stable_export_templates.tpz +# 安装:解压后把 templates/* 放到 +# ~/.local/share/godot/export_templates/<版本>.stable/ +# +# 用法:bash build/export-windows.sh [debug|release] +# ============================================================ +set -euo pipefail + +PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +cd "$PROJECT_DIR" + +MODE="${1:-release}" +if [ "$MODE" != "debug" ] && [ "$MODE" != "release" ]; then + echo "用法:bash build/export-windows.sh [debug|release]" >&2 + exit 2 +fi + +# ---- 定位 Godot ---- +GODOT_BIN="${GODOT_BIN:-}" +if [ -z "$GODOT_BIN" ]; then + for candidate in godot godot4 Godot; do + if command -v "$candidate" >/dev/null 2>&1; then GODOT_BIN="$candidate"; break; fi + done +fi +if [ -z "$GODOT_BIN" ]; then + echo "错误:未找到 Godot 可执行文件。请安装 Godot 4 或用 GODOT_BIN=<路径> 指定。" >&2 + exit 1 +fi + +OUTPUT_DIR="$PROJECT_DIR/build/windows" +mkdir -p "$OUTPUT_DIR" + +GODOT_VERSION="$("$GODOT_BIN" --version 2>/dev/null | head -1 || true)" +echo "[export] Godot:$GODOT_BIN($GODOT_VERSION)" +echo "[export] 项目:$PROJECT_DIR" +echo "[export] 模式:$MODE" + +# ---- 导出预设必须位于项目根目录(Godot 的硬性要求)---- +if [ ! -f "$PROJECT_DIR/export_presets.cfg" ]; then + if [ -f "$PROJECT_DIR/build/export_presets.cfg" ]; then + cp "$PROJECT_DIR/build/export_presets.cfg" "$PROJECT_DIR/export_presets.cfg" + echo "[export] 已从 build/ 复制导出预设到项目根目录" + else + echo "错误:缺少 export_presets.cfg(Godot 要求放在项目根目录)。" >&2 + exit 1 + fi +fi + +# ---- 检查导出模板 ---- +TEMPLATE_DIR="${HOME}/.local/share/godot/export_templates" +if [ ! -d "$TEMPLATE_DIR" ] || [ -z "$(ls -A "$TEMPLATE_DIR" 2>/dev/null)" ]; then + echo "错误:未找到导出模板($TEMPLATE_DIR 为空)。" >&2 + echo " 请从 https://github.com/godotengine/godot/releases 下载" >&2 + echo " Godot_v<版本>-stable_export_templates.tpz 并安装到" >&2 + echo " ~/.local/share/godot/export_templates/<版本>.stable/" >&2 + exit 1 +fi +echo "[export] 导出模板:$(ls "$TEMPLATE_DIR" | tr '\n' ' ')" + +# ---- 确保资源已导入(缺失会让导出静默产出不完整的包)---- +echo "[export] 导入资源…" +"$GODOT_BIN" --headless --path "$PROJECT_DIR" --import >/dev/null 2>&1 || true + +# ---- 导出 ---- +echo "[export] 导出中…" +set +e +"$GODOT_BIN" --headless --path "$PROJECT_DIR" \ + --export-"$MODE" "Windows Desktop" "$OUTPUT_DIR/OpenDance.exe" +EXPORT_STATUS=$? +set -e + +if [ "$EXPORT_STATUS" -ne 0 ]; then + echo "[export] 导出命令返回 $EXPORT_STATUS,检查产物是否存在…" +fi + +# ---- 校验产物 ---- +EXE="$OUTPUT_DIR/OpenDance.exe" +PCK="$OUTPUT_DIR/OpenDance.pck" +CONSOLE="$OUTPUT_DIR/OpenDance.console.exe" + +MISSING=0 +[ -f "$EXE" ] || { echo "错误:未生成 $EXE" >&2; MISSING=1; } +[ -f "$PCK" ] || { echo "错误:未生成 $PCK" >&2; MISSING=1; } +[ -f "$CONSOLE" ] || echo "[export] 提示:未生成 console 包装器(非致命)" + +if [ "$MISSING" -ne 0 ]; then + echo "导出失败。" >&2 + exit 1 +fi + +# 校验 PE 头,确认确实是 Windows 可执行文件 +MAGIC=$(head -c 2 "$EXE" | od -An -tx1 | tr -d ' \n') +if [ "$MAGIC" != "4d5a" ]; then + echo "错误:$EXE 不是合法的 Windows PE 文件(头 2 字节为 $MAGIC,期望 4d5a 'MZ')" >&2 + exit 1 +fi + +SIZE_EXE=$(stat -c%s "$EXE") +SIZE_PCK=$(stat -c%s "$PCK") +echo "" +echo "[export] 完成:" +echo " $EXE ($((SIZE_EXE / 1024)) KiB, PE 头校验通过)" +echo " $PCK ($((SIZE_PCK / 1024)) KiB)" +[ -f "$CONSOLE" ] && echo " $CONSOLE ($(( $(stat -c%s "$CONSOLE") / 1024 )) KiB)" +echo "" +echo "在 Windows 10/11 上运行 $EXE 即可(.pck 必须与 .exe 同目录)。" diff --git a/build/package-release.sh b/build/package-release.sh new file mode 100755 index 0000000..f67e442 --- /dev/null +++ b/build/package-release.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# ============================================================ +# OpenDance Studio —— Windows 客户端发布打包 +# +# 产物(文档 §5.3): +# opendance-windows-v0.1.0.zip +# ├── OpenDance.exe +# ├── OpenDance.pck +# ├── THIRD_PARTY.md +# ├── README.txt +# └── contracts/ 契约快照 +# +# 用法:bash build/package-release.sh [版本号] +# ============================================================ +set -euo pipefail + +PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +cd "$PROJECT_DIR" + +VERSION="${1:-0.1.0}" +NAME="opendance-windows-v${VERSION}" +OUT_DIR="$PROJECT_DIR/dist-release" +STAGE="$OUT_DIR/$NAME" + +log() { printf '\033[1;34m[package]\033[0m %s\n' "$*"; } +die() { printf '\033[1;31m[error]\033[0m %s\n' "$*" >&2; exit 1; } + +EXE="$PROJECT_DIR/build/windows/OpenDance.exe" +PCK="$PROJECT_DIR/build/windows/OpenDance.pck" + +[ -f "$EXE" ] || die "未找到 $EXE,请先执行 bash build/export-windows.sh release" +[ -f "$PCK" ] || die "未找到 $PCK,请先执行 bash build/export-windows.sh release" + +# 校验 PE 头 +MAGIC=$(head -c 2 "$EXE" | od -An -tx1 | tr -d ' \n') +[ "$MAGIC" = "4d5a" ] || die "$EXE 不是合法的 Windows PE 文件(头 $MAGIC)" + +log "整理产物到 $STAGE …" +rm -rf "$STAGE" +mkdir -p "$STAGE" + +cp "$EXE" "$STAGE/OpenDance.exe" +cp "$PCK" "$STAGE/OpenDance.pck" +cp THIRD_PARTY.md README.txt "$STAGE/" +cp -r contracts "$STAGE/contracts" + +log "生成压缩包…" +( cd "$OUT_DIR" && zip -qr "$NAME.zip" "$NAME" ) + +log "完成:$OUT_DIR/$NAME.zip($(du -h "$OUT_DIR/$NAME.zip" | cut -f1))" +echo "" +echo "产物内容:" +unzip -l "$OUT_DIR/$NAME.zip" | sed -n '4,20p' +echo "" +echo "分发:把 zip 交给 Windows 用户,解压后运行 OpenDance.exe(.pck 必须同目录)。" diff --git a/build/verify.sh b/build/verify.sh new file mode 100755 index 0000000..67910da --- /dev/null +++ b/build/verify.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# ============================================================ +# OpenDance Studio —— Windows 客户端一键验证 +# +# 依次执行:脚本编译检查 → 无头单元/集成测试 → 应用启动检查 +# 任一环节失败即退出非 0。 +# +# 用法:GODOT_BIN=/path/to/godot bash build/verify.sh +# ============================================================ +set -uo pipefail + +PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +cd "$PROJECT_DIR" + +GODOT_BIN="${GODOT_BIN:-}" +if [ -z "$GODOT_BIN" ]; then + for candidate in godot godot4 Godot; do + if command -v "$candidate" >/dev/null 2>&1; then GODOT_BIN="$candidate"; break; fi + done +fi +if [ -z "$GODOT_BIN" ]; then + echo "错误:未找到 Godot。请用 GODOT_BIN=<路径> 指定。" >&2 + exit 1 +fi + +FAILED=0 + +echo "============================================================" +echo " 1/3 脚本编译检查" +echo "============================================================" +IMPORT_LOG=$(mktemp) +"$GODOT_BIN" --headless --path "$PROJECT_DIR" --import >"$IMPORT_LOG" 2>&1 +if grep -qE "SCRIPT ERROR|Parse Error|Compile Error" "$IMPORT_LOG"; then + echo "✗ 存在脚本编译错误:" + grep -E "SCRIPT ERROR|Parse Error|Compile Error" "$IMPORT_LOG" | head -20 + FAILED=1 +else + echo "✓ 全部脚本编译通过" +fi +rm -f "$IMPORT_LOG" + +echo "" +echo "============================================================" +echo " 2/3 无头测试(单元 + 真实 API 集成)" +echo "============================================================" +TEST_LOG=$(mktemp) +"$GODOT_BIN" --headless --path "$PROJECT_DIR" --script res://tests/run_tests.gd >"$TEST_LOG" 2>&1 +TEST_STATUS=$? +grep -E "^(== 结果|全部通过)" "$TEST_LOG" || true +if [ "$TEST_STATUS" -ne 0 ]; then + echo "✗ 测试失败(退出码 $TEST_STATUS)" + grep -E "✗" "$TEST_LOG" | head -20 + FAILED=1 +else + echo "✓ 测试全部通过" +fi +rm -f "$TEST_LOG" + +echo "" +echo "============================================================" +echo " 3/3 应用启动检查(180 帧)" +echo "============================================================" +RUN_LOG=$(mktemp) +timeout 120 "$GODOT_BIN" --headless --path "$PROJECT_DIR" --quit-after 180 >"$RUN_LOG" 2>&1 +RUN_STATUS=$? +if [ "$RUN_STATUS" -ne 0 ]; then + echo "✗ 启动失败(退出码 $RUN_STATUS)" + tail -20 "$RUN_LOG" + FAILED=1 +elif grep -qE "SCRIPT ERROR|ERROR:" "$RUN_LOG"; then + echo "✗ 启动过程有错误:" + grep -E "SCRIPT ERROR|ERROR:" "$RUN_LOG" | head -10 + FAILED=1 +else + echo "✓ 应用启动正常,无运行时错误" +fi +rm -f "$RUN_LOG" + +echo "" +if [ "$FAILED" -ne 0 ]; then + echo "============================================================" + echo " 验证失败" + echo "============================================================" + exit 1 +fi + +echo "============================================================" +echo " 验证全部通过" +echo "============================================================" diff --git a/export_presets.cfg b/export_presets.cfg new file mode 100644 index 0000000..ed2294a --- /dev/null +++ b/export_presets.cfg @@ -0,0 +1,61 @@ +[preset.0] + +name="Windows Desktop" +platform="Windows Desktop" +runnable=true +advanced_options=false +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="_scratch/*, tests/*, *.md" +export_path="build/windows/OpenDance.exe" +patches=PackedStringArray() +encryption_include_filters="" +encryption_exclude_filters="" +seed=0 +encrypt_pck=false +encrypt_directory=false +script_export_mode=2 + +[preset.0.options] + +custom_template/debug="" +custom_template/release="" +debug/export_console_wrapper=1 +binary_format/embed_pck=false +texture_format/s3tc_bptc=true +texture_format/etc2_astc=false +binary_format/architecture="x86_64" +codesign/enable=false +codesign/timestamp=true +codesign/timestamp_server_url="" +codesign/digest_algorithm=1 +codesign/description="" +codesign/custom_options=PackedStringArray() +application/modify_resources=true +application/icon="" +application/console_wrapper_icon="" +application/icon_interpolation=4 +application/file_version="0.1.0" +application/product_version="0.1.0" +application/company_name="OpenDance Studio" +application/product_name="OpenDance Studio" +application/file_description="OpenDance Studio 摄像头跳舞评分客户端" +application/copyright="OpenDance Studio" +application/trademarks="" +application/export_angle=0 +application/export_d3d12=0 +application/d3d12_agility_sdk_multiarch=true +ssh_remote_deploy/enabled=false +ssh_remote_deploy/host="user@host_ip" +ssh_remote_deploy/port="22" +ssh_remote_deploy/extra_args_ssh="" +ssh_remote_deploy/extra_args_scp="" +ssh_remote_deploy/run_script="#!/usr/bin/env bash +export DISPLAY=:0 +unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\" +\"{temp_dir}/{exe_name}\" {cmd_args}" +ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash +kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\") +rm -rf \"{temp_dir}\""