chore(audience-sample): trim 30 unused packages and engine modules #213
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: Audience SDK — PlayMode (IL2CPP + Mono) | |
| on: | |
| pull_request: | |
| paths: | |
| - 'src/Packages/Audience/Runtime/**' | |
| - 'src/Packages/Audience/Tests/**' | |
| - 'src/Packages/Audience/package.json' | |
| - 'src/Packages/Audience/Directory.Build.props' | |
| - 'src/Packages/Audience/link.xml' | |
| - 'examples/audience/Assets/**' | |
| - 'examples/audience/Packages/**' | |
| - 'examples/audience/ProjectSettings/**' | |
| - '.github/workflows/test-audience-sample-app.yml' | |
| schedule: | |
| # Weekly full-matrix run on the default branch. | |
| # Cron is UTC; cron has no DST awareness, so the local time shifts by one | |
| # hour twice a year. Saturday 14:00 UTC maps to: | |
| # Sun 00:00 Sydney AEST / Sun 02:00 NZ NZST (winter, Apr to Oct) | |
| # Sun 01:00 Sydney AEDT / Sun 03:00 NZ NZDT (summer, Oct to Apr) | |
| - cron: '0 14 * * 6' | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| # CI run identifier injected into the player so events on CDP can be | |
| # filtered as test traffic. Run id is workflow-wide; matrix-aware cell | |
| # id is set on the test jobs below. The marker test reads these env | |
| # vars and Assert.Ignores when both are unset. | |
| env: | |
| AUDIENCE_TEST_RUN_ID: ${{ github.run_id }}-${{ github.run_attempt }} | |
| jobs: | |
| # Reduced matrix on pull_request, full matrix on schedule and | |
| # workflow_dispatch. The self-hosted Windows runner pool is small, so | |
| # trimming PR cells keeps PR feedback fast. `matrix.exclude` below is | |
| # the source of truth for which cells are dropped on pull_request. | |
| playmode: | |
| if: | | |
| (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) | |
| || github.event_name == 'schedule' | |
| || github.event_name == 'workflow_dispatch' | |
| name: ${{ matrix.target }} / ${{ matrix.backend }} / Unity ${{ matrix.unity }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| unity: ['2021.3.45f2', '6000.4.0f1', '2022.3.62f2'] | |
| target: [StandaloneWindows64, StandaloneOSX] | |
| backend: [IL2CPP, Mono2x] | |
| include: | |
| - unity: '2021.3.45f2' | |
| changeset: 88f88f591b2e | |
| - unity: '6000.4.0f1' | |
| changeset: 8cf496087c8f | |
| - unity: '2022.3.62f2' | |
| changeset: 7670c08855a9 | |
| - target: StandaloneWindows64 | |
| runner: [self-hosted, Windows, X64] | |
| - target: StandaloneOSX | |
| runner: [self-hosted, macOS, ARM64] | |
| exclude: ${{ fromJSON(github.event_name == 'pull_request' && '[{"unity":"2022.3.62f2"}]' || '[]') }} | |
| runs-on: ${{ matrix.runner }} | |
| # Healthy cells finish in ~10 min. 30 min covers cold caches + | |
| # IL2CPP + Unity 6 startup; anything past that is a hang. Capping | |
| # short releases the self-hosted runner sooner so queued cells can | |
| # progress instead of waiting 60 min on a stuck job. | |
| timeout-minutes: 30 | |
| env: | |
| AUDIENCE_TEST_CELL_ID: ${{ matrix.target }}-${{ matrix.backend }}-${{ matrix.unity }} | |
| steps: | |
| - name: Kill stale Unity processes (Windows pre-checkout) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| continue-on-error: true | |
| run: | | |
| # actions/checkout@v4 deletes the prior workspace before cloning. If a | |
| # previous run's Unity Editor / IL2CPP build process is still holding | |
| # handles inside examples/audience, checkout dies with EBUSY. Kill any | |
| # leftover Unity-family process here so checkout's cleanup succeeds. | |
| Get-Process | Where-Object { | |
| $_.Name -like 'Unity*' -or | |
| $_.Name -like 'il2cpp*' -or | |
| $_.Name -like 'UnityShaderCompiler*' -or | |
| $_.Name -like 'UnityCrashHandler*' | |
| } | ForEach-Object { | |
| Write-Host "Killing stale process: $($_.Name) (pid $($_.Id))" | |
| Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue | |
| } | |
| Start-Sleep -Seconds 2 | |
| - uses: actions/checkout@v4 | |
| with: | |
| lfs: true | |
| - name: Cache Unity Library | |
| uses: actions/cache@v4 | |
| with: | |
| path: examples/audience/Library | |
| key: Library-${{ matrix.backend }}-${{ matrix.target }}-${{ matrix.unity }}-${{ hashFiles('examples/audience/Assets/**', 'examples/audience/Packages/**', 'examples/audience/ProjectSettings/**', 'src/Packages/Audience/**') }} | |
| restore-keys: | | |
| Library-${{ matrix.backend }}-${{ matrix.target }}-${{ matrix.unity }}- | |
| Library-${{ matrix.backend }}-${{ matrix.target }}- | |
| - name: Ensure MSVC + Windows 10 SDK (Windows IL2CPP) | |
| if: runner.os == 'Windows' && matrix.backend == 'IL2CPP' | |
| shell: pwsh | |
| run: | | |
| $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" | |
| # Match Unity's detection logic exactly: vswhere requires VC.Tools | |
| # (any version), registry probe for any Win10 SDK at v10.0/InstallationFolder. | |
| # Pinning a specific SDK version in -requires is too strict — VCTools | |
| # ships with whatever Win10 SDK is current, and Unity accepts any. | |
| function Test-Toolchain { | |
| $vc = if (Test-Path $vswhere) { | |
| & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2>$null | |
| } else { '' } | |
| $sdk = (Get-ItemProperty 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0' -ErrorAction SilentlyContinue).InstallationFolder | |
| return @{ VcTools = $vc; Win10Sdk = $sdk } | |
| } | |
| $state = Test-Toolchain | |
| if ($state.VcTools -and $state.Win10Sdk) { | |
| Write-Host "VC.Tools at: $($state.VcTools)" | |
| Write-Host "Win10 SDK at: $($state.Win10Sdk)" | |
| exit 0 | |
| } | |
| Write-Host "Toolchain incomplete. VC.Tools='$($state.VcTools)' Win10Sdk='$($state.Win10Sdk)'" | |
| Write-Host "::group::Install VS 2022 Build Tools (VCTools + Win10 SDK)" | |
| $installer = "$env:RUNNER_TEMP\vs_BuildTools.exe" | |
| Invoke-WebRequest -Uri 'https://aka.ms/vs/17/release/vs_BuildTools.exe' -OutFile $installer | |
| $installArgs = @( | |
| '--quiet','--wait','--norestart','--nocache', | |
| '--add','Microsoft.VisualStudio.Workload.VCTools', | |
| '--add','Microsoft.VisualStudio.Component.VC.Tools.x86.x64', | |
| '--add','Microsoft.VisualStudio.Component.Windows10SDK.20348', | |
| '--includeRecommended' | |
| ) | |
| $p = Start-Process -FilePath $installer -ArgumentList $installArgs -Wait -PassThru -NoNewWindow | |
| # 3010 = success, reboot pending (tools are usable without reboot). | |
| if ($p.ExitCode -ne 0 -and $p.ExitCode -ne 3010) { | |
| Write-Host "::error::VS Build Tools installer exited $($p.ExitCode)" | |
| exit $p.ExitCode | |
| } | |
| Write-Host "::endgroup::" | |
| $state = Test-Toolchain | |
| if (-not ($state.VcTools -and $state.Win10Sdk)) { | |
| Write-Host "::group::diagnostic" | |
| Write-Host "VC.Tools path (vswhere): '$($state.VcTools)'" | |
| Write-Host "Win10 SDK (registry v10.0/InstallationFolder): '$($state.Win10Sdk)'" | |
| Write-Host "--- all VS installations ---" | |
| if (Test-Path $vswhere) { & $vswhere -all -products * -format json } | |
| Write-Host "--- HKLM Win10 SDK roots ---" | |
| Get-ChildItem 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows' -ErrorAction SilentlyContinue | Format-List | |
| Write-Host "::endgroup::" | |
| Write-Host "::error::Install reported success but VC.Tools or Win10 SDK still not detected — runner service account likely lacks admin to install system-wide. Install VS Build Tools manually on IMX_SDKBUILD: vs_BuildTools.exe --quiet --wait --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended" | |
| exit 1 | |
| } | |
| Write-Host "Verified VC.Tools at: $($state.VcTools)" | |
| Write-Host "Verified Win10 SDK at: $($state.Win10Sdk)" | |
| - name: Resolve Unity ${{ matrix.unity }} (macOS) | |
| if: runner.os == 'macOS' | |
| shell: bash | |
| env: | |
| UNITY_VER: ${{ matrix.unity }} | |
| UNITY_CS: ${{ matrix.changeset }} | |
| run: | | |
| set -uo pipefail | |
| HUB="/Applications/Unity Hub.app/Contents/MacOS/Unity Hub" | |
| echo "::group::install editor" | |
| "$HUB" -- --headless install \ | |
| --version "$UNITY_VER" --changeset "$UNITY_CS" --architecture arm64 \ | |
| || echo "(install non-zero — OK if 'Editor already installed in this location')" | |
| echo "::endgroup::" | |
| if [ "${{ matrix.backend }}" = "IL2CPP" ]; then | |
| echo "::group::install mac-il2cpp module" | |
| "$HUB" -- --headless install-modules \ | |
| --version "$UNITY_VER" --changeset "$UNITY_CS" --architecture arm64 \ | |
| --module mac-il2cpp \ | |
| || echo "(install-modules non-zero — OK if 'No modules found to install')" | |
| echo "::endgroup::" | |
| fi | |
| EDITOR_APP="" | |
| for cand in \ | |
| "/Applications/Unity/Hub/Editor/$UNITY_VER-arm64/Unity.app" \ | |
| "/Applications/Unity/Hub/Editor/$UNITY_VER/Unity.app"; do | |
| if [ -x "$cand/Contents/MacOS/Unity" ]; then EDITOR_APP="$cand"; break; fi | |
| done | |
| IL2CPP_DIR="" | |
| if [ "${{ matrix.backend }}" = "IL2CPP" ] && [ -n "$EDITOR_APP" ]; then | |
| for d in \ | |
| "$EDITOR_APP/Contents/PlaybackEngines/MacStandaloneSupport/Variations/macos_arm64_player_nondevelopment_il2cpp" \ | |
| "$EDITOR_APP/Contents/PlaybackEngines/MacStandaloneSupport/Variations/macos_x64_player_nondevelopment_il2cpp"; do | |
| if [ -d "$d" ]; then IL2CPP_DIR="$d"; break; fi | |
| done | |
| fi | |
| MISSING="" | |
| [ -z "$EDITOR_APP" ] && MISSING="editor" | |
| [ "${{ matrix.backend }}" = "IL2CPP" ] && [ -z "$IL2CPP_DIR" ] && MISSING="${MISSING:+$MISSING+}mac-il2cpp" | |
| if [ -n "$MISSING" ]; then | |
| echo "::error::Unity $UNITY_VER missing: $MISSING" | |
| ls -la /Applications/Unity/Hub/Editor/ 2>&1 || true | |
| "$HUB" -- --headless editors --installed 2>&1 || true | |
| exit 1 | |
| fi | |
| echo "Found Unity: $EDITOR_APP/Contents/MacOS/Unity" | |
| [ -n "$IL2CPP_DIR" ] && echo "Found IL2CPP: $IL2CPP_DIR" | |
| echo "UNITY_PATH=$EDITOR_APP/Contents/MacOS/Unity" >> "$GITHUB_ENV" | |
| - name: Resolve Unity ${{ matrix.unity }} (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| env: | |
| UNITY_VER: ${{ matrix.unity }} | |
| UNITY_CS: ${{ matrix.changeset }} | |
| run: | | |
| $hub = "C:\Program Files\Unity Hub\Unity Hub.exe" | |
| Write-Host "::group::install editor" | |
| $installArgs = @('--','--headless','install','--version',$env:UNITY_VER,'--changeset',$env:UNITY_CS,'--architecture','x86_64') | |
| & $hub @installArgs 2>&1 | Write-Host | |
| if ($LASTEXITCODE -ne 0) { Write-Host "(install non-zero — OK if 'Editor already installed in this location')" } | |
| $global:LASTEXITCODE = 0 | |
| Write-Host "::endgroup::" | |
| if ('${{ matrix.backend }}' -eq 'IL2CPP') { | |
| Write-Host "::group::install windows-il2cpp module" | |
| $modArgs = @('--','--headless','install-modules','--version',$env:UNITY_VER,'--changeset',$env:UNITY_CS,'--architecture','x86_64','--module','windows-il2cpp') | |
| & $hub @modArgs 2>&1 | Write-Host | |
| if ($LASTEXITCODE -ne 0) { Write-Host "(install-modules non-zero — OK if 'No modules found to install')" } | |
| $global:LASTEXITCODE = 0 | |
| Write-Host "::endgroup::" | |
| } | |
| $editor = "C:\Program Files\Unity\Hub\Editor\$env:UNITY_VER\Editor\Unity.exe" | |
| $il2cpp = "C:\Program Files\Unity\Hub\Editor\$env:UNITY_VER\Editor\Data\PlaybackEngines\windowsstandalonesupport\Variations\win64_player_nondevelopment_il2cpp" | |
| $missing = @() | |
| if (-not (Test-Path $editor)) { $missing += 'editor' } | |
| if ('${{ matrix.backend }}' -eq 'IL2CPP' -and -not (Test-Path $il2cpp)) { $missing += 'windows-il2cpp' } | |
| if ($missing.Count -gt 0) { | |
| Write-Host "::error::Unity $env:UNITY_VER missing: $($missing -join '+')" | |
| Get-ChildItem "C:\Program Files\Unity\Hub\Editor\" -ErrorAction SilentlyContinue | Format-Table | |
| & $hub -- --headless editors --installed | |
| exit 1 | |
| } | |
| Write-Host "Found Unity: $editor" | |
| if ('${{ matrix.backend }}' -eq 'IL2CPP') { Write-Host "Found IL2CPP: $il2cpp" } | |
| "UNITY_PATH=$editor" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| - name: Run PlayMode tests (macOS) | |
| if: runner.os == 'macOS' | |
| shell: bash | |
| env: | |
| AUDIENCE_TEST_PUBLISHABLE_KEY: ${{ secrets.AUDIENCE_TEST_PUBLISHABLE_KEY }} | |
| AUDIENCE_SCRIPTING_BACKEND: ${{ matrix.backend }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p artifacts | |
| # Tee Unity's stdout to artifacts/unity.log so the annotation step has a | |
| # file to scan, while still streaming progress to the job log. pipefail | |
| # propagates Unity's exit code through tee. The annotation step reads this | |
| # file in-job; the actions/upload-artifact step below also uploads it so | |
| # compile failures retain a full post-mortem (annotations are matched-line | |
| # only and drop IL2CPP linker output, build config dumps, etc). | |
| "$UNITY_PATH" \ | |
| -batchmode -nographics \ | |
| -projectPath examples/audience \ | |
| -runTests \ | |
| -testPlatform ${{ matrix.target }} \ | |
| -testResults "$(pwd)/artifacts/test-results.xml" \ | |
| -logFile - 2>&1 | tee "$(pwd)/artifacts/unity.log" | |
| - name: Run PlayMode tests (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| env: | |
| AUDIENCE_TEST_PUBLISHABLE_KEY: ${{ secrets.AUDIENCE_TEST_PUBLISHABLE_KEY }} | |
| AUDIENCE_SCRIPTING_BACKEND: ${{ matrix.backend }} | |
| run: | | |
| New-Item -ItemType Directory -Force -Path artifacts | Out-Null | |
| $logFile = "$pwd\artifacts\unity.log" | |
| $unityArgs = @( | |
| '-batchmode','-nographics', | |
| '-projectPath','examples/audience', | |
| '-runTests', | |
| '-testPlatform','${{ matrix.target }}', | |
| '-testResults',"$pwd\artifacts\test-results.xml", | |
| '-logFile',$logFile | |
| ) | |
| Write-Host "Launching Unity: $env:UNITY_PATH $($unityArgs -join ' ')" | |
| $p = Start-Process -FilePath $env:UNITY_PATH -ArgumentList $unityArgs -Wait -PassThru -NoNewWindow | |
| $exit = $p.ExitCode | |
| Write-Host "::group::Unity log" | |
| Get-Content $logFile -ErrorAction SilentlyContinue | Write-Host | |
| Write-Host "::endgroup::" | |
| Write-Host "Unity exited with code $exit" | |
| if ($exit -ne 0) { exit $exit } | |
| - name: Mark workspace safe for git (Windows) | |
| if: always() && runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| git config --global --add safe.directory $env:GITHUB_WORKSPACE.Replace('\','/') | |
| - name: Capture player log (macOS) | |
| if: always() && runner.os == 'macOS' | |
| shell: bash | |
| run: | | |
| # The test-runner builds + launches a player binary that writes its own | |
| # log separately from Unity's editor log. When the editor reports | |
| # "Test execution timed out. No activity received from the player ..." | |
| # the editor unity.log alone cannot tell us whether the player crashed, | |
| # hung, or never started. Copy whatever Player.log files Unity wrote | |
| # into artifacts/ so the upload-artifact step preserves them. | |
| mkdir -p artifacts | |
| src="$HOME/Library/Logs" | |
| if [ -d "$src" ]; then | |
| find "$src" -name "Player.log" 2>/dev/null | while IFS= read -r f; do | |
| cp "$f" "artifacts/Player-$(basename "$(dirname "$f")").log" 2>/dev/null || true | |
| done | |
| fi | |
| - name: Capture player log (Windows) | |
| if: always() && runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| # See macOS counterpart for rationale. Windows player log location: | |
| # %USERPROFILE%\AppData\LocalLow\<CompanyName>\<ProductName>\Player.log | |
| New-Item -ItemType Directory -Force -Path artifacts | Out-Null | |
| $src = "$env:USERPROFILE\AppData\LocalLow" | |
| if (Test-Path $src) { | |
| Get-ChildItem -Path $src -Recurse -Filter "Player.log" -ErrorAction SilentlyContinue | | |
| ForEach-Object { | |
| $name = $_.Directory.Name | |
| Copy-Item -Path $_.FullName -Destination "artifacts/Player-$name.log" -ErrorAction SilentlyContinue | |
| } | |
| } | |
| - name: Surface Unity compile errors as annotations (macOS) | |
| if: always() && runner.os == 'macOS' | |
| shell: bash | |
| run: | | |
| set -uo pipefail | |
| # Unity writes compile errors as 'error CS####:' or 'Compilation failed: <n>'. | |
| # When a cell fails compile (vs fails a test), the test-results.xml is empty | |
| # and the only signal otherwise is the artifact zip. Promote those lines to | |
| # ::error:: annotations so the PR UI shows the cause inline. | |
| LOG_FILE="artifacts/unity.log" | |
| if [ ! -f "$LOG_FILE" ]; then | |
| echo "::notice::No Unity log file at $LOG_FILE." | |
| exit 0 | |
| fi | |
| # `|| true` guards the success path: with `pipefail`, grep exits 1 when no | |
| # matches (the clean-build case), which would otherwise propagate as the | |
| # step's exit code and falsely mark every green cell red. | |
| grep -E '(error CS[0-9]+:|Compilation failed:)' "$LOG_FILE" | sort -u | while IFS= read -r line; do | |
| trimmed="${line#"${line%%[![:space:]]*}"}" | |
| # Sanitize '::' so log lines containing workflow commands (e.g. ::endgroup::) | |
| # cannot terminate the annotation early or inject other commands. | |
| sanitized="${trimmed//::/%3A%3A}" | |
| echo "::error::$sanitized" | |
| done || true | |
| - name: Surface Unity compile errors as annotations (Windows) | |
| if: always() && runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $logFile = "artifacts\unity.log" | |
| if (-not (Test-Path $logFile)) { | |
| Write-Host "::notice::No Unity log file at $logFile." | |
| exit 0 | |
| } | |
| Get-Content $logFile | | |
| Select-String -Pattern '(error CS\d+:|Compilation failed:)' | | |
| ForEach-Object { $_.Line.Trim() } | | |
| Sort-Object -Unique | | |
| ForEach-Object { | |
| # Sanitize '::' so log lines containing workflow commands cannot | |
| # terminate the annotation early or inject other commands. | |
| $sanitized = $_ -replace '::', '%3A%3A' | |
| Write-Host "::error::$sanitized" | |
| } | |
| - name: Publish test report | |
| uses: dorny/test-reporter@v3 | |
| if: always() | |
| with: | |
| name: PlayMode (${{ matrix.backend }} / ${{ matrix.target }}) | |
| path: artifacts/test-results.xml | |
| reporter: dotnet-nunit | |
| fail-on-error: true | |
| - uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: playmode-${{ matrix.backend }}-${{ matrix.target }}-${{ matrix.unity }} | |
| path: | | |
| artifacts/test-results.xml | |
| artifacts/unity.log | |
| artifacts/Player-*.log | |
| examples/audience/Logs/** | |
| playmode-linux: | |
| if: | | |
| (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) | |
| || github.event_name == 'schedule' | |
| || github.event_name == 'workflow_dispatch' | |
| name: ${{ matrix.target }} / ${{ matrix.backend }} / Unity ${{ matrix.unity }} | |
| runs-on: ubuntu-latest-8-cores | |
| # Cells settle to ~5-7 min for Unity 2021.3 (both backends) and | |
| # ~22-25 min for Unity 6000.4 (Mono). Unity 6 IL2CPP on Mesa | |
| # software OpenGL is the slow path. The 45 min cap covers the inner | |
| # 40 min watchdog cap plus post-Unity steps (license return, | |
| # Player.log copy, artifact upload, dorny/test-reporter) without | |
| # leaving a stuck job sitting on the runner for the default 6 hours. | |
| timeout-minutes: 45 | |
| env: | |
| AUDIENCE_TEST_CELL_ID: ${{ matrix.target }}-${{ matrix.backend }}-${{ matrix.unity }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| unity: ['2021.3.45f2', '6000.4.0f1', '2022.3.62f2'] | |
| target: [StandaloneLinux64] | |
| backend: [IL2CPP, Mono2x] | |
| exclude: ${{ fromJSON(github.event_name == 'pull_request' && '[{"unity":"2022.3.62f2"}]' || '[]') }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| lfs: true | |
| - uses: actions/cache@v4 | |
| with: | |
| path: examples/audience/Library | |
| key: Library-${{ matrix.backend }}-${{ matrix.target }}-${{ matrix.unity }}-${{ hashFiles('examples/audience/Assets/**', 'examples/audience/Packages/**', 'examples/audience/ProjectSettings/**', 'src/Packages/Audience/**') }} | |
| restore-keys: | | |
| Library-${{ matrix.backend }}-${{ matrix.target }}-${{ matrix.unity }}- | |
| Library-${{ matrix.backend }}-${{ matrix.target }}- | |
| - name: Run PlayMode tests under xvfb | |
| # Manual `docker run` instead of game-ci/unity-test-runner@v4: the | |
| # action hardcodes `-batchmode -nographics` and never starts a | |
| # virtual display, so every [UnityTest] in SampleAppLiveFireTests | |
| # came back "inconclusive" (passed=0, failed=0). NUnit does not | |
| # treat inconclusive as failure and the action's USE_EXIT_CODE=false | |
| # suppresses Unity's exit code 2, so the cells silently went green | |
| # without executing a single test. See SDK-318 for the diagnosis. | |
| shell: bash | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| AUDIENCE_TEST_PUBLISHABLE_KEY: ${{ secrets.AUDIENCE_TEST_PUBLISHABLE_KEY }} | |
| AUDIENCE_SCRIPTING_BACKEND: ${{ matrix.backend }} | |
| UNITY_VERSION: ${{ matrix.unity }} | |
| AUDIENCE_LINUX_GLCORE_ONLY: "1" | |
| AUDIENCE_PLAYER_PROFILE_PATH: "/github/workspace/artifacts/player-profile.raw" | |
| run: | | |
| set -uo pipefail | |
| mkdir -p artifacts | |
| # The unityci/editor:ubuntu-...-linux-il2cpp-3 image ships both | |
| # the Mono and IL2CPP playback engines plus xvfb, so the same | |
| # tag works for both backends. The previous GameCI runs proved | |
| # this: the StandaloneLinux64/Mono2x cell pulled the il2cpp tag. | |
| image="unityci/editor:ubuntu-${UNITY_VERSION}-linux-il2cpp-3" | |
| docker run --rm \ | |
| --workdir /github/workspace \ | |
| --env UNITY_EMAIL --env UNITY_PASSWORD --env UNITY_SERIAL \ | |
| --env AUDIENCE_TEST_PUBLISHABLE_KEY --env AUDIENCE_SCRIPTING_BACKEND \ | |
| --env AUDIENCE_TEST_RUN_ID --env AUDIENCE_TEST_CELL_ID \ | |
| --env AUDIENCE_LINUX_GLCORE_ONLY \ | |
| --env AUDIENCE_PLAYER_PROFILE_PATH \ | |
| --volume "$PWD":/github/workspace:z \ | |
| --cpus=8 --memory=30487m \ | |
| "$image" \ | |
| /bin/bash -c ' | |
| set -uo pipefail | |
| # Per-run license activation. Unity occasionally exits non-zero | |
| # on a successful activation (warnings about prior cached state | |
| # in /root), so swallow the rc and assert on the success marker | |
| # in the log instead. Same approach as the dropped self-hosted | |
| # Linux job from SDK-255. | |
| unity-editor -batchmode -nographics -quit \ | |
| -username "$UNITY_EMAIL" \ | |
| -password "$UNITY_PASSWORD" \ | |
| -serial "$UNITY_SERIAL" \ | |
| -logFile - 2>&1 | tee /github/workspace/artifacts/activation.log || true | |
| if grep -qE "License activation has failed|\[Licensing::Client\] Error: Code [0-9]+" \ | |
| /github/workspace/artifacts/activation.log; then | |
| echo "::error::Unity license activation failed." | |
| exit 1 | |
| fi | |
| if ! grep -qE "Successfully activated the entitlement license" \ | |
| /github/workspace/artifacts/activation.log; then | |
| echo "::error::Unity license activation: no success marker in log." | |
| exit 1 | |
| fi | |
| # xvfb-run gives Unity a virtual X display so PlayMode tests | |
| # that load scenes and exercise UI Toolkit can actually launch | |
| # the player. GLX + render are required for UIElements; the | |
| # image already bundles mesa-llvmpipe for software OpenGL, so | |
| # no GPU is needed. -noreset keeps the X server up across | |
| # Unity client reconnects. | |
| # | |
| # Why the watchdog instead of a simple `timeout` wrapper: | |
| # - Unity 6 on Linux has a known shutdown hang. After | |
| # "Test run completed", the editor begins | |
| # `Application is shutting down...` and never fully exits. | |
| # Without intervention the cell sits idle until the cell | |
| # timeout fires before the post-Unity steps can run. | |
| # - Tests on Unity 6 + Mesa software OpenGL take ~22 min for | |
| # Mono and longer for IL2CPP, vs ~2 min on Unity 2021.3. | |
| # A fixed timeout that fits 2021.3 cuts Unity 6 off mid-run; | |
| # a fixed timeout sized for Unity 6 makes 2021.3 cells wait | |
| # up to 30+ min on a shutdown hang they would not have hit. | |
| # - The watchdog adapts: it scans the log, sees | |
| # "Test run completed" the moment Unity finishes the suite, | |
| # gives the editor 30 s to flush playmode-results.xml, then | |
| # sends SIGTERM. SIGKILL follows 15 s later if the editor | |
| # refuses to exit. Cells finish as soon as their tests do, | |
| # regardless of how slow the underlying Unity version is or | |
| # what shutdown bug it happens to hit. | |
| # - 40 min hard cap as a fallback for the case where | |
| # "Test run completed" never appears (player hang, etc.). | |
| log=/github/workspace/artifacts/playmode.log | |
| # The player renders at 320x240 (Unity -screen-width and | |
| # -screen-height below). The xvfb desktop size does not | |
| # affect fragment fill because Unity creates a GL context | |
| # at the requested window size, regardless of how big the | |
| # underlying X desktop is. xvfb-run with -a picks an | |
| # unused display number and uses a default screen | |
| # geometry; the X11 extension flags below are the only | |
| # --server-args we actually need. Earlier comments in | |
| # this file claimed the xvfb -screen flag was the source | |
| # of the per-frame fill reduction; it was not. | |
| # UI Toolkit lays out fine because tests assert on the | |
| # VisualElement tree, not on rendered pixels. | |
| # | |
| # Why -force-glcore: Unity 6 prefers Vulkan on Linux and | |
| # falls back to OpenGL when Vulkan init fails. Each frame | |
| # carries the negotiation overhead. -force-glcore tells the | |
| # player to skip Vulkan entirely and open a GLX context | |
| # directly, the same path Unity 2021.3 takes by default. | |
| xvfb-run -a --server-args="-ac +extension GLX +render -noreset" -- \ | |
| unity-editor \ | |
| -batchmode \ | |
| -force-glcore \ | |
| -screen-fullscreen 0 \ | |
| -screen-width 320 \ | |
| -screen-height 240 \ | |
| -projectPath /github/workspace/examples/audience \ | |
| -runTests \ | |
| -testPlatform StandaloneLinux64 \ | |
| -testResults /github/workspace/artifacts/playmode-results.xml \ | |
| -logFile "$log" & | |
| unity_pid=$! | |
| # Stream the log to job stdout for live visibility while the | |
| # editor is alive. tail --pid exits when unity_pid does. | |
| tail --pid=$unity_pid -F "$log" 2>/dev/null & | |
| deadline=$((SECONDS + 2400)) # 40 min hard cap | |
| flush_deadline=0 | |
| kill_reason="" | |
| while kill -0 $unity_pid 2>/dev/null; do | |
| if [ "$SECONDS" -ge "$deadline" ]; then | |
| kill_reason="hard-cap-40m" | |
| break | |
| fi | |
| if [ "$flush_deadline" -eq 0 ] && grep -q "Test run completed" "$log" 2>/dev/null; then | |
| flush_deadline=$((SECONDS + 30)) | |
| echo "[watchdog] saw \"Test run completed\" at ${SECONDS}s; SIGTERM after 30s flush window" | |
| fi | |
| if [ "$flush_deadline" -gt 0 ] && [ "$SECONDS" -ge "$flush_deadline" ]; then | |
| kill_reason="flush-window-elapsed" | |
| break | |
| fi | |
| sleep 5 | |
| done | |
| if [ -n "$kill_reason" ]; then | |
| echo "[watchdog] sending SIGTERM to Unity (reason: $kill_reason)" | |
| kill -TERM $unity_pid 2>/dev/null || true | |
| # 15 s grace, then SIGKILL if still alive. | |
| for _ in 1 2 3; do | |
| kill -0 $unity_pid 2>/dev/null || break | |
| sleep 5 | |
| done | |
| if kill -0 $unity_pid 2>/dev/null; then | |
| echo "[watchdog] SIGTERM not honored, sending SIGKILL" | |
| kill -KILL $unity_pid 2>/dev/null || true | |
| fi | |
| fi | |
| wait $unity_pid 2>/dev/null | |
| test_rc=$? | |
| if [ "$kill_reason" = "hard-cap-40m" ]; then | |
| echo "::warning::Unity hit the 40 min hard cap without logging \"Test run completed\". The player may have hung mid-suite. Inspect Player.log to see how far it got." | |
| fi | |
| # Capture the standalone test player log. PlayMode tests on | |
| # StandaloneLinux64 build a player binary that runs the suite | |
| # in its own process; the editor stdout we tee above never | |
| # sees the player'\''s HTTP traces, OnError callbacks, or cert | |
| # failures. Without this, "39 passed" tells us FlushAsync | |
| # returned without throwing but says nothing about whether | |
| # events actually reached CDP. | |
| # Linux convention: ~/.config/unity3d/<Company>/<Product>/Player.log. | |
| # Glob across companies / products so the capture survives any | |
| # rename of the sample app. | |
| find /root/.config/unity3d -name "Player.log" 2>/dev/null | while IFS= read -r f; do | |
| co=$(basename "$(dirname "$(dirname "$f")")") | |
| pr=$(basename "$(dirname "$f")") | |
| cp "$f" "/github/workspace/artifacts/Player-${co}-${pr}.log" 2>/dev/null || true | |
| done | |
| # Always return the seat so reruns and parallel cells do not | |
| # exhaust the activation pool. Tolerate non-zero in case the | |
| # editor process is still mid-shutdown. | |
| unity-editor -batchmode -nographics -quit -returnlicense -logFile - 2>&1 || true | |
| # Unity exits 2 when any test fails or comes back inconclusive. | |
| # Propagating the rc here means the step fails on real failures | |
| # without needing the USE_EXIT_CODE=false hack the GameCI | |
| # action applies. | |
| exit $test_rc | |
| ' | |
| - name: Fail when no tests actually executed | |
| # Defense in depth: catches the silent-pass case if a future change | |
| # accidentally re-disables the display, breaks the player launch, | |
| # or restores USE_EXIT_CODE=false on the docker invocation. NUnit | |
| # marks tests "inconclusive" rather than "failed" when the player | |
| # never starts, and dorny/test-reporter does not flag inconclusive | |
| # as failure, so the suite has to be inspected directly. | |
| if: always() | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| xml="artifacts/playmode-results.xml" | |
| if [ ! -f "$xml" ]; then | |
| echo "::error::No test-results.xml at $xml. Unity did not produce results." | |
| exit 1 | |
| fi | |
| # Unity's NUnit writer puts the root summary attributes on a | |
| # single <test-run ...> line, so a grep parse is enough and | |
| # avoids depending on libxml2-utils (which is not preinstalled | |
| # on ubuntu-latest-8-cores; the previous xmllint guard failed | |
| # with "command not found" while tests had actually passed). | |
| line=$(grep -m1 '<test-run ' "$xml" || true) | |
| if [ -z "$line" ]; then | |
| echo "::error::No <test-run> element in $xml. The XML is malformed." | |
| exit 1 | |
| fi | |
| extract() { | |
| printf %s "$1" | grep -oE " $2=\"[0-9]+\"" | head -1 | grep -oE '[0-9]+' | |
| } | |
| passed=$(extract "$line" passed) | |
| failed=$(extract "$line" failed) | |
| inconclusive=$(extract "$line" inconclusive) | |
| echo "passed=${passed:-?} failed=${failed:-?} inconclusive=${inconclusive:-?}" | |
| if [ "${inconclusive:-0}" -gt 0 ]; then | |
| echo "::error::$inconclusive test(s) came back inconclusive. Unity could not actually execute them. Check that xvfb is running and the player launches." | |
| exit 1 | |
| fi | |
| if [ "${passed:-0}" -eq 0 ] && [ "${failed:-0}" -eq 0 ]; then | |
| echo "::error::Zero tests passed and zero failed. The suite did not execute." | |
| exit 1 | |
| fi | |
| - name: Surface CI provenance to job summary | |
| # The SampleApp prints `[CI] buildGuid=... runId=... cellId=...` | |
| # to Player.log on player startup. buildGuid is per-build and | |
| # already lands on every game_launch event the SDK emits, so a | |
| # one-line grep here gives a 1:1 match between the CI cell and | |
| # CDP rows. The job summary makes it copy-pasteable from the | |
| # Actions UI without downloading the artifact. | |
| if: always() | |
| shell: bash | |
| run: | | |
| set -uo pipefail | |
| log="" | |
| for candidate in artifacts/Player-*.log; do | |
| [ -f "$candidate" ] && { log="$candidate"; break; } | |
| done | |
| { | |
| echo "## CI provenance" | |
| if [ -z "$log" ]; then | |
| echo "_No Player.log captured for this cell._" | |
| exit 0 | |
| fi | |
| line=$(grep -m1 '\[CI\]' "$log" || true) | |
| if [ -z "$line" ]; then | |
| echo "_No [CI] line found in \`$log\`. The SampleApp's RuntimeInitializeOnLoad hook may not have fired._" | |
| exit 0 | |
| fi | |
| echo | |
| echo '```' | |
| echo "$line" | |
| echo '```' | |
| echo | |
| echo "Filter CDP for events with this \`buildGuid\` to see the rows from this cell." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Publish test report | |
| uses: dorny/test-reporter@v3 | |
| if: always() | |
| with: | |
| name: PlayMode (${{ matrix.backend }} / ${{ matrix.target }}) | |
| path: artifacts/playmode-results.xml | |
| reporter: dotnet-nunit | |
| fail-on-error: true | |
| - uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: playmode-${{ matrix.backend }}-${{ matrix.target }}-${{ matrix.unity }} | |
| path: | | |
| artifacts/playmode-results.xml | |
| artifacts/playmode.log | |
| artifacts/activation.log | |
| artifacts/Player-*.log | |
| artifacts/player-profile.raw | |
| examples/audience/Logs/** | |
| # Mobile IL2CPP build validation — runs on GitHub-hosted Ubuntu via GameCI Docker | |
| # containers so self-hosted macOS/Windows machines are not occupied. | |
| # Scope: IL2CPP compile pipeline only. Runtime tests require a real device and | |
| # are out of scope until a device farm is available. | |
| mobile-build: | |
| if: | | |
| (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) | |
| || github.event_name == 'schedule' | |
| || github.event_name == 'workflow_dispatch' | |
| name: ${{ matrix.target }} / IL2CPP / Unity ${{ matrix.unity }} | |
| runs-on: ubuntu-latest-8-cores | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: [Android, iOS] | |
| unity: ['2021.3.45f2', '2022.3.62f2', '6000.4.0f1'] | |
| include: | |
| - target: Android | |
| method: AndroidBuilder.Build | |
| - target: iOS | |
| method: IosBuilder.Build | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| lfs: true | |
| - uses: actions/cache@v4 | |
| with: | |
| path: examples/audience/Library | |
| key: Library-mobile-${{ matrix.target }}-${{ matrix.unity }}-${{ hashFiles('examples/audience/Assets/**', 'examples/audience/Packages/**', 'examples/audience/ProjectSettings/**', 'src/Packages/Audience/**') }} | |
| restore-keys: | | |
| Library-mobile-${{ matrix.target }}-${{ matrix.unity }}- | |
| Library-mobile-${{ matrix.target }}- | |
| - uses: game-ci/unity-builder@v4 | |
| env: | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | |
| with: | |
| unityVersion: ${{ matrix.unity }} | |
| targetPlatform: ${{ matrix.target }} | |
| projectPath: examples/audience | |
| buildMethod: Immutable.Audience.Samples.SampleApp.Editor.${{ matrix.method }} | |
| - uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: mobile-build-${{ matrix.target }}-${{ matrix.unity }} | |
| if-no-files-found: ignore | |
| path: | | |
| examples/audience/Builds/Android/*.apk | |
| examples/audience/Logs/** |