diff --git a/CLAUDE.md b/CLAUDE.md
index f68c3912a..36d356257 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -156,7 +156,7 @@ Key targets defined in `Directory.Build.targets`:
| `BuildAndroidSDK` | Builds Android SDK via Gradle |
| `BuildLinuxSDK` | Builds Linux SDK via CMake |
| `BuildWindowsSDK` | Builds Windows SDK via CMake (Crashpad) |
-| `BuildCocoaSDK` | Downloads iOS/macOS SDKs from releases |
+| `BuildCocoaSDK` | Builds iOS/macOS SDKs via Xcode |
| `UnityEditModeTest` | Runs edit-mode unit tests |
| `UnityPlayModeTest` | Runs play-mode tests |
@@ -320,6 +320,18 @@ modules/
└── sentry-cocoa/ # iOS/macOS (prebuilt XCFramework)
```
+### Local Android NDK Development
+
+When iterating on `modules/sentry-native/ndk` together with `modules/sentry-java`, publish the local NDK build to `~/.m2` so sentry-java picks it up instead of mavenCentral:
+
+```bash
+pwsh scripts/build-native-ndk-local.ps1 # publish only
+pwsh scripts/build-native-ndk-local.ps1 -BuildJava # publish + rebuild :sentry-android-ndk
+pwsh scripts/build-native-ndk-local.ps1 -PurgeCache -BuildJava # first switch from central, or after stale builds
+```
+
+Prerequisite: `mavenLocal()` must precede `mavenCentral()` in `modules/sentry-java/settings.gradle.kts` (`dependencyResolutionManagement` block). The script aborts otherwise.
+
### Key Source Files
**Android (`src/Sentry.Unity.Android/`):**
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 8f6613858..49fe224c5 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -117,33 +117,57 @@
-
-
+
-
+
+ -Clean
+
+
+
-
+
+
+
+
+
+
+
+
+
+
$([System.IO.File]::ReadAllText("$(RepoRoot)modules/sentry-java/gradle/libs.versions.toml"))
$([System.Text.RegularExpressions.Regex]::Match($(PropertiesContent), 'sentry-native-ndk\s*=\s*\{[^}]*version\s*=\s*"([^"]+)"').Groups[1].Value)
-
+
-
+
-
+
-
+
+
+
+
+
+ -PurgeCache
+
+
+
+
+
+
+
+
+ And ('$(RebuildNativeSdk)' == 'true' Or !Exists('$(SentryWindowsArtifactsDestination)sentry.dll'))" BeforeTargets="BeforeBuild">
@@ -222,10 +286,15 @@
-
+
+ And ('$(RebuildNativeSdk)' == 'true' Or !Exists('$(SentryLinuxArtifactsDestination)libsentry.so'))" BeforeTargets="BeforeBuild">
diff --git a/scripts/build-cocoa-sdk.ps1 b/scripts/build-cocoa-sdk.ps1
index deb8444b7..ef745627f 100644
--- a/scripts/build-cocoa-sdk.ps1
+++ b/scripts/build-cocoa-sdk.ps1
@@ -8,7 +8,9 @@ param(
[string]$iOSDestination,
[Parameter(Mandatory = $true)]
- [string]$macOSDestination
+ [string]$macOSDestination,
+
+ [switch]$Clean
)
Set-StrictMode -Version latest
@@ -25,6 +27,11 @@ $buildPath = Join-Path $CocoaRoot "XCFrameworkBuildPath"
$iOSXcframeworkPath = Join-Path $buildPath "Sentry-Dynamic-iOS.xcframework"
$macOSXcframeworkPath = Join-Path $buildPath "Sentry-Dynamic-macOS.xcframework"
+if ($Clean -and (Test-Path $buildPath)) {
+ Write-Host "Clean build requested — removing $buildPath" -ForegroundColor Yellow
+ Remove-Item -Path $buildPath -Recurse -Force
+}
+
Write-Host "Building Cocoa SDK from source..." -ForegroundColor Yellow
Push-Location $CocoaRoot
diff --git a/scripts/build-native-ndk-local.ps1 b/scripts/build-native-ndk-local.ps1
new file mode 100644
index 000000000..05773f490
--- /dev/null
+++ b/scripts/build-native-ndk-local.ps1
@@ -0,0 +1,124 @@
+<#
+.SYNOPSIS
+ Builds modules/sentry-native (NDK) and publishes the artifact to the local
+ Maven repo so modules/sentry-java consumes it instead of mavenCentral.
+
+.DESCRIPTION
+ Runs :sentry-native-ndk:publishToMavenLocal in modules/sentry-native/ndk,
+ producing io.sentry:sentry-native-ndk: at ~/.m2.
+
+ Requires mavenLocal() to be listed before mavenCentral() in
+ modules/sentry-java/settings.gradle.kts. The script verifies this and
+ aborts otherwise.
+
+ Because both repos publish the same version coordinate, Gradle's module
+ and transform caches can hold a previously-resolved mavenCentral copy.
+ The first time you switch to local (or when the module cache holds a
+ stale build), pass -PurgeCache to wipe sentry-native-ndk caches and
+ stop the Gradle daemon so the next build re-resolves from mavenLocal.
+
+.PARAMETER PurgeCache
+ Delete sentry-native-ndk from the Gradle module cache and the related
+ transform directories, then stop the Gradle daemon. Use when switching
+ from mavenCentral resolution or when the consumed artifact looks stale.
+
+.PARAMETER BuildJava
+ After publishing, run :sentry-android-ndk:assembleRelease in
+ modules/sentry-java to consume the freshly published artifact.
+
+.EXAMPLE
+ pwsh scripts/build-native-ndk-local.ps1
+ # Publish ndk to ~/.m2 (assumes caches are already clean).
+
+.EXAMPLE
+ pwsh scripts/build-native-ndk-local.ps1 -PurgeCache -BuildJava
+ # Wipe stale caches, publish, then rebuild sentry-android-ndk against
+ # the local artifact.
+#>
+
+param(
+ [switch] $PurgeCache,
+ [switch] $BuildJava
+)
+
+$ErrorActionPreference = 'Stop'
+Set-StrictMode -Version Latest
+
+$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..')
+$ndkDir = Join-Path $repoRoot 'modules/sentry-native/ndk'
+$javaDir = Join-Path $repoRoot 'modules/sentry-java'
+$javaSettings = Join-Path $javaDir 'settings.gradle.kts'
+
+if (-not (Test-Path $ndkDir)) {
+ throw "sentry-native NDK module not found at $ndkDir. Did you check out the submodule?"
+}
+if (-not (Test-Path $javaSettings)) {
+ throw "sentry-java settings.gradle.kts not found at $javaSettings."
+}
+
+$settingsContent = Get-Content $javaSettings -Raw
+$drmMatch = [regex]::Match($settingsContent, 'dependencyResolutionManagement\s*\{[^}]*repositories\s*\{(?[^}]*)\}')
+if (-not $drmMatch.Success) {
+ throw "Could not locate dependencyResolutionManagement.repositories block in $javaSettings."
+}
+$reposBlock = $drmMatch.Groups['repos'].Value
+$localIdx = $reposBlock.IndexOf('mavenLocal()')
+$centralIdx = $reposBlock.IndexOf('mavenCentral()')
+if ($localIdx -lt 0 -or $centralIdx -lt 0 -or $localIdx -gt $centralIdx) {
+ throw @"
+mavenLocal() must appear before mavenCentral() in
+$javaSettings (dependencyResolutionManagement block) so sentry-java
+resolves the locally-published sentry-native-ndk artifact. Reorder the
+repositories and re-run this script.
+"@
+}
+
+if ($PurgeCache) {
+ Write-Host '==> Purging Gradle caches for sentry-native-ndk'
+ $gradleCaches = Join-Path $HOME '.gradle/caches'
+ $moduleCache = Join-Path $gradleCaches 'modules-2/files-2.1/io.sentry/sentry-native-ndk'
+ if (Test-Path $moduleCache) {
+ Remove-Item -Recurse -Force $moduleCache
+ Write-Host " removed $moduleCache"
+ }
+
+ if (Test-Path $gradleCaches) {
+ $transformRoots = Get-ChildItem -Path $gradleCaches -Recurse -Force -ErrorAction SilentlyContinue `
+ | Where-Object { $_.FullName -like '*sentry-native-ndk*' } `
+ | ForEach-Object {
+ $idx = $_.FullName.IndexOf('/transformed/')
+ if ($idx -lt 0) { $idx = $_.FullName.IndexOf([IO.Path]::DirectorySeparatorChar + 'transformed' + [IO.Path]::DirectorySeparatorChar) }
+ if ($idx -ge 0) { $_.FullName.Substring(0, $idx) } else { $null }
+ } `
+ | Where-Object { $_ } `
+ | Sort-Object -Unique
+ foreach ($dir in $transformRoots) {
+ if (Test-Path $dir) {
+ Remove-Item -Recurse -Force $dir
+ Write-Host " removed $dir"
+ }
+ }
+ }
+
+ Write-Host '==> Stopping Gradle daemon to clear in-memory transform registry'
+ Push-Location $ndkDir
+ try { & ./gradlew --stop | Out-Null } finally { Pop-Location }
+}
+
+Write-Host '==> Publishing sentry-native-ndk to mavenLocal'
+Push-Location $ndkDir
+try {
+ & ./gradlew :sentry-native-ndk:publishToMavenLocal
+ if ($LASTEXITCODE -ne 0) { throw "publishToMavenLocal failed (exit $LASTEXITCODE)" }
+} finally { Pop-Location }
+
+if ($BuildJava) {
+ Write-Host '==> Building :sentry-android-ndk:assembleRelease against mavenLocal'
+ Push-Location $javaDir
+ try {
+ & ./gradlew :sentry-android-ndk:assembleRelease
+ if ($LASTEXITCODE -ne 0) { throw "sentry-android-ndk assembleRelease failed (exit $LASTEXITCODE)" }
+ } finally { Pop-Location }
+}
+
+Write-Host '==> Done.'