feat(win): T-WIN-011 Windows 打包与一键验证脚本
This commit is contained in:
@@ -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
|
||||
Executable
+116
@@ -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 同目录)。"
|
||||
Executable
+55
@@ -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 必须同目录)。"
|
||||
Executable
+89
@@ -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 "============================================================"
|
||||
@@ -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}\""
|
||||
Reference in New Issue
Block a user