From 8f1c6951bff653e7097d5f707b256fb43b73267e Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Tue, 27 Oct 2026 12:12:03 +0100 Subject: [PATCH 01/19] build(samples): Move the Android sample to its own build The Android sample is now a standalone Gradle build that includes the SDK build. It depends on io.sentry:* coordinates, which Gradle substitutes with the SDK's projects, so there is nothing to publish first. The SDK build doesn't include it back, so the sample's plugins (KSP, SQLDelight, Room, CameraX) no longer load in the SDK build. The macrobenchmark moves into the sample's build, because AGP requires a benchmark and the app it targets to be in the same build. Root check and spotlessApply no longer reach the sample, so preMerge runs the sample build's check, and the format workflow runs its spotlessApply. That check covers what the root check ran for it: lint, unit tests, SQLDelight migrations and spotless. Run it with ./gradlew -p sentry-samples/sentry-samples-android . Co-Authored-By: Claude Opus 5.5 --- .github/workflows/format-code.yml | 4 +- .../integration-tests-macrobenchmark.yml | 4 +- .../sentry-uitest-android-macrobenchmark.yml | 2 +- AGENTS.md | 5 +- Makefile | 9 ++- .../sentry-samples-android/README.md | 13 ++-- .../sentry-samples-android/build.gradle.kts | 40 +++++----- .../sentry-samples-android/gradle.properties | 24 ++++++ .../macrobenchmark}/README.md | 4 +- .../macrobenchmark}/build.gradle.kts | 5 +- .../src/main/AndroidManifest.xml | 0 .../macrobenchmark/SentryStartupBenchmark.kt | 0 .../settings.gradle.kts | 77 +++++++++++++++++++ settings.gradle.kts | 2 - 14 files changed, 154 insertions(+), 35 deletions(-) create mode 100644 sentry-samples/sentry-samples-android/gradle.properties rename {sentry-android-integration-tests/sentry-uitest-android-macrobenchmark => sentry-samples/sentry-samples-android/macrobenchmark}/README.md (97%) rename {sentry-android-integration-tests/sentry-uitest-android-macrobenchmark => sentry-samples/sentry-samples-android/macrobenchmark}/build.gradle.kts (90%) rename {sentry-android-integration-tests/sentry-uitest-android-macrobenchmark => sentry-samples/sentry-samples-android/macrobenchmark}/src/main/AndroidManifest.xml (100%) rename {sentry-android-integration-tests/sentry-uitest-android-macrobenchmark => sentry-samples/sentry-samples-android/macrobenchmark}/src/main/java/io/sentry/uitest/android/macrobenchmark/SentryStartupBenchmark.kt (100%) create mode 100644 sentry-samples/sentry-samples-android/settings.gradle.kts diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml index fb1a1da7102..522baa31cda 100644 --- a/.github/workflows/format-code.yml +++ b/.github/workflows/format-code.yml @@ -20,7 +20,9 @@ jobs: uses: gradle/actions/setup-gradle@8da46b539cd8fe6ab27ca75128919a18d757604c # v6.4.1 - name: Format with spotlessApply - run: ./gradlew spotlessApply + run: | + ./gradlew spotlessApply + ./gradlew -p sentry-samples/sentry-samples-android spotlessApply # actions/checkout fetches only a single commit in a detached HEAD state. Therefore # we need to pass the current branch, otherwise we can't commit the changes. diff --git a/.github/workflows/integration-tests-macrobenchmark.yml b/.github/workflows/integration-tests-macrobenchmark.yml index 654e8c17f76..eb1778adcd8 100644 --- a/.github/workflows/integration-tests-macrobenchmark.yml +++ b/.github/workflows/integration-tests-macrobenchmark.yml @@ -1,5 +1,5 @@ name: 'Integration Tests - Macrobenchmark' -# Runs the sentry-uitest-android-macrobenchmark cold-start benchmark on a Sauce Labs real +# Runs the sentry-samples-android macrobenchmark cold-start benchmark on a Sauce Labs real # device and recovers timeToInitialDisplay from the device log. # on: @@ -33,7 +33,7 @@ jobs: - name: Assemble target app and Macrobenchmark apk if: env.SAUCE_USERNAME != null - run: ./gradlew :sentry-samples:sentry-samples-android:assembleRelease :sentry-android-integration-tests:sentry-uitest-android-macrobenchmark:assembleBenchmark + run: ./gradlew -p sentry-samples/sentry-samples-android :assembleRelease :macrobenchmark:assembleBenchmark - name: Run Macrobenchmark in SauceLab uses: saucelabs/saucectl-run-action@283660aa934c02723c497efa151d582a3acc5801 # pin@v3 diff --git a/.sauce/sentry-uitest-android-macrobenchmark.yml b/.sauce/sentry-uitest-android-macrobenchmark.yml index 4e3d5cfbb3d..a6ef85e7c4a 100644 --- a/.sauce/sentry-uitest-android-macrobenchmark.yml +++ b/.sauce/sentry-uitest-android-macrobenchmark.yml @@ -15,7 +15,7 @@ defaults: espresso: app: ./sentry-samples/sentry-samples-android/build/outputs/apk/release/sentry-samples-android-release.apk - testApp: ./sentry-android-integration-tests/sentry-uitest-android-macrobenchmark/build/outputs/apk/benchmark/sentry-uitest-android-macrobenchmark-benchmark.apk + testApp: ./sentry-samples/sentry-samples-android/macrobenchmark/build/outputs/apk/benchmark/macrobenchmark-benchmark.apk suites: diff --git a/AGENTS.md b/AGENTS.md index f447fc31512..5ea69289a16 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,6 +64,9 @@ resolves the per-module test task, including integration test suites, for you. # Run critical UI tests ./scripts/test-ui-critical.sh + +# Build the Android sample, a separate Gradle build that includes this one +./gradlew -p sentry-samples/sentry-samples-android assembleDebug ``` ## Development Workflow Rules @@ -110,7 +113,7 @@ The repository is organized into multiple modules: ### Utility Modules - **`sentry-test-support`** - Shared test utilities, including `MockSentryServer` and envelope matchers -- **`sentry-samples`** - Example applications +- **`sentry-samples`** - Example applications. `sentry-samples-android` is a separate Gradle build that includes this one, so root tasks such as `check` and `spotlessApply` don't reach it - **`sentry-bom`** - Bill of Materials for dependency management ### Key Architectural Patterns diff --git a/Makefile b/Makefile index c5bb6ac2313..414ccec440a 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ -.PHONY: all clean compile javadocs dryRelease update checkFormat api assembleUiTestRelease assembleUiTestCriticalRelease runUiTestCritical check preMerge publish +.PHONY: all clean compile javadocs dryRelease update checkFormat api assembleUiTestRelease assembleUiTestCriticalRelease runUiTestCritical check checkSampleAndroid preMerge publish all: stop clean javadocs compile assembleUiTests: assembleUiTestRelease -preMerge: check +preMerge: check checkSampleAndroid publish: clean dryRelease # deep clean @@ -28,6 +28,7 @@ update: # Spotless check's code checkFormat: ./gradlew spotlessJavaCheck spotlessKotlinCheck + ./gradlew -p sentry-samples/sentry-samples-android spotlessJavaCheck spotlessKotlinCheck # Binary compatibility validator api: @@ -48,3 +49,7 @@ runUiTestCritical: # Run tests and lint check: ./gradlew check + +# The Android sample is a separate build, so the root `check` doesn't reach it +checkSampleAndroid: + ./gradlew -p sentry-samples/sentry-samples-android check diff --git a/sentry-samples/sentry-samples-android/README.md b/sentry-samples/sentry-samples-android/README.md index b065b91e955..35692c7b837 100644 --- a/sentry-samples/sentry-samples-android/README.md +++ b/sentry-samples/sentry-samples-android/README.md @@ -8,18 +8,21 @@ profiling) and integrations (Compose, OkHttp, SQLite, etc.). Install the app on your device or emulator: ``` -./gradlew :sentry-samples:sentry-samples-android:installDebug +./gradlew -p sentry-samples/sentry-samples-android installDebug ``` -or simply open the project in Android Studio and run the `sentry-samples-android` configuration. +or open the `sentry-samples/sentry-samples-android` directory in Android Studio and run the `sentry-samples-android` configuration. + +The sample is a separate Gradle build that includes the SDK build, so changes to the SDK show up in the next build of the sample, with nothing to publish first. +The SDK build doesn't include the sample, so `./gradlew` from the repository root doesn't build, lint or format it. You can also apply the [Sentry Android Gradle Plugin](https://github.com/getsentry/sentry-android-gradle-plugin) (SAGP) when building (not applied by default): ``` -./gradlew :sentry-samples:sentry-samples-android:installDebug -PuseSagp +./gradlew -p sentry-samples/sentry-samples-android installDebug -PuseSagp ``` -In Android Studio, add `useSagp=` (empty value) to `gradle.properties`, or pass `-PuseSagp` as a Gradle project property. +In Android Studio, add `useSagp=` (empty value) to the sample's `gradle.properties`, or pass `-PuseSagp` as a Gradle project property. For LLM-driven verification, use `adb` to inspect and interact with the running emulator. Compose-based screens often expose stable `Modifier.testTag()` values, which can be used with the UI hierarchy dump to locate controls reliably. @@ -55,7 +58,7 @@ Re-run `publishToMavenLocal` after each SAGP change. Then build from sentry-java: ``` -./gradlew :sentry-samples:sentry-samples-android:installDebug -PuseSagp +./gradlew -p sentry-samples/sentry-samples-android installDebug -PuseSagp ``` ## Viewing SDK output diff --git a/sentry-samples/sentry-samples-android/build.gradle.kts b/sentry-samples/sentry-samples-android/build.gradle.kts index 28d8748faa9..8fb29bb4ecd 100644 --- a/sentry-samples/sentry-samples-android/build.gradle.kts +++ b/sentry-samples/sentry-samples-android/build.gradle.kts @@ -8,7 +8,9 @@ import org.gradle.internal.extensions.stdlib.capitalized import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion plugins { - id("com.android.application") + alias(libs.plugins.android.application) + id("io.sentry.spotless") + id("io.sentry.jdk-target") alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) alias(libs.plugins.ksp) @@ -16,6 +18,8 @@ plugins { alias(libs.plugins.sqldelight) } +val sentryVersion = libs.versions.sentry.get() + if (providers.gradleProperty("useSagp").isPresent) { apply(plugin = "io.sentry.android.gradle") } @@ -53,7 +57,7 @@ android { minSdk = 23 targetSdk = libs.versions.targetSdk.get().toInt() versionCode = 2 - versionName = project.version.toString() + versionName = sentryVersion externalNativeBuild { cmake { @@ -104,7 +108,7 @@ android { signingConfigs { getByName("debug") { - storeFile = rootProject.file("debug.keystore") + storeFile = file("../../debug.keystore") storePassword = "android" keyAlias = "androiddebugkey" keyPassword = "android" @@ -146,7 +150,7 @@ android { val toggleNativeLoggingTask = project.tasks.register(taskName) { mergedManifest.set(variant.artifacts.get(SingleArtifact.MERGED_MANIFEST)) - rootDir.set(project.rootDir.absolutePath) + repoDir.set(layout.projectDirectory.dir("../..").asFile.canonicalPath) } project.afterEvaluate { (variant as? VariantImpl<*>)?.taskContainer?.assembleTask?.configure { @@ -175,19 +179,19 @@ sqldelight { dependencies { implementation(kotlin("stdlib-jdk8", getKotlinPluginVersion())) - implementation(projects.sentryAndroid) - implementation(projects.sentryAndroidFragment) - implementation(projects.sentryAndroidNavigation) - implementation(projects.sentryAndroidNavigation3) - implementation(projects.sentryAndroidSqlite) - implementation(projects.sentryAndroidTimber) - implementation(projects.sentryCompose) - implementation(projects.sentryKotlinExtensions) - implementation(projects.sentryOkhttp) - implementation(projects.sentrySpotlight) + implementation("io.sentry:sentry-android:$sentryVersion") + implementation("io.sentry:sentry-android-fragment:$sentryVersion") + implementation("io.sentry:sentry-android-navigation:$sentryVersion") + implementation("io.sentry:sentry-android-navigation3:$sentryVersion") + implementation("io.sentry:sentry-android-sqlite:$sentryVersion") + implementation("io.sentry:sentry-android-timber:$sentryVersion") + implementation("io.sentry:sentry-compose:$sentryVersion") + implementation("io.sentry:sentry-kotlin-extensions:$sentryVersion") + implementation("io.sentry:sentry-okhttp:$sentryVersion") + implementation("io.sentry:sentry-spotlight:$sentryVersion") // how to exclude androidx if release health feature is disabled - // implementation(projects.sentryAndroid) { + // implementation("io.sentry:sentry-android:$sentryVersion") { // exclude(group = "androidx.lifecycle", module = "lifecycle-process") // exclude(group = "androidx.lifecycle", module = "lifecycle-common-java8") // exclude(group = "androidx.core", module = "core") @@ -228,7 +232,7 @@ dependencies { ksp(libs.androidx.room.compiler) ksp(libs.androidx.room3.compiler) - debugImplementation(projects.sentryAndroidDistribution) + debugImplementation("io.sentry:sentry-android-distribution:$sentryVersion") debugImplementation(libs.leakcanary) testImplementation(libs.google.truth) @@ -236,7 +240,7 @@ dependencies { } abstract class ToggleNativeLoggingTask : Exec() { - @get:Input abstract val rootDir: Property + @get:Input abstract val repoDir: Property @get:InputFile abstract val mergedManifest: RegularFileProperty @@ -254,7 +258,7 @@ abstract class ToggleNativeLoggingTask : Exec() { args.add(0, "cmd") args.add(1, "/c") } - args.add("${rootDir.get()}/scripts/toggle-codec-logs.sh") + args.add("${repoDir.get()}/scripts/toggle-codec-logs.sh") args.add(if (value) "enable" else "disable") commandLine(args) super.exec() diff --git a/sentry-samples/sentry-samples-android/gradle.properties b/sentry-samples/sentry-samples-android/gradle.properties new file mode 100644 index 00000000000..8751888c1a0 --- /dev/null +++ b/sentry-samples/sentry-samples-android/gradle.properties @@ -0,0 +1,24 @@ +# Gradle properties are per build, so this repeats what the sample needs from the SDK build's +# gradle.properties. settings.gradle.kts reads the SDK version from that file. + +org.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=2g -XX:+CrashOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC +org.gradle.caching=true +org.gradle.parallel=true +org.gradle.configureondemand=true +org.gradle.configuration-cache=true +org.gradle.configuration-cache.parallel=true + +android.useAndroidX=true +# AGP 9+ migration opt-outs until we remove kotlin-android plugin and adopt built-in Kotlin. +android.builtInKotlin=false +android.newDsl=false + +android.defaults.buildfeatures.renderscript=false +android.defaults.buildfeatures.shaders=false +android.defaults.buildfeatures.aidl=false +android.defaults.buildfeatures.resvalues=false +android.nonTransitiveRClass=true + +kotlin.stdlib.default.dependency=false + +systemProp.org.gradle.internal.http.socketTimeout=120000 diff --git a/sentry-android-integration-tests/sentry-uitest-android-macrobenchmark/README.md b/sentry-samples/sentry-samples-android/macrobenchmark/README.md similarity index 97% rename from sentry-android-integration-tests/sentry-uitest-android-macrobenchmark/README.md rename to sentry-samples/sentry-samples-android/macrobenchmark/README.md index 9914825584d..23c5fab6921 100644 --- a/sentry-android-integration-tests/sentry-uitest-android-macrobenchmark/README.md +++ b/sentry-samples/sentry-samples-android/macrobenchmark/README.md @@ -1,4 +1,4 @@ -# sentry-uitest-android-macrobenchmark +# macrobenchmark Jetpack Macrobenchmark for cold-start of `sentry-samples-android`, used to evaluate SDK-init performance changes on a real device in a **stable, reproducible** way. Not run in CI. @@ -28,7 +28,7 @@ unlocked-clock device after ~14 iterations, inflating the tail of longer runs. Connect a device, then: ```bash -./gradlew :sentry-android-integration-tests:sentry-uitest-android-macrobenchmark:connectedBenchmarkAndroidTest +./gradlew -p sentry-samples/sentry-samples-android :macrobenchmark:connectedBenchmarkAndroidTest ``` Results print to the console and are written to diff --git a/sentry-android-integration-tests/sentry-uitest-android-macrobenchmark/build.gradle.kts b/sentry-samples/sentry-samples-android/macrobenchmark/build.gradle.kts similarity index 90% rename from sentry-android-integration-tests/sentry-uitest-android-macrobenchmark/build.gradle.kts rename to sentry-samples/sentry-samples-android/macrobenchmark/build.gradle.kts index cab7c28f934..b819504ddcb 100644 --- a/sentry-android-integration-tests/sentry-uitest-android-macrobenchmark/build.gradle.kts +++ b/sentry-samples/sentry-samples-android/macrobenchmark/build.gradle.kts @@ -1,5 +1,7 @@ plugins { id("com.android.test") + id("io.sentry.spotless") + id("io.sentry.jdk-target") alias(libs.plugins.kotlin.android) } @@ -26,7 +28,8 @@ android { } } - targetProjectPath = ":sentry-samples:sentry-samples-android" + // The sample app is the root project of this build. + targetProjectPath = ":" // Run the test in its own process so it measures the target app cold, not itself. experimentalProperties["android.experimental.self-instrumenting"] = true } diff --git a/sentry-android-integration-tests/sentry-uitest-android-macrobenchmark/src/main/AndroidManifest.xml b/sentry-samples/sentry-samples-android/macrobenchmark/src/main/AndroidManifest.xml similarity index 100% rename from sentry-android-integration-tests/sentry-uitest-android-macrobenchmark/src/main/AndroidManifest.xml rename to sentry-samples/sentry-samples-android/macrobenchmark/src/main/AndroidManifest.xml diff --git a/sentry-android-integration-tests/sentry-uitest-android-macrobenchmark/src/main/java/io/sentry/uitest/android/macrobenchmark/SentryStartupBenchmark.kt b/sentry-samples/sentry-samples-android/macrobenchmark/src/main/java/io/sentry/uitest/android/macrobenchmark/SentryStartupBenchmark.kt similarity index 100% rename from sentry-android-integration-tests/sentry-uitest-android-macrobenchmark/src/main/java/io/sentry/uitest/android/macrobenchmark/SentryStartupBenchmark.kt rename to sentry-samples/sentry-samples-android/macrobenchmark/src/main/java/io/sentry/uitest/android/macrobenchmark/SentryStartupBenchmark.kt diff --git a/sentry-samples/sentry-samples-android/settings.gradle.kts b/sentry-samples/sentry-samples-android/settings.gradle.kts new file mode 100644 index 00000000000..06d5fc1c770 --- /dev/null +++ b/sentry-samples/sentry-samples-android/settings.gradle.kts @@ -0,0 +1,77 @@ +// A standalone build that includes the SDK build, so it depends on the SDK through the same +// io.sentry:* coordinates an app would, and Gradle substitutes the SDK's projects for them. +// The SDK build doesn't include this one back, so its plugins and versions stay out of the SDK +// build. Run it with `./gradlew -p sentry-samples/sentry-samples-android `. + +pluginManagement { + repositories { + // Prefer local SAGP artifact if one exists; otherwise fall back to libs.versions.toml. + if (providers.gradleProperty("useSagp").isPresent) { + mavenLocal { + content { + includeGroup("io.sentry") + includeGroup("io.sentry.android.gradle") + } + } + } + mavenCentral() + gradlePluginPortal() + google() + } + + // Every build in a composite must use the same AGP, so each build's settings applies the AGP + // matrix job's override. + System.getenv("VERSION_AGP")?.let { agpVersion -> + resolutionStrategy { + eachPlugin { + if (requested.id.id.startsWith("com.android")) { + useVersion(agpVersion) + } + } + } + } +} + +plugins { + id("com.gradle.develocity") version "4.5.0" + id("com.gradle.common-custom-user-data-gradle-plugin") version "2.8.0" +} + +develocity { + buildScan { + termsOfUseUrl.set("https://gradle.com/help/legal-terms-of-use") + termsOfUseAgree.set("yes") + } +} + +// Gradle reads gradle.properties only for the build it belongs to, so read the SDK version from the +// SDK build's file. +val sentryVersion: String = + providers + .fileContents(layout.settingsDirectory.file("../../gradle.properties")) + .asText + .map { java.util.Properties().apply { load(it.reader()) }.getProperty("versionName") } + .get() + +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + mavenLocal() + } + versionCatalogs { + create("libs") { + from(files("../../gradle/libs.versions.toml")) + version("sentry", sentryVersion) + } + } +} + +rootProject.name = "sentry-samples-android" + +includeBuild("../..") + +// For the io.sentry.spotless and io.sentry.jdk-target convention plugins. +includeBuild("../../build-logic") + +include("macrobenchmark") diff --git a/settings.gradle.kts b/settings.gradle.kts index 2849794723f..378979411f0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -112,7 +112,6 @@ include( "sentry-reactor", "sentry-async-profiler", "sentry-ktor-client", - "sentry-samples:sentry-samples-android", "sentry-samples:sentry-samples-console", "sentry-samples:sentry-samples-console-otlp", "sentry-samples:sentry-samples-console-opentelemetry-noagent", @@ -141,7 +140,6 @@ include( "sentry-samples:sentry-samples-spring-boot-4-webflux", "sentry-samples:sentry-samples-netflix-dgs", "sentry-android-integration-tests:sentry-uitest-android-critical", - "sentry-android-integration-tests:sentry-uitest-android-macrobenchmark", "sentry-android-integration-tests:sentry-uitest-android", "sentry-android-integration-tests:test-app-size", "sentry-samples:sentry-samples-openfeign" From 4c6e2cd9bca77d73bdd8909d1c49baed5f7a39b2 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Mon, 2 Nov 2026 14:12:57 +0100 Subject: [PATCH 02/19] ci(samples): Run the Android sample's check in its own workflow Instead of adding it to `make preMerge`, which the SDK's Build job runs, a new Samples workflow runs the Android sample build's `check` as its own job. A broken sample then fails its own check, it runs in parallel with the SDK build, and the Build job doesn't get longer. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/samples.yml | 76 +++++++++++++++++++++++++++++++++++ Makefile | 8 +--- 2 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/samples.yml diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml new file mode 100644 index 00000000000..b34f8fff744 --- /dev/null +++ b/.github/workflows/samples.yml @@ -0,0 +1,76 @@ +name: 'Samples' +# Runs `check` in each build the SDK build doesn't include, so a broken sample or +# integration test fails its own job instead of the SDK's Build job. +on: + push: + branches: + - main + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + name: Check ${{ matrix.name }} + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + include: + - name: sentry-samples-android + path: sentry-samples/sentry-samples-android + + env: + GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + + steps: + - name: Checkout Repo + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + submodules: 'recursive' + + - name: Setup Java Version + uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0 + with: + distribution: 'temurin' + java-version: '21' + + # Workaround for https://github.com/gradle/actions/issues/21 to use config cache + - name: Cache buildSrc + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: buildSrc/build + key: build-logic-${{ hashFiles('buildSrc/src/**', 'buildSrc/build.gradle.kts','buildSrc/settings.gradle.kts') }} + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + + - name: Run Tests and Lint + run: ./gradlew -p ${{ matrix.path }} check + + - name: Upload test results + if: always() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 + with: + name: test-results-${{ matrix.name }} + path: | + **/build/reports/* + + - name: Test Report + uses: phoenix-actions/test-reporting@7317eea6e13c47348dd0bb318669485157c518d6 # pin@v16 + if: always() + with: + name: JUnit ${{ matrix.name }} + list-suites: 'failed' + list-tests: 'failed' + path: | + **/build/test-results/**/*.xml + reporter: java-junit + output-to: step-summary + fail-on-error: false diff --git a/Makefile b/Makefile index 414ccec440a..45c6ed2a7a0 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ -.PHONY: all clean compile javadocs dryRelease update checkFormat api assembleUiTestRelease assembleUiTestCriticalRelease runUiTestCritical check checkSampleAndroid preMerge publish +.PHONY: all clean compile javadocs dryRelease update checkFormat api assembleUiTestRelease assembleUiTestCriticalRelease runUiTestCritical check preMerge publish all: stop clean javadocs compile assembleUiTests: assembleUiTestRelease -preMerge: check checkSampleAndroid +preMerge: check publish: clean dryRelease # deep clean @@ -49,7 +49,3 @@ runUiTestCritical: # Run tests and lint check: ./gradlew check - -# The Android sample is a separate build, so the root `check` doesn't reach it -checkSampleAndroid: - ./gradlew -p sentry-samples/sentry-samples-android check From 931259c092852a7481533ba550c2e7e1e0cd42b7 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Mon, 2 Nov 2026 15:41:37 +0100 Subject: [PATCH 03/19] ci(samples): Drop the Gradle encryption key and buildSrc cache from the Samples workflow Co-Authored-By: Claude Opus 5.5 --- .github/workflows/samples.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index b34f8fff744..839dae6025f 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -23,9 +23,6 @@ jobs: - name: sentry-samples-android path: sentry-samples/sentry-samples-android - env: - GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - steps: - name: Checkout Repo uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -39,17 +36,8 @@ jobs: distribution: 'temurin' java-version: '21' - # Workaround for https://github.com/gradle/actions/issues/21 to use config cache - - name: Cache buildSrc - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: buildSrc/build - key: build-logic-${{ hashFiles('buildSrc/src/**', 'buildSrc/build.gradle.kts','buildSrc/settings.gradle.kts') }} - - name: Setup Gradle uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0 - with: - cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - name: Run Tests and Lint run: ./gradlew -p ${{ matrix.path }} check From 21551f0935c7dc4169d7fd095e6380217d6cd021 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Mon, 2 Nov 2026 15:52:28 +0100 Subject: [PATCH 04/19] ci(samples): Check out the default ref without submodules in the Samples workflow The head-SHA ref only matters for build.yml's snapshot upload, and the repository has had no submodules since #3189. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/samples.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 839dae6025f..238f4f3debd 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -26,9 +26,6 @@ jobs: steps: - name: Checkout Repo uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - submodules: 'recursive' - name: Setup Java Version uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0 From 7f922aee384d1d07c3fffa6b2734331fb34f70f3 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 5 Nov 2026 15:29:50 +0100 Subject: [PATCH 05/19] build(samples): Link the Android sample's Gradle files to the SDK build's gradle.properties, gradlew, gradle/wrapper and gradle/gradle-daemon-jvm.properties are now symlinks to the SDK build's files. Both builds pick the same daemon JDK and JVM arguments, so they can share a daemon, and the sample reads the SDK version from the versionName property instead of parsing the root file. The copied gradle.properties had drifted from the root one (-Xmx6g and CrashOnOutOfMemoryError instead of -Xmx8g and HeapDumpOnOutOfMemoryError). Dependabot also scans the sample build, so the Develocity plugins in its settings stay current. Co-Authored-By: Claude Opus 5.5 --- .github/dependabot.yml | 5 +++- .../sentry-samples-android/README.md | 3 ++- .../sentry-samples-android/build.gradle.kts | 3 ++- .../sentry-samples-android/gradle.properties | 25 +------------------ .../gradle/gradle-daemon-jvm.properties | 1 + .../sentry-samples-android/gradle/wrapper | 1 + sentry-samples/sentry-samples-android/gradlew | 1 + .../settings.gradle.kts | 16 +----------- 8 files changed, 13 insertions(+), 42 deletions(-) mode change 100644 => 120000 sentry-samples/sentry-samples-android/gradle.properties create mode 120000 sentry-samples/sentry-samples-android/gradle/gradle-daemon-jvm.properties create mode 120000 sentry-samples/sentry-samples-android/gradle/wrapper create mode 120000 sentry-samples/sentry-samples-android/gradlew diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2824699563c..fb278498bcf 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,7 +7,10 @@ registries: password: dummy # Required by dependabot updates: - package-ecosystem: "gradle" - directory: "/" + # The Android sample is a separate build that the root build doesn't include. + directories: + - "/" + - "/sentry-samples/sentry-samples-android" registries: - gradle-plugin-portal schedule: diff --git a/sentry-samples/sentry-samples-android/README.md b/sentry-samples/sentry-samples-android/README.md index 35692c7b837..2c2b328e43e 100644 --- a/sentry-samples/sentry-samples-android/README.md +++ b/sentry-samples/sentry-samples-android/README.md @@ -15,6 +15,7 @@ or open the `sentry-samples/sentry-samples-android` directory in Android Studio The sample is a separate Gradle build that includes the SDK build, so changes to the SDK show up in the next build of the sample, with nothing to publish first. The SDK build doesn't include the sample, so `./gradlew` from the repository root doesn't build, lint or format it. +`gradle.properties`, `gradlew`, `gradle/wrapper` and `gradle/gradle-daemon-jvm.properties` are symlinks to the SDK build's, so both builds use the same Gradle, JDK and settings. You can also apply the [Sentry Android Gradle Plugin](https://github.com/getsentry/sentry-android-gradle-plugin) (SAGP) when building (not applied by default): @@ -22,7 +23,7 @@ You can also apply the [Sentry Android Gradle Plugin](https://github.com/getsent ./gradlew -p sentry-samples/sentry-samples-android installDebug -PuseSagp ``` -In Android Studio, add `useSagp=` (empty value) to the sample's `gradle.properties`, or pass `-PuseSagp` as a Gradle project property. +In Android Studio, add `useSagp=` (empty value) to `gradle.properties`, or pass `-PuseSagp` as a Gradle project property. For LLM-driven verification, use `adb` to inspect and interact with the running emulator. Compose-based screens often expose stable `Modifier.testTag()` values, which can be used with the UI hierarchy dump to locate controls reliably. diff --git a/sentry-samples/sentry-samples-android/build.gradle.kts b/sentry-samples/sentry-samples-android/build.gradle.kts index 8fb29bb4ecd..30e05043d8f 100644 --- a/sentry-samples/sentry-samples-android/build.gradle.kts +++ b/sentry-samples/sentry-samples-android/build.gradle.kts @@ -18,7 +18,8 @@ plugins { alias(libs.plugins.sqldelight) } -val sentryVersion = libs.versions.sentry.get() +// gradle.properties links to the SDK build's, so this is the SDK version. +val sentryVersion = providers.gradleProperty("versionName").get() if (providers.gradleProperty("useSagp").isPresent) { apply(plugin = "io.sentry.android.gradle") diff --git a/sentry-samples/sentry-samples-android/gradle.properties b/sentry-samples/sentry-samples-android/gradle.properties deleted file mode 100644 index 8751888c1a0..00000000000 --- a/sentry-samples/sentry-samples-android/gradle.properties +++ /dev/null @@ -1,24 +0,0 @@ -# Gradle properties are per build, so this repeats what the sample needs from the SDK build's -# gradle.properties. settings.gradle.kts reads the SDK version from that file. - -org.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=2g -XX:+CrashOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -org.gradle.caching=true -org.gradle.parallel=true -org.gradle.configureondemand=true -org.gradle.configuration-cache=true -org.gradle.configuration-cache.parallel=true - -android.useAndroidX=true -# AGP 9+ migration opt-outs until we remove kotlin-android plugin and adopt built-in Kotlin. -android.builtInKotlin=false -android.newDsl=false - -android.defaults.buildfeatures.renderscript=false -android.defaults.buildfeatures.shaders=false -android.defaults.buildfeatures.aidl=false -android.defaults.buildfeatures.resvalues=false -android.nonTransitiveRClass=true - -kotlin.stdlib.default.dependency=false - -systemProp.org.gradle.internal.http.socketTimeout=120000 diff --git a/sentry-samples/sentry-samples-android/gradle.properties b/sentry-samples/sentry-samples-android/gradle.properties new file mode 120000 index 00000000000..03ca90c515b --- /dev/null +++ b/sentry-samples/sentry-samples-android/gradle.properties @@ -0,0 +1 @@ +../../gradle.properties \ No newline at end of file diff --git a/sentry-samples/sentry-samples-android/gradle/gradle-daemon-jvm.properties b/sentry-samples/sentry-samples-android/gradle/gradle-daemon-jvm.properties new file mode 120000 index 00000000000..b6233ee4449 --- /dev/null +++ b/sentry-samples/sentry-samples-android/gradle/gradle-daemon-jvm.properties @@ -0,0 +1 @@ +../../../gradle/gradle-daemon-jvm.properties \ No newline at end of file diff --git a/sentry-samples/sentry-samples-android/gradle/wrapper b/sentry-samples/sentry-samples-android/gradle/wrapper new file mode 120000 index 00000000000..166ffc09a82 --- /dev/null +++ b/sentry-samples/sentry-samples-android/gradle/wrapper @@ -0,0 +1 @@ +../../../gradle/wrapper \ No newline at end of file diff --git a/sentry-samples/sentry-samples-android/gradlew b/sentry-samples/sentry-samples-android/gradlew new file mode 120000 index 00000000000..343e0d2caa4 --- /dev/null +++ b/sentry-samples/sentry-samples-android/gradlew @@ -0,0 +1 @@ +../../gradlew \ No newline at end of file diff --git a/sentry-samples/sentry-samples-android/settings.gradle.kts b/sentry-samples/sentry-samples-android/settings.gradle.kts index 06d5fc1c770..b8b12cc6993 100644 --- a/sentry-samples/sentry-samples-android/settings.gradle.kts +++ b/sentry-samples/sentry-samples-android/settings.gradle.kts @@ -44,27 +44,13 @@ develocity { } } -// Gradle reads gradle.properties only for the build it belongs to, so read the SDK version from the -// SDK build's file. -val sentryVersion: String = - providers - .fileContents(layout.settingsDirectory.file("../../gradle.properties")) - .asText - .map { java.util.Properties().apply { load(it.reader()) }.getProperty("versionName") } - .get() - dependencyResolutionManagement { repositories { google() mavenCentral() mavenLocal() } - versionCatalogs { - create("libs") { - from(files("../../gradle/libs.versions.toml")) - version("sentry", sentryVersion) - } - } + versionCatalogs { create("libs") { from(files("../../gradle/libs.versions.toml")) } } } rootProject.name = "sentry-samples-android" From d5f25eadd822cfd4e9ce9de05f9ff18a7047822c Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 5 Nov 2026 15:34:50 +0100 Subject: [PATCH 06/19] ci(samples): Drop the JUnit report step from the Samples workflow The samples and integration test builds have no unit tests, and phoenix-actions/test-reporting fails the job when it finds no JUnit XML, even with fail-on-error set to false. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/samples.yml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 238f4f3debd..819204d8e4b 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -46,16 +46,3 @@ jobs: name: test-results-${{ matrix.name }} path: | **/build/reports/* - - - name: Test Report - uses: phoenix-actions/test-reporting@7317eea6e13c47348dd0bb318669485157c518d6 # pin@v16 - if: always() - with: - name: JUnit ${{ matrix.name }} - list-suites: 'failed' - list-tests: 'failed' - path: | - **/build/test-results/**/*.xml - reporter: java-junit - output-to: step-summary - fail-on-error: false From 6d06ad6123256f6fcac2db2f828e7d8e9d0a3024 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 5 Nov 2026 17:31:10 +0100 Subject: [PATCH 07/19] build(samples): Bring back make format for every build The root spotlessApply can't reach the Android sample, because the SDK build doesn't include it. make format runs spotlessApply in the root and in the sample build, and the format workflow, AGENTS.md, CONTRIBUTING.md and the create-java-pr skill use it. CONTRIBUTING.md already documented make format, which #4476 removed. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/create-java-pr/SKILL.md | 4 ++-- .github/workflows/format-code.yml | 4 +--- AGENTS.md | 6 +++--- CONTRIBUTING.md | 6 +----- Makefile | 7 ++++++- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.claude/skills/create-java-pr/SKILL.md b/.claude/skills/create-java-pr/SKILL.md index 70bc4453a58..ef813f4b1c8 100644 --- a/.claude/skills/create-java-pr/SKILL.md +++ b/.claude/skills/create-java-pr/SKILL.md @@ -74,10 +74,10 @@ Derive the branch name from the changes being made. Use `feat/`, `fix/`, `ref/`, ## Step 2: Format Code and Regenerate API Files ```bash -./gradlew spotlessApply updateKotlinAbi +make format api ``` -This is **required** before every PR in this repo. It formats all Java/Kotlin code via Spotless and regenerates the `.api` binary compatibility files. +This is **required** before every PR in this repo. It formats all Java/Kotlin code via Spotless, including the separate builds the root `spotlessApply` doesn't reach, and regenerates the `.api` binary compatibility files. If the command fails, diagnose and fix the issue before continuing. diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml index 522baa31cda..e55cddb74fd 100644 --- a/.github/workflows/format-code.yml +++ b/.github/workflows/format-code.yml @@ -20,9 +20,7 @@ jobs: uses: gradle/actions/setup-gradle@8da46b539cd8fe6ab27ca75128919a18d757604c # v6.4.1 - name: Format with spotlessApply - run: | - ./gradlew spotlessApply - ./gradlew -p sentry-samples/sentry-samples-android spotlessApply + run: make format # actions/checkout fetches only a single commit in a detached HEAD state. Therefore # we need to pass the current branch, otherwise we can't commit the changes. diff --git a/AGENTS.md b/AGENTS.md index 5ea69289a16..43347411346 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,8 +41,8 @@ The project uses **Gradle** with Kotlin DSL. Key build files: ## Essential Commands ```bash -# Format code and regenerate .api files (REQUIRED before committing) -./gradlew spotlessApply updateKotlinAbi +# Format code in every build and regenerate .api files (REQUIRED before committing) +make format api # Run all tests and linter ./gradlew check @@ -77,7 +77,7 @@ resolves the per-module test task, including integration test suites, for you. 3. **Use todo tracking**: Work through todo items, marking them as complete as you go 4. **High-level communication**: Give high-level explanations of changes made, not step-by-step descriptions 5. **Simplicity first**: Make every task and code change as simple as possible. Avoid massive or complex changes. Impact as little code as possible. -6. **Format and regenerate**: Once done, format code and regenerate .api files: `./gradlew spotlessApply updateKotlinAbi` +6. **Format and regenerate**: Once done, format code and regenerate .api files: `make format api` 7. **Propose commit**: As final step, git stage relevant files and propose (but not execute) a single git commit command. This applies to implementation work; when the task is to open a PR, the `create-java-pr` skill takes over from here and does commit, push, and open it. ## Repository Skills diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 77714917518..d0642308ff8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -73,11 +73,7 @@ To format the changed code and make CI happy you can run: make format ``` -or - -```shell -./gradlew spotlessApply -``` +`./gradlew spotlessApply` alone formats only the SDK build, not the separate builds it doesn't include, such as the Android sample. # Binary compatibility validation diff --git a/Makefile b/Makefile index 45c6ed2a7a0..14f1ce9326c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all clean compile javadocs dryRelease update checkFormat api assembleUiTestRelease assembleUiTestCriticalRelease runUiTestCritical check preMerge publish +.PHONY: all clean compile javadocs dryRelease update format checkFormat api assembleUiTestRelease assembleUiTestCriticalRelease runUiTestCritical check preMerge publish all: stop clean javadocs compile assembleUiTests: assembleUiTestRelease @@ -25,6 +25,11 @@ dryRelease: update: ./gradlew dependencyUpdates -Drevision=release +# Spotless formats the code. The root spotlessApply doesn't reach the separate builds. +format: + ./gradlew spotlessApply + ./gradlew -p sentry-samples/sentry-samples-android spotlessApply + # Spotless check's code checkFormat: ./gradlew spotlessJavaCheck spotlessKotlinCheck From 175e10b507ebe83c141817dec1783ee963689b11 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 5 Nov 2026 18:15:32 +0100 Subject: [PATCH 08/19] build(samples): Read the sample's SDK version through the catalog The sample's settings now add versionName to the catalog as libs.versions.sentry, as the other separate builds do, and use the root build's Develocity plugin version. Co-Authored-By: Claude Opus 5.5 --- sentry-samples/sentry-samples-android/build.gradle.kts | 3 +-- .../sentry-samples-android/settings.gradle.kts | 10 ++++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/sentry-samples/sentry-samples-android/build.gradle.kts b/sentry-samples/sentry-samples-android/build.gradle.kts index 30e05043d8f..8fb29bb4ecd 100644 --- a/sentry-samples/sentry-samples-android/build.gradle.kts +++ b/sentry-samples/sentry-samples-android/build.gradle.kts @@ -18,8 +18,7 @@ plugins { alias(libs.plugins.sqldelight) } -// gradle.properties links to the SDK build's, so this is the SDK version. -val sentryVersion = providers.gradleProperty("versionName").get() +val sentryVersion = libs.versions.sentry.get() if (providers.gradleProperty("useSagp").isPresent) { apply(plugin = "io.sentry.android.gradle") diff --git a/sentry-samples/sentry-samples-android/settings.gradle.kts b/sentry-samples/sentry-samples-android/settings.gradle.kts index b8b12cc6993..d36c09c1554 100644 --- a/sentry-samples/sentry-samples-android/settings.gradle.kts +++ b/sentry-samples/sentry-samples-android/settings.gradle.kts @@ -33,7 +33,7 @@ pluginManagement { } plugins { - id("com.gradle.develocity") version "4.5.0" + id("com.gradle.develocity") version "4.6.1" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.8.0" } @@ -50,7 +50,13 @@ dependencyResolutionManagement { mavenCentral() mavenLocal() } - versionCatalogs { create("libs") { from(files("../../gradle/libs.versions.toml")) } } + versionCatalogs { + create("libs") { + from(files("../../gradle/libs.versions.toml")) + // gradle.properties links to the SDK build's, so versionName is the SDK version. + version("sentry", providers.gradleProperty("versionName").get()) + } + } } rootProject.name = "sentry-samples-android" From 645b4cf533ac3b2cc373a7fe67c8721b561ceb11 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 5 Nov 2026 18:15:32 +0100 Subject: [PATCH 09/19] ci(samples): Use the setup-gradle version of the other workflows Co-Authored-By: Claude Opus 5.5 --- .github/dependabot.yml | 2 +- .github/workflows/samples.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fb278498bcf..8015fe0a0d8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,7 +7,7 @@ registries: password: dummy # Required by dependabot updates: - package-ecosystem: "gradle" - # The Android sample is a separate build that the root build doesn't include. + # The root build doesn't include the separate sample and test builds. directories: - "/" - "/sentry-samples/sentry-samples-android" diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 819204d8e4b..70e42119c49 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -34,7 +34,7 @@ jobs: java-version: '21' - name: Setup Gradle - uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0 + uses: gradle/actions/setup-gradle@8da46b539cd8fe6ab27ca75128919a18d757604c # v6.4.1 - name: Run Tests and Lint run: ./gradlew -p ${{ matrix.path }} check From 306b092882d54a7e347f9f43d4b1b58f75e72245 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 5 Nov 2026 18:31:21 +0100 Subject: [PATCH 10/19] build(samples): Drop the comment on the macrobenchmark's target project Co-Authored-By: Claude Opus 5.5 --- .../sentry-samples-android/macrobenchmark/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/sentry-samples/sentry-samples-android/macrobenchmark/build.gradle.kts b/sentry-samples/sentry-samples-android/macrobenchmark/build.gradle.kts index b819504ddcb..a26fe088cd7 100644 --- a/sentry-samples/sentry-samples-android/macrobenchmark/build.gradle.kts +++ b/sentry-samples/sentry-samples-android/macrobenchmark/build.gradle.kts @@ -28,7 +28,6 @@ android { } } - // The sample app is the root project of this build. targetProjectPath = ":" // Run the test in its own process so it measures the target app cold, not itself. experimentalProperties["android.experimental.self-instrumenting"] = true From 84290c77568a05bae68b910b3a8eb52aec8c729d Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 5 Nov 2026 18:34:09 +0100 Subject: [PATCH 11/19] build(samples): Drop the header comment from the Android sample's settings Co-Authored-By: Claude Opus 5.5 --- sentry-samples/sentry-samples-android/settings.gradle.kts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sentry-samples/sentry-samples-android/settings.gradle.kts b/sentry-samples/sentry-samples-android/settings.gradle.kts index d36c09c1554..9d024bf958a 100644 --- a/sentry-samples/sentry-samples-android/settings.gradle.kts +++ b/sentry-samples/sentry-samples-android/settings.gradle.kts @@ -1,8 +1,3 @@ -// A standalone build that includes the SDK build, so it depends on the SDK through the same -// io.sentry:* coordinates an app would, and Gradle substitutes the SDK's projects for them. -// The SDK build doesn't include this one back, so its plugins and versions stay out of the SDK -// build. Run it with `./gradlew -p sentry-samples/sentry-samples-android `. - pluginManagement { repositories { // Prefer local SAGP artifact if one exists; otherwise fall back to libs.versions.toml. From c835065534e775677286b8fb228a1d6e26a9365d Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Tue, 27 Oct 2026 13:03:54 +0100 Subject: [PATCH 12/19] build(samples): Move the Android integration tests to their own build sentry-uitest-android, sentry-uitest-android-critical and test-app-size are now a standalone Gradle build that includes the SDK build, like the Android sample. They depend on io.sentry:* coordinates, which Gradle substitutes with the SDK's projects, including the unpublished sentry-test-support. The SDK build doesn't include them back, so SAGP, errorprone's uitest checks and their AndroidX test dependencies no longer load in the SDK build. AGP stays shared: a composite build can only use one AGP version, so the AGP matrix job's VERSION_AGP still applies to both builds. Root check and spotlessApply no longer reach these apps, so preMerge runs this build's check, which covers what the root check ran for them: lint, detekt and spotless. The format workflow runs its spotlessApply, and the AGP matrix, UI test and size workflows call it with -p. APK paths don't change. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/agp-matrix.yml | 2 +- .github/workflows/integration-tests-size.yml | 2 +- AGENTS.md | 4 +- Makefile | 14 +++- sentry-android-integration-tests/README.md | 4 + .../build.gradle.kts | 10 +++ .../gradle.properties | 24 ++++++ .../build.gradle.kts | 6 +- .../sentry-uitest-android/README.md | 4 +- .../sentry-uitest-android/build.gradle.kts | 20 +++-- .../settings.gradle.kts | 77 +++++++++++++++++++ .../test-app-size/build.gradle.kts | 18 +++-- settings.gradle.kts | 3 - 13 files changed, 162 insertions(+), 26 deletions(-) create mode 100644 sentry-android-integration-tests/build.gradle.kts create mode 100644 sentry-android-integration-tests/gradle.properties create mode 100644 sentry-android-integration-tests/settings.gradle.kts diff --git a/.github/workflows/agp-matrix.yml b/.github/workflows/agp-matrix.yml index 81127cb468f..b55ee483720 100644 --- a/.github/workflows/agp-matrix.yml +++ b/.github/workflows/agp-matrix.yml @@ -85,7 +85,7 @@ jobs: disable-spellchecker: true emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disk-size: 4096M - script: ./gradlew sentry-android-integration-tests:sentry-uitest-android:connectedReleaseAndroidTest -Denvironment=github --daemon + script: ./gradlew -p sentry-android-integration-tests :sentry-uitest-android:connectedReleaseAndroidTest -Denvironment=github --daemon - name: Upload test results if: always() diff --git a/.github/workflows/integration-tests-size.yml b/.github/workflows/integration-tests-size.yml index 9705499f430..4254edcf4b2 100644 --- a/.github/workflows/integration-tests-size.yml +++ b/.github/workflows/integration-tests-size.yml @@ -29,6 +29,6 @@ jobs: uses: gradle/actions/setup-gradle@8da46b539cd8fe6ab27ca75128919a18d757604c # v6.4.1 - name: Size Analysis - run: ./gradlew :sentry-android-integration-tests:test-app-size:bundleRelease + run: ./gradlew -p sentry-android-integration-tests :test-app-size:bundleRelease env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} diff --git a/AGENTS.md b/AGENTS.md index 43347411346..ee6436f8d23 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -59,8 +59,8 @@ resolves the per-module test task, including integration test suites, for you. ### Android-Specific Commands ```bash -# Assemble Android test APKs -./gradlew :sentry-android-integration-tests:sentry-uitest-android:assembleRelease :sentry-android-integration-tests:sentry-uitest-android:assembleAndroidTest +# Assemble Android test APKs, in a separate Gradle build that includes this one +./gradlew -p sentry-android-integration-tests :sentry-uitest-android:assembleRelease :sentry-uitest-android:assembleAndroidTest # Run critical UI tests ./scripts/test-ui-critical.sh diff --git a/Makefile b/Makefile index 14f1ce9326c..dca9b7bbe12 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ -.PHONY: all clean compile javadocs dryRelease update format checkFormat api assembleUiTestRelease assembleUiTestCriticalRelease runUiTestCritical check preMerge publish +.PHONY: all clean compile javadocs dryRelease update format checkFormat api assembleUiTestRelease assembleUiTestCriticalRelease runUiTestCritical check checkIntegrationTests preMerge publish all: stop clean javadocs compile assembleUiTests: assembleUiTestRelease -preMerge: check +preMerge: check checkIntegrationTests publish: clean dryRelease # deep clean @@ -29,11 +29,13 @@ update: format: ./gradlew spotlessApply ./gradlew -p sentry-samples/sentry-samples-android spotlessApply + ./gradlew -p sentry-android-integration-tests spotlessApply # Spotless check's code checkFormat: ./gradlew spotlessJavaCheck spotlessKotlinCheck ./gradlew -p sentry-samples/sentry-samples-android spotlessJavaCheck spotlessKotlinCheck + ./gradlew -p sentry-android-integration-tests spotlessJavaCheck spotlessKotlinCheck # Binary compatibility validator api: @@ -41,11 +43,11 @@ api: # Assemble release and Android test apk of the uitest-android module assembleUiTestRelease: - ./gradlew :sentry-android-integration-tests:sentry-uitest-android:assembleRelease :sentry-android-integration-tests:sentry-uitest-android:assembleAndroidTest + ./gradlew -p sentry-android-integration-tests :sentry-uitest-android:assembleRelease :sentry-uitest-android:assembleAndroidTest # Assemble release of the uitest-android-critical module assembleUiTestCriticalRelease: - ./gradlew :sentry-android-integration-tests:sentry-uitest-android-critical:assembleRelease + ./gradlew -p sentry-android-integration-tests :sentry-uitest-android-critical:assembleRelease # Run Maestro tests for the uitest-android-critical module runUiTestCritical: @@ -54,3 +56,7 @@ runUiTestCritical: # Run tests and lint check: ./gradlew check + +# The Android integration tests are a separate build, so the root `check` doesn't reach them +checkIntegrationTests: + ./gradlew -p sentry-android-integration-tests check diff --git a/sentry-android-integration-tests/README.md b/sentry-android-integration-tests/README.md index 4b3c495a978..581d2df5439 100644 --- a/sentry-android-integration-tests/README.md +++ b/sentry-android-integration-tests/README.md @@ -1,4 +1,8 @@ # Android performance-impact and integration tests +This directory is a separate Gradle build that includes the SDK build, so run its tasks with +`./gradlew -p sentry-android-integration-tests `. The SDK build doesn't include it, so +`./gradlew` from the repository root doesn't build, lint or format these apps. + * [Espresso-based UI tests](./sentry-uitest-android) - run within SauceLabs (see /.sauce/*.yml) * Also used for compatibility test matrix against new AGP versions diff --git a/sentry-android-integration-tests/build.gradle.kts b/sentry-android-integration-tests/build.gradle.kts new file mode 100644 index 00000000000..be90495faf9 --- /dev/null +++ b/sentry-android-integration-tests/build.gradle.kts @@ -0,0 +1,10 @@ +// Loads the plugins once for every subproject, so they all share one classpath. +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.compose) apply false + alias(libs.plugins.errorprone) apply false + alias(libs.plugins.detekt) apply false + alias(libs.plugins.gradle.versions) apply false + alias(libs.plugins.sentry) apply false +} diff --git a/sentry-android-integration-tests/gradle.properties b/sentry-android-integration-tests/gradle.properties new file mode 100644 index 00000000000..e83338c383b --- /dev/null +++ b/sentry-android-integration-tests/gradle.properties @@ -0,0 +1,24 @@ +# Gradle properties are per build, so this repeats what these apps need from the SDK build's +# gradle.properties. settings.gradle.kts reads the SDK version from that file. + +org.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=2g -XX:+CrashOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC +org.gradle.caching=true +org.gradle.parallel=true +org.gradle.configureondemand=true +org.gradle.configuration-cache=true +org.gradle.configuration-cache.parallel=true + +android.useAndroidX=true +# AGP 9+ migration opt-outs until we remove kotlin-android plugin and adopt built-in Kotlin. +android.builtInKotlin=false +android.newDsl=false + +android.defaults.buildfeatures.renderscript=false +android.defaults.buildfeatures.shaders=false +android.defaults.buildfeatures.aidl=false +android.defaults.buildfeatures.resvalues=false +android.nonTransitiveRClass=true + +kotlin.stdlib.default.dependency=false + +systemProp.org.gradle.internal.http.socketTimeout=120000 diff --git a/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts b/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts index 8a22b91890c..fc43a6ad062 100644 --- a/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts +++ b/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts @@ -5,8 +5,12 @@ plugins { id("com.android.application") alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) + id("io.sentry.spotless") + id("io.sentry.jdk-target") } +val sentryVersion = libs.versions.sentry.get() + android { compileSdk = libs.versions.compileSdk.get().toInt() compileSdkMinor = libs.versions.compileSdkMinor.get().toInt() @@ -47,7 +51,7 @@ dependencies { implementation(libs.androidx.compose.material3) implementation(libs.androidx.constraintlayout) implementation(libs.androidx.core) - implementation(projects.sentryAndroidCore) + implementation("io.sentry:sentry-android-core:$sentryVersion") } tasks.withType().configureEach { diff --git a/sentry-android-integration-tests/sentry-uitest-android/README.md b/sentry-android-integration-tests/sentry-uitest-android/README.md index d6ccf2f1f93..9f6f57b3fc0 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/README.md +++ b/sentry-android-integration-tests/sentry-uitest-android/README.md @@ -5,13 +5,13 @@ By default the envelopes sent to relay are caught by a mock server which allows # How to use -Simply run `./gradlew connectedCheck` to run all ui tests of all modules (requires a connected device, either physical or an emulator). +Simply run `./gradlew -p sentry-android-integration-tests connectedCheck` to run all ui tests of all modules (requires a connected device, either physical or an emulator). # SauceLabs To run on saucelabs execute following commands (need also `SAUCE_USERNAME` and `SAUCE_ACCESS_KEY` environment variables): For End 2 End: ``` -./gradlew :sentry-android-integration-tests:sentry-uitest-android:assembleRelease :sentry-android-integration-tests:sentry-uitest-android:assembleAndroidTest +./gradlew -p sentry-android-integration-tests :sentry-uitest-android:assembleRelease :sentry-uitest-android:assembleAndroidTest saucectl run -c .sauce/sentry-uitest-android-end2end.yml ``` diff --git a/sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts b/sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts index 53cc88fec46..4aa91bf3735 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts +++ b/sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts @@ -9,8 +9,12 @@ plugins { alias(libs.plugins.errorprone) alias(libs.plugins.gradle.versions) alias(libs.plugins.detekt) + id("io.sentry.spotless") + id("io.sentry.jdk-target") } +val sentryVersion = libs.versions.sentry.get() + android { compileSdk = libs.versions.compileSdk.get().toInt() compileSdkMinor = libs.versions.compileSdkMinor.get().toInt() @@ -46,7 +50,7 @@ android { signingConfigs { getByName("debug") { - storeFile = rootProject.file("debug.keystore") + storeFile = file("../../debug.keystore") storePassword = "android" keyAlias = "androiddebugkey" keyPassword = "android" @@ -90,10 +94,10 @@ dependencies { implementation(kotlin("stdlib-jdk8", getKotlinPluginVersion())) if (applySentryIntegrations) { - implementation(projects.sentryAndroid) - implementation(projects.sentryCompose) + implementation("io.sentry:sentry-android:$sentryVersion") + implementation("io.sentry:sentry-compose:$sentryVersion") } else { - implementation(projects.sentryAndroidCore) + implementation("io.sentry:sentry-android-core:$sentryVersion") } implementation(libs.androidx.appcompat) implementation(libs.androidx.activity.compose) @@ -112,7 +116,7 @@ dependencies { errorprone(libs.nullaway) androidTestUtil(libs.androidx.test.orchestrator) - androidTestImplementation(projects.sentryTestSupport) + androidTestImplementation("io.sentry:sentry-test-support:$sentryVersion") androidTestImplementation(libs.androidx.test.core.ktx) androidTestImplementation(libs.androidx.test.espresso.core) androidTestImplementation(libs.androidx.test.ext.junit) @@ -132,6 +136,12 @@ tasks.withType().configureEach { } } +detekt { + buildUponDefaultConfig = true + allRules = true + config.setFrom(file("../../detekt.yml")) +} + tasks.withType().configureEach { // Target version of the generated JVM bytecode. It is used for type resolution. jvmTarget = JavaVersion.VERSION_1_8.toString() diff --git a/sentry-android-integration-tests/settings.gradle.kts b/sentry-android-integration-tests/settings.gradle.kts new file mode 100644 index 00000000000..f4cbdf257fa --- /dev/null +++ b/sentry-android-integration-tests/settings.gradle.kts @@ -0,0 +1,77 @@ +// A standalone build that includes the SDK build, so it depends on the SDK through the same +// io.sentry:* coordinates an app would, and Gradle substitutes the SDK's projects for them. +// The SDK build doesn't include this one back, so its plugins and versions stay out of the SDK +// build. Run it with `./gradlew -p sentry-android-integration-tests `. + +pluginManagement { + repositories { + // Prefer local SAGP artifact if one exists; otherwise fall back to libs.versions.toml. + if (providers.gradleProperty("useSagp").isPresent) { + mavenLocal { + content { + includeGroup("io.sentry") + includeGroup("io.sentry.android.gradle") + } + } + } + mavenCentral() + gradlePluginPortal() + google() + } + + // Every build in a composite must use the same AGP, so each build's settings applies the AGP + // matrix job's override. + System.getenv("VERSION_AGP")?.let { agpVersion -> + resolutionStrategy { + eachPlugin { + if (requested.id.id.startsWith("com.android")) { + useVersion(agpVersion) + } + } + } + } +} + +plugins { + id("com.gradle.develocity") version "4.5.0" + id("com.gradle.common-custom-user-data-gradle-plugin") version "2.8.0" +} + +develocity { + buildScan { + termsOfUseUrl.set("https://gradle.com/help/legal-terms-of-use") + termsOfUseAgree.set("yes") + } +} + +// Gradle reads gradle.properties only for the build it belongs to, so read the SDK version from the +// SDK build's file. +val sentryVersion: String = + providers + .fileContents(layout.settingsDirectory.file("../gradle.properties")) + .asText + .map { java.util.Properties().apply { load(it.reader()) }.getProperty("versionName") } + .get() + +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + mavenLocal() + } + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + version("sentry", sentryVersion) + } + } +} + +rootProject.name = "sentry-android-integration-tests" + +includeBuild("..") + +// For the io.sentry.spotless and io.sentry.jdk-target convention plugins. +includeBuild("../build-logic") + +include("sentry-uitest-android", "sentry-uitest-android-critical", "test-app-size") diff --git a/sentry-android-integration-tests/test-app-size/build.gradle.kts b/sentry-android-integration-tests/test-app-size/build.gradle.kts index 6260c761d53..678ae4103e0 100644 --- a/sentry-android-integration-tests/test-app-size/build.gradle.kts +++ b/sentry-android-integration-tests/test-app-size/build.gradle.kts @@ -1,8 +1,12 @@ plugins { id("com.android.application") id("io.sentry.android.gradle") + id("io.sentry.spotless") + id("io.sentry.jdk-target") } +val sentryVersion = libs.versions.sentry.get() + android { namespace = "io.sentry.tests.size" compileSdk = libs.versions.compileSdk.get().toInt() @@ -13,7 +17,7 @@ android { minSdk = libs.versions.minSdk.get().toInt() targetSdk = libs.versions.targetSdk.get().toInt() versionCode = 1 - versionName = project.version.toString() + versionName = sentryVersion } buildTypes { @@ -42,12 +46,12 @@ configurations.configureEach { } dependencies { - implementation(projects.sentryAndroid) - implementation(projects.sentryAndroidTimber) - implementation(projects.sentryAndroidSqlite) - implementation(projects.sentryOkhttp) - implementation(projects.sentryCompose) - implementation(projects.sentryAndroidFragment) + implementation("io.sentry:sentry-android:$sentryVersion") + implementation("io.sentry:sentry-android-timber:$sentryVersion") + implementation("io.sentry:sentry-android-sqlite:$sentryVersion") + implementation("io.sentry:sentry-okhttp:$sentryVersion") + implementation("io.sentry:sentry-compose:$sentryVersion") + implementation("io.sentry:sentry-android-fragment:$sentryVersion") } sentry { diff --git a/settings.gradle.kts b/settings.gradle.kts index 378979411f0..bd377c7a03b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -139,8 +139,5 @@ include( "sentry-samples:sentry-samples-spring-boot-4-otlp", "sentry-samples:sentry-samples-spring-boot-4-webflux", "sentry-samples:sentry-samples-netflix-dgs", - "sentry-android-integration-tests:sentry-uitest-android-critical", - "sentry-android-integration-tests:sentry-uitest-android", - "sentry-android-integration-tests:test-app-size", "sentry-samples:sentry-samples-openfeign" ) From ce6fb57dc1a48588e618da47081abf26ff0cf6a7 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Mon, 2 Nov 2026 14:13:24 +0100 Subject: [PATCH 13/19] ci(samples): Run the Android integration tests' check in the Samples workflow Adds the integration tests build to the Samples workflow's matrix instead of a `make preMerge` target. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/samples.yml | 2 ++ Makefile | 8 ++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 70e42119c49..c0376a85328 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -22,6 +22,8 @@ jobs: include: - name: sentry-samples-android path: sentry-samples/sentry-samples-android + - name: sentry-android-integration-tests + path: sentry-android-integration-tests steps: - name: Checkout Repo diff --git a/Makefile b/Makefile index dca9b7bbe12..d88f81d09ab 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ -.PHONY: all clean compile javadocs dryRelease update format checkFormat api assembleUiTestRelease assembleUiTestCriticalRelease runUiTestCritical check checkIntegrationTests preMerge publish +.PHONY: all clean compile javadocs dryRelease update format checkFormat api assembleUiTestRelease assembleUiTestCriticalRelease runUiTestCritical check preMerge publish all: stop clean javadocs compile assembleUiTests: assembleUiTestRelease -preMerge: check checkIntegrationTests +preMerge: check publish: clean dryRelease # deep clean @@ -56,7 +56,3 @@ runUiTestCritical: # Run tests and lint check: ./gradlew check - -# The Android integration tests are a separate build, so the root `check` doesn't reach them -checkIntegrationTests: - ./gradlew -p sentry-android-integration-tests check From d6eff98a1f4778919082741e439aea2b90faa8f3 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 5 Nov 2026 16:56:36 +0100 Subject: [PATCH 14/19] build: Drop the root's SAGP declaration and local SAGP repository With the Android sample and integration tests in their own builds, no project in the SDK build applies the Sentry Android Gradle Plugin. The root no longer declares it or looks it up in mavenLocal when useSagp is set; those builds declare it and handle useSagp themselves. The root keeps the AGP declaration: the SDK's Android modules apply com.android.library without a version and get AGP from it. Co-Authored-By: Claude Opus 5.5 --- build.gradle.kts | 4 ---- settings.gradle.kts | 9 --------- 2 files changed, 13 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index f880ab151b5..27a81c7a9a6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,7 +25,6 @@ plugins { alias(libs.plugins.gradle.versions) apply false alias(libs.plugins.spring.dependency.management) apply false id("io.sentry.javadoc.aggregate") - alias(libs.plugins.sentry) apply false alias(libs.plugins.android.application) apply false } @@ -34,9 +33,6 @@ buildscript { google() } dependencies { - // add classpath of sentry android gradle plugin - // classpath("io.sentry:sentry-android-gradle-plugin:{version}") - classpath(libs.commons.compress) } } diff --git a/settings.gradle.kts b/settings.gradle.kts index bd377c7a03b..aae18edb922 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,15 +2,6 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") pluginManagement { repositories { - // Prefer local SAGP artifact if one exists; otherwise fall back to libs.versions.toml. - if (providers.gradleProperty("useSagp").isPresent) { - mavenLocal { - content { - includeGroup("io.sentry") - includeGroup("io.sentry.android.gradle") - } - } - } mavenCentral() gradlePluginPortal() google() From 96f4e02cd90744d623970b2b8b2be1c4320ac290 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 5 Nov 2026 17:25:18 +0100 Subject: [PATCH 15/19] build(samples): Link the integration tests' Gradle files to the SDK build's gradle.properties, gradlew, gradle/wrapper and gradle/gradle-daemon-jvm.properties are now symlinks to the SDK build's files, as in the Android sample. The copied gradle.properties had drifted from the root one (-Xmx6g and CrashOnOutOfMemoryError), and the build had no wrapper or daemon JDK pin. The SDK version now comes from the shared versionName property instead of parsing the root file. The root drops android.experimental.lint.version. It equals the lint bundled with AGP 9.4.0, so it changes nothing for the SDK build, and sentry-uitest-android's warningsAsErrors fails on it once the integration tests share the root gradle.properties. Co-Authored-By: Claude Opus 5.5 --- gradle.properties | 1 - sentry-android-integration-tests/README.md | 2 ++ .../gradle.properties | 25 +------------------ .../gradle/gradle-daemon-jvm.properties | 1 + .../gradle/wrapper | 1 + sentry-android-integration-tests/gradlew | 1 + .../settings.gradle.kts | 12 ++------- 7 files changed, 8 insertions(+), 35 deletions(-) mode change 100644 => 120000 sentry-android-integration-tests/gradle.properties create mode 120000 sentry-android-integration-tests/gradle/gradle-daemon-jvm.properties create mode 120000 sentry-android-integration-tests/gradle/wrapper create mode 120000 sentry-android-integration-tests/gradlew diff --git a/gradle.properties b/gradle.properties index eeefd38c92d..417334e771c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,6 @@ android.useAndroidX=true # AGP 9+ migration opt-outs until we remove kotlin-android plugin and adopt built-in Kotlin. android.builtInKotlin=false android.newDsl=false -android.experimental.lint.version=9.4.0 android.disableAgpUpgradePrompt=true diff --git a/sentry-android-integration-tests/README.md b/sentry-android-integration-tests/README.md index 581d2df5439..2e84e1cbd06 100644 --- a/sentry-android-integration-tests/README.md +++ b/sentry-android-integration-tests/README.md @@ -3,6 +3,8 @@ This directory is a separate Gradle build that includes the SDK build, so run its tasks with `./gradlew -p sentry-android-integration-tests `. The SDK build doesn't include it, so `./gradlew` from the repository root doesn't build, lint or format these apps. +`gradle.properties`, `gradlew`, `gradle/wrapper` and `gradle/gradle-daemon-jvm.properties` are +symlinks to the SDK build's, so both builds use the same Gradle, JDK and settings. * [Espresso-based UI tests](./sentry-uitest-android) - run within SauceLabs (see /.sauce/*.yml) * Also used for compatibility test matrix against new AGP versions diff --git a/sentry-android-integration-tests/gradle.properties b/sentry-android-integration-tests/gradle.properties deleted file mode 100644 index e83338c383b..00000000000 --- a/sentry-android-integration-tests/gradle.properties +++ /dev/null @@ -1,24 +0,0 @@ -# Gradle properties are per build, so this repeats what these apps need from the SDK build's -# gradle.properties. settings.gradle.kts reads the SDK version from that file. - -org.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=2g -XX:+CrashOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -org.gradle.caching=true -org.gradle.parallel=true -org.gradle.configureondemand=true -org.gradle.configuration-cache=true -org.gradle.configuration-cache.parallel=true - -android.useAndroidX=true -# AGP 9+ migration opt-outs until we remove kotlin-android plugin and adopt built-in Kotlin. -android.builtInKotlin=false -android.newDsl=false - -android.defaults.buildfeatures.renderscript=false -android.defaults.buildfeatures.shaders=false -android.defaults.buildfeatures.aidl=false -android.defaults.buildfeatures.resvalues=false -android.nonTransitiveRClass=true - -kotlin.stdlib.default.dependency=false - -systemProp.org.gradle.internal.http.socketTimeout=120000 diff --git a/sentry-android-integration-tests/gradle.properties b/sentry-android-integration-tests/gradle.properties new file mode 120000 index 00000000000..7677fb73be8 --- /dev/null +++ b/sentry-android-integration-tests/gradle.properties @@ -0,0 +1 @@ +../gradle.properties \ No newline at end of file diff --git a/sentry-android-integration-tests/gradle/gradle-daemon-jvm.properties b/sentry-android-integration-tests/gradle/gradle-daemon-jvm.properties new file mode 120000 index 00000000000..284e270637d --- /dev/null +++ b/sentry-android-integration-tests/gradle/gradle-daemon-jvm.properties @@ -0,0 +1 @@ +../../gradle/gradle-daemon-jvm.properties \ No newline at end of file diff --git a/sentry-android-integration-tests/gradle/wrapper b/sentry-android-integration-tests/gradle/wrapper new file mode 120000 index 00000000000..3232fe4e272 --- /dev/null +++ b/sentry-android-integration-tests/gradle/wrapper @@ -0,0 +1 @@ +../../gradle/wrapper \ No newline at end of file diff --git a/sentry-android-integration-tests/gradlew b/sentry-android-integration-tests/gradlew new file mode 120000 index 00000000000..502f5a2d3ec --- /dev/null +++ b/sentry-android-integration-tests/gradlew @@ -0,0 +1 @@ +../gradlew \ No newline at end of file diff --git a/sentry-android-integration-tests/settings.gradle.kts b/sentry-android-integration-tests/settings.gradle.kts index f4cbdf257fa..3eba475aec0 100644 --- a/sentry-android-integration-tests/settings.gradle.kts +++ b/sentry-android-integration-tests/settings.gradle.kts @@ -44,15 +44,6 @@ develocity { } } -// Gradle reads gradle.properties only for the build it belongs to, so read the SDK version from the -// SDK build's file. -val sentryVersion: String = - providers - .fileContents(layout.settingsDirectory.file("../gradle.properties")) - .asText - .map { java.util.Properties().apply { load(it.reader()) }.getProperty("versionName") } - .get() - dependencyResolutionManagement { repositories { google() @@ -62,7 +53,8 @@ dependencyResolutionManagement { versionCatalogs { create("libs") { from(files("../gradle/libs.versions.toml")) - version("sentry", sentryVersion) + // gradle.properties links to the SDK build's, so versionName is the SDK version. + version("sentry", providers.gradleProperty("versionName").get()) } } } From 9b474103c4be9dacf4ad80e4a94184ac895e4c40 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 5 Nov 2026 17:49:38 +0100 Subject: [PATCH 16/19] build(samples): Include each integration test project on its own line Adding or removing a project then changes only its own line. Co-Authored-By: Claude Opus 5.5 --- sentry-android-integration-tests/settings.gradle.kts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sentry-android-integration-tests/settings.gradle.kts b/sentry-android-integration-tests/settings.gradle.kts index 3eba475aec0..8fd56bf5535 100644 --- a/sentry-android-integration-tests/settings.gradle.kts +++ b/sentry-android-integration-tests/settings.gradle.kts @@ -66,4 +66,6 @@ includeBuild("..") // For the io.sentry.spotless and io.sentry.jdk-target convention plugins. includeBuild("../build-logic") -include("sentry-uitest-android", "sentry-uitest-android-critical", "test-app-size") +include("sentry-uitest-android") +include("sentry-uitest-android-critical") +include("test-app-size") From 8dd0deb3877b60cc257e1f00ba57dbd64f15607f Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 5 Nov 2026 18:16:01 +0100 Subject: [PATCH 17/19] build(samples): Keep the integration tests' Develocity and dependency versions current The integration tests' settings use the root build's Develocity plugin version, and Dependabot also scans their build, as it does the Android sample's. Co-Authored-By: Claude Opus 5.5 --- .github/dependabot.yml | 1 + sentry-android-integration-tests/settings.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8015fe0a0d8..8e712646a1e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,6 +11,7 @@ updates: directories: - "/" - "/sentry-samples/sentry-samples-android" + - "/sentry-android-integration-tests" registries: - gradle-plugin-portal schedule: diff --git a/sentry-android-integration-tests/settings.gradle.kts b/sentry-android-integration-tests/settings.gradle.kts index 8fd56bf5535..4eacc2ddab4 100644 --- a/sentry-android-integration-tests/settings.gradle.kts +++ b/sentry-android-integration-tests/settings.gradle.kts @@ -33,7 +33,7 @@ pluginManagement { } plugins { - id("com.gradle.develocity") version "4.5.0" + id("com.gradle.develocity") version "4.6.1" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.8.0" } From 2565610c527c26325c247267a8c54758ad84d40f Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 5 Nov 2026 18:31:30 +0100 Subject: [PATCH 18/19] build(samples): Drop the comment on the integration tests' root plugins Co-Authored-By: Claude Opus 5.5 --- sentry-android-integration-tests/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/sentry-android-integration-tests/build.gradle.kts b/sentry-android-integration-tests/build.gradle.kts index be90495faf9..53972e7fdb1 100644 --- a/sentry-android-integration-tests/build.gradle.kts +++ b/sentry-android-integration-tests/build.gradle.kts @@ -1,4 +1,3 @@ -// Loads the plugins once for every subproject, so they all share one classpath. plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.android) apply false From 27ec18e6831ea14cf5cbbfd0ca07952635e13f20 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 5 Nov 2026 18:34:23 +0100 Subject: [PATCH 19/19] build(samples): Drop the header comment from the integration tests' settings Co-Authored-By: Claude Opus 5.5 --- sentry-android-integration-tests/settings.gradle.kts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sentry-android-integration-tests/settings.gradle.kts b/sentry-android-integration-tests/settings.gradle.kts index 4eacc2ddab4..5a0d060f7d7 100644 --- a/sentry-android-integration-tests/settings.gradle.kts +++ b/sentry-android-integration-tests/settings.gradle.kts @@ -1,8 +1,3 @@ -// A standalone build that includes the SDK build, so it depends on the SDK through the same -// io.sentry:* coordinates an app would, and Gradle substitutes the SDK's projects for them. -// The SDK build doesn't include this one back, so its plugins and versions stay out of the SDK -// build. Run it with `./gradlew -p sentry-android-integration-tests `. - pluginManagement { repositories { // Prefer local SAGP artifact if one exists; otherwise fall back to libs.versions.toml.