Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6e98be7
added xbox
bitsandfoxes Mar 20, 2026
5f8bd87
development build for xbox
bitsandfoxes Mar 23, 2026
2fa8fc1
support loose builds
bitsandfoxes Mar 23, 2026
1b923ab
fileloghandler
bitsandfoxes Mar 23, 2026
119d980
no development builds
bitsandfoxes Mar 23, 2026
9721689
Merge branch 'main' into feat/xbox-app-runner
bitsandfoxes Mar 24, 2026
c67cfc2
file logging and master mode
bitsandfoxes Mar 26, 2026
0206268
logfile path
bitsandfoxes Mar 26, 2026
3ae6fff
logs have a new home
bitsandfoxes Mar 27, 2026
7190270
dedicated logger
bitsandfoxes Mar 27, 2026
e1c2ff2
no special config for xbox
bitsandfoxes Mar 27, 2026
4f0104a
diagnostics
bitsandfoxes Mar 27, 2026
1d73ec0
more diagnostics
bitsandfoxes Mar 27, 2026
a75bf9d
try and see
bitsandfoxes Mar 27, 2026
244396e
diagnostics
bitsandfoxes Mar 27, 2026
5b454ba
found the path
bitsandfoxes Mar 30, 2026
a447521
the builder does this now
bitsandfoxes Mar 30, 2026
4ec0d43
loose build
bitsandfoxes Mar 30, 2026
e43aa10
diagnostics
bitsandfoxes Mar 30, 2026
6c927fb
build package
bitsandfoxes Mar 31, 2026
11735f2
simplified running
bitsandfoxes Mar 31, 2026
aa6cdf9
added ps5 and switch build configuration methods
bitsandfoxes Mar 31, 2026
e3359ae
logger
bitsandfoxes Mar 31, 2026
1f7099f
editor
bitsandfoxes Mar 31, 2026
068fc33
iteration
bitsandfoxes Mar 31, 2026
20cc116
fixed platform check and ci logging
bitsandfoxes Mar 31, 2026
2b02ba4
logging
bitsandfoxes Mar 31, 2026
fd5d0f4
use the right var
bitsandfoxes Mar 31, 2026
4722be9
Revert app-runner submodule to main
bitsandfoxes Mar 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/Sentry.Unity.Native/SentryNativeBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ public static bool Init(SentryUnityOptions options)
UseLibC = Application.platform
is RuntimePlatform.LinuxPlayer or RuntimePlatform.LinuxServer
or RuntimePlatform.PS5 or RuntimePlatform.Switch;
IsWindows = Application.platform is RuntimePlatform.WindowsPlayer or RuntimePlatform.WindowsServer;
IsWindows = Application.platform
is RuntimePlatform.WindowsPlayer or RuntimePlatform.WindowsServer
or RuntimePlatform.GameCoreXboxSeries or RuntimePlatform.GameCoreXboxOne;

var cOptions = sentry_options_new();

Expand Down
79 changes: 71 additions & 8 deletions test/IntegrationTest/Integration.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
# Integration tests for Sentry Unity SDK
#
# Environment variables:
# SENTRY_TEST_PLATFORM: target platform (Android, Desktop, iOS, WebGL)
# SENTRY_TEST_DSN: test DSN
# SENTRY_TEST_PLATFORM: target platform (Android, Desktop, iOS, WebGL, Xbox)
# SENTRY_DSN: test DSN
# SENTRY_AUTH_TOKEN: authentication token for Sentry API
#
# SENTRY_TEST_APP: path to the test app (APK, executable, .app bundle, or WebGL build directory)
# SENTRY_TEST_APP: path to the test app (APK, executable, .app bundle, WebGL build directory,
# or Xbox packaged build directory containing a .xvc)
#
# Platform-specific environment variables:
# iOS: SENTRY_IOS_VERSION - iOS simulator version (e.g. "17.0" or "latest")
# Xbox: XBCONNECT_TARGET - Xbox devkit IP address

Set-StrictMode -Version latest
$ErrorActionPreference = "Stop"
Expand All @@ -30,9 +32,45 @@ BeforeAll {
"Android" { return @("-e", "test", $Action) }
"Desktop" { return @("--test", $Action, "-logFile", "-") }
"iOS" { return @("--test", $Action) }
"Xbox" { return @("--test", $Action) }
}
}

# Retrieve the integration test log file from Xbox and attach it to the run result.
# The Unity app writes to D:\Logs\UnityIntegrationTest.log on Xbox (UNITY_GAMECORE).
function Get-XboxLogOutput {
param(
[Parameter(Mandatory=$true)]
$RunResult,

[Parameter(Mandatory=$true)]
[string]$Action
)

$logFileName = "UnityIntegrationTest.log"
$logLocalDir = "$PSScriptRoot/results/xbox-logs/$Action"
New-Item -ItemType Directory -Path $logLocalDir -Force | Out-Null

try {
Copy-DeviceItem -DevicePath "D:\Logs\$logFileName" -Destination $logLocalDir
} catch {
throw "Failed to retrieve Xbox log file (D:\Logs\$logFileName): $_"
}

$localFile = Join-Path $logLocalDir $logFileName
$logContent = Get-Content $localFile -ErrorAction SilentlyContinue
if (-not $logContent -or $logContent.Count -eq 0) {
$localFiles = Get-ChildItem -Path $logLocalDir -ErrorAction SilentlyContinue
$localContents = if ($localFiles) { ($localFiles | ForEach-Object { $_.Name }) -join ", " } else { "(empty — D:\Logs\ likely did not exist on device)" }
throw "Xbox log file was empty or missing (D:\Logs\$logFileName). Copied directory contains: $localContents"
}

Write-Host "Retrieved log file from Xbox ($($logContent.Count) lines)" -ForegroundColor Green

$RunResult.Output = $logContent
return $RunResult
}

# Run a WebGL test action via headless Chrome
function Invoke-WebGLTestAction {
param (
Expand Down Expand Up @@ -110,6 +148,12 @@ BeforeAll {
$appArgs = Get-AppArguments -Action $Action
$runResult = Invoke-DeviceApp -ExecutablePath $script:ExecutablePath -Arguments $appArgs

# On Xbox, console output is not available in non-development builds.
# Retrieve the log file the app writes directly to disk.
if ($script:Platform -eq "Xbox") {
$runResult = Get-XboxLogOutput -RunResult $runResult -Action $Action
}

# Save result to JSON file
$runResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "${Action}-result.json")

Expand All @@ -120,6 +164,10 @@ BeforeAll {
$sendArgs = Get-AppArguments -Action "crash-send"
$sendResult = Invoke-DeviceApp -ExecutablePath $script:ExecutablePath -Arguments $sendArgs

if ($script:Platform -eq "Xbox") {
$sendResult = Get-XboxLogOutput -RunResult $sendResult -Action "crash-send"
}

# Save crash-send result to JSON for debugging
$sendResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "crash-send-result.json")

Expand All @@ -142,12 +190,12 @@ BeforeAll {

$script:Platform = $env:SENTRY_TEST_PLATFORM
if ([string]::IsNullOrEmpty($script:Platform)) {
throw "SENTRY_TEST_PLATFORM environment variable is not set. Expected: Android, Desktop, iOS, or WebGL"
throw "SENTRY_TEST_PLATFORM environment variable is not set. Expected: Android, Desktop, iOS, WebGL, or Xbox"
}

# Validate common environment
if ([string]::IsNullOrEmpty($env:SENTRY_TEST_DSN)) {
throw "SENTRY_TEST_DSN environment variable is not set."
if ([string]::IsNullOrEmpty($env:SENTRY_DSN)) {
throw "SENTRY_DSN environment variable is not set."
}
if ([string]::IsNullOrEmpty($env:SENTRY_AUTH_TOKEN)) {
throw "SENTRY_AUTH_TOKEN environment variable is not set."
Expand Down Expand Up @@ -195,10 +243,25 @@ BeforeAll {
Connect-Device -Platform "iOSSimulator" -Target $target
Install-DeviceApp -Path $env:SENTRY_TEST_APP
}
"Xbox" {
if ([string]::IsNullOrEmpty($env:XBCONNECT_TARGET)) {
throw "XBCONNECT_TARGET environment variable is not set."
}

Connect-Device -Platform "Xbox" -Target $env:XBCONNECT_TARGET

$xvcFile = Get-ChildItem -Path $env:SENTRY_TEST_APP -Filter "*.xvc" | Select-Object -First 1
if (-not $xvcFile) {
throw "No .xvc found in SENTRY_TEST_APP: $env:SENTRY_TEST_APP"
}
Install-DeviceApp -Path $xvcFile.FullName
$script:ExecutablePath = Get-PackageAumid -PackagePath $env:SENTRY_TEST_APP
Write-Host "Using AUMID: $($script:ExecutablePath)"
}
"WebGL" {
}
default {
throw "Unknown platform: $($script:Platform). Expected: Android, Desktop, iOS, or WebGL"
throw "Unknown platform: $($script:Platform). Expected: Android, Desktop, iOS, WebGL, or Xbox"
}
}

Expand All @@ -209,7 +272,7 @@ BeforeAll {
# Initialize test parameters
$script:TestSetup = [PSCustomObject]@{
Platform = $script:Platform
Dsn = $env:SENTRY_TEST_DSN
Dsn = $env:SENTRY_DSN
AuthToken = $env:SENTRY_AUTH_TOKEN
}

Expand Down
130 changes: 129 additions & 1 deletion test/Scripts.Integration.Test/Editor/Builder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group,

// Make sure the configuration is right.
EditorUserBuildSettings.selectedBuildTargetGroup = group;
EditorUserBuildSettings.development = false;
EditorUserBuildSettings.allowDebugging = false;
PlayerSettings.SetScriptingBackend(NamedBuildTarget.FromBuildTargetGroup(group), ScriptingImplementation.IL2CPP);
// Making sure that the app keeps on running in the background. Linux CI is very unhappy with coroutines otherwise.
Expand Down Expand Up @@ -113,16 +114,21 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group,
}
}

[MenuItem("Tools/Builder/Windows")]
public static void BuildWindowsIl2CPPPlayer()
{
Debug.Log("Builder: Building Windows IL2CPP Player");
BuildIl2CPPPlayer(BuildTarget.StandaloneWindows64, BuildTargetGroup.Standalone, BuildOptions.StrictMode);
}

[MenuItem("Tools/Builder/macOS")]
public static void BuildMacIl2CPPPlayer()
{
Debug.Log("Builder: Building macOS IL2CPP Player");
BuildIl2CPPPlayer(BuildTarget.StandaloneOSX, BuildTargetGroup.Standalone, BuildOptions.StrictMode);
}

[MenuItem("Tools/Builder/Linux")]
public static void BuildLinuxIl2CPPPlayer()
{
Debug.Log("Builder: Building Linux IL2CPP Player");
Expand All @@ -132,6 +138,8 @@ public static void BuildLinuxIl2CPPPlayer()
PlayerSettings.graphicsJobs = false;
BuildIl2CPPPlayer(BuildTarget.StandaloneLinux64, BuildTargetGroup.Standalone, BuildOptions.StrictMode);
}

[MenuItem("Tools/Builder/Android")]
public static void BuildAndroidIl2CPPPlayer()
{
Debug.Log("Builder: Building Android IL2CPP Player");
Expand All @@ -156,54 +164,174 @@ public static void BuildAndroidIl2CPPPlayer()

BuildIl2CPPPlayer(BuildTarget.Android, BuildTargetGroup.Android, BuildOptions.StrictMode);
}
[MenuItem("Tools/Builder/Android Project")]
public static void BuildAndroidIl2CPPProject()
{
Debug.Log("Builder: Building Android IL2CPP Project");
EditorUserBuildSettings.exportAsGoogleAndroidProject = true;
BuildIl2CPPPlayer(BuildTarget.Android, BuildTargetGroup.Android, BuildOptions.AcceptExternalModificationsToPlayer);
}

[MenuItem("Tools/Builder/iOS")]
public static void BuildIOSProject()
{
Debug.Log("Builder: Building iOS Project");
BuildIl2CPPPlayer(BuildTarget.iOS, BuildTargetGroup.iOS, BuildOptions.StrictMode);
}

[MenuItem("Tools/Builder/WebGL")]
public static void BuildWebGLPlayer()
{
Debug.Log("Builder: Building WebGL Player");
PlayerSettings.WebGL.compressionFormat = WebGLCompressionFormat.Brotli;
BuildIl2CPPPlayer(BuildTarget.WebGL, BuildTargetGroup.WebGL, BuildOptions.StrictMode);
}

[MenuItem("Tools/Builder/Switch")]
public static void BuildSwitchIL2CPPPlayer()
{
Debug.Log("Builder: Building Switch IL2CPP Player");
SetSwitchCreateNspRomFile();
BuildIl2CPPPlayer(BuildTarget.Switch, BuildTargetGroup.Switch, BuildOptions.StrictMode);
}

[MenuItem("Tools/Builder/Xbox Series X|S")]
public static void BuildXSXIL2CPPPlayer()
{
Debug.Log("Builder: Building Xbox Series X|S IL2CPP Player");
SetXboxSubtargetToMaster();
BuildIl2CPPPlayer(BuildTarget.GameCoreXboxSeries, BuildTargetGroup.GameCoreXboxSeries, BuildOptions.StrictMode);
}

[MenuItem("Tools/Builder/Xbox One")]
public static void BuildXB1IL2CPPPlayer()
{
Debug.Log("Builder: Building Xbox One IL2CPP Player");
SetXboxSubtargetToMaster();
BuildIl2CPPPlayer(BuildTarget.GameCoreXboxOne, BuildTargetGroup.GameCoreXboxOne, BuildOptions.StrictMode);
}

[MenuItem("Tools/Builder/PS5")]
public static void BuildPS5IL2CPPPlayer()
{
Debug.Log("Builder: Building PS5 IL2CPP Player");
SetPS5BuildTypeToPackage();
BuildIl2CPPPlayer(BuildTarget.PS5, BuildTargetGroup.PS5, BuildOptions.StrictMode);
}

private static void SetXboxSubtargetToMaster()
{
// The actual editor API to set this has been deprecated: https://docs.unity3d.com/6000.3/Documentation/ScriptReference/XboxBuildSubtarget.html
// Modifying the build profiles and build setting assets on disk does not work. Some of the properties are
// stored inside a binary. Instead we're setting the properties via reflection and then saving the asset.
var buildProfileType = Type.GetType("UnityEditor.Build.Profile.BuildProfile, UnityEditor.CoreModule");
if (buildProfileType == null)
{
return;
}

foreach (var profile in Resources.FindObjectsOfTypeAll(buildProfileType))
{
// BuildTarget.GameCoreXboxSeries = 42, BuildTarget.GameCoreXboxOne = 43.
var buildTarget = new SerializedObject(profile).FindProperty("m_BuildTarget")?.intValue ?? -1;
if (buildTarget != 42 && buildTarget != 43)
continue;

var platformSettings = buildProfileType
.GetProperty("platformBuildProfile", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
?.GetValue(profile);
var settingsData = platformSettings?.GetType()
.GetField("m_settingsData", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
?.GetValue(platformSettings);

GetFieldInHierarchy(settingsData?.GetType(), "buildSubtarget")?.SetValue(settingsData, 1); // 1 = Master
GetFieldInHierarchy(platformSettings?.GetType(), "m_Development")?.SetValue(platformSettings, false);
GetFieldInHierarchy(settingsData?.GetType(), "deploymentMethod")?.SetValue(settingsData, 2); // 2 = Package

EditorUtility.SetDirty(profile);
Debug.Log($"Builder: Xbox Build Profile (BuildTarget {buildTarget}) set to Master, deploy method set to Package");
}

AssetDatabase.SaveAssets();
}

private static void SetPS5BuildTypeToPackage()
{
var buildProfileType = Type.GetType("UnityEditor.Build.Profile.BuildProfile, UnityEditor.CoreModule");
if (buildProfileType == null)
{
return;
}

foreach (var profile in Resources.FindObjectsOfTypeAll(buildProfileType))
{
// BuildTarget.PS5 = 44.
var buildTarget = new SerializedObject(profile).FindProperty("m_BuildTarget")?.intValue ?? -1;
if (buildTarget != 44)
continue;

var platformSettings = buildProfileType
.GetProperty("platformBuildProfile", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
?.GetValue(profile);

GetFieldInHierarchy(platformSettings?.GetType(), "m_Development")?.SetValue(platformSettings, false);
GetFieldInHierarchy(platformSettings?.GetType(), "m_BuildSubtarget")?.SetValue(platformSettings, 1); // 1 = Package

EditorUtility.SetDirty(profile);
Debug.Log("Builder: PS5 Build Profile set to Package");
}

AssetDatabase.SaveAssets();
}

private static void SetSwitchCreateNspRomFile()
{
var buildProfileType = Type.GetType("UnityEditor.Build.Profile.BuildProfile, UnityEditor.CoreModule");
if (buildProfileType == null)
{
return;
}

foreach (var profile in Resources.FindObjectsOfTypeAll(buildProfileType))
{
// BuildTarget.Switch = 38.
var buildTarget = new SerializedObject(profile).FindProperty("m_BuildTarget")?.intValue ?? -1;
if (buildTarget != 38)
continue;

var platformSettings = buildProfileType
.GetProperty("platformBuildProfile", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
?.GetValue(profile);

GetFieldInHierarchy(platformSettings?.GetType(), "m_Development")?.SetValue(platformSettings, false);
GetFieldInHierarchy(platformSettings?.GetType(), "m_SwitchCreateRomFile")?.SetValue(platformSettings, 1); // 1 = enabled

EditorUtility.SetDirty(profile);
Debug.Log("Builder: Switch Build Profile set to Create NSP ROM File");
}

AssetDatabase.SaveAssets();
}

private static FieldInfo GetFieldInHierarchy(Type type, string fieldName)
{
while (type != null)
{
var field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
if (field != null)
return field;
type = type.BaseType;
}
return null;
}

private static void ValidateArguments(Dictionary<string, string> args)
{
Debug.Log("Builder: Validating command line arguments");
if (!args.ContainsKey("buildPath") || string.IsNullOrWhiteSpace(args["buildPath"]))
{
throw new Exception("No valid '-buildPath' has been provided.");
args["buildPath"] = "./Builds/";
Debug.Log("Builder: No '-buildPath' provided, defaulting to './Builds/'");
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using Sentry;
using Sentry.Extensibility;
using Sentry.Unity;
using UnityEngine;

Expand All @@ -11,6 +14,7 @@ public override void Configure(SentryUnityOptions options)

// DSN is baked into SentryOptions.asset at build time by configure-sentry.ps1
// which passes the SENTRY_DSN env var to ConfigureOptions via the -dsn argument.
// At test-run time, Integration.Tests.ps1 also reads SENTRY_DSN to verify events.

options.Environment = "integration-test";
options.Release = "sentry-unity-test@1.0.0";
Expand All @@ -19,6 +23,7 @@ public override void Configure(SentryUnityOptions options)
options.AttachScreenshot = true;
options.Debug = true;
options.DiagnosticLevel = SentryLevel.Debug;
options.DiagnosticLogger = Logger.Instance;
options.TracesSampleRate = 1.0d;

// No custom HTTP handler -- events go to real sentry.io
Expand Down
Loading
Loading