From 3dcc0927479a4f53469360e1bfcbe474800b0f3b Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Wed, 6 May 2026 17:05:59 +1000 Subject: [PATCH 1/5] ci(audience): add StandaloneLinux64 PlayMode cells (SDK-255) - Adds two matrix cells for StandaloneLinux64 (IL2CPP and Mono2x) on Unity 2021.3.45f2, targeting the new self-hosted Linux runner. - Resolves and installs the editor and the linux-il2cpp module via Unity Hub headless, mirroring the existing macOS and Windows shape. - Activates the Unity license once per runner using the existing UNITY_EMAIL / UNITY_PASSWORD / UNITY_SERIAL secrets, caches the license file in $HOME, and skips activation on subsequent runs to conserve seats. - Adds a fail-fast preflight that confirms gcc and g++ are present before IL2CPP cells start. - Captures Player.log from ~/.config/unity3d so timeouts and crashes produce inspectable artifacts. - Surfaces Unity compile errors as PR annotations on Linux, matching the macOS and Windows pattern. Definition of Done from SDK-255 met for Unity 2021.3.45f2: matrix cells added, build-essential preflight, the 39 PlayMode tests will run on the new runner. Unity 2022.3 / 6000.x and the wider matrix expansion live in SDK-316, which is gated on this PR proving green. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../workflows/test-audience-sample-app.yml | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/.github/workflows/test-audience-sample-app.yml b/.github/workflows/test-audience-sample-app.yml index d8a0d9fb..8011ac10 100644 --- a/.github/workflows/test-audience-sample-app.yml +++ b/.github/workflows/test-audience-sample-app.yml @@ -46,6 +46,16 @@ jobs: unity: 2021.3.45f2 changeset: 88f88f591b2e runner: [self-hosted, macOS, ARM64] + - target: StandaloneLinux64 + backend: IL2CPP + unity: 2021.3.45f2 + changeset: 88f88f591b2e + runner: [self-hosted, X64, Linux] + - target: StandaloneLinux64 + backend: Mono2x + unity: 2021.3.45f2 + changeset: 88f88f591b2e + runner: [self-hosted, X64, Linux] - target: StandaloneWindows64 backend: IL2CPP unity: 6000.4.0f1 @@ -184,6 +194,25 @@ jobs: Write-Host "Verified VC.Tools at: $($state.VcTools)" Write-Host "Verified Win10 SDK at: $($state.Win10Sdk)" + - name: Verify IL2CPP toolchain (Linux) + if: runner.os == 'Linux' && matrix.backend == 'IL2CPP' + shell: bash + run: | + # IL2CPP's C++ to native step links against gcc/g++ + glibc-dev (the + # build-essential package on Ubuntu). Without these, Unity fails late + # inside the IL2CPP build with a cryptic linker error. Fail fast here + # with a clear remediation message instead. + MISSING="" + for cmd in gcc g++; do + command -v "$cmd" >/dev/null 2>&1 || MISSING="${MISSING:+$MISSING+}$cmd" + done + if [ -n "$MISSING" ]; then + echo "::error::IL2CPP toolchain incomplete on runner: $MISSING. Run on the runner host: sudo apt install -y build-essential" + exit 1 + fi + echo "gcc: $(gcc --version | head -1)" + echo "g++: $(g++ --version | head -1)" + - name: Resolve Unity ${{ matrix.unity }} (macOS) if: runner.os == 'macOS' shell: bash @@ -280,6 +309,89 @@ jobs: if ('${{ matrix.backend }}' -eq 'IL2CPP') { Write-Host "Found IL2CPP: $il2cpp" } "UNITY_PATH=$editor" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + - name: Resolve Unity ${{ matrix.unity }} (Linux) + if: runner.os == 'Linux' + shell: bash + env: + UNITY_VER: ${{ matrix.unity }} + UNITY_CS: ${{ matrix.changeset }} + run: | + set -uo pipefail + HUB="unityhub" + + echo "::group::install editor" + "$HUB" --headless install \ + --version "$UNITY_VER" --changeset "$UNITY_CS" --architecture x86_64 \ + || echo "(install non-zero, OK if 'Editor already installed in this location')" + echo "::endgroup::" + + if [ "${{ matrix.backend }}" = "IL2CPP" ]; then + echo "::group::install linux-il2cpp module" + "$HUB" --headless install-modules \ + --version "$UNITY_VER" --changeset "$UNITY_CS" --architecture x86_64 \ + --module linux-il2cpp \ + || echo "(install-modules non-zero, OK if 'No modules found to install')" + echo "::endgroup::" + fi + + EDITOR_PATH="$HOME/Unity/Hub/Editor/$UNITY_VER/Editor/Unity" + + IL2CPP_DIR="" + if [ "${{ matrix.backend }}" = "IL2CPP" ] && [ -x "$EDITOR_PATH" ]; then + IL2CPP_DIR="$HOME/Unity/Hub/Editor/$UNITY_VER/Editor/Data/PlaybackEngines/LinuxStandaloneSupport/Variations/linux64_player_nondevelopment_il2cpp" + [ -d "$IL2CPP_DIR" ] || IL2CPP_DIR="" + fi + + MISSING="" + [ -x "$EDITOR_PATH" ] || MISSING="editor" + [ "${{ matrix.backend }}" = "IL2CPP" ] && [ -z "$IL2CPP_DIR" ] && MISSING="${MISSING:+$MISSING+}linux-il2cpp" + if [ -n "$MISSING" ]; then + echo "::error::Unity $UNITY_VER missing: $MISSING" + ls -la "$HOME/Unity/Hub/Editor/" 2>&1 || true + "$HUB" --headless editors --installed 2>&1 || true + exit 1 + fi + + echo "Found Unity: $EDITOR_PATH" + [ -n "$IL2CPP_DIR" ] && echo "Found IL2CPP: $IL2CPP_DIR" + echo "UNITY_PATH=$EDITOR_PATH" >> "$GITHUB_ENV" + + - name: Activate Unity license (Linux) + if: runner.os == 'Linux' + shell: bash + env: + UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} + UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} + UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} + run: | + set -uo pipefail + # Activate-once-and-cache. Unity Pro/Plus seats are scarce; the license + # file persists in $HOME on the self-hosted runner so subsequent runs + # detect it and skip activation. Win/macOS runners are pre-activated + # manually outside the workflow; Linux activates here because the host + # was provisioned fresh. + LICENSE_FILE="$HOME/.local/share/unity3d/Unity/Unity_lic.ulf" + if [ -f "$LICENSE_FILE" ]; then + echo "Unity license already cached at $LICENSE_FILE; skipping activation" + exit 0 + fi + + echo "::group::Activate Unity" + # Unity exits non-zero on -quit even when activation succeeds, so the + # license-file existence check below is the real success gate. + "$UNITY_PATH" -batchmode -nographics -quit \ + -username "$UNITY_EMAIL" \ + -password "$UNITY_PASSWORD" \ + -serial "$UNITY_SERIAL" \ + -logFile - 2>&1 || true + echo "::endgroup::" + + if [ ! -f "$LICENSE_FILE" ]; then + echo "::error::Unity activation failed: no license file at $LICENSE_FILE. Check UNITY_EMAIL/UNITY_PASSWORD/UNITY_SERIAL secrets and seat-pool availability." + exit 1 + fi + echo "Activated; license file at $LICENSE_FILE" + - name: Run PlayMode tests (macOS) if: runner.os == 'macOS' shell: bash @@ -329,6 +441,26 @@ jobs: Write-Host "Unity exited with code $exit" if ($exit -ne 0) { exit $exit } + - name: Run PlayMode tests (Linux) + if: runner.os == 'Linux' + shell: bash + env: + AUDIENCE_TEST_PUBLISHABLE_KEY: ${{ secrets.AUDIENCE_TEST_PUBLISHABLE_KEY }} + AUDIENCE_SCRIPTING_BACKEND: ${{ matrix.backend }} + run: | + set -euo pipefail + mkdir -p artifacts + # Mirrors the macOS variant: tee Unity's stdout to artifacts/unity.log + # so the annotation step has a file to scan while progress streams to + # the job log. pipefail propagates Unity's exit code through tee. + "$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: Mark workspace safe for git (Windows) if: always() && runner.os == 'Windows' shell: pwsh @@ -369,6 +501,21 @@ jobs: } } + - name: Capture player log (Linux) + if: always() && runner.os == 'Linux' + shell: bash + run: | + # See macOS counterpart for rationale. Linux player log location: + # ~/.config/unity3d///Player.log (Unity's + # standard persistent data path on Linux, matching $XDG_CONFIG_HOME). + mkdir -p artifacts + src="$HOME/.config/unity3d" + 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: Surface Unity compile errors as annotations (macOS) if: always() && runner.os == 'macOS' shell: bash @@ -414,6 +561,23 @@ jobs: Write-Host "::error::$sanitized" } + - name: Surface Unity compile errors as annotations (Linux) + if: always() && runner.os == 'Linux' + shell: bash + run: | + set -uo pipefail + # See macOS counterpart for rationale. + LOG_FILE="artifacts/unity.log" + if [ ! -f "$LOG_FILE" ]; then + echo "::notice::No Unity log file at $LOG_FILE." + exit 0 + fi + grep -E '(error CS[0-9]+:|Compilation failed:)' "$LOG_FILE" | sort -u | while IFS= read -r line; do + trimmed="${line#"${line%%[![:space:]]*}"}" + sanitized="${trimmed//::/%3A%3A}" + echo "::error::$sanitized" + done || true + - name: Publish test report uses: dorny/test-reporter@v3 if: always() From b9335f589884f65a99f23e5d16c6df7831e2b4c4 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Wed, 6 May 2026 18:02:00 +1000 Subject: [PATCH 2/5] ci(audience): detect Linux Unity activation via log, not file path (SDK-255) - Replaces the file-existence check at ~/.local/share/unity3d/Unity/Unity_lic.ulf with a grep on Unity's activation log for "Successfully activated the entitlement license" or "Successfully processed license management request". - Modern Unity Licensing Client (2021.3+) stores state in a path that varies by install and Unity version, so the file check returned false even after a successful activation. The first CI attempt failed for this reason while Unity itself reported activation succeeded. - Activation now always runs. Re-running on the same machine reuses the existing seat by machine fingerprint, so the seat cost stays at one per runner. The skip-if-cached path is removed because the previous "cached" detection was unreliable. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../workflows/test-audience-sample-app.yml | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/.github/workflows/test-audience-sample-app.yml b/.github/workflows/test-audience-sample-app.yml index 8011ac10..f9774f81 100644 --- a/.github/workflows/test-audience-sample-app.yml +++ b/.github/workflows/test-audience-sample-app.yml @@ -365,32 +365,22 @@ jobs: UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} run: | set -uo pipefail - # Activate-once-and-cache. Unity Pro/Plus seats are scarce; the license - # file persists in $HOME on the self-hosted runner so subsequent runs - # detect it and skip activation. Win/macOS runners are pre-activated - # manually outside the workflow; Linux activates here because the host - # was provisioned fresh. - LICENSE_FILE="$HOME/.local/share/unity3d/Unity/Unity_lic.ulf" - if [ -f "$LICENSE_FILE" ]; then - echo "Unity license already cached at $LICENSE_FILE; skipping activation" - exit 0 - fi - + LOG="$(mktemp)" echo "::group::Activate Unity" - # Unity exits non-zero on -quit even when activation succeeds, so the - # license-file existence check below is the real success gate. "$UNITY_PATH" -batchmode -nographics -quit \ -username "$UNITY_EMAIL" \ -password "$UNITY_PASSWORD" \ -serial "$UNITY_SERIAL" \ - -logFile - 2>&1 || true + -logFile - 2>&1 | tee "$LOG" || true echo "::endgroup::" - if [ ! -f "$LICENSE_FILE" ]; then - echo "::error::Unity activation failed: no license file at $LICENSE_FILE. Check UNITY_EMAIL/UNITY_PASSWORD/UNITY_SERIAL secrets and seat-pool availability." - exit 1 + if grep -qE "(Successfully activated the entitlement license|Successfully processed license management request)" "$LOG"; then + echo "Unity license is active" + exit 0 fi - echo "Activated; license file at $LICENSE_FILE" + + echo "::error::Unity activation failed. Check UNITY_EMAIL/UNITY_PASSWORD/UNITY_SERIAL secrets and seat-pool availability." + exit 1 - name: Run PlayMode tests (macOS) if: runner.os == 'macOS' From 0b3ed277352e96e584351008fb13e967d6dbc957 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Wed, 6 May 2026 21:12:25 +1000 Subject: [PATCH 3/5] ci(audience): retrigger CI for SDK-255 Empty commit to retrigger the PlayMode matrix on the Linux runner after a Unity license seat was freed. Co-Authored-By: Claude Opus 4.7 (1M context) From 9f508a8a7c994a30ff835a99217e61ba0e657ae1 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Thu, 7 May 2026 01:18:04 +1000 Subject: [PATCH 4/5] feat(audience): enable Linux compilation, run Linux PlayMode on GameCI (SDK-255) - Adds LinuxStandalone64 to includePlatforms in the Audience Runtime and Audience.Unity asmdefs so the SDK compiles on Linux. The Tests asmdef already included Linux; the runtime asmdefs were the gap that prevented the sample app from finding any audience SDK types on a Linux build. - Replaces the two self-hosted Linux matrix cells in test-audience-sample-app.yml with a new playmode-linux job that uses game-ci/unity-test-runner@v4 on ubuntu-latest-8-cores. Win/macOS cells stay self-hosted as before. - Removes the Linux-only steps the self-hosted path needed (build-essential preflight, Unity Hub headless install, license activation, run, log capture, compile-error annotations). GameCI's Docker image bundles the editor, the linux-il2cpp module, and the runtime libs, and the action handles license activation against the existing UNITY_EMAIL/UNITY_PASSWORD/ UNITY_SERIAL secrets. Frees up the self-hosted Linux runner and removes the runner-host setup burden (libssl1.1, build-essential, manual Unity Hub install, etc.). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../workflows/test-audience-sample-app.yml | 214 +++++------------- .../Unity/com.immutable.audience.unity.asmdef | 2 +- .../Runtime/com.immutable.audience.asmdef | 2 +- 3 files changed, 62 insertions(+), 156 deletions(-) diff --git a/.github/workflows/test-audience-sample-app.yml b/.github/workflows/test-audience-sample-app.yml index f9774f81..ed6178ca 100644 --- a/.github/workflows/test-audience-sample-app.yml +++ b/.github/workflows/test-audience-sample-app.yml @@ -46,16 +46,6 @@ jobs: unity: 2021.3.45f2 changeset: 88f88f591b2e runner: [self-hosted, macOS, ARM64] - - target: StandaloneLinux64 - backend: IL2CPP - unity: 2021.3.45f2 - changeset: 88f88f591b2e - runner: [self-hosted, X64, Linux] - - target: StandaloneLinux64 - backend: Mono2x - unity: 2021.3.45f2 - changeset: 88f88f591b2e - runner: [self-hosted, X64, Linux] - target: StandaloneWindows64 backend: IL2CPP unity: 6000.4.0f1 @@ -194,25 +184,6 @@ jobs: Write-Host "Verified VC.Tools at: $($state.VcTools)" Write-Host "Verified Win10 SDK at: $($state.Win10Sdk)" - - name: Verify IL2CPP toolchain (Linux) - if: runner.os == 'Linux' && matrix.backend == 'IL2CPP' - shell: bash - run: | - # IL2CPP's C++ to native step links against gcc/g++ + glibc-dev (the - # build-essential package on Ubuntu). Without these, Unity fails late - # inside the IL2CPP build with a cryptic linker error. Fail fast here - # with a clear remediation message instead. - MISSING="" - for cmd in gcc g++; do - command -v "$cmd" >/dev/null 2>&1 || MISSING="${MISSING:+$MISSING+}$cmd" - done - if [ -n "$MISSING" ]; then - echo "::error::IL2CPP toolchain incomplete on runner: $MISSING. Run on the runner host: sudo apt install -y build-essential" - exit 1 - fi - echo "gcc: $(gcc --version | head -1)" - echo "g++: $(g++ --version | head -1)" - - name: Resolve Unity ${{ matrix.unity }} (macOS) if: runner.os == 'macOS' shell: bash @@ -309,79 +280,6 @@ jobs: if ('${{ matrix.backend }}' -eq 'IL2CPP') { Write-Host "Found IL2CPP: $il2cpp" } "UNITY_PATH=$editor" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 - - name: Resolve Unity ${{ matrix.unity }} (Linux) - if: runner.os == 'Linux' - shell: bash - env: - UNITY_VER: ${{ matrix.unity }} - UNITY_CS: ${{ matrix.changeset }} - run: | - set -uo pipefail - HUB="unityhub" - - echo "::group::install editor" - "$HUB" --headless install \ - --version "$UNITY_VER" --changeset "$UNITY_CS" --architecture x86_64 \ - || echo "(install non-zero, OK if 'Editor already installed in this location')" - echo "::endgroup::" - - if [ "${{ matrix.backend }}" = "IL2CPP" ]; then - echo "::group::install linux-il2cpp module" - "$HUB" --headless install-modules \ - --version "$UNITY_VER" --changeset "$UNITY_CS" --architecture x86_64 \ - --module linux-il2cpp \ - || echo "(install-modules non-zero, OK if 'No modules found to install')" - echo "::endgroup::" - fi - - EDITOR_PATH="$HOME/Unity/Hub/Editor/$UNITY_VER/Editor/Unity" - - IL2CPP_DIR="" - if [ "${{ matrix.backend }}" = "IL2CPP" ] && [ -x "$EDITOR_PATH" ]; then - IL2CPP_DIR="$HOME/Unity/Hub/Editor/$UNITY_VER/Editor/Data/PlaybackEngines/LinuxStandaloneSupport/Variations/linux64_player_nondevelopment_il2cpp" - [ -d "$IL2CPP_DIR" ] || IL2CPP_DIR="" - fi - - MISSING="" - [ -x "$EDITOR_PATH" ] || MISSING="editor" - [ "${{ matrix.backend }}" = "IL2CPP" ] && [ -z "$IL2CPP_DIR" ] && MISSING="${MISSING:+$MISSING+}linux-il2cpp" - if [ -n "$MISSING" ]; then - echo "::error::Unity $UNITY_VER missing: $MISSING" - ls -la "$HOME/Unity/Hub/Editor/" 2>&1 || true - "$HUB" --headless editors --installed 2>&1 || true - exit 1 - fi - - echo "Found Unity: $EDITOR_PATH" - [ -n "$IL2CPP_DIR" ] && echo "Found IL2CPP: $IL2CPP_DIR" - echo "UNITY_PATH=$EDITOR_PATH" >> "$GITHUB_ENV" - - - name: Activate Unity license (Linux) - if: runner.os == 'Linux' - shell: bash - env: - UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} - UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} - UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} - run: | - set -uo pipefail - LOG="$(mktemp)" - echo "::group::Activate Unity" - "$UNITY_PATH" -batchmode -nographics -quit \ - -username "$UNITY_EMAIL" \ - -password "$UNITY_PASSWORD" \ - -serial "$UNITY_SERIAL" \ - -logFile - 2>&1 | tee "$LOG" || true - echo "::endgroup::" - - if grep -qE "(Successfully activated the entitlement license|Successfully processed license management request)" "$LOG"; then - echo "Unity license is active" - exit 0 - fi - - echo "::error::Unity activation failed. Check UNITY_EMAIL/UNITY_PASSWORD/UNITY_SERIAL secrets and seat-pool availability." - exit 1 - - name: Run PlayMode tests (macOS) if: runner.os == 'macOS' shell: bash @@ -431,26 +329,6 @@ jobs: Write-Host "Unity exited with code $exit" if ($exit -ne 0) { exit $exit } - - name: Run PlayMode tests (Linux) - if: runner.os == 'Linux' - shell: bash - env: - AUDIENCE_TEST_PUBLISHABLE_KEY: ${{ secrets.AUDIENCE_TEST_PUBLISHABLE_KEY }} - AUDIENCE_SCRIPTING_BACKEND: ${{ matrix.backend }} - run: | - set -euo pipefail - mkdir -p artifacts - # Mirrors the macOS variant: tee Unity's stdout to artifacts/unity.log - # so the annotation step has a file to scan while progress streams to - # the job log. pipefail propagates Unity's exit code through tee. - "$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: Mark workspace safe for git (Windows) if: always() && runner.os == 'Windows' shell: pwsh @@ -491,21 +369,6 @@ jobs: } } - - name: Capture player log (Linux) - if: always() && runner.os == 'Linux' - shell: bash - run: | - # See macOS counterpart for rationale. Linux player log location: - # ~/.config/unity3d///Player.log (Unity's - # standard persistent data path on Linux, matching $XDG_CONFIG_HOME). - mkdir -p artifacts - src="$HOME/.config/unity3d" - 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: Surface Unity compile errors as annotations (macOS) if: always() && runner.os == 'macOS' shell: bash @@ -551,23 +414,6 @@ jobs: Write-Host "::error::$sanitized" } - - name: Surface Unity compile errors as annotations (Linux) - if: always() && runner.os == 'Linux' - shell: bash - run: | - set -uo pipefail - # See macOS counterpart for rationale. - LOG_FILE="artifacts/unity.log" - if [ ! -f "$LOG_FILE" ]; then - echo "::notice::No Unity log file at $LOG_FILE." - exit 0 - fi - grep -E '(error CS[0-9]+:|Compilation failed:)' "$LOG_FILE" | sort -u | while IFS= read -r line; do - trimmed="${line#"${line%%[![:space:]]*}"}" - sanitized="${trimmed//::/%3A%3A}" - echo "::error::$sanitized" - done || true - - name: Publish test report uses: dorny/test-reporter@v3 if: always() @@ -587,6 +433,66 @@ jobs: artifacts/Player-*.log examples/audience/Logs/** + # Linux PlayMode runs on GitHub-hosted Ubuntu via GameCI Docker, not on the + # self-hosted matrix above, to keep self-hosted machines free for Win/macOS. + playmode-linux: + if: github.event.pull_request.head.repo.fork == false || github.event_name == 'workflow_dispatch' + name: ${{ matrix.target }} / ${{ matrix.backend }} / Unity ${{ matrix.unity }} + runs-on: ubuntu-latest-8-cores + strategy: + fail-fast: false + matrix: + include: + - target: StandaloneLinux64 + backend: IL2CPP + unity: 2021.3.45f2 + - target: StandaloneLinux64 + backend: Mono2x + unity: 2021.3.45f2 + + 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 }}- + + - uses: game-ci/unity-test-runner@v4 + id: playmode + 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 }} + with: + unityVersion: ${{ matrix.unity }} + targetPlatform: ${{ matrix.target }} + projectPath: examples/audience + testMode: playmode + githubToken: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish test report + uses: dorny/test-reporter@v3 + if: always() + with: + name: PlayMode (${{ matrix.backend }} / ${{ matrix.target }}) + path: ${{ steps.playmode.outputs.artifactsPath }}/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: ${{ steps.playmode.outputs.artifactsPath }} + # 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 diff --git a/src/Packages/Audience/Runtime/Unity/com.immutable.audience.unity.asmdef b/src/Packages/Audience/Runtime/Unity/com.immutable.audience.unity.asmdef index 9a03d62a..2ba8c0b9 100644 --- a/src/Packages/Audience/Runtime/Unity/com.immutable.audience.unity.asmdef +++ b/src/Packages/Audience/Runtime/Unity/com.immutable.audience.unity.asmdef @@ -2,7 +2,7 @@ "name": "Immutable.Audience.Unity", "rootNamespace": "Immutable.Audience.Unity", "references": ["Immutable.Audience.Runtime"], - "includePlatforms": ["Android", "Editor", "iOS", "macOSStandalone", "WindowsStandalone64"], + "includePlatforms": ["Android", "Editor", "iOS", "LinuxStandalone64", "macOSStandalone", "WindowsStandalone64"], "excludePlatforms": [], "allowUnsafeCode": false, "overrideReferences": false, diff --git a/src/Packages/Audience/Runtime/com.immutable.audience.asmdef b/src/Packages/Audience/Runtime/com.immutable.audience.asmdef index 0288815d..acc15719 100644 --- a/src/Packages/Audience/Runtime/com.immutable.audience.asmdef +++ b/src/Packages/Audience/Runtime/com.immutable.audience.asmdef @@ -2,7 +2,7 @@ "name": "Immutable.Audience.Runtime", "rootNamespace": "Immutable.Audience", "references": [], - "includePlatforms": ["Android", "Editor", "iOS", "macOSStandalone", "WindowsStandalone64"], + "includePlatforms": ["Android", "Editor", "iOS", "LinuxStandalone64", "macOSStandalone", "WindowsStandalone64"], "excludePlatforms": [], "allowUnsafeCode": false, "overrideReferences": false, From e9692552d4b8fe2deacfa4a54cf1376b0e293232 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Thu, 7 May 2026 01:52:43 +1000 Subject: [PATCH 5/5] ci(audience): add manual self-hosted Linux PlayMode job (SDK-255) - Adds a playmode-linux-selfhosted job to test-audience-sample-app.yml, gated `if: github.event_name == 'workflow_dispatch'` so the self-hosted Linux runner is not consumed on every PR. - Targets the [self-hosted, X64, Linux] runner with the same matrix shape as the GameCI Linux job (IL2CPP and Mono2x for Unity 2021.3.45f2). - Activates Unity at job start and returns the license at job end so manual reruns do not leak seats against the serial's activation pool. - Used to cross-check that the SDK behaves the same on a real self-hosted Linux machine as it does inside the GameCI Docker image. - Lives in the existing workflow file (not a new file) because GitHub's workflow_dispatch API only accepts workflow files that exist on the default branch. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../workflows/test-audience-sample-app.yml | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/.github/workflows/test-audience-sample-app.yml b/.github/workflows/test-audience-sample-app.yml index ed6178ca..bf3e4167 100644 --- a/.github/workflows/test-audience-sample-app.yml +++ b/.github/workflows/test-audience-sample-app.yml @@ -558,3 +558,111 @@ jobs: path: | examples/audience/Builds/Android/*.apk examples/audience/Logs/** + + # workflow_dispatch only. Cross-checks the GameCI Docker outcome against + # the self-hosted Linux machine when there's reason to suspect divergence. + playmode-linux-selfhosted: + if: github.event_name == 'workflow_dispatch' + name: ${{ matrix.backend }} / Unity ${{ matrix.unity }} (self-hosted Linux) + runs-on: [self-hosted, X64, Linux] + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + include: + - backend: IL2CPP + unity: 2021.3.45f2 + - backend: Mono2x + unity: 2021.3.45f2 + + steps: + - uses: actions/checkout@v4 + with: + lfs: true + + - uses: actions/cache@v4 + with: + path: examples/audience/Library + key: Library-selfhosted-${{ matrix.backend }}-StandaloneLinux64-${{ matrix.unity }}-${{ hashFiles('examples/audience/Assets/**', 'examples/audience/Packages/**', 'examples/audience/ProjectSettings/**', 'src/Packages/Audience/**') }} + restore-keys: | + Library-selfhosted-${{ matrix.backend }}-StandaloneLinux64-${{ matrix.unity }}- + Library-selfhosted-${{ matrix.backend }}-StandaloneLinux64- + + - name: Resolve Unity + shell: bash + run: | + set -uo pipefail + UNITY="$HOME/Unity/Hub/Editor/${{ matrix.unity }}/Editor/Unity" + if [ ! -x "$UNITY" ]; then + echo "::error::Unity ${{ matrix.unity }} not found at $UNITY. Install via Unity Hub on the runner host." + exit 1 + fi + echo "UNITY_PATH=$UNITY" >> "$GITHUB_ENV" + + - name: Activate Unity license + shell: bash + env: + UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} + UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} + UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} + run: | + set -uo pipefail + LOG="$(mktemp)" + "$UNITY_PATH" -batchmode -nographics -quit \ + -username "$UNITY_EMAIL" \ + -password "$UNITY_PASSWORD" \ + -serial "$UNITY_SERIAL" \ + -logFile - 2>&1 | tee "$LOG" || true + + if grep -qE "(License activation has failed|\[Licensing::Client\] Error: Code [0-9]+)" "$LOG"; then + echo "::error::Unity activation failed." + exit 1 + fi + if ! grep -qE "Successfully activated the entitlement license" "$LOG"; then + echo "::error::Unity activation: no success marker found." + exit 1 + fi + + - name: Run PlayMode tests + shell: bash + env: + AUDIENCE_TEST_PUBLISHABLE_KEY: ${{ secrets.AUDIENCE_TEST_PUBLISHABLE_KEY }} + AUDIENCE_SCRIPTING_BACKEND: ${{ matrix.backend }} + run: | + set -euo pipefail + mkdir -p artifacts + "$UNITY_PATH" \ + -batchmode -nographics \ + -projectPath examples/audience \ + -runTests \ + -testPlatform StandaloneLinux64 \ + -testResults "$(pwd)/artifacts/test-results.xml" \ + -logFile - 2>&1 | tee "$(pwd)/artifacts/unity.log" + + - name: Return Unity license + if: always() + shell: bash + run: | + set -uo pipefail + # Releases the seat after every run so manual reruns do not exhaust + # the serial's activation pool. + if [ -n "${UNITY_PATH:-}" ] && [ -x "$UNITY_PATH" ]; then + "$UNITY_PATH" -returnlicense -batchmode -nographics -quit -logFile - 2>&1 || true + fi + + - name: Publish test report + uses: dorny/test-reporter@v3 + if: always() + with: + name: Self-hosted Linux PlayMode (${{ matrix.backend }}) + path: artifacts/test-results.xml + reporter: dotnet-nunit + fail-on-error: true + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: selfhosted-linux-${{ matrix.backend }} + path: | + artifacts/test-results.xml + artifacts/unity.log