项目文件夹

文件
2026-07-13 12:49:17 +08:00

286 行
9.8 KiB
Bash
可执行文件

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
#!/usr/bin/env bash
# Local parity check for the CI Unity-version matrix.
#
# Usage:
# tools/check-unity-versions.sh # compile-only, all installed versions from tools/unity-versions.json
# tools/check-unity-versions.sh --full # full EditMode test run (matches CI behavior)
# tools/check-unity-versions.sh --only 6000.0 # check only versions whose id starts with the given prefix
# tools/check-unity-versions.sh --docker # run inside GameCI containers (no local Unity Hub install needed)
# tools/check-unity-versions.sh --pre-push # hint mode used by the pre-push hook (changes failure message)
#
# Modes:
# - Default (local): looks for Unity editors under Unity Hub. Versions not installed are skipped.
# - --docker: runs each version inside unityci/editor:ubuntu-<id>-base-<tag>. Requires UNITY_LICENSE env
# (contents of a .ulf file). On macOS arm64, expect ~5-10× slowdown from amd64 emulation.
#
# Exits non-zero if any *checked* version fails. Versions skipped (not installed locally / image not pulled
# in offline mode) do not cause failure on their own.
#
# Linked to CI: both this script and .github/workflows/unity-tests.yml read tools/unity-versions.json.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
VERSIONS_JSON="${REPO_ROOT}/tools/unity-versions.json"
PROJECT_PATH="${REPO_ROOT}/TestProjects/UnityMCPTests"
LOG_DIR="${REPO_ROOT}/tools/.unity-check-logs"
# Default GameCI image tag suffix. GameCI publishes both sliding (base-3) and pinned (base-3.1.0) tags;
# we default to the major-major (base-3) sliding tag and let users pin via --docker-image-tag.
DOCKER_IMAGE_TAG="base-3"
FULL=0
ONLY=""
PRE_PUSH=0
USE_DOCKER=0
require_value() {
# Validate that a flag taking a value got one (not another flag, not nothing).
local flag="$1" value="${2:-}"
if [[ -z "$value" || "$value" == --* ]]; then
echo "error: $flag requires a value" >&2
exit 2
fi
}
while [[ $# -gt 0 ]]; do
case "$1" in
--full|--with-tests) FULL=1 ;;
--only) require_value "$1" "${2:-}"; ONLY="$2"; shift ;;
--docker) USE_DOCKER=1 ;;
--docker-image-tag) require_value "$1" "${2:-}"; DOCKER_IMAGE_TAG="$2"; shift ;;
--pre-push) PRE_PUSH=1 ;;
-h|--help)
sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//'
exit 0 ;;
*) echo "Unknown argument: $1" >&2; exit 2 ;;
esac
shift
done
if ! command -v jq >/dev/null 2>&1; then
echo "error: 'jq' is required (brew install jq / apt-get install jq)" >&2
exit 2
fi
if [[ ! -f "$VERSIONS_JSON" ]]; then
echo "error: $VERSIONS_JSON missing" >&2
exit 2
fi
if [[ ! -d "$PROJECT_PATH" ]]; then
echo "error: project path not found: $PROJECT_PATH" >&2
exit 2
fi
mkdir -p "$LOG_DIR"
# ---- mode setup ---------------------------------------------------------------------
if [[ $USE_DOCKER -eq 1 ]]; then
if ! command -v docker >/dev/null 2>&1; then
echo "error: --docker requires Docker (https://docs.docker.com/get-docker/)" >&2
exit 2
fi
if ! docker info >/dev/null 2>&1; then
echo "error: Docker daemon not reachable. Start Docker Desktop / dockerd and retry." >&2
exit 2
fi
if [[ -z "${UNITY_LICENSE:-}" ]]; then
cat >&2 <<'EOF'
error: --docker requires a Unity license. Set UNITY_LICENSE to the contents of a Unity_lic.ulf file.
One-time setup (free Personal license):
1. Generate a request file inside a GameCI container:
docker run --rm -v "$PWD":/work unityci/editor:ubuntu-2021.3.45f2-base-3 \
/opt/unity/Editor/Unity -batchmode -nographics -quit -createManualActivationFile \
-logFile /dev/stdout
This writes Unity_v<version>.alf to your current directory.
2. Upload that .alf at https://license.unity3d.com/manual → choose Personal → save the .ulf it returns.
3. Export the .ulf contents in your shell (add to ~/.zshrc or similar to persist):
export UNITY_LICENSE="$(cat /path/to/Unity_v<version>.ulf)"
4. Re-run this script.
(The same UNITY_LICENSE secret is what the GitHub Actions workflow uses; one .ulf works across all matrix
versions in practice — Unity Personal activations are tied to the machine, not the editor version.)
EOF
exit 2
fi
if [[ "$(uname -m)" == "arm64" || "$(uname -m)" == "aarch64" ]]; then
if [[ "$(uname -s)" == "Darwin" ]]; then
echo "note: arm64 Mac — GameCI images run via amd64 emulation; expect ~5-10× slowdown."
fi
fi
else
case "$(uname -s)" in
Darwin) HUB_ROOT="/Applications/Unity/Hub/Editor" ; UNITY_RELPATH="Unity.app/Contents/MacOS/Unity" ;;
Linux) HUB_ROOT="${HOME}/Unity/Hub/Editor" ; UNITY_RELPATH="Editor/Unity" ;;
*) echo "error: unsupported OS '$(uname -s)' — use check-unity-versions.ps1 on Windows, or --docker on any platform" >&2; exit 2 ;;
esac
fi
# Pretty output even without colors set up.
if [[ -t 1 ]]; then
C_OK=$'\033[32m'; C_FAIL=$'\033[31m'; C_SKIP=$'\033[33m'; C_DIM=$'\033[2m'; C_RST=$'\033[0m'
else
C_OK=""; C_FAIL=""; C_SKIP=""; C_DIM=""; C_RST=""
fi
VERSIONS=()
while IFS= read -r line; do
VERSIONS+=("$line")
done < <(jq -r '.versions[].id' "$VERSIONS_JSON")
if [[ -n "$ONLY" ]]; then
filtered=()
for v in "${VERSIONS[@]}"; do
[[ "$v" == "$ONLY"* ]] && filtered+=("$v")
done
if [[ ${#filtered[@]} -eq 0 ]]; then
echo "No versions matched --only '$ONLY'" >&2
exit 2
fi
VERSIONS=("${filtered[@]}")
fi
mode_label="compile-only"
[[ $FULL -eq 1 ]] && mode_label="full EditMode test run"
runner_label="local Unity Hub"
[[ $USE_DOCKER -eq 1 ]] && runner_label="GameCI Docker (${DOCKER_IMAGE_TAG})"
echo "Unity-version check (${mode_label}, ${runner_label}) — ${#VERSIONS[@]} version(s) requested"
echo " Project: $PROJECT_PATH"
echo " Logs: $LOG_DIR"
echo
fail_count=0
pass_count=0
skip_count=0
# ---- per-version runners ------------------------------------------------------------
run_local() {
local version="$1" log_file="$2"
local unity_bin="${HUB_ROOT}/${version}/${UNITY_RELPATH}"
if [[ ! -x "$unity_bin" ]]; then
echo " ${C_SKIP}[SKIP]${C_RST} ${version} — not installed (expected at ${C_DIM}${unity_bin}${C_RST})"
return 2 # skip
fi
printf " [ .. ] %s — running...\r" "$version"
# -quit on both paths so Unity batchmode always exits — without it -runTests can hang waiting
# on test framework shutdown on some Unity versions.
local args
if [[ $FULL -eq 1 ]]; then
args=(-batchmode -quit -nographics -projectPath "$PROJECT_PATH" -runTests -testPlatform editmode -logFile "$log_file")
else
args=(-batchmode -quit -nographics -projectPath "$PROJECT_PATH" -logFile "$log_file")
fi
if "$unity_bin" "${args[@]}" >/dev/null 2>&1; then
return 0
else
return 1
fi
}
run_docker() {
local version="$1" log_file="$2"
local image="unityci/editor:ubuntu-${version}-${DOCKER_IMAGE_TAG}"
printf " [ .. ] %s — pulling %s ...\r" "$version" "$image"
if ! docker pull "$image" >>"$log_file" 2>&1; then
echo " ${C_FAIL}[FAIL]${C_RST} ${version} — image pull failed (${C_DIM}${image}${C_RST}); see ${log_file}"
echo " Pull errors:"
tail -5 "$log_file" | sed 's/^/ /'
return 1
fi
printf " [ .. ] %s — running in container...\r" "$version"
# GameCI images run as root and expect the .ulf at /root/.local/share/unity3d/Unity/Unity_lic.ulf.
# Write the license from env on container start, then run Unity with -logFile /dev/stdout so
# everything (mkdir output, Unity compile log, errors) streams through docker's stdout into our
# host log_file via a single `>> "$log_file"` redirection. No bind-mount race.
# -quit on both paths (see run_local note).
local unity_extra
if [[ $FULL -eq 1 ]]; then
unity_extra="-quit -runTests -testPlatform editmode"
else
unity_extra="-quit"
fi
if docker run --rm \
--platform linux/amd64 \
-e UNITY_LICENSE \
-v "${PROJECT_PATH}:/project" \
--entrypoint /bin/bash \
"$image" \
-c 'set -e
mkdir -p /root/.local/share/unity3d/Unity
printf "%s" "$UNITY_LICENSE" > /root/.local/share/unity3d/Unity/Unity_lic.ulf
/opt/unity/Editor/Unity -batchmode -nographics -projectPath /project '"$unity_extra"' -logFile /dev/stdout' \
>>"$log_file" 2>&1; then
return 0
else
return 1
fi
}
# ---- main loop ----------------------------------------------------------------------
for version in "${VERSIONS[@]}"; do
log_file="${LOG_DIR}/${version}.log"
: >"$log_file" # truncate stale log
if [[ $USE_DOCKER -eq 1 ]]; then
run_docker "$version" "$log_file" && rc=0 || rc=$?
else
run_local "$version" "$log_file" && rc=0 || rc=$?
fi
case "$rc" in
0)
echo " ${C_OK}[PASS]${C_RST} ${version} "
pass_count=$((pass_count + 1))
;;
2)
skip_count=$((skip_count + 1))
;;
*)
echo " ${C_FAIL}[FAIL]${C_RST} ${version} — see ${C_DIM}${log_file}${C_RST}"
if grep -q "error CS" "$log_file" 2>/dev/null; then
echo " Compile errors:"
grep -E "error CS[0-9]+" "$log_file" | head -10 | sed 's/^/ /'
else
echo " Last 20 lines of log:"
tail -20 "$log_file" | sed 's/^/ /'
fi
fail_count=$((fail_count + 1))
;;
esac
done
echo
echo "Summary: ${pass_count} passed, ${fail_count} failed, ${skip_count} skipped (of ${#VERSIONS[@]} configured)"
if [[ $fail_count -gt 0 ]]; then
if [[ $PRE_PUSH -eq 1 ]]; then
echo
echo "Pre-push check failed. To push anyway (skipping this hook): git push --no-verify"
fi
exit 1
fi
if [[ $pass_count -eq 0 && $skip_count -gt 0 ]]; then
echo
if [[ $USE_DOCKER -eq 1 ]]; then
echo "Note: no versions ran. Check image pull errors above."
else
echo "Note: no versions from tools/unity-versions.json are installed on this machine."
echo "Either install via Unity Hub or use --docker (see --help for license setup)."
fi
fi
exit 0