feat(win): T-WIN-011 Windows 打包与一键验证脚本

This commit is contained in:
2026-09-14 20:04:54 +08:00
parent 13d6dc915d
commit 315cf79708
5 changed files with 433 additions and 0 deletions
+112
View File
@@ -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
+116
View File
@@ -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 同目录)。"
+55
View File
@@ -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 必须同目录)。"
+89
View File
@@ -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 "============================================================"