Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dotnet build
## Native SDKs

```sh
dotnet msbuild /t:DownloadNativeSDKs src/Sentry.Unity
pwsh scripts/download-native-sdks.ps1
```

- Requires `gh`. Use when native artifacts are missing; `dotnet build` does not download them.
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- Added experimental auto game-metrics. When enabled, the SDK periodically collects common performance metrics and sends them to Sentry via the metrics API. ([#2777](https://github.com/getsentry/sentry-unity/pull/2777))

### Dependencies

- Bump Cocoa SDK from v9.23.0 to v9.24.0 ([#2792](https://github.com/getsentry/sentry-unity/pull/2792))
Expand Down
149 changes: 140 additions & 9 deletions bootstrap.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,128 @@ function Test-Command {
return [bool](Get-Command $Name -ErrorAction SilentlyContinue)
}

function Test-DirectoryWritable {
param([Parameter(Mandatory)][string] $Path)

$probePath = Join-Path $Path ".sentry-write-test-$([Guid]::NewGuid())"
try {
New-Item -ItemType Directory -Path $probePath | Out-Null
return $true
}
catch {
return $false
}
finally {
Remove-Item -Path $probePath -Force -ErrorAction SilentlyContinue
}
}

function Test-WorkloadRestoreNeeded {
param([Parameter(Mandatory)][string] $DotnetPath)

$requiredWorkloads = @("android")
if ($IsMacOS) {
$requiredWorkloads += "ios", "maccatalyst"
}

$expectedVersion = (Get-Content (Join-Path $repoRoot "global.json") -Raw | ConvertFrom-Json).sdk.workloadVersion
$previousEAP = $PSNativeCommandUseErrorActionPreference
$PSNativeCommandUseErrorActionPreference = $false
try {
$versionOutput = @(& $DotnetPath workload --version 2>&1)
$versionExitCode = $LASTEXITCODE
$workloadList = @(& $DotnetPath workload list 2>&1)
$listExitCode = $LASTEXITCODE
}
finally {
$PSNativeCommandUseErrorActionPreference = $previousEAP
}

if ($versionExitCode -ne 0 -or $listExitCode -ne 0) {
return $true
}

$versionLines = @($versionOutput | ForEach-Object { "$($_)".Trim() } | Where-Object { $_ })
if ($versionLines.Count -eq 0 -or $versionLines[-1] -ne $expectedVersion) {
return $true
}

$workloadOutput = $workloadList -join [Environment]::NewLine
foreach ($workload in $requiredWorkloads) {
if ($workloadOutput -notmatch "(?m)^$([regex]::Escape($workload))\s+") {
return $true
}
}

return $false
}

function Restore-DotnetWorkloads {
$dotnetPath = (Get-Command dotnet -ErrorAction Stop).Source
$dotnetRoot = Split-Path -Parent $dotnetPath

if (-not (Test-WorkloadRestoreNeeded -DotnetPath $dotnetPath)) {
Write-Host "Required .NET workloads already installed." -ForegroundColor Green
return
}

$requiresElevation = -not $IsWindows -and -not (Test-DirectoryWritable -Path $dotnetRoot)

if ($requiresElevation) {
if (-not (Test-Command "sudo")) {
throw "The .NET installation at '$dotnetRoot' requires elevated permissions, but sudo is unavailable."
}

Write-Host "Restoring .NET workloads with elevated privileges..." -ForegroundColor Cyan
& sudo -v
}
else {
Write-Host "Restoring .NET workloads..." -ForegroundColor Cyan
}

# Keep routine restore output out of bootstrap; retain its final error when it fails.
$previousEAP = $PSNativeCommandUseErrorActionPreference
$PSNativeCommandUseErrorActionPreference = $false
$restoreOutput = @()
$restoreExitCode = 0
try {
if ($requiresElevation) {
$restoreOutput = @(& sudo $dotnetPath workload restore 2>&1)
}
else {
$restoreOutput = @(& $dotnetPath workload restore 2>&1)
}
$restoreExitCode = $LASTEXITCODE
}
finally {
$PSNativeCommandUseErrorActionPreference = $previousEAP
}

if ($restoreExitCode -ne 0) {
$failureLines = @($restoreOutput | ForEach-Object { "$($_)".Trim() } | Where-Object { $_ })
$failureDetail = "dotnet workload restore exited with code $restoreExitCode."
if ($failureLines.Count -gt 0) {
$failureDetail = $failureLines[-1]
}

Write-Host "Workload restore failed: $failureDetail" -ForegroundColor Red
throw $failureDetail
}

Write-Host "Workloads restored." -ForegroundColor Green
}

function Get-StatusColor {
param([Parameter(Mandatory)][string] $Status)

switch ($Status) {
"PASS" { return "Green" }
"WARN" { return "Yellow" }
"FAIL" { return "Red" }
"SKIP" { return "DarkGray" }
}
}

Write-Host "Bootstrapping Sentry Unity from '$repoRoot'"

if (-not (Test-Path (Join-Path $repoRoot ".git"))) {
Expand All @@ -71,12 +193,14 @@ else {
$hasDotnet = Test-Command "dotnet"
if ($hasDotnet) {
Add-Result -Status "PASS" -Name "dotnet" -Detail "Found dotnet."
Invoke-Stage -Name "Workloads" -Action { dotnet workload restore } -FailureStatus "WARN" -Fix "Run: dotnet workload restore. On macOS or Linux, elevate it if dotnet reports inadequate permissions."
Invoke-Stage -Name "Workloads" -Action { Restore-DotnetWorkloads } -FailureStatus "WARN" -Fix "Check the workload restore output, then rerun: pwsh bootstrap.ps1"
}
else {
Add-Result -Status "FAIL" -Name "dotnet" -Detail "dotnet is not available on PATH." -Fix "Install the SDK pinned in global.json, then rerun: pwsh bootstrap.ps1"
}

Write-Host ""

$hasGh = Test-Command "gh"
if ($hasGh) {
Add-Result -Status "PASS" -Name "GitHub CLI" -Detail "Found gh."
Expand All @@ -93,30 +217,35 @@ else {
Add-Result -Status "WARN" -Name "Unity CLI" -Detail "unity is not available on PATH." -Fix "Install Unity CLI and add it to PATH before running: pwsh scripts/run-tests.ps1"
}

if ($hasDotnet -and $hasGh) {
Invoke-Stage -Name "Prebuilt native SDKs" -Action { dotnet msbuild /t:DownloadNativeSDKs src/Sentry.Unity } -FailureStatus "WARN" -Fix "Authenticate with 'gh auth login' and rerun, or build a platform SDK with: dotnet msbuild /t:Build<Platform>SDK src/Sentry.Unity"
}
elseif (-not $hasGh) {
Add-Result -Status "WARN" -Name "Prebuilt native SDKs" -Detail "GitHub CLI is not available." -Fix "Install gh and run 'gh auth login', then rerun: pwsh bootstrap.ps1"
if ($hasGh) {
Invoke-Stage -Name "Prebuilt native SDKs" -Action { & (Join-Path $repoRoot "scripts/download-native-sdks.ps1") -RepoRoot $repoRoot } -FailureStatus "WARN" -Fix "Authenticate with 'gh auth login' and rerun, or build a platform SDK with: dotnet msbuild /t:Build<Platform>SDK src/Sentry.Unity"
}
else {
Add-Result -Status "WARN" -Name "Prebuilt native SDKs" -Detail "dotnet is unavailable." -Fix "Install the SDK pinned in global.json, then rerun: pwsh bootstrap.ps1"
Add-Result -Status "WARN" -Name "Prebuilt native SDKs" -Detail "GitHub CLI is not available." -Fix "Install gh and run 'gh auth login', then rerun: pwsh bootstrap.ps1"
}

Write-Host ""

if (Test-Path (Join-Path $repoRoot "modules/sentry-cli.properties")) {
Invoke-Stage -Name "Sentry CLI" -Action { & (Join-Path $repoRoot "scripts/download-sentry-cli.ps1") } -FailureStatus "WARN" -Fix "Check network access, then rerun: pwsh bootstrap.ps1"
}
else {
Add-Result -Status "WARN" -Name "Sentry CLI" -Detail "sentry-cli submodule files are unavailable." -Fix "Run: git submodule update --init --recursive, then rerun: pwsh bootstrap.ps1"
}

Write-Host ""

if ($hasDotnet) {
Write-Host "Building managed SDK..." -ForegroundColor Cyan
Invoke-Stage -Name "Managed SDK build" -Action { dotnet build } -Fix "Fix the reported build error, then rerun: dotnet build"
}
else {
Add-Result -Status "FAIL" -Name "Managed SDK build" -Detail "dotnet is unavailable." -Fix "Install the SDK pinned in global.json, then run: dotnet build"
}

Write-Host ""
Write-Host "Configuring sample projects..." -ForegroundColor Cyan

$appleSettings = @(
(Join-Path $repoRoot "samples/unity-of-bugs/ProjectSettings/ProjectSettings.asset"),
(Join-Path $repoRoot "samples/unity-of-bugs-local/ProjectSettings/ProjectSettings.asset")
Expand Down Expand Up @@ -152,9 +281,11 @@ else {
}

Write-Host ""
Write-Host "Bootstrap summary"
Write-Host "$($PSStyle.Bold)Bootstrap summary$($PSStyle.Reset)"
foreach ($result in $results) {
Write-Host "[$($result.Status)] $($result.Name): $($result.Detail)"
Write-Host "[" -NoNewline
Write-Host $result.Status -ForegroundColor (Get-StatusColor -Status $result.Status) -NoNewline
Write-Host "] $($result.Name): $($result.Detail)"
if ($result.Fix) {
Write-Host " $($result.Fix)"
}
Expand Down
11 changes: 0 additions & 11 deletions build/local-dev.targets
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,4 @@
<Exec Command="pwsh -Command &quot;Invoke-Pester -Path '$(RepoRoot)test/IntegrationTest/Integration.Tests.ps1' -CI&quot;" EnvironmentVariables="SENTRY_TEST_PLATFORM=WebGL;SENTRY_TEST_APP=$(PlayerBuildPath)WebGL" IgnoreStandardErrorWarningFormat="true" />
</Target>

<!--
Downloads native SDKs from the latest successful GitHub Actions workflow run.
Only downloads SDKs that are not already present.
Depends on a GH CLI installation - https://cli.github.com/

dotnet msbuild /t:DownloadNativeSDKs src/Sentry.Unity
-->
<Target Name="DownloadNativeSDKs" Condition="'$(MSBuildProjectName)' == 'Sentry.Unity'">
<Exec Command="pwsh &quot;$(RepoRoot)scripts/download-native-sdks.ps1&quot; -RepoRoot &quot;$(RepoRoot)&quot;" />
</Target>

</Project>
2 changes: 1 addition & 1 deletion docs/agent-guides/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pwsh bootstrap.ps1
## Prebuilt Native SDKs

```sh
dotnet msbuild /t:DownloadNativeSDKs src/Sentry.Unity
pwsh scripts/download-native-sdks.ps1
```

- Downloads missing artifacts from successful GitHub Actions CI runs, preferring `main` then current branch.
Expand Down
3 changes: 0 additions & 3 deletions scripts/download-native-sdks.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ function Test-TrackedPluginChanges {

# Main logic
Write-Host "Checking native SDK status..." -ForegroundColor Cyan
Write-Host ""

$sdksToDownload = @()

Expand All @@ -170,8 +169,6 @@ foreach ($sdk in $SDKs) {
}
}

Write-Host ""

if ($sdksToDownload.Count -eq 0) {
Write-Host "All native SDKs are already present." -ForegroundColor Green
exit 0
Expand Down
23 changes: 17 additions & 6 deletions scripts/download-sentry-cli.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@ $targetFiles = $platforms | ForEach-Object {
Join-Path $targetDir "sentry-cli-$name"
}

Write-Host "Checking Sentry CLI status..." -ForegroundColor Cyan

$missingTargetFiles = @($targetFiles | Where-Object { -not (Test-Path $_) })
if ($missingTargetFiles.Count -eq 0) {
Write-Host "Sentry CLI already present, skipping download." -ForegroundColor Green
return
}

Write-Host "Sentry CLI not found, will download." -ForegroundColor Yellow
Write-Host ""

if (Test-Path $targetDir) {
$missingTargetFiles = @($targetFiles | Where-Object { -not (Test-Path $_) })
if ($missingTargetFiles.Count -eq 0) {
Write-Host "Sentry CLI already downloaded at $targetDir"
return
}

Remove-Item -Recurse -Force $targetDir
}
Expand All @@ -30,11 +36,16 @@ foreach ($name in $platforms)
}

$targetFile = "$targetDir/sentry-cli-$name"
Write-Host "Downloading $targetFile"
Write-Host "Downloading Sentry CLI for $name..." -ForegroundColor Yellow
Invoke-WebRequest -Uri "$baseUrl$name" -OutFile $targetFile

if (Get-Command 'chmod' -ErrorAction SilentlyContinue)
{
chmod +x $targetFile
}

Write-Host " Downloaded $name" -ForegroundColor Green
}

Write-Host ""
Write-Host "Sentry CLI download completed successfully!" -ForegroundColor Green
60 changes: 60 additions & 0 deletions src/Sentry.Unity.Editor/ConfigurationWindow/EnrichmentTab.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,66 @@ internal static void Display(ScriptableSentryUnityOptions options)
EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 1), Color.gray);
EditorGUILayout.Space();

{
GUILayout.Label("EXPERIMENTAL: Auto-collect common game performance metrics and send them " +
"to Sentry. Requires 'Enable Metrics'.", EditorStyles.wordWrappedMiniLabel);

options.AutoFrameTimeMetrics = EditorGUILayout.BeginToggleGroup(
new GUIContent("Auto Frame-Time Metrics", "Periodically emit frame time and FPS as Sentry metrics."),
options.AutoFrameTimeMetrics);
EditorGUI.indentLevel++;

options.FrameTimeMetricSampleInterval = EditorGUILayout.IntField(
new GUIContent("Sample Interval (frames)", "Emit a frame-time sample every Nth frame." +
"\nDefault: 30"),
options.FrameTimeMetricSampleInterval);
if (options.FrameTimeMetricSampleInterval < 1)
{
options.FrameTimeMetricSampleInterval = 1;
}

EditorGUI.indentLevel--;
EditorGUILayout.EndToggleGroup();

options.AutoGameStatsMetrics = EditorGUILayout.Toggle(
new GUIContent("Auto Game-Stats Metrics", "Periodically emit memory usage as Sentry metrics."),
options.AutoGameStatsMetrics);

options.AutoGcMetrics = EditorGUILayout.Toggle(
new GUIContent("Auto GC Metrics", "Periodically emit garbage-collection counts (per generation) " +
"as Sentry metrics."),
options.AutoGcMetrics);

options.AutoNetworkMetrics = EditorGUILayout.Toggle(
new GUIContent("Auto Network Metrics", "Periodically emit basic multiplayer network metrics " +
"(round-trip time and connected-client count) as Sentry " +
"metrics. Requires the Netcode for GameObjects package."),
options.AutoNetworkMetrics);

options.GameStatsMetricSampleIntervalSeconds = EditorGUILayout.IntField(
new GUIContent("Game-Stats/GC Interval (s)", "How often, in seconds, the game-stats and GC " +
"metrics are sampled." +
"\nDefault: 60"),
options.GameStatsMetricSampleIntervalSeconds);
if (options.GameStatsMetricSampleIntervalSeconds < 1)
{
options.GameStatsMetricSampleIntervalSeconds = 1;
}

options.NetworkMetricsSampleIntervalSeconds = EditorGUILayout.IntField(
new GUIContent("Network Interval (s)", "How often, in seconds, the network metrics are sampled." +
"\nDefault: 10"),
options.NetworkMetricsSampleIntervalSeconds);
if (options.NetworkMetricsSampleIntervalSeconds < 1)
{
options.NetworkMetricsSampleIntervalSeconds = 1;
}
}

EditorGUILayout.Space();
EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 1), Color.gray);
EditorGUILayout.Space();

{
options.ReleaseOverride = EditorGUILayout.TextField(
new GUIContent("Override Release", "By default release is built from the Application info as: " +
Expand Down
Loading
Loading