From bd9b90504019e650ac79ad7043b5e4e53eca7b65 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Thu, 7 May 2026 14:24:04 +1000 Subject: [PATCH] ci(audience): route all matrix jobs through set-matrix, harden Windows checkout (SDK-330) - Replaces the cross-product matrix (unity x target x backend with axis-matching include items) on `playmode` with a `set-matrix` helper job that emits a fully-specified JSON matrix. - The cross-product approach silently expanded to zero playmode cells on every run since SDK-327, so Windows and macOS PlayMode tests have not actually run on PRs (verified on the SDK-327 merge commit and on PR #748). Root cause: a unity-keyed include item that has no cell to augment after the conditional `exclude` removes Unity 2022 on PR runs spawns an orphan combination missing `target`, `backend`, and `runner`; `runs-on: ${{ matrix.runner }}` then evaluates to empty and GitHub aborts the matrix. - `set-matrix` runs on ubuntu-latest, defines the full 12-cell playmode matrix, 6-cell playmode-linux matrix and 6-cell mobile matrix inline as JSON, and uses jq to strip Unity 2022.3.62f2 cells when the trigger is pull_request. Schedule and workflow_dispatch get the full sets. - All three matrix-driven jobs now declare `needs: set-matrix` and consume `matrix.include: fromJSON(needs.set-matrix.outputs.)`. Each cell carries every key the steps need, so no axis-match augmentation step can silently drop keys, and the workflow graph shows all three jobs hanging off set-matrix in one place. - `mobile-build` PR runs drop Unity 2022.3.62f2 to match the playmode and playmode-linux trim. Schedule and workflow_dispatch keep all 3 Unity versions. Steps unchanged. - `playmode-linux` matrix moved from cross-product + conditional exclude to the same set-matrix-fed include pattern. Same 6 cells on schedule, same 4 cells on PR; behaviour unchanged. - Hardens the Windows pre-checkout cleanup. The previous Kill-stale step only covered Unity-family processes and slept 2 seconds, then handed off to actions/checkout@v4 which would die with EBUSY on stuck files in examples/audience. New step adds bee_backend and mono to the kill list, sleeps 3 seconds, and force-removes the workspace contents in a retry loop so checkout's own cleanup is left with nothing to do. Linear: https://linear.app/imtbl/issue/SDK-330 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../workflows/test-audience-sample-app.yml | 127 +++++++++++++----- 1 file changed, 90 insertions(+), 37 deletions(-) diff --git a/.github/workflows/test-audience-sample-app.yml b/.github/workflows/test-audience-sample-app.yml index 104f7d4dd..9263173ef 100644 --- a/.github/workflows/test-audience-sample-app.yml +++ b/.github/workflows/test-audience-sample-app.yml @@ -26,11 +26,67 @@ concurrency: cancel-in-progress: true 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. + # The playmode, playmode-linux and mobile-build matrices are built here + # and consumed by the dependent jobs via fromJSON. PR runs trim Unity + # 2022.3.62f2 cells; schedule and workflow_dispatch run the full set. + set-matrix: + runs-on: ubuntu-latest + outputs: + playmode: ${{ steps.set.outputs.playmode }} + playmode_linux: ${{ steps.set.outputs.playmode_linux }} + mobile: ${{ steps.set.outputs.mobile }} + steps: + - id: set + shell: bash + run: | + playmode_full='[ + {"target":"StandaloneWindows64","backend":"IL2CPP","unity":"2021.3.45f2","changeset":"88f88f591b2e","runner":["self-hosted","Windows","X64"]}, + {"target":"StandaloneWindows64","backend":"Mono2x","unity":"2021.3.45f2","changeset":"88f88f591b2e","runner":["self-hosted","Windows","X64"]}, + {"target":"StandaloneOSX","backend":"IL2CPP","unity":"2021.3.45f2","changeset":"88f88f591b2e","runner":["self-hosted","macOS","ARM64"]}, + {"target":"StandaloneOSX","backend":"Mono2x","unity":"2021.3.45f2","changeset":"88f88f591b2e","runner":["self-hosted","macOS","ARM64"]}, + {"target":"StandaloneWindows64","backend":"IL2CPP","unity":"6000.4.0f1","changeset":"8cf496087c8f","runner":["self-hosted","Windows","X64"]}, + {"target":"StandaloneWindows64","backend":"Mono2x","unity":"6000.4.0f1","changeset":"8cf496087c8f","runner":["self-hosted","Windows","X64"]}, + {"target":"StandaloneOSX","backend":"IL2CPP","unity":"6000.4.0f1","changeset":"8cf496087c8f","runner":["self-hosted","macOS","ARM64"]}, + {"target":"StandaloneOSX","backend":"Mono2x","unity":"6000.4.0f1","changeset":"8cf496087c8f","runner":["self-hosted","macOS","ARM64"]}, + {"target":"StandaloneWindows64","backend":"IL2CPP","unity":"2022.3.62f2","changeset":"7670c08855a9","runner":["self-hosted","Windows","X64"]}, + {"target":"StandaloneWindows64","backend":"Mono2x","unity":"2022.3.62f2","changeset":"7670c08855a9","runner":["self-hosted","Windows","X64"]}, + {"target":"StandaloneOSX","backend":"IL2CPP","unity":"2022.3.62f2","changeset":"7670c08855a9","runner":["self-hosted","macOS","ARM64"]}, + {"target":"StandaloneOSX","backend":"Mono2x","unity":"2022.3.62f2","changeset":"7670c08855a9","runner":["self-hosted","macOS","ARM64"]} + ]' + playmode_linux_full='[ + {"target":"StandaloneLinux64","backend":"IL2CPP","unity":"2021.3.45f2"}, + {"target":"StandaloneLinux64","backend":"Mono2x","unity":"2021.3.45f2"}, + {"target":"StandaloneLinux64","backend":"IL2CPP","unity":"6000.4.0f1"}, + {"target":"StandaloneLinux64","backend":"Mono2x","unity":"6000.4.0f1"}, + {"target":"StandaloneLinux64","backend":"IL2CPP","unity":"2022.3.62f2"}, + {"target":"StandaloneLinux64","backend":"Mono2x","unity":"2022.3.62f2"} + ]' + mobile_full='[ + {"target":"Android","unity":"2021.3.45f2","method":"AndroidBuilder.Build"}, + {"target":"Android","unity":"2022.3.62f2","method":"AndroidBuilder.Build"}, + {"target":"Android","unity":"6000.4.0f1","method":"AndroidBuilder.Build"}, + {"target":"iOS","unity":"2021.3.45f2","method":"IosBuilder.Build"}, + {"target":"iOS","unity":"2022.3.62f2","method":"IosBuilder.Build"}, + {"target":"iOS","unity":"6000.4.0f1","method":"IosBuilder.Build"} + ]' + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + filter='[.[] | select(.unity != "2022.3.62f2")]' + playmode=$(jq -c "$filter" <<<"$playmode_full") + playmode_linux=$(jq -c "$filter" <<<"$playmode_linux_full") + mobile=$(jq -c "$filter" <<<"$mobile_full") + else + playmode=$(jq -c '.' <<<"$playmode_full") + playmode_linux=$(jq -c '.' <<<"$playmode_linux_full") + mobile=$(jq -c '.' <<<"$mobile_full") + fi + { + echo "playmode=$playmode" + echo "playmode_linux=$playmode_linux" + echo "mobile=$mobile" + } >> "$GITHUB_OUTPUT" + playmode: + needs: set-matrix if: | (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) || github.event_name == 'schedule' @@ -39,21 +95,7 @@ jobs: 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"}]' || '[]') }} + include: ${{ fromJSON(needs.set-matrix.outputs.playmode) }} 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 @@ -62,25 +104,43 @@ jobs: timeout-minutes: 30 steps: - - name: Kill stale Unity processes (Windows pre-checkout) + - name: Clean Windows workspace (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. + # actions/checkout@v4 removes the prior workspace before cloning. If + # a previous run's Unity build / IL2CPP linker / bee_backend / shader + # compiler is still holding handles, checkout dies with EBUSY on + # examples/audience. Kill known offenders, then force-remove the + # workspace contents ourselves so checkout's cleanup succeeds. Get-Process | Where-Object { $_.Name -like 'Unity*' -or $_.Name -like 'il2cpp*' -or $_.Name -like 'UnityShaderCompiler*' -or - $_.Name -like 'UnityCrashHandler*' + $_.Name -like 'UnityCrashHandler*' -or + $_.Name -like 'bee_backend*' -or + $_.Name -like 'mono*' } | ForEach-Object { Write-Host "Killing stale process: $($_.Name) (pid $($_.Id))" Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue } - Start-Sleep -Seconds 2 + Start-Sleep -Seconds 3 + + $ws = "$env:GITHUB_WORKSPACE" + if (-not (Test-Path $ws)) { return } + for ($i = 1; $i -le 6; $i++) { + try { + Get-ChildItem -Path $ws -Force -ErrorAction Stop | + Remove-Item -Recurse -Force -ErrorAction Stop + Write-Host "Cleaned $ws on attempt ${i}" + return + } catch { + Write-Host "Attempt ${i}: $($_.Exception.Message)" + Start-Sleep -Seconds 3 + } + } + Write-Host "::warning::Workspace not fully cleaned; checkout may fail" - uses: actions/checkout@v4 with: @@ -406,6 +466,7 @@ jobs: examples/audience/Logs/** playmode-linux: + needs: set-matrix if: | (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) || github.event_name == 'schedule' @@ -415,10 +476,7 @@ jobs: 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"}]' || '[]') }} + include: ${{ fromJSON(needs.set-matrix.outputs.playmode_linux) }} steps: - uses: actions/checkout@v4 @@ -468,6 +526,7 @@ jobs: # Scope: IL2CPP compile pipeline only. Runtime tests require a real device and # are out of scope until a device farm is available. mobile-build: + needs: set-matrix if: | (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) || github.event_name == 'schedule' @@ -477,13 +536,7 @@ jobs: 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 + include: ${{ fromJSON(needs.set-matrix.outputs.mobile) }} steps: - uses: actions/checkout@v4