fix(execute_code): load Roslyn from Unity instead of falling back to C# 6 #179
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: E2E Bridge Smoke (deterministic, no LLM) | |
| # Boots a headless Unity Editor, starts the Python MCP server's wire path, and | |
| # drives a fixed sequence of real tool calls with exact assertions | |
| # (Server/tests/e2e/bridge_smoke.py). Unlike claude-nl-suite.yml this needs | |
| # NO Anthropic API key -- it is deterministic and cheap, so it can gate PRs and | |
| # releases. It still needs Unity license secrets to boot the Editor. | |
| on: | |
| workflow_dispatch: | |
| pull_request: | |
| paths: | |
| - "MCPForUnity/Editor/**" | |
| - "MCPForUnity/Runtime/**" | |
| - "Server/src/**" | |
| - "Server/tests/e2e/**" | |
| - "tools/local_harness.py" | |
| - ".github/workflows/e2e-bridge.yml" | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| UNITY_IMAGE: unityci/editor:ubuntu-2021.3.45f2-linux-il2cpp-3 | |
| jobs: | |
| license: | |
| # Hoisted out of the smoke job so the gate is a JOB-level condition. A step-level `if:` | |
| # produces step-conclusion `skipped`, which contributes nothing to the job conclusion, | |
| # so this workflow reported a green check having booted no Editor and exercised no tool | |
| # call. A job skipped by a job-level `if:` reports "Skipped" in the merge box instead. | |
| # Branch protection treats a skipped required check as satisfied, so merges are not blocked. | |
| name: Detect Unity license secrets | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| unity_ok: ${{ steps.detect.outputs.unity_ok }} | |
| steps: | |
| - name: Detect Unity license secrets | |
| id: detect | |
| env: | |
| UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| run: | | |
| set -e | |
| if [ -n "$UNITY_LICENSE" ] || { [ -n "$UNITY_EMAIL" ] && [ -n "$UNITY_PASSWORD" ] && [ -n "$UNITY_SERIAL" ]; }; then | |
| echo "unity_ok=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "unity_ok=false" >> "$GITHUB_OUTPUT" | |
| echo "::warning::E2E bridge smoke SKIPPED - no license secrets in scope (normal for fork PRs). This check is NOT a pass: nothing was booted or exercised." | |
| # The dependent job is gated on this output, so it reports Skipped rather | |
| # than a green check. Still say plainly on the run page what did not happen. | |
| { | |
| echo "## :warning: E2E bridge smoke was SKIPPED" | |
| echo | |
| echo "No Unity license secrets were in scope, so **no Editor was booted and no tool call was exercised**." | |
| echo "The dependent job reports **Skipped** - it did not pass, it did not run." | |
| echo | |
| echo "GitHub withholds repository secrets from workflow runs triggered by a fork's pull request." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| e2e-bridge: | |
| runs-on: ubuntu-24.04 | |
| needs: license | |
| if: needs.license.outputs.unity_ok == 'true' | |
| timeout-minutes: 40 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: astral-sh/setup-uv@v4 | |
| with: | |
| python-version: "3.11" | |
| - name: Install MCP server | |
| run: | | |
| set -eux | |
| uv venv | |
| echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> "$GITHUB_ENV" | |
| echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH" | |
| uv pip install -e Server | |
| # --- License staging (mirrors claude-nl-suite.yml) --- | |
| - name: Decide license sources | |
| id: lic | |
| shell: bash | |
| env: | |
| UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| run: | | |
| set -eu | |
| use_ulf=false; use_ebl=false | |
| [[ -n "${UNITY_LICENSE:-}" ]] && use_ulf=true | |
| [[ -n "${UNITY_EMAIL:-}" && -n "${UNITY_PASSWORD:-}" && -n "${UNITY_SERIAL:-}" ]] && use_ebl=true | |
| echo "use_ulf=$use_ulf" >> "$GITHUB_OUTPUT" | |
| echo "use_ebl=$use_ebl" >> "$GITHUB_OUTPUT" | |
| - name: Stage Unity .ulf license (from secret) | |
| if: steps.lic.outputs.use_ulf == 'true' | |
| id: ulf | |
| env: | |
| UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} | |
| shell: bash | |
| run: | | |
| set -eu | |
| mkdir -p "$RUNNER_TEMP/unity-license-ulf" "$RUNNER_TEMP/unity-local/Unity" | |
| f="$RUNNER_TEMP/unity-license-ulf/Unity_lic.ulf" | |
| if printf "%s" "$UNITY_LICENSE" | base64 -d - >/dev/null 2>&1; then | |
| printf "%s" "$UNITY_LICENSE" | base64 -d - > "$f" | |
| else | |
| printf "%s" "$UNITY_LICENSE" > "$f" | |
| fi | |
| chmod 600 "$f" || true | |
| if grep -qi '<Signature>' "$f"; then | |
| cp -f "$f" "$RUNNER_TEMP/unity-local/Unity/Unity_lic.ulf" | |
| echo "ok=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "ok=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Activate Unity (EBL via container) | |
| if: steps.lic.outputs.use_ebl == 'true' | |
| shell: bash | |
| env: | |
| UNITY_IMAGE: ${{ env.UNITY_IMAGE }} | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$RUNNER_TEMP/unity-config" "$RUNNER_TEMP/unity-local" | |
| docker run --rm --network host \ | |
| -e HOME=/root -e UNITY_EMAIL -e UNITY_PASSWORD -e UNITY_SERIAL \ | |
| -v "$RUNNER_TEMP/unity-config:/root/.config/unity3d" \ | |
| -v "$RUNNER_TEMP/unity-local:/root/.local/share/unity3d" \ | |
| "$UNITY_IMAGE" bash -lc ' | |
| set -euxo pipefail | |
| /opt/unity/Editor/Unity -batchmode -nographics -logFile - \ | |
| -username "$UNITY_EMAIL" -password "$UNITY_PASSWORD" -serial "$UNITY_SERIAL" -quit || true | |
| ' | |
| - name: Warm up project (import Library once) | |
| shell: bash | |
| env: | |
| UNITY_IMAGE: ${{ env.UNITY_IMAGE }} | |
| ULF_OK: ${{ steps.ulf.outputs.ok }} | |
| run: | | |
| set -euxo pipefail | |
| manual_args=() | |
| if [[ "${ULF_OK:-false}" == "true" ]]; then | |
| manual_args=(-manualLicenseFile "/root/.local/share/unity3d/Unity/Unity_lic.ulf") | |
| fi | |
| docker run --rm --network host \ | |
| -e HOME=/root \ | |
| -v "${{ github.workspace }}:${{ github.workspace }}" -w "${{ github.workspace }}" \ | |
| -v "$RUNNER_TEMP/unity-config:/root/.config/unity3d" \ | |
| -v "$RUNNER_TEMP/unity-local:/root/.local/share/unity3d" \ | |
| -v "$RUNNER_TEMP/unity-cache:/root/.cache/unity3d" \ | |
| "$UNITY_IMAGE" /opt/unity/Editor/Unity -batchmode -nographics -logFile - \ | |
| -projectPath "${{ github.workspace }}/TestProjects/UnityMCPTests" \ | |
| "${manual_args[@]}" -quit | |
| - name: Clean old MCP status | |
| run: | | |
| set -eux | |
| mkdir -p "$GITHUB_WORKSPACE/.unity-mcp" | |
| rm -f "$GITHUB_WORKSPACE/.unity-mcp"/unity-mcp-status-*.json || true | |
| - name: Run headless bridge harness (boot + wait + smoke/editmode/playmode) | |
| shell: bash | |
| env: | |
| UNITY_IMAGE: ${{ env.UNITY_IMAGE }} | |
| ULF_OK: ${{ steps.ulf.outputs.ok }} | |
| run: | | |
| set -euxo pipefail | |
| # In --ci mode the harness drives the DockerLauncher: it runs the same | |
| # docker container (repo .unity-mcp status dir, docker liveness/teardown, | |
| # log redaction), waits on the status file, derives the instance, then | |
| # runs the smoke + EditMode + PlayMode legs over the bridge. | |
| license_args=() | |
| if [[ "${ULF_OK:-false}" == "true" ]]; then | |
| license_args=(--editor-arg -manualLicenseFile \ | |
| --editor-arg "/root/.local/share/unity3d/Unity/Unity_lic.ulf") | |
| fi | |
| python3 tools/local_harness.py --ci \ | |
| --legs smoke,editmode,playmode \ | |
| --project-path TestProjects/UnityMCPTests \ | |
| --reports reports \ | |
| "${license_args[@]}" | |
| - name: Unity logs on failure | |
| if: failure() | |
| run: docker logs unity-mcp --tail 200 | sed -E 's/((email|serial|license|password|token)[^[:space:]]*)/[REDACTED]/Ig' || true | |
| - name: Upload E2E report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: e2e-bridge-report | |
| path: reports/junit-*.xml | |
| if-no-files-found: ignore |