diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 51fb96f3c..abf9f29d3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -107,12 +107,24 @@ jobs: name: Linux-sdk path: package-dev/Plugins/Linux/Sentry~ + - name: Download LinuxArm64 SDK + uses: ./.github/actions/wait-for-artifact + with: + name: LinuxArm64-sdk + path: package-dev/Plugins/Linux/Sentry~ + - name: Download LinuxNative SDK uses: ./.github/actions/wait-for-artifact with: name: LinuxNative-sdk path: package-dev/Plugins/Linux/SentryNative~ + - name: Download LinuxNativeArm64 SDK + uses: ./.github/actions/wait-for-artifact + with: + name: LinuxNativeArm64-sdk + path: package-dev/Plugins/Linux/SentryNative~ + - name: Download Windows SDK uses: ./.github/actions/wait-for-artifact with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6151395e8..130331920 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,10 +39,17 @@ jobs: include: - target: Android host: ubuntu-latest + # 22.04 keeps the required glibc version low. - target: Linux - host: ubuntu-latest + host: ubuntu-22.04 - target: LinuxNative - host: ubuntu-latest + host: ubuntu-22.04 + - target: Linux + arch: Arm64 + host: ubuntu-22.04-arm + - target: LinuxNative + arch: Arm64 + host: ubuntu-22.04-arm - target: Windows host: windows-latest - target: WindowsNative @@ -54,6 +61,7 @@ jobs: uses: ./.github/workflows/sdk.yml with: target: ${{ matrix.target }} + arch: ${{ matrix.arch }} runsOn: ${{ matrix.host }} build-unity-sdk: @@ -130,12 +138,13 @@ jobs: with: unity-version: ${{ matrix.unity-version }} - # A Linux, docker-based build to prepare a WebGL player. The tests run in `test-run-webgl`. + # A Linux build to prepare a WebGL player. The tests run in `test-run-webgl`. test-build-webgl: name: Build ${{ matrix.platform }} ${{ matrix.unity-version }} Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} needs: [test-create, create-unity-matrix] - runs-on: ubuntu-latest + # Unity 6 runs on the Unity CLI, on 24.04: the newest Ubuntu Unity supports (26.04 lacks libxml2.so.2 for IL2CPP). + runs-on: ${{ fromJSON(matrix.unity-version) >= 6000 && 'ubuntu-24.04' || 'ubuntu-latest' }} strategy: fail-fast: false matrix: @@ -144,8 +153,6 @@ jobs: include: - platform: WebGL build_platform: WebGL - env: - UNITY_PATH: docker exec unity unity-editor steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -161,6 +168,7 @@ jobs: swap-storage: true - name: Docker Login + if: ${{ fromJSON(matrix.unity-version) < 6000 }} uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io @@ -168,6 +176,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Start the Unity docker container + if: ${{ fromJSON(matrix.unity-version) < 6000 }} run: ./scripts/ci-docker.sh "$UNITY_VERSION" "$PLATFORM_WITH_SUFFIX" "$UNITY_LICENSE_SERVER_CONFIG" shell: bash env: @@ -177,6 +186,12 @@ jobs: PLATFORM_WITH_SUFFIX: ${{ matrix.platform }}${{ matrix.image-suffix }} UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} + - name: Setup Unity + if: ${{ fromJSON(matrix.unity-version) >= 6000 }} + run: ./scripts/setup-unity.ps1 -UnityVersion "${{ matrix.unity-version }}" -Modules webgl + env: + UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} + - name: Download IntegrationTest project uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -448,6 +463,21 @@ jobs: with: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version[0] }} + test-build-linux-arm64: + # Unity ships an arm64 Linux player only as an IL2CPP dedicated server, from 6000.3 on. One version, + # Native backend only: the x86_64 matrix already covers the version spread and Breakpad. + # The version is the newest matrix entry, so Unity-bump PRs for older versions skip it. + name: Build Linux Server arm64 ${{ needs.create-unity-matrix.outputs.unity-latest }} Integration Test + if: ${{ !startsWith(github.ref, 'refs/heads/release/') && fromJSON(needs.create-unity-matrix.outputs.unity-latest) >= 6000.3 }} + needs: [test-create, create-unity-matrix] + secrets: + UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_TEST_DSN: ${{ secrets.SENTRY_TEST_DSN }} + uses: ./.github/workflows/test-build-linux-arm64.yml + with: + unity-version: ${{ needs.create-unity-matrix.outputs.unity-latest }} + test-build-windows-mono: # The Mono scripting backend resolves P/Invokes differently than IL2CPP, so it needs its own # player. One version, default (Crashpad) backend only: the IL2CPP matrix already covers the @@ -517,6 +547,20 @@ jobs: platform: windows backend: ${{ matrix.backend }} + test-run-linux-arm64: + name: Run Linux arm64 native ${{ needs.create-unity-matrix.outputs.unity-latest }} Integration Test + if: ${{ !startsWith(github.ref, 'refs/heads/release/') && fromJSON(needs.create-unity-matrix.outputs.unity-latest) >= 6000.3 }} + needs: [test-build-linux-arm64, create-unity-matrix] + secrets: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_TEST_DSN: ${{ secrets.SENTRY_TEST_DSN }} + uses: ./.github/workflows/test-run-desktop.yml + with: + unity-version: ${{ needs.create-unity-matrix.outputs.unity-latest }} + platform: linux + architecture: arm64 + backend: native + test-run-windows-x86: name: Run Windows x86 native ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version[0] }} Integration Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} diff --git a/.github/workflows/create-unity-matrix.yml b/.github/workflows/create-unity-matrix.yml index 2ad5eb93d..d437a84e6 100644 --- a/.github/workflows/create-unity-matrix.yml +++ b/.github/workflows/create-unity-matrix.yml @@ -16,6 +16,9 @@ on: unity-matrix: description: 'Unity version matrix' value: ${{ jobs.create-unity-matrix.outputs.unity-matrix }} + unity-latest: + description: 'Newest Unity version in the matrix' + value: ${{ jobs.create-unity-matrix.outputs.unity-latest }} env: # Unity versions used in PRs @@ -28,6 +31,7 @@ jobs: runs-on: ubuntu-latest outputs: unity-matrix: ${{ steps.set-matrix.outputs.matrix }} + unity-latest: ${{ steps.set-matrix.outputs.latest }} steps: - name: Set Unity version matrices based on event type id: set-matrix @@ -49,4 +53,5 @@ jobs: fi # Unity version matrix - echo "matrix={\"unity-version\":$versions}" >> $GITHUB_OUTPUT \ No newline at end of file + echo "matrix={\"unity-version\":$versions}" >> $GITHUB_OUTPUT + echo "latest=$(jq -r '.[-1]' <<< "$versions")" >> $GITHUB_OUTPUT \ No newline at end of file diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index d565d5539..a7660b02f 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -8,11 +8,16 @@ on: target: required: true type: string + arch: + required: false + type: string + default: "" env: DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_NOLOGO: 1 TARGET: ${{ inputs.target }} + ARTIFACT: ${{ inputs.target }}${{ inputs.arch }} jobs: build: @@ -63,7 +68,7 @@ jobs: # hash of package/package.json for cache busting on release builds (version bump) path: | package-dev/Plugins - key: sdk=${{ env.TARGET }}-${{ hashFiles('submodules-status', 'package/package.json', 'Directory.Build.targets', 'build/native-sdks.targets', 'sdk-static/**', 'scripts/build-cocoa-sdk.ps1', 'scripts/build-native-ndk-local.ps1') }} + key: sdk=${{ env.ARTIFACT }}-${{ hashFiles('submodules-status', 'package/package.json', 'Directory.Build.targets', 'build/native-sdks.targets', 'sdk-static/**', 'scripts/build-cocoa-sdk.ps1', 'scripts/build-native-ndk-local.ps1') }} - name: Installing Linux Dependencies if: ${{ (env.TARGET == 'Linux' || env.TARGET == 'LinuxNative') && steps.cache.outputs.cache-hit != 'true' }} @@ -143,7 +148,7 @@ jobs: - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: ${{ env.TARGET == 'Linux' }} with: - name: ${{ env.TARGET }}-sdk + name: ${{ env.ARTIFACT }}-sdk path: package-dev/Plugins/Linux/Sentry~ # Lower retention period - we only need this to retry CI. retention-days: 14 @@ -151,7 +156,7 @@ jobs: - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: ${{ env.TARGET == 'LinuxNative' }} with: - name: ${{ env.TARGET }}-sdk + name: ${{ env.ARTIFACT }}-sdk path: package-dev/Plugins/Linux/SentryNative~ # Lower retention period - we only need this to retry CI. retention-days: 14 diff --git a/.github/workflows/test-build-android.yml b/.github/workflows/test-build-android.yml index 1524018ed..a7160736d 100644 --- a/.github/workflows/test-build-android.yml +++ b/.github/workflows/test-build-android.yml @@ -20,17 +20,29 @@ defaults: jobs: build: name: ${{ inputs.unity-version }} - runs-on: ubuntu-latest-4-cores + # Unity 6 runs on the Unity CLI, on 24.04: the newest Ubuntu Unity supports (26.04 lacks libxml2.so.2 for IL2CPP). + runs-on: ${{ fromJSON(inputs.unity-version) >= 6000 && 'ubuntu-24.04' || 'ubuntu-latest-4-cores' }} env: GITHUB_ACTOR: ${{ github.actor }} - UNITY_PATH: docker exec unity unity-editor UNITY_VERSION: ${{ inputs.unity-version }} steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Free Disk Space + if: ${{ fromJSON(inputs.unity-version) >= 6000 }} + uses: jlumbroso/free-disk-space@f68fdb76e2ea636224182cfb7377ff9a1708f9b8 # v1.3.0 + with: + android: true + dotnet: false + haskell: true + large-packages: false + docker-images: false + swap-storage: true + - name: Docker Login + if: ${{ fromJSON(inputs.unity-version) < 6000 }} uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io @@ -38,12 +50,19 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Start the Unity docker container + if: ${{ fromJSON(inputs.unity-version) < 6000 }} run: ./scripts/ci-docker.sh "$UNITY_VERSION" 'android' "$UNITY_LICENSE_SERVER_CONFIG" shell: bash env: UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + - name: Setup Unity + if: ${{ fromJSON(inputs.unity-version) >= 6000 }} + run: ./scripts/setup-unity.ps1 -UnityVersion "$env:UNITY_VERSION" -Modules android + env: + UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} + - name: Download IntegrationTest project uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -105,6 +124,8 @@ jobs: - name: Export APK - Runtime Initialization run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "Android" -UnityVersion "$env:UNITY_VERSION" + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - name: Assert symbols and sources were uploaded (Runtime) run: ./test/Scripts.Integration.Test/assert-symbol-upload.ps1 -LogPath samples/IntegrationTest/Logs/sentry-symbols-upload.log @@ -144,6 +165,8 @@ jobs: - name: Export APK - Build-Time Initialization run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "Android" -UnityVersion "$env:UNITY_VERSION" + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - name: Assert symbols and sources were uploaded (Build-Time) run: ./test/Scripts.Integration.Test/assert-symbol-upload.ps1 -LogPath samples/IntegrationTest/Logs/sentry-symbols-upload.log diff --git a/.github/workflows/test-build-ios.yml b/.github/workflows/test-build-ios.yml index 9dab592ec..673fe0aa8 100644 --- a/.github/workflows/test-build-ios.yml +++ b/.github/workflows/test-build-ios.yml @@ -20,7 +20,8 @@ defaults: jobs: build: name: ${{ inputs.unity-version }} - runs-on: ubuntu-latest + # Unity 6 runs on the Unity CLI, on 24.04: the newest Ubuntu Unity supports (26.04 lacks libxml2.so.2 for IL2CPP). + runs-on: ${{ fromJSON(inputs.unity-version) >= 6000 && 'ubuntu-24.04' || 'ubuntu-latest' }} strategy: matrix: include: @@ -29,7 +30,6 @@ jobs: build_platform: iOS env: GITHUB_ACTOR: ${{ github.actor }} - UNITY_PATH: docker exec unity unity-editor UNITY_VERSION: ${{ inputs.unity-version }} steps: @@ -37,6 +37,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Docker Login + if: ${{ fromJSON(inputs.unity-version) < 6000 }} uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io @@ -44,12 +45,19 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Start the Unity docker container + if: ${{ fromJSON(inputs.unity-version) < 6000 }} run: ./scripts/ci-docker.sh "$UNITY_VERSION" "iOS" "$UNITY_LICENSE_SERVER_CONFIG" shell: bash env: UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + - name: Setup Unity + if: ${{ fromJSON(inputs.unity-version) >= 6000 }} + run: ./scripts/setup-unity.ps1 -UnityVersion "$env:UNITY_VERSION" -Modules ios + env: + UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} + - name: Download IntegrationTest project uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -127,6 +135,7 @@ jobs: run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -UnityVersion "$env:UNITY_VERSION" env: BUILD_PLATFORM: ${{ matrix.build_platform }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} # We create tar explicitly because upload-artifact is slow for many files. - name: Create archive (runtime initialization) @@ -155,6 +164,7 @@ jobs: run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -UnityVersion "$env:UNITY_VERSION" env: BUILD_PLATFORM: ${{ matrix.build_platform }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - name: Create archive (build-time initialization) run: | diff --git a/.github/workflows/test-build-linux-arm64.yml b/.github/workflows/test-build-linux-arm64.yml new file mode 100644 index 000000000..aba9ae72c --- /dev/null +++ b/.github/workflows/test-build-linux-arm64.yml @@ -0,0 +1,133 @@ +name: "Test: Build Linux Server arm64" +on: + workflow_call: + inputs: + unity-version: + required: true + type: string + secrets: + UNITY_LICENSE_SERVER_CONFIG: + required: true + SENTRY_AUTH_TOKEN: + required: true + SENTRY_TEST_DSN: + required: true + +defaults: + run: + shell: pwsh + +jobs: + build: + name: Linux Server arm64 ${{ inputs.unity-version }} + # Unity has no arm64 Linux editor and the GameCI images lack the arm64 server player, so this + # cross-compiles on x86_64 with the Unity CLI install instead. Pinned to 24.04: Unity's bundled + # IL2CPP linker needs libxml2.so.2, which 26.04 no longer ships. + runs-on: ubuntu-24.04 + env: + UNITY_VERSION: ${{ inputs.unity-version }} + BUILD_PLATFORM: Linux-Server-arm64 + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup Unity + run: ./scripts/setup-unity.ps1 -UnityVersion "${{ inputs.unity-version }}" -Modules linux-server + env: + UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} + + - name: Download IntegrationTest project + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: test-${{ env.UNITY_VERSION }} + + - name: Extract project archive + run: tar -xvzf test-project.tar.gz + + - name: Restore Unity Library cache + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: samples/IntegrationTest/Library + key: it-library-linux-arm64-${{ env.UNITY_VERSION }}-${{ github.run_id }} + restore-keys: | + it-library-linux-arm64-${{ env.UNITY_VERSION }}- + + - name: Download UPM package + uses: ./.github/actions/wait-for-artifact + with: + name: package-release + + - name: Extract UPM package + run: ./test/Scripts.Integration.Test/extract-package.ps1 + + - name: Add Sentry to the project + run: ./test/Scripts.Integration.Test/add-sentry.ps1 -UnityPath "$env:UNITY_PATH" -PackagePath "test-package-release" + + - name: Download DependencyConflict package + uses: ./.github/actions/wait-for-artifact + with: + name: dependency-conflict-package + path: dependency-conflict-package + + - name: Add DependencyConflict to the project + run: ./test/Scripts.Integration.Test/add-dependency-conflict.ps1 -PackagePath "dependency-conflict-package" + + - name: Configure Sentry + run: ./test/Scripts.Integration.Test/configure-sentry.ps1 -UnityPath "$env:UNITY_PATH" -Platform Linux + env: + SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }} + + - name: Use the Native backend + run: | + $optionsPath = "samples/IntegrationTest/Assets/Scripts/IntegrationOptionsConfiguration.cs" + $content = Get-Content $optionsPath -Raw + $content = $content -replace 'LinuxBackend = LinuxBackend.Breakpad', 'LinuxBackend = LinuxBackend.Native' + Set-Content $optionsPath $content + + - name: Build with Sentry SDK + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform Linux-Server-arm64 -UnityVersion "$env:UNITY_VERSION" + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + + - name: Assert the player is arm64 + run: | + $info = file samples/IntegrationTest/Build/test + Write-Host $info + if ($info -notmatch 'aarch64') { throw "Expected an arm64 player" } + + - name: Assert symbols and sources were uploaded + run: ./test/Scripts.Integration.Test/assert-symbol-upload.ps1 -LogPath unity.log + + # We create tar explicitly because upload-artifact is slow for many files. + - name: Create archive + run: | + Remove-Item -Recurse -Force samples/IntegrationTest/Build/*_BackUpThisFolder_ButDontShipItWithYourGame -ErrorAction SilentlyContinue + Copy-Item unity.log samples/IntegrationTest/Build/ -ErrorAction SilentlyContinue + tar -cvzf test-app-desktop.tar.gz samples/IntegrationTest/Build + + - name: Upload test app + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: testapp-desktop-compiled-${{ env.UNITY_VERSION }}-linux-arm64-native + if-no-files-found: error + path: test-app-desktop.tar.gz + retention-days: 14 + + - name: Save Unity Library cache + if: github.ref == 'refs/heads/main' + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: samples/IntegrationTest/Library + key: it-library-linux-arm64-${{ env.UNITY_VERSION }}-${{ github.run_id }} + + - name: Upload IntegrationTest project on failure + if: ${{ failure() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: failed-project-desktop-linux-arm64-${{ env.UNITY_VERSION }} + path: | + samples/IntegrationTest + unity.log + !samples/IntegrationTest/Build/*_BackUpThisFolder_ButDontShipItWithYourGame + retention-days: 14 diff --git a/.github/workflows/test-build-linux.yml b/.github/workflows/test-build-linux.yml index 5535dc050..fd6f99667 100644 --- a/.github/workflows/test-build-linux.yml +++ b/.github/workflows/test-build-linux.yml @@ -20,10 +20,10 @@ defaults: jobs: build: name: Linux ${{ inputs.unity-version }} - runs-on: ubuntu-latest + # Unity 6 runs on the Unity CLI, on 24.04: the newest Ubuntu Unity supports (26.04 lacks libxml2.so.2 for IL2CPP). + runs-on: ${{ fromJSON(inputs.unity-version) >= 6000 && 'ubuntu-24.04' || 'ubuntu-latest' }} env: GITHUB_ACTOR: ${{ github.actor }} - UNITY_PATH: docker exec unity unity-editor UNITY_VERSION: ${{ inputs.unity-version }} BUILD_PLATFORM: Linux @@ -42,6 +42,7 @@ jobs: swap-storage: true - name: Log into Docker + if: ${{ fromJSON(inputs.unity-version) < 6000 }} uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io @@ -49,12 +50,19 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Start the Unity docker container + if: ${{ fromJSON(inputs.unity-version) < 6000 }} run: ./scripts/ci-docker.sh "$UNITY_VERSION" "Linux-il2cpp" "$UNITY_LICENSE_SERVER_CONFIG" shell: bash env: UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + - name: Setup Unity + if: ${{ fromJSON(inputs.unity-version) >= 6000 }} + run: ./scripts/setup-unity.ps1 -UnityVersion "$env:UNITY_VERSION" -Modules linux-il2cpp + env: + UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} + - name: Download IntegrationTest project uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -116,6 +124,8 @@ jobs: - name: Build with Sentry SDK (Breakpad backend) run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform Linux -UnityVersion "$env:UNITY_VERSION" + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - name: Assert symbols and sources were uploaded (Breakpad backend) run: ./test/Scripts.Integration.Test/assert-symbol-upload.ps1 -LogPath unity.log @@ -154,6 +164,8 @@ jobs: - name: Build with Sentry SDK (Native backend) run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform Linux -UnityVersion "$env:UNITY_VERSION" + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - name: Assert symbols and sources were uploaded (Native backend) run: ./test/Scripts.Integration.Test/assert-symbol-upload.ps1 -LogPath unity.log @@ -181,7 +193,7 @@ jobs: key: it-library-ubuntu-${{ env.UNITY_VERSION }}-${{ github.run_id }} - name: Docker diagnostics on failure - if: ${{ failure() }} + if: ${{ failure() && fromJSON(inputs.unity-version) < 6000 }} run: | Write-Host "=== Docker container status ===" docker ps -a --filter "name=^/unity$" diff --git a/.github/workflows/test-create.yml b/.github/workflows/test-create.yml index 067df280d..9cf2702d6 100644 --- a/.github/workflows/test-create.yml +++ b/.github/workflows/test-create.yml @@ -16,10 +16,10 @@ defaults: jobs: create: name: ${{ inputs.unity-version }} - runs-on: ubuntu-latest + # Unity 6 runs on the Unity CLI, on 24.04: the newest Ubuntu Unity supports (26.04 lacks libxml2.so.2 for IL2CPP). + runs-on: ${{ fromJSON(inputs.unity-version) >= 6000 && 'ubuntu-24.04' || 'ubuntu-latest' }} env: GITHUB_ACTOR: ${{ github.actor }} - UNITY_PATH: docker exec unity unity-editor UNITY_VERSION: ${{ inputs.unity-version }} steps: @@ -27,6 +27,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Docker Login + if: ${{ fromJSON(inputs.unity-version) < 6000 }} uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io @@ -43,12 +44,18 @@ jobs: IntegrationTest-Project-${{ env.UNITY_VERSION }}- - name: Start the Unity docker container - if: steps.cache-project.outputs.cache-hit != 'true' + if: steps.cache-project.outputs.cache-hit != 'true' && fromJSON(inputs.unity-version) < 6000 run: ./scripts/ci-docker.sh "$UNITY_VERSION" 'base' "$UNITY_LICENSE_SERVER_CONFIG" shell: bash env: UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} + - name: Setup Unity + if: steps.cache-project.outputs.cache-hit != 'true' && fromJSON(inputs.unity-version) >= 6000 + run: ./scripts/setup-unity.ps1 -UnityVersion "$env:UNITY_VERSION" + env: + UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} + - name: Create new Project if: steps.cache-project.outputs.cache-hit != 'true' run: ./test/Scripts.Integration.Test/create-project.ps1 -UnityPath "$env:UNITY_PATH" diff --git a/.github/workflows/test-run-desktop.yml b/.github/workflows/test-run-desktop.yml index 4cf4f7e24..7c13cf380 100644 --- a/.github/workflows/test-run-desktop.yml +++ b/.github/workflows/test-run-desktop.yml @@ -18,7 +18,7 @@ on: required: false type: string default: "" - description: "Only set for non-default architectures, e.g. x86 for the 32-bit Windows player." + description: "Only set for non-default architectures, e.g. x86 for the 32-bit Windows player or arm64 for the Linux server." scripting: required: false type: string @@ -37,7 +37,7 @@ defaults: jobs: run: name: ${{ inputs.platform }}${{ inputs.architecture && format(' {0}', inputs.architecture) || '' }}${{ inputs.scripting && format(' {0}', inputs.scripting) || '' }}${{ inputs.backend && format(' ({0})', inputs.backend) || '' }} ${{ inputs.unity-version }} - runs-on: ${{ inputs.platform == 'linux' && 'ubuntu-latest' || inputs.platform == 'macos' && 'macos-latest' || 'windows-latest' }} + runs-on: ${{ inputs.platform == 'linux' && (inputs.architecture == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest') || inputs.platform == 'macos' && 'macos-latest' || 'windows-latest' }} env: SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} @@ -71,8 +71,9 @@ jobs: - name: Run Integration Tests (Linux) if: inputs.platform == 'linux' timeout-minutes: 20 + # The arm64 player is a headless dedicated server and needs no display. run: | - xvfb-run pwsh -Command ' + ${{ inputs.architecture != 'arm64' && 'xvfb-run' || '' }} pwsh -Command ' $env:SENTRY_TEST_PLATFORM = "Desktop"; $env:SENTRY_TEST_APP = "samples/IntegrationTest/Build/test"; Invoke-Pester -Path test/IntegrationTest/Integration.Tests.ps1 -CI' diff --git a/CHANGELOG.md b/CHANGELOG.md index f0906ca56..cc014a7b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,13 @@ ### Fixes +- The Linux native SDK now loads on distros with older glibc versions, such as Ubuntu 22.04 and Debian 12. ([#2894](https://github.com/getsentry/sentry-unity/pull/2894)) - Attaching a screenshot no longer stalls the main thread. Unless `SetBeforeSendScreenshot` is set, the frame gets read back from the GPU asynchronously and encoded on a background thread. ([#2835](https://github.com/getsentry/sentry-unity/pull/2835)) +### Features + +- The SDK now supports Linux ARM64 dedicated servers (Unity 6000.3 and newer, IL2CPP). The native SDK for both backends ships for `x86_64` and `ARM64`, and the build picks the one matching the player. ([#2894](https://github.com/getsentry/sentry-unity/pull/2894)) + ### Dependencies - Bump CLI from v3.8.0 to v3.8.2 ([#2893](https://github.com/getsentry/sentry-unity/pull/2893)) diff --git a/build/native-sdks.targets b/build/native-sdks.targets index c5008ba7b..f15748bd4 100644 --- a/build/native-sdks.targets +++ b/build/native-sdks.targets @@ -14,8 +14,11 @@ $(RepoRoot)modules/sentry-native/ - $(SentryArtifactsDestination)Linux/Sentry~/ - $(SentryArtifactsDestination)Linux/SentryNative~/ + + ARM64 + x86_64 + $(SentryArtifactsDestination)Linux/Sentry~/$(SentryLinuxArch)/ + $(SentryArtifactsDestination)Linux/SentryNative~/$(SentryLinuxArch)/ $(SentryArtifactsDestination)Windows/Sentry~/ $(SentryArtifactsDestination)Windows/SentryNative~/ @@ -46,19 +49,17 @@ - + - - - - + @@ -70,7 +71,7 @@ - + - + - - - - - - + @@ -110,7 +106,7 @@ - + diff --git a/docs/agent-guides/platform-native.md b/docs/agent-guides/platform-native.md index ceb8da4e7..1767d1e76 100644 --- a/docs/agent-guides/platform-native.md +++ b/docs/agent-guides/platform-native.md @@ -27,7 +27,7 @@ Experimental native modes raise minimum shutdown timeout to 10 seconds. - Windows: runtime files beside player `.exe`; the library lands as `sentry-native.dll`. Both architectures ship, under `x86/` and `x64/` inside each backend folder, because Unity still builds a 32-bit standalone player. `BuildTarget.StandaloneWindows` picks `x86/`. -- Linux: `libsentry-native.so` under `_Data/Plugins/x86_64`; native daemon beside executable. +- Linux: `libsentry-native.so` under `_Data/Plugins/` (`x86_64` or `ARM64`, from the Linux build settings' architecture); native daemon beside executable. - macOS: `libsentry-native.dylib` in `.app/Contents/PlugIns`; handler in `.app/Contents/MacOS`. Cocoa's `Sentry.dylib` keeps its name, it is dlopened not P/Invoked. - Post-build copies names through unchanged; stale cleanup wipes pre-rename names. - Placement is what sentry-native probes by default. `SentryUnityOptions.NativeHandlerPath` overrides it via `handler_path`, for handlers staged somewhere else; sidecars the handler resolves relative to itself, such as `crashpad_wer.dll`, move with it. diff --git a/scripts/ci-docker.sh b/scripts/ci-docker.sh index 769647a88..f680b6c20 100755 --- a/scripts/ci-docker.sh +++ b/scripts/ci-docker.sh @@ -64,5 +64,9 @@ $suexec $container chown -R $uid /usr/share/unity3d/config/ # Unity 2021+ tries to write to this directory during asset import... $suexec $container chmod -R 755 /opt/unity/Editor/Data/UnityReferenceAssemblies/ +if [[ -n "${GITHUB_ENV:-}" ]]; then + echo "UNITY_PATH=docker exec $container unity-editor" >> "$GITHUB_ENV" +fi + echo "Container started successfully: " docker ps --filter "name=^/$container$" diff --git a/scripts/download-native-sdks.ps1 b/scripts/download-native-sdks.ps1 index efccd359d..65b35f864 100644 --- a/scripts/download-native-sdks.ps1 +++ b/scripts/download-native-sdks.ps1 @@ -32,12 +32,22 @@ $SDKs = @( @{ Name = "Linux" Destination = Join-Path $ArtifactsDestination "Linux/Sentry~" - CheckFile = "libsentry-native.so" + CheckFile = "x86_64/libsentry-native.so" + }, + @{ + Name = "LinuxArm64" + Destination = Join-Path $ArtifactsDestination "Linux/Sentry~" + CheckFile = "ARM64/libsentry-native.so" }, @{ Name = "LinuxNative" Destination = Join-Path $ArtifactsDestination "Linux/SentryNative~" - CheckFile = "libsentry-native.so" + CheckFile = "x86_64/libsentry-native.so" + }, + @{ + Name = "LinuxNativeArm64" + Destination = Join-Path $ArtifactsDestination "Linux/SentryNative~" + CheckFile = "ARM64/libsentry-native.so" }, @{ Name = "Android" @@ -201,11 +211,11 @@ if ($currentBranch -and $currentBranch -ne 'main' -and $currentBranch -ne 'HEAD' $failed = @() foreach ($sdk in $sdksToDownload) { Write-Host "Downloading $($sdk.Name) SDK..." -ForegroundColor Yellow - if (Try-DownloadSDK -Name $sdk.Name -Destination $sdk.Destination -RunId $mainRunId) { + if ((Try-DownloadSDK -Name $sdk.Name -Destination $sdk.Destination -RunId $mainRunId) -and (Test-SDKPresent $sdk)) { Write-Host " Downloaded from main run $mainRunId" -ForegroundColor Green continue } - if ($branchRunId -and (Try-DownloadSDK -Name $sdk.Name -Destination $sdk.Destination -RunId $branchRunId)) { + if ($branchRunId -and (Try-DownloadSDK -Name $sdk.Name -Destination $sdk.Destination -RunId $branchRunId) -and (Test-SDKPresent $sdk)) { Write-Host " Not on main; downloaded from branch '$currentBranch' run $branchRunId" -ForegroundColor Yellow continue } diff --git a/scripts/setup-unity.ps1 b/scripts/setup-unity.ps1 index 4a875311a..dc1b3b237 100644 --- a/scripts/setup-unity.ps1 +++ b/scripts/setup-unity.ps1 @@ -9,6 +9,7 @@ $PSNativeCommandUseErrorActionPreference = $true $CliVersion = '1.0.0-beta.14' $CliBinaries = @{ 'darwin-arm64' = @('unity-darwin-arm64', '367ea124c61e91dc22409699cb40d2e65d23952f3220e911ccea1f6d8a1ff37f') + 'linux-x64' = @('unity-linux-x64', 'd9a2c1716ce723b970898f7f40adc09cceee04e84ce2f1f1638f07ac3e9898aa') 'win32-x64' = @('unity-windows-x64.exe', '78761a0547cd4a9bc212e27a217c66e6a621bf48c753ba440e4b2327ee7ff8ac') } @@ -20,7 +21,7 @@ $env:UNITY_NON_INTERACTIVE = '1' $env:UNITY_NO_BANNER = '1' $env:UNITY_NO_PAGER = '1' -# Unity CLI, pinned by version and checksum. Linux CI uses the GameCI Docker images instead (scripts/ci-docker.sh). +# Unity CLI, pinned by version and checksum. $os = $IsWindows ? 'win32' : $IsMacOS ? 'darwin' : 'linux' $arch = "$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)".ToLower() $cliFile, $cliSha = $CliBinaries["$os-$arch"] @@ -51,7 +52,7 @@ if ($env:UNITY_LICENSE_SERVER_CONFIG) { New-Item -ItemType Directory -Force -Path 'C:/ProgramData/Unity/config' | Out-Null Set-Content -Path 'C:/ProgramData/Unity/config/services-config.json' -Value $env:UNITY_LICENSE_SERVER_CONFIG } else { - $unityDataDir = '/Library/Application Support/Unity' + $unityDataDir = $IsMacOS ? '/Library/Application Support/Unity' : '/usr/share/unity3d' $configFile = Join-Path $cliDir 'services-config.json' Set-Content -Path $configFile -Value $env:UNITY_LICENSE_SERVER_CONFIG sudo mkdir -p "$unityDataDir/config" diff --git a/src/Sentry.Unity.Editor/Native/BuildPostProcess.cs b/src/Sentry.Unity.Editor/Native/BuildPostProcess.cs index b98df038e..5b728242a 100644 --- a/src/Sentry.Unity.Editor/Native/BuildPostProcess.cs +++ b/src/Sentry.Unity.Editor/Native/BuildPostProcess.cs @@ -10,6 +10,7 @@ using UnityEditor.Build.Reporting; using System.Diagnostics; using System.Linq; +using System.Reflection; namespace Sentry.Unity.Editor.Native; @@ -84,7 +85,9 @@ _ when targetGroup.IsSwitch2() => Path.GetDirectoryName(executablePath), _ => string.Empty }; - UploadDebugSymbols(logger, target, buildOutputDir, executableName, options, cliOptions, isMono); + var linuxArch = target == BuildTarget.StandaloneLinux64 ? GetLinuxArchitecture() : string.Empty; + + UploadDebugSymbols(logger, target, buildOutputDir, executableName, linuxArch, options, cliOptions, isMono); if (!IsEnabledForPlatform(target, options)) { @@ -108,10 +111,10 @@ _ when targetGroup.IsSwitch2() => Path.GetDirectoryName(executablePath), else if (target == BuildTarget.StandaloneLinux64) { // Since the backend can change between iterative builds we need to clean up after ourselves - CleanupStaleLinuxArtifacts(logger, buildOutputDir); + CleanupStaleLinuxArtifacts(logger, buildOutputDir, linuxArch); } - foreach (var artifact in GetNativePluginArtifact(target, options, executablePath, buildOutputDir)) + foreach (var artifact in GetNativePluginArtifact(target, options, executablePath, buildOutputDir, linuxArch)) { _ = Directory.CreateDirectory(Path.GetDirectoryName(artifact.Destination)); logger.LogDebug("Copying '{0}' to '{1}'", artifact.Source, artifact.Destination); @@ -157,6 +160,24 @@ internal static string GetWindowsBackendDir(BuildTarget target, SentryUnityOptio return Path.GetFullPath($"Packages/{SentryPackageInfo.GetName()}/Plugins/Windows/{backend}/{arch}"); } + // Linux ships x86_64 and ARM64, each in a subfolder named like the player's plugin folder. Both build for `StandaloneLinux64`, + // so the architecture comes from the Linux build settings. + internal static string GetLinuxBackendDir(SentryUnityOptions options, string arch) + { + var backend = options.Experimental.LinuxBackend == LinuxBackend.Native ? "SentryNative~" : "Sentry~"; + return Path.GetFullPath($"Packages/{SentryPackageInfo.GetName()}/Plugins/Linux/{backend}/{arch}"); + } + + // Unity's Linux build picks the player from `UnityEditor.LinuxStandalone.UserBuildSettings.architecture`. + // It lives in the Linux module's assembly and is missing on versions without ARM64 support. + internal static string GetLinuxArchitecture() + { + var architecture = Type.GetType("UnityEditor.LinuxStandalone.UserBuildSettings, UnityEditor.LinuxStandalone.Extensions") + ?.GetProperty("architecture", BindingFlags.Public | BindingFlags.Static) + ?.GetValue(null); + return architecture?.ToString() == "ARM64" ? "ARM64" : "x86_64"; + } + private readonly struct NativePluginArtifact(string source, string destination, bool isExecutable = false) { public readonly string Source = source; @@ -165,7 +186,7 @@ private readonly struct NativePluginArtifact(string source, string destination, } private static IEnumerable GetNativePluginArtifact( - BuildTarget target, SentryUnityOptions options, string executablePath, string buildOutputDir) + BuildTarget target, SentryUnityOptions options, string executablePath, string buildOutputDir, string linuxArch) { var pluginsPath = Path.GetFullPath($"Packages/{SentryPackageInfo.GetName()}/Plugins"); @@ -214,9 +235,7 @@ private static IEnumerable GetNativePluginArtifact( break; case BuildTarget.StandaloneLinux64: - var linuxBackendSourcePath = options.Experimental.LinuxBackend == LinuxBackend.Native - ? Path.Combine(pluginsPath, "Linux", "SentryNative~") - : Path.Combine(pluginsPath, "Linux", "Sentry~"); + var linuxBackendSourcePath = GetLinuxBackendDir(options, linuxArch); if (!Directory.Exists(linuxBackendSourcePath)) { var buildTarget = options.Experimental.LinuxBackend == LinuxBackend.Native ? "BuildLinuxNativeSDK" : "BuildLinuxSDK"; @@ -226,7 +245,7 @@ private static IEnumerable GetNativePluginArtifact( } // The Linux player resolves the P/Invoke from the plugin dir, and sentry-native spawns // the crash daemon from next to the executable. Debug sidecars stay in the package. - var linuxPluginDir = GetLinuxPluginDir(buildOutputDir); + var linuxPluginDir = GetLinuxPluginDir(buildOutputDir, linuxArch); foreach (var file in Directory.GetFiles(linuxBackendSourcePath)) { var name = Path.GetFileName(file); @@ -309,9 +328,9 @@ private static void CleanupStaleWindowsArtifacts(IDiagnosticLogger logger, strin } } - // Unity places Linux native plugins under _Data/Plugins/x86_64, which the player adds - // to its dlopen search path. We resolve the data dir by globbing (the player name isn't known here). - private static string GetLinuxPluginDir(string buildOutputDir) + // Unity places Linux native plugins under _Data/Plugins/, using the plugin importer's + // CPU names (`x86_64`, `ARM64`). We resolve the data dir by globbing (the player name isn't known here). + private static string GetLinuxPluginDir(string buildOutputDir, string arch) { var dataDir = Directory.GetDirectories(buildOutputDir, "*_Data").FirstOrDefault(); if (dataDir is null) @@ -320,20 +339,20 @@ private static string GetLinuxPluginDir(string buildOutputDir) $"Could not locate the player '*_Data' directory under '{buildOutputDir}' to place the Sentry native plugin."); } - return Path.Combine(dataDir, "Plugins", "x86_64"); + return Path.Combine(dataDir, "Plugins", arch); } // Switching Linux backends between iterative builds leaves the other backend's artifacts behind // (a stale sentry-crash next to the player, or the other backend's libsentry.so in the plugin dir). // Wipe them before copying the current backend's files in. - private static void CleanupStaleLinuxArtifacts(IDiagnosticLogger logger, string buildOutputDir) + private static void CleanupStaleLinuxArtifacts(IDiagnosticLogger logger, string buildOutputDir, string arch) { var stalePaths = new List { Path.Combine(buildOutputDir, "sentry-crash") }; var dataDir = Directory.GetDirectories(buildOutputDir, "*_Data").FirstOrDefault(); if (dataDir is not null) { - stalePaths.Add(Path.Combine(dataDir, "Plugins", "x86_64", "libsentry.so")); - stalePaths.Add(Path.Combine(dataDir, "Plugins", "x86_64", LinuxLibraryName)); + stalePaths.Add(Path.Combine(dataDir, "Plugins", arch, "libsentry.so")); + stalePaths.Add(Path.Combine(dataDir, "Plugins", arch, LinuxLibraryName)); } foreach (var stale in stalePaths) @@ -364,7 +383,7 @@ internal static void AddPath(List paths, string path, IDiagnosticLogger } private static void UploadDebugSymbols(IDiagnosticLogger logger, BuildTarget target, string buildOutputDir, - string executableName, SentryUnityOptions options, SentryCliOptions? cliOptions, bool isMono) + string executableName, string linuxArch, SentryUnityOptions options, SentryCliOptions? cliOptions, bool isMono) { var projectDir = Directory.GetParent(Application.dataPath)?.FullName ?? ""; if (cliOptions?.IsValid(logger, EditorUserBuildSettings.development) is not true) @@ -454,11 +473,7 @@ private static void UploadDebugSymbols(IDiagnosticLogger logger, BuildTarget tar // Sentry native SDK symbols from package. // Glob the debug sidecars (libsentry.dbg.so, and sentry-crash.dbg for the native backend) // from whichever backend's source dir is in use. - var linuxBackendDir = options.Experimental.LinuxBackend == LinuxBackend.Native - ? "SentryNative~" - : "Sentry~"; - var linuxSymbolDir = Path.GetFullPath( - $"Packages/{SentryPackageInfo.GetName()}/Plugins/Linux/{linuxBackendDir}"); + var linuxSymbolDir = GetLinuxBackendDir(options, linuxArch); if (Directory.Exists(linuxSymbolDir)) { foreach (var file in Directory.GetFiles(linuxSymbolDir)) diff --git a/src/Sentry.Unity.Native/SentryNativeBridge.cs b/src/Sentry.Unity.Native/SentryNativeBridge.cs index df451069e..095edad80 100644 --- a/src/Sentry.Unity.Native/SentryNativeBridge.cs +++ b/src/Sentry.Unity.Native/SentryNativeBridge.cs @@ -18,27 +18,29 @@ internal static class SentryNativeBridge private static IDiagnosticLogger? Logger; // This is also the logger we're forwarding native messages to. private static bool UseLibC; + private static bool IsLinuxArm64; private static bool IsWindows; public static bool Init(SentryUnityOptions options) { Logger = options.DiagnosticLogger; - // Only x86-64 System V platforms need the `VaListLinux64` copy in `nativeLogImpl`. There - // `va_list` is `__va_list_tag[1]` - an array that decays to a pointer - so `vsnprintf` - // consumes the caller's list and it cannot be handed over twice. On AArch64 (Android, iOS, - // Switch, Switch 2, Apple silicon) `va_list` is a struct that is copied by value at every - // call, so the pointer can be reused as-is. - UseLibC = Application.platform - is RuntimePlatform.LinuxPlayer or RuntimePlatform.LinuxServer - or RuntimePlatform.PS5; + // `nativeLogImpl` hands the `va_list` to `vsnprintf` twice. On x86-64 System V it is + // `__va_list_tag[1]` and on Linux AArch64 a 32-byte struct passed by reference, so `vsnprintf` + // may consume the caller's list and each call needs a fresh copy. On Windows and Apple + // silicon it is a plain pointer, and Switch copies it through `vsnprintf_sentry`. + UseLibC = Application.platform is RuntimePlatform.PS5; if ((Application.platform - is RuntimePlatform.OSXPlayer or RuntimePlatform.OSXServer) + is RuntimePlatform.LinuxPlayer or RuntimePlatform.LinuxServer + or RuntimePlatform.OSXPlayer or RuntimePlatform.OSXServer) && RuntimeInformation.ProcessArchitecture == Architecture.X64) { UseLibC = true; } + IsLinuxArm64 = (Application.platform is RuntimePlatform.LinuxPlayer or RuntimePlatform.LinuxServer) + && RuntimeInformation.ProcessArchitecture == Architecture.Arm64; + IsWindows = Application.platform is RuntimePlatform.WindowsPlayer or RuntimePlatform.WindowsServer or RuntimePlatform.GameCoreXboxSeries or RuntimePlatform.GameCoreXboxOne; @@ -279,20 +281,11 @@ private static void nativeLogImpl(int cLevel, IntPtr format, IntPtr args, IntPtr // For Linux and PlayStation, we must make a copy of the VaList to be able to pass it back... if (UseLibC) { - var argsStruct = Marshal.PtrToStructure(args); - var formattedLength = 0; - - WithMarshalledStruct(argsStruct, argsPtr => - { - formattedLength = 1 + vsnprintf(IntPtr.Zero, UIntPtr.Zero, format, argsPtr); - }); - - WithAllocatedPtr(formattedLength, buffer => - WithMarshalledStruct(argsStruct, argsPtr => - { - vsnprintf(buffer, (UIntPtr)formattedLength, format, argsPtr); - message = Marshal.PtrToStringAnsi(buffer); - })); + message = FormatWithCopiedVaList(format, args); + } + else if (IsLinuxArm64) + { + message = FormatWithCopiedVaList(format, args); } else { @@ -354,6 +347,38 @@ private struct VaListLinux64 IntPtr reg_save_area; } + // AAPCS64 `va_list` + [StructLayout(LayoutKind.Sequential)] + private struct VaListAarch64 + { + IntPtr stack; + IntPtr gr_top; + IntPtr vr_top; + int gr_offs; + int vr_offs; + } + + private static string? FormatWithCopiedVaList(IntPtr format, IntPtr args) where T : struct + { + var argsStruct = Marshal.PtrToStructure(args); + var formattedLength = 0; + string? message = null; + + WithMarshalledStruct(argsStruct, argsPtr => + { + formattedLength = 1 + vsnprintf(IntPtr.Zero, UIntPtr.Zero, format, argsPtr); + }); + + WithAllocatedPtr(formattedLength, buffer => + WithMarshalledStruct(argsStruct, argsPtr => + { + vsnprintf(buffer, (UIntPtr)formattedLength, format, argsPtr); + message = Marshal.PtrToStringAnsi(buffer); + })); + + return message; + } + private static void WithAllocatedPtr(int size, Action action) { var ptr = IntPtr.Zero; diff --git a/test/Scripts.Integration.Test/Editor/Builder.cs b/test/Scripts.Integration.Test/Editor/Builder.cs index 410709fbc..f4d960614 100644 --- a/test/Scripts.Integration.Test/Editor/Builder.cs +++ b/test/Scripts.Integration.Test/Editor/Builder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Reflection; using UnityEditor; using UnityEditor.Build; using UnityEditor.Build.Reporting; @@ -9,7 +10,8 @@ public class Builder { public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group, BuildOptions buildOptions, - string defaultBuildPath = "./Builds/", ScriptingImplementation scripting = ScriptingImplementation.IL2CPP) + string defaultBuildPath = "./Builds/", ScriptingImplementation scripting = ScriptingImplementation.IL2CPP, + StandaloneBuildSubtarget subtarget = StandaloneBuildSubtarget.Player) { Debug.Log("Builder: Starting to build"); @@ -21,9 +23,12 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group, // Make sure the configuration is right. EditorUserBuildSettings.selectedBuildTargetGroup = group; + var namedTarget = subtarget == StandaloneBuildSubtarget.Server + ? NamedBuildTarget.Server + : NamedBuildTarget.FromBuildTargetGroup(group); EditorUserBuildSettings.development = false; EditorUserBuildSettings.allowDebugging = false; - PlayerSettings.SetScriptingBackend(NamedBuildTarget.FromBuildTargetGroup(group), scripting); + PlayerSettings.SetScriptingBackend(namedTarget, scripting); // Making sure that the app keeps on running in the background. Linux CI is very unhappy with coroutines otherwise. PlayerSettings.runInBackground = true; @@ -34,7 +39,7 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group, { Debug.Log("Builder: Setting IL2CPP generation to OptimizeSpeed"); #if UNITY_2022_1_OR_NEWER - PlayerSettings.SetIl2CppCodeGeneration(NamedBuildTarget.FromBuildTargetGroup(group), UnityEditor.Build.Il2CppCodeGeneration.OptimizeSpeed); + PlayerSettings.SetIl2CppCodeGeneration(namedTarget, UnityEditor.Build.Il2CppCodeGeneration.OptimizeSpeed); #elif UNITY_2021_2_OR_NEWER EditorUserBuildSettings.il2CppCodeGeneration = UnityEditor.Build.Il2CppCodeGeneration.OptimizeSpeed; #endif @@ -42,9 +47,9 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group, Debug.Log("Builder: Configuring code stripping level"); #if UNITY_6000_0_OR_NEWER - PlayerSettings.SetManagedStrippingLevel(NamedBuildTarget.FromBuildTargetGroup(group), ManagedStrippingLevel.High); + PlayerSettings.SetManagedStrippingLevel(namedTarget, ManagedStrippingLevel.High); #else - PlayerSettings.SetManagedStrippingLevel(NamedBuildTarget.FromBuildTargetGroup(group), ManagedStrippingLevel.Low); + PlayerSettings.SetManagedStrippingLevel(namedTarget, ManagedStrippingLevel.Low); #endif Debug.Log("Builder: Updating BuildPlayerOptions"); @@ -56,11 +61,16 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group, options = buildOptions }; + if (subtarget == StandaloneBuildSubtarget.Server) + { + buildPlayerOptions.subtarget = (int)subtarget; + } + Debug.Log("Builder: Disabling optimizations to reduce build time"); // TODO Linux fails with `free(): invalid pointer` in the test, after everything seems to have shut down. if (scripting == ScriptingImplementation.IL2CPP && target != BuildTarget.StandaloneLinux64) { - PlayerSettings.SetIl2CppCompilerConfiguration(NamedBuildTarget.FromBuildTargetGroup(group), Il2CppCompilerConfiguration.Debug); + PlayerSettings.SetIl2CppCompilerConfiguration(namedTarget, Il2CppCompilerConfiguration.Debug); } if (target == BuildTarget.Android) @@ -171,6 +181,30 @@ public static void BuildLinuxIl2CPPPlayer() defaultBuildPath: "./Builds/Linux/test"); } +#if UNITY_6000_3_OR_NEWER + // Unity only ships an arm64 Linux player as a dedicated server (6000.3+, IL2CPP). The architecture + // setting lives in the Linux module's extension assembly, which only exists with that module installed. + [MenuItem("Tools/Builder/Linux Server arm64")] + public static void BuildLinuxServerArm64Il2CPPPlayer() + { + Debug.Log("Builder: Building Linux Server arm64 IL2CPP Player"); + // `UserBuildSettings.architecture` writes to the settings of the subtarget selected in the build window. + typeof(EditorUserBuildSettings).GetProperty("selectedStandaloneBuildSubtarget", BindingFlags.Static | BindingFlags.NonPublic) + ?.SetValue(null, StandaloneBuildSubtarget.Server); + + var architecture = Type.GetType("UnityEditor.LinuxStandalone.UserBuildSettings, UnityEditor.LinuxStandalone.Extensions") + ?.GetProperty("architecture") ?? throw new Exception("Linux build support is not installed."); + architecture.SetValue(null, OSArchitecture.ARM64); + if (!Equals(architecture.GetValue(null), OSArchitecture.ARM64)) + { + throw new Exception($"Failed to set the Linux architecture to ARM64, got '{architecture.GetValue(null)}'."); + } + + BuildIl2CPPPlayer(BuildTarget.StandaloneLinux64, BuildTargetGroup.Standalone, BuildOptions.StrictMode, + defaultBuildPath: "./Builds/Linux/test", subtarget: StandaloneBuildSubtarget.Server); + } +#endif + [MenuItem("Tools/Builder/Android")] public static void BuildAndroidIl2CPPPlayer() { diff --git a/test/Scripts.Integration.Test/globals.ps1 b/test/Scripts.Integration.Test/globals.ps1 index 275438f5d..d75340b4e 100644 --- a/test/Scripts.Integration.Test/globals.ps1 +++ b/test/Scripts.Integration.Test/globals.ps1 @@ -151,6 +151,7 @@ function BuildMethodFor([string] $platform) "Windows-x86" { return "Builder.BuildWindowsX86Il2CPPPlayer" } "Windows-Mono" { return "Builder.BuildWindowsMonoPlayer" } "Linux" { return "Builder.BuildLinuxIl2CPPPlayer" } + "Linux-Server-arm64" { return "Builder.BuildLinuxServerArm64Il2CPPPlayer" } "WebGL" { return "Builder.BuildWebGLPlayer" } "iOS" { return "Builder.BuildIOSProject" } "Switch" { return "Builder.BuildSwitchIL2CPPPlayer" } diff --git a/test/Scripts.Tests/package-release.zip.snapshot b/test/Scripts.Tests/package-release.zip.snapshot index 5d884886c..c07306eea 100644 --- a/test/Scripts.Tests/package-release.zip.snapshot +++ b/test/Scripts.Tests/package-release.zip.snapshot @@ -267,12 +267,20 @@ Plugins/iOS/SentryObjC.xcframework~/ios-arm64/dSYMs/SentryObjC.framework.dSYM/Co Plugins/iOS/SentryObjC.xcframework~/ios-arm64/dSYMs/SentryObjC.framework.dSYM/Contents/Resources/Relocations/ Plugins/iOS/SentryObjC.xcframework~/ios-arm64/dSYMs/SentryObjC.framework.dSYM/Contents/Resources/Relocations/aarch64/SentryObjC.yml Plugins/iOS/SentryObjC.xcframework~/ios-arm64/dSYMs/SentryObjC.framework.dSYM/Contents/Resources/DWARF/SentryObjC -Plugins/Linux/SentryNative~/libsentry-native.so -Plugins/Linux/SentryNative~/libsentry.dbg.so -Plugins/Linux/SentryNative~/sentry-crash -Plugins/Linux/SentryNative~/sentry-crash.dbg -Plugins/Linux/Sentry~/libsentry-native.so -Plugins/Linux/Sentry~/libsentry.dbg.so +Plugins/Linux/Sentry~/ +Plugins/Linux/SentryNative~/ +Plugins/Linux/SentryNative~/ARM64/libsentry-native.so +Plugins/Linux/SentryNative~/ARM64/libsentry.dbg.so +Plugins/Linux/SentryNative~/ARM64/sentry-crash +Plugins/Linux/SentryNative~/ARM64/sentry-crash.dbg +Plugins/Linux/SentryNative~/x86_64/libsentry-native.so +Plugins/Linux/SentryNative~/x86_64/libsentry.dbg.so +Plugins/Linux/SentryNative~/x86_64/sentry-crash +Plugins/Linux/SentryNative~/x86_64/sentry-crash.dbg +Plugins/Linux/Sentry~/ARM64/libsentry-native.so +Plugins/Linux/Sentry~/ARM64/libsentry.dbg.so +Plugins/Linux/Sentry~/x86_64/libsentry-native.so +Plugins/Linux/Sentry~/x86_64/libsentry.dbg.so Plugins/Android/proguard-sentry-unity.pro Plugins/Android/proguard-sentry-unity.pro.meta Plugins/Android/Sentry~/epitaph.jar diff --git a/test/Sentry.Unity.Editor.Tests/Native/BuildPostProcessTests.cs b/test/Sentry.Unity.Editor.Tests/Native/BuildPostProcessTests.cs index c8817cfbc..1097a4037 100644 --- a/test/Sentry.Unity.Editor.Tests/Native/BuildPostProcessTests.cs +++ b/test/Sentry.Unity.Editor.Tests/Native/BuildPostProcessTests.cs @@ -97,4 +97,20 @@ public void GetWindowsBackendDir_ResolvesBackendAndArchitecture( Assert.AreEqual(expectedArchDir, Path.GetFileName(actual)); Assert.AreEqual(expectedBackendDir, Path.GetFileName(Path.GetDirectoryName(actual))); } + + [TestCase(LinuxBackend.Breakpad, "Sentry~", "x86_64")] + [TestCase(LinuxBackend.Breakpad, "Sentry~", "ARM64")] + [TestCase(LinuxBackend.Native, "SentryNative~", "x86_64")] + [TestCase(LinuxBackend.Native, "SentryNative~", "ARM64")] + public void GetLinuxBackendDir_ResolvesBackendAndArchitecture( + LinuxBackend backend, string expectedBackendDir, string arch) + { + var options = new SentryUnityOptions(); + options.Experimental.LinuxBackend = backend; + + var actual = BuildPostProcess.GetLinuxBackendDir(options, arch); + + Assert.AreEqual(arch, Path.GetFileName(actual)); + Assert.AreEqual(expectedBackendDir, Path.GetFileName(Path.GetDirectoryName(actual))); + } }