Files
2026-07-13 12:49:17 +08:00

286 lines
9.8 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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