From 40fffae7206513e718a27cb37041260d3902e46a Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 29 May 2026 14:35:35 +0200 Subject: [PATCH 01/17] ci(benchmark): Add help task configuration benchmark on PRs Add a pull_request workflow that benchmarks this project's `help` task with the configuration cache disabled (2 warm-ups, 5 builds), comparing the PR base commit against the head commit in a single gradle-profiler run via the git-checkout mutator. This is separate from the existing duckduckgo benchmark build: it profiles configuration time of this project rather than the runtime cost of applying the plugin to a sample app, and it runs automatically on every PR. Results are uploaded as an artifact and summarized as a sticky PR comment. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/benchmark-help-config.yml | 69 +++++++++++++++++++ scripts/benchmark/help-config-cache.scenarios | 35 ++++++++++ scripts/benchmark/help-config-comment.py | 62 +++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 .github/workflows/benchmark-help-config.yml create mode 100644 scripts/benchmark/help-config-cache.scenarios create mode 100644 scripts/benchmark/help-config-comment.py diff --git a/.github/workflows/benchmark-help-config.yml b/.github/workflows/benchmark-help-config.yml new file mode 100644 index 000000000..4a2af2853 --- /dev/null +++ b/.github/workflows/benchmark-help-config.yml @@ -0,0 +1,69 @@ +name: Benchmark help task configuration + +on: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + benchmark-help-config: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - name: Checkout Repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + # Full history so gradle-profiler's git-checkout can reach both the + # base and head commits of the PR. + fetch-depth: 0 + + - name: Set up Java + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + distribution: 'temurin' + java-version: '17' + + - name: Install Gradle Profiler and benchmark the help task + env: + BASE_REF: ${{ github.event.pull_request.base.sha }} + HEAD_REF: ${{ github.event.pull_request.head.sha }} + run: | + curl -s "https://get.sdkman.io" | bash + source "$HOME/.sdkman/bin/sdkman-init.sh" + sdk install gradleprofiler 0.24.0 + gradle-profiler --benchmark \ + --scenario-file scripts/benchmark/help-config-cache.scenarios \ + --output-dir out/help-config + + - name: Upload results + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: help-config-benchmark + path: out/help-config/ + + - name: Build PR comment + run: python3 scripts/benchmark/help-config-comment.py out/help-config/benchmark.csv comment.md + + - name: Post comparison comment + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const fs = require('fs'); + const marker = ''; + const body = fs.readFileSync('comment.md', 'utf8'); + const { owner, repo } = context.repo; + const issue_number = context.issue.number; + const comments = await github.paginate(github.rest.issues.listComments, { + owner, repo, issue_number, + }); + const existing = comments.find((c) => c.body.includes(marker)); + if (existing) { + await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); + } else { + await github.rest.issues.createComment({ owner, repo, issue_number, body }); + } diff --git a/scripts/benchmark/help-config-cache.scenarios b/scripts/benchmark/help-config-cache.scenarios new file mode 100644 index 000000000..391fbb7e8 --- /dev/null +++ b/scripts/benchmark/help-config-cache.scenarios @@ -0,0 +1,35 @@ +# Benchmarks the configuration-phase performance of this project's `help` task +# with the configuration cache disabled, comparing the PR base commit against +# the PR head commit in a single gradle-profiler run via the git-checkout +# mutator. +# +# BASE_REF and HEAD_REF are supplied as environment variables by the CI +# workflow; HOCON falls back to environment variables for ${...} lookups. +# +# `help` configures the whole build while executing essentially nothing, so it +# is a clean configuration-time signal. --no-configuration-cache forces a full +# configuration on every build. + +help_base { + title = "help base" + tasks = ["help"] + gradle-args = ["--no-configuration-cache"] + warm-ups = 2 + iterations = 5 + git-checkout { + build = ${BASE_REF} + cleanup = ${HEAD_REF} + } +} + +help_pr { + title = "help PR" + tasks = ["help"] + gradle-args = ["--no-configuration-cache"] + warm-ups = 2 + iterations = 5 + git-checkout { + build = ${HEAD_REF} + cleanup = ${HEAD_REF} + } +} diff --git a/scripts/benchmark/help-config-comment.py b/scripts/benchmark/help-config-comment.py new file mode 100644 index 000000000..a85c149d1 --- /dev/null +++ b/scripts/benchmark/help-config-comment.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Render a gradle-profiler benchmark.csv into a Markdown comparison table. + +Usage: help-config-comment.py + +The CSV has one column per scenario; we read the `scenario` header row for the +titles and the `mean` row for the mean total build time (milliseconds). +""" +import csv +import sys + +MARKER = "" + + +def main(csv_path: str, out_path: str) -> None: + header = None + mean = None + with open(csv_path, newline="") as f: + for row in csv.reader(f): + if not row: + continue + if row[0] == "scenario": + header = row + elif row[0] == "mean": + mean = row + + if header is None or mean is None: + body = f"{MARKER}\n### `help` configuration benchmark\n\nCould not parse benchmark results." + with open(out_path, "w") as f: + f.write(body) + return + + titles = header[1:] + means = [float(v) for v in mean[1 : 1 + len(titles)]] + by_title = dict(zip(titles, means)) + + base = by_title.get("help base") + pr = by_title.get("help PR") + + lines = [ + MARKER, + "### `help` configuration benchmark (configuration cache disabled)", + "", + "Mean of 5 builds after 2 warm-ups.", + "", + "| Scenario | Mean build time |", + "| --- | --- |", + f"| Base (`help base`) | {base:.0f} ms |", + f"| PR (`help PR`) | {pr:.0f} ms |", + ] + + delta = pr - base + pct = (delta / base) * 100 if base else 0 + sign = "🔺" if delta > 0 else "✅" + lines.append(f"| **Difference** | {sign} {delta:+.0f} ms ({pct:+.1f}%) |") + + with open(out_path, "w") as f: + f.write("\n".join(lines) + "\n") + + +if __name__ == "__main__": + main(sys.argv[1], sys.argv[2]) From 30ae4ba4e0b57e74f823f2166d20c8052795a034 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 29 May 2026 14:52:19 +0200 Subject: [PATCH 02/17] fix(benchmark): Compute help benchmark mean from measured builds gradle-profiler does not write summary rows (mean, min, max) to benchmark.csv; those only appear in the HTML report. The comment parser looked for a non-existent `mean` row and always fell back to "Could not parse benchmark results". Average the `measured build #N` rows instead. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/benchmark/help-config-comment.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/scripts/benchmark/help-config-comment.py b/scripts/benchmark/help-config-comment.py index a85c149d1..d7b2ac8a7 100644 --- a/scripts/benchmark/help-config-comment.py +++ b/scripts/benchmark/help-config-comment.py @@ -3,8 +3,10 @@ Usage: help-config-comment.py -The CSV has one column per scenario; we read the `scenario` header row for the -titles and the `mean` row for the mean total build time (milliseconds). +The CSV has one column per scenario. gradle-profiler does not write summary +rows to the CSV (those live in the HTML report), so we read the `scenario` +header row for the titles and average the `measured build #N` rows ourselves to +get the mean total build time in milliseconds. """ import csv import sys @@ -14,24 +16,27 @@ def main(csv_path: str, out_path: str) -> None: header = None - mean = None + measured = [] with open(csv_path, newline="") as f: for row in csv.reader(f): if not row: continue if row[0] == "scenario": header = row - elif row[0] == "mean": - mean = row + elif row[0].startswith("measured build"): + measured.append(row) - if header is None or mean is None: + if header is None or not measured: body = f"{MARKER}\n### `help` configuration benchmark\n\nCould not parse benchmark results." with open(out_path, "w") as f: f.write(body) return titles = header[1:] - means = [float(v) for v in mean[1 : 1 + len(titles)]] + means = [] + for col in range(1, 1 + len(titles)): + values = [float(r[col]) for r in measured] + means.append(sum(values) / len(values)) by_title = dict(zip(titles, means)) base = by_title.get("help base") From bf72162a93bb3ffb066ac9b916e933de03ec9d1b Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 22 May 2026 14:57:56 +0200 Subject: [PATCH 03/17] fix(config-cache): Defer CLI path resolution to execution phase The sentry-cli binary path was resolved during Gradle's configuration phase via SentryCliValueSource and cached. When the binary was deleted (OS temp cleanup, build dir cleaned) or the plugin was upgraded (different CLI version), the cached path became stale, causing build failures like "Could not start sentry-cli*.exe". Move CLI path resolution entirely to the execution phase: - Remove SentryCliValueSource, cliExecutableProvider(), and the cliExecutable @Input property from all tasks - Resolve the CLI path fresh inside computeCommandLineArgs() at execution time via getSentryCliPath() + maybeExtractFromResources() - Remove SentryCliInfoValueSource and SentryCliVersionValueSource which spawned sentry-cli processes during configuration phase; use BuildConfig.CliVersion and URL-based heuristics instead - Improve maybeExtractFromResources() to handle CLI version changes by extracting the current bundled version when a stale path is detected in build/tmp/ Fixes #613 Co-Authored-By: Claude Opus 4.6 --- .../android/gradle/AndroidComponentsConfig.kt | 19 +- .../android/gradle/SentryCliProvider.kt | 119 +++-------- .../io/sentry/android/gradle/SentryPlugin.kt | 3 - .../gradle/sourcecontext/BundleSourcesTask.kt | 2 - .../gradle/sourcecontext/SourceContext.kt | 3 - .../sourcecontext/UploadSourceBundleTask.kt | 2 - .../android/gradle/tasks/SentryCliExecTask.kt | 14 +- .../tasks/SentryUploadAppArtifactTask.kt | 5 +- .../tasks/SentryUploadNativeSymbolsTask.kt | 4 - .../tasks/SentryUploadProguardMappingsTask.kt | 2 - .../gradle/tasks/SentryUploadSnapshotsTask.kt | 2 - .../telemetry/SentryTelemetryService.kt | 196 +----------------- .../io/sentry/jvm/gradle/SentryJvmPlugin.kt | 4 - .../gradle/tasks/BundleSourcesTaskTest.kt | 10 +- .../gradle/tasks/SentryCliExecTaskTest.kt | 39 +--- .../tasks/SentryUploadAppArtifactTaskTest.kt | 17 +- .../SentryUploadNativeSymbolsTaskTest.kt | 10 +- .../SentryUploadProguardMappingTaskTest.kt | 12 +- .../tasks/SentryUploadSnapshotsTaskTest.kt | 13 +- .../tasks/UploadSourceBundleTaskTest.kt | 11 +- .../telemetry/SentryTelemetryServiceTest.kt | 63 ++++-- 21 files changed, 99 insertions(+), 451 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt index eb1b8ca06..93a51ecb4 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt @@ -54,7 +54,6 @@ fun ApplicationAndroidComponentsExtension.configure( project: Project, extension: SentryPluginExtension, buildEvents: BuildEventListenerRegistryInternal, - cliExecutable: Provider, sentryOrg: String?, sentryProject: String?, ) { @@ -77,13 +76,13 @@ fun ApplicationAndroidComponentsExtension.configure( } if (extension.snapshots.enabled.get()) { - variant.configureSnapshotsTasks(project, extension, cliExecutable, sentryOrg, sentryProject) + variant.configureSnapshotsTasks(project, extension, sentryOrg, sentryProject) } if (isVariantAllowed(extension, variant.name, variant.flavorName, variant.buildType)) { val paths = OutputPaths(project, variant.name) val sentryTelemetryProvider = - variant.configureTelemetry(project, extension, cliExecutable, sentryOrg, buildEvents) + variant.configureTelemetry(project, extension, sentryOrg, buildEvents) variant.configureDependenciesTask(project, extension, sentryTelemetryProvider) @@ -106,7 +105,6 @@ fun ApplicationAndroidComponentsExtension.configure( sentryTelemetryProvider, paths, sourceFiles, - cliExecutable, sentryOrg, sentryProject, ) @@ -118,7 +116,6 @@ fun ApplicationAndroidComponentsExtension.configure( extension, sentryTelemetryProvider, paths, - cliExecutable, sentryOrg, sentryProject, ) @@ -139,7 +136,6 @@ fun ApplicationAndroidComponentsExtension.configure( project, extension, sentryTelemetryProvider, - cliExecutable, sentryOrg, sentryProject, ) @@ -257,7 +253,6 @@ fun ApplicationAndroidComponentsExtension.configure( project, extension, sentryTelemetryProvider, - cliExecutable, sentryOrg, sentryProject, ) @@ -269,7 +264,6 @@ fun ApplicationAndroidComponentsExtension.configure( private fun Variant.configureTelemetry( project: Project, extension: SentryPluginExtension, - cliExecutable: Provider, sentryOrg: String?, buildEvents: BuildEventListenerRegistryInternal, ): Provider { @@ -281,7 +275,6 @@ private fun Variant.configureTelemetry( project, variant, extension, - cliExecutable, sentryOrg, "Android", ) @@ -297,7 +290,6 @@ private fun Variant.configureSourceBundleTasks( sentryTelemetryProvider: Provider, paths: OutputPaths, sourceFiles: Provider>?, - cliExecutable: Provider, sentryOrg: String?, sentryProject: String?, ): SourceContext.SourceContextTasks? { @@ -313,7 +305,6 @@ private fun Variant.configureSourceBundleTasks( variant, paths, sourceFiles, - cliExecutable, sentryOrg, sentryProject, taskSuffix, @@ -354,7 +345,6 @@ private fun ApplicationVariant.configureProguardMappingsTasks( extension: SentryPluginExtension, sentryTelemetryProvider: Provider, paths: OutputPaths, - cliExecutable: Provider, sentryOrg: String?, sentryProject: String?, ): TaskProvider? { @@ -386,7 +376,6 @@ private fun ApplicationVariant.configureProguardMappingsTasks( extension, sentryTelemetryProvider, debug = extension.debug, - cliExecutable = cliExecutable, generateUuidTask = generateUuidTask, sentryProperties = sentryProps, mappingFiles = mappings, @@ -460,7 +449,6 @@ private fun ApplicationVariant.configureDistributionPropertiesTask( private fun ApplicationVariant.configureSnapshotsTasks( project: Project, extension: SentryPluginExtension, - cliExecutable: Provider, sentryOrg: String?, sentryProject: String?, ) { @@ -478,7 +466,6 @@ private fun ApplicationVariant.configureSnapshotsTasks( project = project, extension = extension, sentryTelemetryProvider = null, - cliExecutable = cliExecutable, sentryOrgOverride = sentryOrg, sentryProjectOverride = sentryProject, applicationId = applicationId, @@ -557,7 +544,6 @@ fun Variant.configureUploadAppTasks( project: Project, extension: SentryPluginExtension, sentryTelemetryProvider: Provider, - cliExecutable: Provider, sentryOrg: String?, sentryProject: String?, ): Pair, TaskProvider> { @@ -570,7 +556,6 @@ fun Variant.configureUploadAppTasks( extension, sentryTelemetryProvider, debug = extension.debug, - cliExecutable = cliExecutable, appBundle = variant.bundle, apk = variant.apk, sentryOrg = sentryOrg?.let { project.provider { it } } ?: extension.org, diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt index bd7a44d7c..d33051039 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt @@ -1,11 +1,7 @@ -@file:Suppress("UnstableApiUsage") - package io.sentry.android.gradle import io.sentry.BuildConfig -import io.sentry.android.gradle.SentryCliValueSource.Params import io.sentry.android.gradle.SentryPlugin.Companion.logger -import io.sentry.android.gradle.util.GradleVersions import io.sentry.android.gradle.util.error import io.sentry.android.gradle.util.info import java.io.File @@ -13,19 +9,9 @@ import java.io.FileInputStream import java.io.FileOutputStream import java.util.Locale import java.util.Properties -import org.gradle.api.Project -import org.gradle.api.file.Directory -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.RegularFile -import org.gradle.api.provider.Provider -import org.gradle.api.provider.ValueSource -import org.gradle.api.provider.ValueSourceParameters -import org.gradle.api.tasks.Input internal object SentryCliProvider { - @field:Volatile private var memoizedCliPath: String? = null - /** * Return the correct sentry-cli executable path to use for the given project. This will look for * a sentry-cli executable in a local node_modules in case it was put there by sentry-react-native @@ -34,23 +20,12 @@ internal object SentryCliProvider { * without actually extracting it. */ @JvmStatic - @Synchronized - fun getSentryCliPath( - projectDir: DirectoryProperty, - projectBuildDir: DirectoryProperty, - rootDir: DirectoryProperty, - ): String { - val cliPath = memoizedCliPath - if (!cliPath.isNullOrEmpty() && File(cliPath).exists()) { - logger.info { "Using memoized cli path: $cliPath" } - return cliPath - } + fun getSentryCliPath(projectDir: File, projectBuildDir: File, rootDir: File): String { // If a path is provided explicitly use that first. logger.info { "Searching cli from sentry.properties file..." } searchCliInPropertiesFile(projectDir, rootDir)?.let { logger.info { "cli Found: $it" } - memoizedCliPath = it return@getSentryCliPath it } ?: logger.info { "sentry-cli not found in sentry.properties file" } @@ -58,16 +33,11 @@ internal object SentryCliProvider { val cliResLocation = getCliLocationInResources() if (!cliResLocation.isNullOrBlank()) { logger.info { "cli present in resources: $cliResLocation" } - // just provide the target extraction path - // actual extraction will be done prior to task execution - val extractedResourcePath = - getCliResourcesExtractionPath(projectBuildDir).get().asFile.absolutePath - memoizedCliPath = extractedResourcePath - return extractedResourcePath + return getCliResourcesExtractionPath(projectBuildDir).absolutePath } logger.error { "Falling back to invoking `sentry-cli` from shell" } - return "sentry-cli".also { memoizedCliPath = it } + return "sentry-cli" } private fun getCliLocationInResources(): String? { @@ -91,19 +61,12 @@ internal object SentryCliProvider { return null } - internal fun getSentryPropertiesPath( - projectDir: DirectoryProperty, - rootDir: DirectoryProperty, - ): String? = - listOf(projectDir.file("sentry.properties"), rootDir.file("sentry.properties")) - .map { it.get().asFile } + internal fun getSentryPropertiesPath(projectDir: File, rootDir: File): String? = + listOf(File(projectDir, "sentry.properties"), File(rootDir, "sentry.properties")) .firstOrNull(File::exists) ?.path - internal fun searchCliInPropertiesFile( - projectDir: DirectoryProperty, - rootDir: DirectoryProperty, - ): String? { + internal fun searchCliInPropertiesFile(projectDir: File, rootDir: File): String? { return getSentryPropertiesPath(projectDir, rootDir)?.let { propertiesFile -> runCatching { Properties().apply { load(FileInputStream(propertiesFile)) }.getProperty("cli.executable") @@ -115,11 +78,9 @@ internal object SentryCliProvider { internal fun getResourceUrl(resourcePath: String): String? = javaClass.getResource(resourcePath)?.toString() - internal fun getCliResourcesExtractionPath( - projectBuildDir: DirectoryProperty - ): Provider { + internal fun getCliResourcesExtractionPath(projectBuildDir: File): File { // usually /build/tmp/ - return projectBuildDir.dir("tmp").map { it.file("sentry-cli-${BuildConfig.CliVersion}.exe") } + return File(projectBuildDir, "tmp/sentry-cli-${BuildConfig.CliVersion}.exe") } internal fun extractCliFromResources(resourcePath: String, outputPath: File): String? { @@ -159,57 +120,27 @@ internal object SentryCliProvider { /** Tries to extract the sentry-cli from resources if the computedCliPath does not exist. */ @Synchronized - internal fun maybeExtractFromResources(buildDir: DirectoryProperty, cliPath: String): String { + internal fun maybeExtractFromResources(buildDir: File, cliPath: String): String { val cli = File(cliPath) - if (!cli.exists()) { - // we only want to auto-extract if the path matches the pre-computed one - if ( - File(cliPath) - .absolutePath - .equals(getCliResourcesExtractionPath(buildDir).get().asFile.absolutePath) - ) { - val cliResPath = getCliLocationInResources() - if (!cliResPath.isNullOrBlank()) { - return extractCliFromResources(cliResPath, cli) ?: cliPath - } - } + if (cli.exists()) { + return cliPath } - return cliPath - } -} - -abstract class SentryCliValueSource : ValueSource { - interface Params : ValueSourceParameters { - @get:Input val projectDir: DirectoryProperty - @get:Input val projectBuildDir: DirectoryProperty - - @get:Input val rootProjDir: DirectoryProperty - } - - override fun obtain(): String? { - return SentryCliProvider.getSentryCliPath( - parameters.projectDir, - parameters.projectBuildDir, - parameters.rootProjDir, - ) - } -} + val currentExtractionPath = getCliResourcesExtractionPath(buildDir) + if (currentExtractionPath.exists()) { + return currentExtractionPath.absolutePath + } -fun Project.cliExecutableProvider(): Provider { - // config-cache compatible way to retrieve the cli path, it properly gets invalidated when - // e.g. switching branches - return providers.of(SentryCliValueSource::class.java) { - it.parameters.projectDir.set(layout.projectDirectory) - it.parameters.projectBuildDir.set(layout.buildDirectory) - it.parameters.rootProjDir.set(getIsolatedRootProjectDir()) - } -} + // Only auto-extract for paths that look like previous resource extractions + val buildTmpDir = File(buildDir, "tmp") + if (cli.absolutePath.startsWith(buildTmpDir.absolutePath)) { + val cliResPath = getCliLocationInResources() + if (!cliResPath.isNullOrBlank()) { + return extractCliFromResources(cliResPath, currentExtractionPath) + ?: currentExtractionPath.absolutePath + } + } -private fun Project.getIsolatedRootProjectDir(): Directory { - return if (GradleVersions.CURRENT >= GradleVersions.VERSION_8_8) { - isolated.rootProject.projectDirectory - } else { - rootProject.layout.projectDirectory + return cliPath } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryPlugin.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryPlugin.kt index 14afdcdbc..6cfc94b4a 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryPlugin.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryPlugin.kt @@ -45,8 +45,6 @@ constructor(private val buildEvents: BuildEventListenerRegistryInternal) : Plugi project.pluginManager.withPlugin("com.android.application") { val androidComponentsExt = project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java) - val cliExecutable = project.cliExecutableProvider() - val extraProperties = project.extensions.getByName("ext") as ExtraPropertiesExtension val sentryOrgParameter = @@ -59,7 +57,6 @@ constructor(private val buildEvents: BuildEventListenerRegistryInternal) : Plugi project, extension, buildEvents, - cliExecutable, sentryOrgParameter, sentryProjectParameter, ) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/BundleSourcesTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/BundleSourcesTask.kt index 9a405c7ea..9a897d17c 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/BundleSourcesTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/BundleSourcesTask.kt @@ -77,7 +77,6 @@ abstract class BundleSourcesTask : SentryCliExecTask() { collectSourcesTask: TaskProvider, output: Provider, debug: Property, - cliExecutable: Provider, sentryOrg: Provider, sentryProject: Provider, sentryAuthToken: Property, @@ -95,7 +94,6 @@ abstract class BundleSourcesTask : SentryCliExecTask() { task.sentryAuthToken.set(sentryAuthToken) task.sentryUrl.set(sentryUrl) task.sourceDir.set(collectSourcesTask.flatMap { it.output }) - task.cliExecutable.set(cliExecutable) SentryPropertiesFileProvider.getPropertiesFilePath(project, variant)?.let { task.sentryProperties.set(File(it)) } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/SourceContext.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/SourceContext.kt index f1835b278..6ccc04b02 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/SourceContext.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/SourceContext.kt @@ -17,7 +17,6 @@ class SourceContext { variant: SentryVariant, paths: OutputPaths, sourceFiles: Provider>?, - cliExecutable: Provider, sentryOrg: String?, sentryProject: String?, taskSuffix: String, @@ -54,7 +53,6 @@ class SourceContext { collectSourcesTask, output = paths.bundleDir, extension.debug, - cliExecutable, sentryOrg?.let { project.provider { it } } ?: extension.org, sentryProject?.let { project.provider { it } } ?: extension.projectName, extension.authToken, @@ -71,7 +69,6 @@ class SourceContext { variant, bundleSourcesTask, extension.debug, - cliExecutable, extension.autoUploadSourceContext, sentryOrg?.let { project.provider { it } } ?: extension.org, sentryProject?.let { project.provider { it } } ?: extension.projectName, diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/UploadSourceBundleTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/UploadSourceBundleTask.kt index a807b029c..4fbcffdd0 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/UploadSourceBundleTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/UploadSourceBundleTask.kt @@ -68,7 +68,6 @@ abstract class UploadSourceBundleTask : SentryCliExecTask() { variant: SentryVariant, bundleSourcesTask: TaskProvider, debug: Property, - cliExecutable: Provider, autoUploadSourceContext: Property, sentryOrg: Provider, sentryProject: Provider, @@ -87,7 +86,6 @@ abstract class UploadSourceBundleTask : SentryCliExecTask() { task.sentryAuthToken.set(sentryAuthToken) task.sentryUrl.set(sentryUrl) task.sourceBundleDir.set(bundleSourcesTask.flatMap { it.output }) - task.cliExecutable.set(cliExecutable) task.autoUploadSourceContext.set(autoUploadSourceContext) SentryPropertiesFileProvider.getPropertiesFilePath(project, variant)?.let { task.sentryProperties.set(File(it)) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt index 215029e4b..c81d74503 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt @@ -4,10 +4,11 @@ import io.sentry.android.gradle.SentryCliProvider import io.sentry.android.gradle.telemetry.SentryTelemetryService import io.sentry.android.gradle.util.info import io.sentry.android.gradle.util.setSentryPipelineEnv +import java.io.File import org.apache.tools.ant.taskdefs.condition.Os -import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider import org.gradle.api.tasks.Exec import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFile @@ -22,8 +23,6 @@ abstract class SentryCliExecTask : Exec() { @get:Input @get:Optional abstract val debug: Property - @get:Input abstract val cliExecutable: Property - @get:InputFile @get:Optional @get:PathSensitive(PathSensitivity.RELATIVE) @@ -39,7 +38,9 @@ abstract class SentryCliExecTask : Exec() { @get:Internal abstract val sentryTelemetryService: Property - private val buildDirectory: DirectoryProperty = project.layout.buildDirectory + private val sentryProjectDir: File = project.projectDir + private val sentryRootDir: File = project.rootDir + private val buildDirectory: Provider = project.layout.buildDirectory.asFile override fun exec() { computeCommandLineArgs().let { @@ -92,8 +93,9 @@ abstract class SentryCliExecTask : Exec() { args.add(1, "/c") } - val cliPath = SentryCliProvider.maybeExtractFromResources(buildDirectory, cliExecutable.get()) - args.add(cliPath) + val cliPath = + SentryCliProvider.getSentryCliPath(sentryProjectDir, buildDirectory.get(), sentryRootDir) + args.add(SentryCliProvider.maybeExtractFromResources(buildDirectory.get(), cliPath)) args.addAll(preArgs()) getArguments(args) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTask.kt index b4bb1f22b..6ddd90f45 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTask.kt @@ -101,7 +101,6 @@ abstract class SentryUploadAppArtifactTask @Inject constructor(objectFactory: Ob debug: Property, appBundle: Provider, apk: Provider, - cliExecutable: Provider, sentryProperties: String?, sentryOrg: Provider, sentryProject: Provider, @@ -118,7 +117,7 @@ abstract class SentryUploadAppArtifactTask @Inject constructor(objectFactory: Ob ) { task -> task.workingDir(project.rootDir) task.debug.set(debug) - task.cliExecutable.set(cliExecutable) + task.sentryProperties.set(sentryProperties?.let { file -> project.file(file) }) task.bundle.set(appBundle) task.sentryOrganization.set(sentryOrg) @@ -148,7 +147,7 @@ abstract class SentryUploadAppArtifactTask @Inject constructor(objectFactory: Ob ) { task -> task.workingDir(project.rootDir) task.debug.set(debug) - task.cliExecutable.set(cliExecutable) + task.sentryProperties.set(sentryProperties?.let { file -> project.file(file) }) task.apk.set(apk) task.sentryOrganization.set(sentryOrg) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt index 77746e773..d823ff196 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt @@ -60,7 +60,6 @@ abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { sentryTelemetryProvider: Provider, variantName: String, debug: Property, - cliExecutable: Provider, sentryProperties: String?, sentryOrg: Provider, sentryProject: Provider, @@ -78,7 +77,6 @@ abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { task.workingDir(project.rootDir) task.debug.set(debug) task.autoUploadNativeSymbol.set(autoUploadNativeSymbols) - task.cliExecutable.set(cliExecutable) task.sentryProperties.set(sentryProperties?.let { file -> project.file(file) }) task.includeNativeSources.set(includeNativeSources) task.variantName.set(variantName) @@ -99,7 +97,6 @@ fun SentryVariant.configureNativeSymbolsTask( project: Project, extension: SentryPluginExtension, sentryTelemetryProvider: Provider, - cliExecutable: Provider, sentryOrg: String?, sentryProject: String?, ) { @@ -113,7 +110,6 @@ fun SentryVariant.configureNativeSymbolsTask( sentryTelemetryProvider = sentryTelemetryProvider, variantName = name, debug = extension.debug, - cliExecutable = cliExecutable, sentryProperties = sentryProps, autoUploadNativeSymbols = extension.autoUploadNativeSymbols, includeNativeSources = extension.includeNativeSources, diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingsTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingsTask.kt index 31ef2b85b..eb1c88f24 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingsTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingsTask.kt @@ -89,7 +89,6 @@ abstract class SentryUploadProguardMappingsTask : SentryCliExecTask() { extension: SentryPluginExtension, sentryTelemetryProvider: Provider?, debug: Property, - cliExecutable: Provider, sentryProperties: String?, generateUuidTask: Provider, mappingFiles: Provider, @@ -108,7 +107,6 @@ abstract class SentryUploadProguardMappingsTask : SentryCliExecTask() { task.dependsOn(generateUuidTask) task.workingDir(project.rootDir) task.debug.set(debug) - task.cliExecutable.set(cliExecutable) task.sentryProperties.set(sentryProperties?.let { file -> project.file(file) }) task.uuidFile.set(generateUuidTask.flatMap { it.outputFile }) task.mappingsFiles = mappingFiles diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTask.kt index 3f5c95532..65724afef 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTask.kt @@ -75,7 +75,6 @@ abstract class SentryUploadSnapshotsTask : SentryCliExecTask() { project: Project, extension: SentryPluginExtension, sentryTelemetryProvider: Provider?, - cliExecutable: Provider, sentryOrgOverride: String?, sentryProjectOverride: String?, applicationId: Provider, @@ -88,7 +87,6 @@ abstract class SentryUploadSnapshotsTask : SentryCliExecTask() { ) { task -> task.workingDir(project.rootDir) task.debug.set(extension.debug) - task.cliExecutable.set(cliExecutable) task.sentryProperties.set(sentryProperties?.let { project.file(it) }) task.sentryOrganization.set( sentryOrgOverride?.let { project.provider { it } } ?: extension.org diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt index d58f976ed..ae3776ff9 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt @@ -13,37 +13,24 @@ import io.sentry.SentryEvent import io.sentry.SentryLevel import io.sentry.SpanStatus import io.sentry.TransactionOptions -import io.sentry.android.gradle.SentryCliProvider import io.sentry.android.gradle.SentryPlugin import io.sentry.android.gradle.SentryPlugin.Companion.logger -import io.sentry.android.gradle.SentryPropertiesFileProvider import io.sentry.android.gradle.extensions.SentryPluginExtension -import io.sentry.android.gradle.telemetry.SentryCliInfoValueSource.InfoParams -import io.sentry.android.gradle.telemetry.SentryCliVersionValueSource.VersionParams import io.sentry.android.gradle.util.AgpVersions import io.sentry.android.gradle.util.SentryCliException import io.sentry.android.gradle.util.error import io.sentry.android.gradle.util.getBuildServiceName import io.sentry.android.gradle.util.info -import io.sentry.android.gradle.util.setSentryPipelineEnv import io.sentry.exception.ExceptionMechanismException import io.sentry.gradle.common.SentryVariant import io.sentry.protocol.Mechanism import io.sentry.protocol.User -import java.io.ByteArrayOutputStream -import java.nio.charset.Charset -import javax.inject.Inject import org.gradle.api.Project import org.gradle.api.Task -import org.gradle.api.file.DirectoryProperty import org.gradle.api.internal.tasks.execution.ExecuteTaskBuildOperationDetails -import org.gradle.api.provider.Property import org.gradle.api.provider.Provider -import org.gradle.api.provider.ValueSource -import org.gradle.api.provider.ValueSourceParameters import org.gradle.api.services.BuildService import org.gradle.api.services.BuildServiceParameters.None -import org.gradle.api.tasks.Input import org.gradle.execution.RunRootBuildWorkBuildOperationType import org.gradle.internal.operations.BuildOperationDescriptor import org.gradle.internal.operations.BuildOperationListener @@ -51,7 +38,6 @@ import org.gradle.internal.operations.OperationFinishEvent import org.gradle.internal.operations.OperationIdentifier import org.gradle.internal.operations.OperationProgressEvent import org.gradle.internal.operations.OperationStartEvent -import org.gradle.process.ExecOperations import org.gradle.util.GradleVersion abstract class SentryTelemetryService : BuildService, BuildOperationListener, AutoCloseable { @@ -238,32 +224,19 @@ abstract class SentryTelemetryService : BuildService, BuildOperationListen val SENTRY_SAAS_DSN: String = "https://000e5dea9770b4537055f8a6d28c021e@o1.ingest.sentry.io/4506241308295168" val MECHANISM_TYPE: String = "GradleTelemetry" - private val orgRegex = Regex("""(?m)Default Organization: (.*)$""") - private val versionRegex = Regex("""(?m)sentry-cli (.*)$""") fun createParameters( project: Project, variant: SentryVariant?, extension: SentryPluginExtension, - cliExecutable: Provider, sentryOrg: String?, buildType: String, ): SentryTelemetryServiceParams { val tags = extraTagsFromExtension(project, extension) val org = sentryOrg ?: extension.org.orNull - val isTelemetryEnabled = extension.telemetry.get() - - // if telemetry is disabled we don't even need to exec sentry-cli as telemetry service - // will be no-op - if (isTelemetryEnabled) { - paramsWithExecAvailable(project, cliExecutable, extension, variant, org, buildType, tags) - ?.let { - return it - } - } - // fallback: sentry-cli is not available or e.g. auth token is not configured + return SentryTelemetryServiceParams( - isTelemetryEnabled, + extension.telemetry.get(), extension.telemetryDsn.get(), org, buildType, @@ -274,73 +247,6 @@ abstract class SentryTelemetryService : BuildService, BuildOperationListen ) } - private fun paramsWithExecAvailable( - project: Project, - cliExecutable: Provider, - extension: SentryPluginExtension, - variant: SentryVariant?, - sentryOrg: String?, - buildType: String, - tags: Map, - ): SentryTelemetryServiceParams? { - var cliVersion: String? = BuildConfig.CliVersion - var defaultSentryOrganization: String? = null - val infoOutput = - project.providers - .of(SentryCliInfoValueSource::class.java) { cliVS -> - cliVS.parameters.buildDirectory.set(project.layout.buildDirectory) - cliVS.parameters.cliExecutable.set(cliExecutable) - cliVS.parameters.authToken.set(extension.authToken) - cliVS.parameters.url.set(extension.url) - variant?.let { v -> - cliVS.parameters.propertiesFilePath.set( - SentryPropertiesFileProvider.getPropertiesFilePath(project, v) - ) - } - } - .get() - - if (infoOutput.isEmpty()) { - return null - } - val isSaas = infoOutput.contains("(?m)Sentry Server: .*sentry.io$".toRegex()) - - orgRegex.find(infoOutput)?.let { matchResult -> - val groupValues = matchResult.groupValues - if (groupValues.size > 1) { - defaultSentryOrganization = groupValues[1] - } - } - - val versionOutput = - project.providers - .of(SentryCliVersionValueSource::class.java) { cliVS -> - cliVS.parameters.buildDirectory.set(project.layout.buildDirectory) - cliVS.parameters.cliExecutable.set(cliExecutable) - cliVS.parameters.url.set(extension.url) - } - .get() - - versionRegex.find(versionOutput)?.let { matchResult -> - val groupValues = matchResult.groupValues - if (groupValues.size > 1) { - cliVersion = groupValues[1] - } - } - - return SentryTelemetryServiceParams( - extension.telemetry.get(), - extension.telemetryDsn.get(), - sentryOrg, - buildType, - tags, - extension.debug.get(), - defaultSentryOrganization, - isSaas, - cliVersion = cliVersion, - ) - } - fun register(project: Project): Provider { return project.gradle.sharedServices.registerIfAbsent( getBuildServiceName(SentryTelemetryService::class.java), @@ -419,104 +325,6 @@ class SentryMinimalException(message: String) : RuntimeException(message) { } } -abstract class SentryCliInfoValueSource : ValueSource { - interface InfoParams : ValueSourceParameters { - @get:Input val buildDirectory: DirectoryProperty - - @get:Input val cliExecutable: Property - - @get:Input val propertiesFilePath: Property - - @get:Input val url: Property - - @get:Input val authToken: Property - } - - @get:Inject abstract val execOperations: ExecOperations - - override fun obtain(): String? { - val stdOutput = ByteArrayOutputStream() - val errOutput = ByteArrayOutputStream() - - val execResult = - execOperations.exec { - it.isIgnoreExitValue = true - SentryCliProvider.maybeExtractFromResources( - parameters.buildDirectory, - parameters.cliExecutable.get(), - ) - - val args = mutableListOf(parameters.cliExecutable.get()) - - parameters.url.orNull?.let { url -> - args.add("--url") - args.add(url) - } - - args.add("--log-level=error") - args.add("info") - - parameters.propertiesFilePath.orNull?.let { path -> - it.environment("SENTRY_PROPERTIES", path) - } - - parameters.authToken.orNull?.let { authToken -> - it.environment("SENTRY_AUTH_TOKEN", authToken) - } - - it.setSentryPipelineEnv() - - it.commandLine(args) - it.standardOutput = stdOutput - it.errorOutput = errOutput - } - - if (execResult.exitValue == 0) { - return String(stdOutput.toByteArray(), Charset.defaultCharset()) - } else { - logger.info { - "Failed to execute sentry-cli info. Error Output: " + - String(errOutput.toByteArray(), Charset.defaultCharset()) - } - return "" - } - } -} - -abstract class SentryCliVersionValueSource : ValueSource { - interface VersionParams : ValueSourceParameters { - @get:Input val buildDirectory: DirectoryProperty - - @get:Input val cliExecutable: Property - - @get:Input val url: Property - } - - @get:Inject abstract val execOperations: ExecOperations - - override fun obtain(): String { - val output = ByteArrayOutputStream() - execOperations.exec { - it.isIgnoreExitValue = true - SentryCliProvider.maybeExtractFromResources( - parameters.buildDirectory, - parameters.cliExecutable.get(), - ) - - val args = mutableListOf(parameters.cliExecutable.get()) - - args.add("--log-level=error") - args.add("--version") - - it.setSentryPipelineEnv() - - it.commandLine(args) - it.standardOutput = output - } - return String(output.toByteArray(), Charset.defaultCharset()) - } -} - data class SentryTelemetryServiceParams( val sendTelemetry: Boolean, val dsn: String, diff --git a/plugin-build/src/main/kotlin/io/sentry/jvm/gradle/SentryJvmPlugin.kt b/plugin-build/src/main/kotlin/io/sentry/jvm/gradle/SentryJvmPlugin.kt index d16e9b9b0..e86a9e54c 100644 --- a/plugin-build/src/main/kotlin/io/sentry/jvm/gradle/SentryJvmPlugin.kt +++ b/plugin-build/src/main/kotlin/io/sentry/jvm/gradle/SentryJvmPlugin.kt @@ -3,7 +3,6 @@ package io.sentry.jvm.gradle import io.sentry.android.gradle.SentryPlugin import io.sentry.android.gradle.SentryTasksProvider import io.sentry.android.gradle.autoinstall.installDependencies -import io.sentry.android.gradle.cliExecutableProvider import io.sentry.android.gradle.extensions.SentryPluginExtension import io.sentry.android.gradle.sourcecontext.OutputPaths import io.sentry.android.gradle.sourcecontext.SourceContext @@ -48,7 +47,6 @@ constructor(private val buildEvents: BuildEventListenerRegistryInternal) : Plugi val javaVariant = JavaVariant(project, javaExtension) val outputPaths = OutputPaths(project, "java") - val cliExecutable = project.cliExecutableProvider() val extraProperties = project.extensions.getByName("ext") as ExtraPropertiesExtension @@ -66,7 +64,6 @@ constructor(private val buildEvents: BuildEventListenerRegistryInternal) : Plugi project, javaVariant, extension, - cliExecutable, sentryOrgParameter, "JVM", ) @@ -90,7 +87,6 @@ constructor(private val buildEvents: BuildEventListenerRegistryInternal) : Plugi javaVariant, outputPaths, sourceFiles, - cliExecutable, sentryOrgParameter, sentryProjectParameter, "Java", diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt index e865e88e1..ff9136855 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt @@ -29,7 +29,6 @@ class BundleSourcesTaskTest { val outDir = File(project.buildDir, "dummy/out") val task: TaskProvider = project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sourceDir.set(sourceDir) it.bundleIdFile.set(debugMetaPropertiesFile) it.output.set(outDir) @@ -37,7 +36,7 @@ class BundleSourcesTaskTest { val args = task.get().computeCommandLineArgs() - assertThat(args).contains("sentry-cli") + assertThat(args.first()).contains("sentry-cli") assertThat(args).contains("debug-files") assertThat(args).contains("bundle-jvm") assertThat(args).contains(sourceDir.absolutePath) @@ -57,7 +56,6 @@ class BundleSourcesTaskTest { val outDir = File(project.buildDir, "dummy/out") val task: TaskProvider = project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sourceDir.set(sourceDir) it.bundleIdFile.set(debugMetaPropertiesFile) it.output.set(outDir) @@ -79,7 +77,6 @@ class BundleSourcesTaskTest { val outDir = File(project.buildDir, "dummy/out") val task: TaskProvider = project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sourceDir.set(sourceDir) it.bundleIdFile.set(debugMetaPropertiesFile) it.output.set(outDir) @@ -101,7 +98,6 @@ class BundleSourcesTaskTest { val outDir = File(project.buildDir, "dummy/out") val task: TaskProvider = project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sourceDir.set(sourceDir) it.bundleIdFile.set(debugMetaPropertiesFile) it.output.set(outDir) @@ -122,7 +118,6 @@ class BundleSourcesTaskTest { val outDir = File(project.buildDir, "dummy/out") val task: TaskProvider = project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sourceDir.set(sourceDir) it.bundleIdFile.set(debugMetaPropertiesFile) it.output.set(outDir) @@ -142,7 +137,6 @@ class BundleSourcesTaskTest { val outDir = File(project.buildDir, "dummy/out") val task: TaskProvider = project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sourceDir.set(sourceDir) it.bundleIdFile.set(debugMetaPropertiesFile) it.output.set(outDir) @@ -164,7 +158,6 @@ class BundleSourcesTaskTest { val outDir = File(project.buildDir, "dummy/out") val task: TaskProvider = project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sourceDir.set(sourceDir) it.bundleIdFile.set(debugMetaPropertiesFile) it.output.set(outDir) @@ -224,7 +217,6 @@ class BundleSourcesTaskTest { val outDir = File(project.buildDir, "dummy/out") val task: TaskProvider = project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sourceDir.set(sourceDir) it.bundleIdFile.set(debugMetaPropertiesFile) it.output.set(outDir) diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt index 404fd305a..504cb4193 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt @@ -18,40 +18,17 @@ class SentryCliExecTaskTest { @get:Rule val tempDir = TemporaryFolder() @Test - fun `cli-executable is set correctly`() { + fun `cli path is resolved and extracted from resources`() { val project = createProject() - - val task: TaskProvider = - project.tasks.register("testTask", TestTask::class.java) { - it.cliExecutable.set("sentry-cli") - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("sentry-cli" in args) - assertFalse("--org" in args) - assertFalse("--project" in args) - assertFalse("--log-level=debug" in args) - } - - @Test - fun `cli-executable is extracted from resources if required`() { - val project = createProject() - - val cliPath = - SentryCliProvider.getCliResourcesExtractionPath(project.layout.buildDirectory).get().asFile + val cliPath = SentryCliProvider.getCliResourcesExtractionPath(project.buildDir) assertTrue(!cliPath.exists()) val task: TaskProvider = - project.tasks.register("testTask", TestTask::class.java) { - it.cliExecutable.set(cliPath.absolutePath) - } + project.tasks.register("testTask", TestTask::class.java) - // when the args are computed (usually during task execution) val args = task.get().computeCommandLineArgs() - // then the CLI should be extracted and set assertTrue(cliPath.exists()) assertEquals(cliPath.absolutePath, File(args[0]).absolutePath) } @@ -62,7 +39,6 @@ class SentryCliExecTaskTest { val task: TaskProvider = project.tasks.register("testTask", TestTask::class.java) { - it.cliExecutable.set("sentry-cli") it.debug.set(true) } @@ -77,7 +53,6 @@ class SentryCliExecTaskTest { val propertiesFile = project.file("dummy/folder/sentry.properties") val task: TaskProvider = project.tasks.register("testTask", TestTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sentryProperties.set(propertiesFile) } @@ -94,7 +69,6 @@ class SentryCliExecTaskTest { val project = createProject() val task: TaskProvider = project.tasks.register("testTask", TestTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sentryAuthToken.set("") } @@ -107,9 +81,7 @@ class SentryCliExecTaskTest { fun `without sentryProperties file SENTRY_PROPERTIES is not set`() { val project = createProject() val task: TaskProvider = - project.tasks.register("testTask", TestTask::class.java) { - it.cliExecutable.set("sentry-cli") - } + project.tasks.register("testTask", TestTask::class.java) task.get().setSentryPropertiesEnv() @@ -121,7 +93,6 @@ class SentryCliExecTaskTest { val project = createProject() val task: TaskProvider = project.tasks.register("testTask", TestTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sentryOrganization.set("dummy-org") } @@ -136,7 +107,6 @@ class SentryCliExecTaskTest { val project = createProject() val task: TaskProvider = project.tasks.register("testTask", TestTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sentryProject.set("dummy-proj") } @@ -151,7 +121,6 @@ class SentryCliExecTaskTest { val project = createProject() val task: TaskProvider = project.tasks.register("testTask", TestTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sentryUrl.set("https://some-host.sentry.io") } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTaskTest.kt index 7572c6a9e..0413c722b 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTaskTest.kt @@ -30,13 +30,12 @@ class SentryUploadAppArtifactTaskTest { val apkFile = project.apkDirProvider(dummyApkName) val task: TaskProvider = project.tasks.register("testUploadAppArtifact", SentryUploadAppArtifactTask::class.java) { - it.cliExecutable.set("sentry-cli") it.apk.set(apkFile) } val args = task.get().computeCommandLineArgs() - assertThat(args).contains("sentry-cli") + assertThat(args.first()).contains("sentry-cli") assertThat(args).contains("build") assertThat(args).contains("upload") assertThatStrings(args).containsEndingWith(dummyApkName) @@ -49,13 +48,12 @@ class SentryUploadAppArtifactTaskTest { val aabFile = project.aabFileProvider(dummyAabName) val task: TaskProvider = project.tasks.register("testUploadAppArtifact", SentryUploadAppArtifactTask::class.java) { - it.cliExecutable.set("sentry-cli") it.bundle.set(aabFile) } val args = task.get().computeCommandLineArgs() - assertThat(args).contains("sentry-cli") + assertThat(args.first()).contains("sentry-cli") assertThat(args).contains("build") assertThat(args).contains("upload") assertThatStrings(args).containsEndingWith(dummyAabName) @@ -68,7 +66,6 @@ class SentryUploadAppArtifactTaskTest { val apkDir = project.apkDirProvider(dummyApkName) val task: TaskProvider = project.tasks.register("testUploadProguardMapping", SentryUploadAppArtifactTask::class.java) { - it.cliExecutable.set("sentry-cli") it.apk.set(apkDir) it.debug.set(true) } @@ -127,7 +124,6 @@ class SentryUploadAppArtifactTaskTest { val task: TaskProvider = project.tasks.register("testUploadProguardMapping", SentryUploadAppArtifactTask::class.java) { it.sentryUrl.set("https://some-host.sentry.io") - it.cliExecutable.set("sentry-cli") it.apk.set(apkDir) } @@ -145,7 +141,6 @@ class SentryUploadAppArtifactTaskTest { val apkDir = project.apkDirProvider(dummyApkName) val task: TaskProvider = project.tasks.register("testUploadProguardMapping", SentryUploadAppArtifactTask::class.java) { - it.cliExecutable.set("sentry-cli") it.apk.set(apkDir) it.sentryOrganization.set("dummy-org") } @@ -163,7 +158,6 @@ class SentryUploadAppArtifactTaskTest { val apkDir = project.apkDirProvider(dummyApkName) val task: TaskProvider = project.tasks.register("testUploadProguardMapping", SentryUploadAppArtifactTask::class.java) { - it.cliExecutable.set("sentry-cli") it.apk.set(apkDir) it.sentryProject.set("dummy-proj") } @@ -180,7 +174,6 @@ class SentryUploadAppArtifactTaskTest { val nonExistentBundle = project.layout.buildDirectory.file("nonexistent/bundle.aab") val task: TaskProvider = project.tasks.register("testUploadAppArtifact", SentryUploadAppArtifactTask::class.java) { - it.cliExecutable.set("sentry-cli") it.bundle.set(nonExistentBundle) } @@ -195,7 +188,6 @@ class SentryUploadAppArtifactTaskTest { val nonExistentApkDir = project.layout.buildDirectory.dir("nonexistent/apk") val task: TaskProvider = project.tasks.register("testUploadAppArtifact", SentryUploadAppArtifactTask::class.java) { - it.cliExecutable.set("sentry-cli") it.apk.set(nonExistentApkDir) } @@ -212,7 +204,6 @@ class SentryUploadAppArtifactTaskTest { emptyApkDir.get().asFile.mkdirs() val task: TaskProvider = project.tasks.register("testUploadAppArtifact", SentryUploadAppArtifactTask::class.java) { - it.cliExecutable.set("sentry-cli") it.apk.set(emptyApkDir) } @@ -226,7 +217,6 @@ class SentryUploadAppArtifactTaskTest { val project = createProject() val task: TaskProvider = project.tasks.register("testUploadAppArtifact", SentryUploadAppArtifactTask::class.java) { - it.cliExecutable.set("sentry-cli") } val exception = assertFailsWith { task.get().computeCommandLineArgs() } @@ -240,7 +230,6 @@ class SentryUploadAppArtifactTaskTest { val apkDir = project.apkDirProvider(dummyApkName) val task: TaskProvider = project.tasks.register("testUploadAppArtifact", SentryUploadAppArtifactTask::class.java) { - it.cliExecutable.set("sentry-cli") it.apk.set(apkDir) it.vcsHeadSha.set("abc123def456") it.vcsBaseSha.set("def456abc123") @@ -276,7 +265,6 @@ class SentryUploadAppArtifactTaskTest { val apkDir = project.apkDirProvider(dummyApkName) val task: TaskProvider = project.tasks.register("testUploadAppArtifact", SentryUploadAppArtifactTask::class.java) { - it.cliExecutable.set("sentry-cli") it.apk.set(apkDir) it.installGroups.set(setOf("internal", "beta", "alpha")) } @@ -295,7 +283,6 @@ class SentryUploadAppArtifactTaskTest { val apkDir = project.apkDirProvider(dummyApkName) val task: TaskProvider = project.tasks.register("testUploadAppArtifact", SentryUploadAppArtifactTask::class.java) { - it.cliExecutable.set("sentry-cli") it.apk.set(apkDir) it.installGroups.set(emptySet()) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt index b5ef6de5a..e1a1af5d1 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt @@ -16,7 +16,6 @@ class SentryUploadNativeSymbolsTaskTest { val project = createProject() val task = createTestTask(project) { - it.cliExecutable.set("sentry-cli") it.includeNativeSources.set(false) it.variantName.set("debug") it.autoUploadNativeSymbol.set(true) @@ -24,7 +23,7 @@ class SentryUploadNativeSymbolsTaskTest { val args = task.computeCommandLineArgs() - assertTrue("sentry-cli" in args) + assertTrue(args.any { it.contains("sentry-cli") }) assertTrue("debug-files" in args) assertTrue("upload" in args) val path = File(project.buildDir, "intermediates/merged_native_libs/debug").absolutePath @@ -38,7 +37,6 @@ class SentryUploadNativeSymbolsTaskTest { val project = createProject() val task = createTestTask(project) { - it.cliExecutable.set("sentry-cli") it.includeNativeSources.set(false) it.variantName.set("debug") it.autoUploadNativeSymbol.set(false) @@ -54,7 +52,6 @@ class SentryUploadNativeSymbolsTaskTest { val project = createProject() val task = createTestTask(project) { - it.cliExecutable.set("sentry-cli") it.includeNativeSources.set(false) it.variantName.set("debug") it.autoUploadNativeSymbol.set(false) @@ -71,7 +68,6 @@ class SentryUploadNativeSymbolsTaskTest { val project = createProject() val task = createTestTask(project) { - it.cliExecutable.set("sentry-cli") it.includeNativeSources.set(true) it.variantName.set("debug") it.autoUploadNativeSymbol.set(true) @@ -108,7 +104,6 @@ class SentryUploadNativeSymbolsTaskTest { val project = createProject() val task = createTestTask(project) { - it.cliExecutable.set("sentry-cli") it.sentryOrganization.set("dummy-org") it.includeNativeSources.set(true) it.variantName.set("debug") @@ -126,7 +121,6 @@ class SentryUploadNativeSymbolsTaskTest { val project = createProject() val task = createTestTask(project) { - it.cliExecutable.set("sentry-cli") it.sentryProject.set("dummy-proj") it.includeNativeSources.set(true) it.variantName.set("debug") @@ -144,7 +138,6 @@ class SentryUploadNativeSymbolsTaskTest { val project = createProject() val task = createTestTask(project) { - it.cliExecutable.set("sentry-cli") it.sentryUrl.set("https://some-host.sentry.io") it.includeNativeSources.set(true) it.variantName.set("debug") @@ -162,7 +155,6 @@ class SentryUploadNativeSymbolsTaskTest { val project = createProject() val task = createTestTask(project) { - it.cliExecutable.set("sentry-cli") it.sentryUrl.set("https://some-host.sentry.io") it.includeNativeSources.set(true) it.variantName.set("debug") diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt index 2c22b99a6..b365dfc62 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt @@ -33,7 +33,6 @@ class SentryUploadProguardMappingTaskTest { "testUploadProguardMapping", SentryUploadProguardMappingsTask::class.java, ) { - it.cliExecutable.set("sentry-cli") it.uuidFile.set(uuidFileProvider) it.mappingsFiles = mappingFile it.autoUploadProguardMapping.set(true) @@ -41,7 +40,7 @@ class SentryUploadProguardMappingTaskTest { val args = task.get().computeCommandLineArgs() - assertTrue("sentry-cli" in args) + assertTrue(args.any { it.contains("sentry-cli") }) assertTrue("upload-proguard" in args) assertTrue("--uuid" in args) assertTrue(randomUuid.toString() in args) @@ -61,7 +60,6 @@ class SentryUploadProguardMappingTaskTest { "testUploadProguardMapping", SentryUploadProguardMappingsTask::class.java, ) { - it.cliExecutable.set("sentry-cli") it.uuidFile.set(uuidFileProvider) it.mappingsFiles = mappingFile it.autoUploadProguardMapping.set(true) @@ -69,7 +67,7 @@ class SentryUploadProguardMappingTaskTest { val args = task.get().computeCommandLineArgs() - assertTrue("sentry-cli" in args) + assertTrue(args.any { it.contains("sentry-cli") }) assertTrue("upload-proguard" in args) assertTrue("--uuid" in args) assertTrue(randomUuid.toString() in args) @@ -101,7 +99,6 @@ class SentryUploadProguardMappingTaskTest { "testUploadProguardMapping", SentryUploadProguardMappingsTask::class.java, ) { - it.cliExecutable.set("sentry-cli") it.uuidFile.set(uuidFileProvider) it.mappingsFiles = mappingFiles it.autoUploadProguardMapping.set(true) @@ -123,7 +120,6 @@ class SentryUploadProguardMappingTaskTest { "testUploadProguardMapping", SentryUploadProguardMappingsTask::class.java, ) { - it.cliExecutable.set("sentry-cli") it.uuidFile.set(uuidFileProvider) it.mappingsFiles = mappingFile it.autoUploadProguardMapping.set(false) @@ -145,7 +141,6 @@ class SentryUploadProguardMappingTaskTest { "testUploadProguardMapping", SentryUploadProguardMappingsTask::class.java, ) { - it.cliExecutable.set("sentry-cli") it.uuidFile.set(uuidFileProvider) it.mappingsFiles = mappingFile it.autoUploadProguardMapping.set(false) @@ -218,7 +213,6 @@ class SentryUploadProguardMappingTaskTest { SentryUploadProguardMappingsTask::class.java, ) { it.sentryUrl.set("https://some-host.sentry.io") - it.cliExecutable.set("sentry-cli") it.uuidFile.set(uuidFileProvider) it.mappingsFiles = mappingFile it.autoUploadProguardMapping.set(false) @@ -241,7 +235,6 @@ class SentryUploadProguardMappingTaskTest { "testUploadProguardMapping", SentryUploadProguardMappingsTask::class.java, ) { - it.cliExecutable.set("sentry-cli") it.uuidFile.set(uuidFileProvider) it.mappingsFiles = mappingFile it.autoUploadProguardMapping.set(false) @@ -265,7 +258,6 @@ class SentryUploadProguardMappingTaskTest { "testUploadProguardMapping", SentryUploadProguardMappingsTask::class.java, ) { - it.cliExecutable.set("sentry-cli") it.uuidFile.set(uuidFileProvider) it.mappingsFiles = mappingFile it.autoUploadProguardMapping.set(false) diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTaskTest.kt index ca2303dbc..ef19513e3 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTaskTest.kt @@ -15,14 +15,13 @@ class SentryUploadSnapshotsTaskTest { @Test fun `cli-executable is set correctly`() { val task = createTestTask { - it.cliExecutable.set("sentry-cli") it.appId.set("com.example") it.snapshotsPath.set(File("/path/to/snapshots")) } val args = task.computeCommandLineArgs() - assertTrue("sentry-cli" in args) + assertTrue(args.any { it.contains("sentry-cli") }) assertTrue("build" in args) assertTrue("snapshots" in args) assertTrue("--app-id" in args) @@ -34,7 +33,6 @@ class SentryUploadSnapshotsTaskTest { @Test fun `--log-level=debug is set correctly`() { val task = createTestTask { - it.cliExecutable.set("sentry-cli") it.appId.set("com.example") it.snapshotsPath.set(File("/path/to/snapshots")) it.debug.set(true) @@ -68,7 +66,6 @@ class SentryUploadSnapshotsTaskTest { @Test fun `with sentryOrganization adds --org`() { val task = createTestTask { - it.cliExecutable.set("sentry-cli") it.sentryOrganization.set("dummy-org") it.appId.set("com.example") it.snapshotsPath.set(File("/path/to/snapshots")) @@ -83,7 +80,6 @@ class SentryUploadSnapshotsTaskTest { @Test fun `with sentryProject adds --project`() { val task = createTestTask { - it.cliExecutable.set("sentry-cli") it.sentryProject.set("dummy-proj") it.appId.set("com.example") it.snapshotsPath.set(File("/path/to/snapshots")) @@ -98,7 +94,6 @@ class SentryUploadSnapshotsTaskTest { @Test fun `with sentryUrl adds --url`() { val task = createTestTask { - it.cliExecutable.set("sentry-cli") it.sentryUrl.set("https://some-host.sentry.io") it.appId.set("com.example") it.snapshotsPath.set(File("/path/to/snapshots")) @@ -113,7 +108,6 @@ class SentryUploadSnapshotsTaskTest { @Test fun `the --url parameter is placed as the first argument`() { val task = createTestTask { - it.cliExecutable.set("sentry-cli") it.sentryUrl.set("https://some-host.sentry.io") it.appId.set("com.example") it.snapshotsPath.set(File("/path/to/snapshots")) @@ -127,7 +121,6 @@ class SentryUploadSnapshotsTaskTest { @Test fun `all vcs parameters are passed to CLI correctly`() { val task = createTestTask { - it.cliExecutable.set("sentry-cli") it.appId.set("com.example") it.snapshotsPath.set(File("/path/to/snapshots")) it.vcsHeadSha.set("abc123def456") @@ -159,7 +152,6 @@ class SentryUploadSnapshotsTaskTest { @Test fun `diffThreshold is passed to CLI when non-zero`() { val task = createTestTask { - it.cliExecutable.set("sentry-cli") it.appId.set("com.example") it.snapshotsPath.set(File("/path/to/snapshots")) it.diffThreshold.set(0.05) @@ -173,7 +165,6 @@ class SentryUploadSnapshotsTaskTest { @Test fun `diffThreshold is omitted when zero`() { val task = createTestTask { - it.cliExecutable.set("sentry-cli") it.appId.set("com.example") it.snapshotsPath.set(File("/path/to/snapshots")) it.diffThreshold.set(0.0) @@ -187,7 +178,6 @@ class SentryUploadSnapshotsTaskTest { @Test fun `diffThreshold is omitted when not set`() { val task = createTestTask { - it.cliExecutable.set("sentry-cli") it.appId.set("com.example") it.snapshotsPath.set(File("/path/to/snapshots")) } @@ -200,7 +190,6 @@ class SentryUploadSnapshotsTaskTest { @Test fun `vcs parameters are omitted when not set`() { val task = createTestTask { - it.cliExecutable.set("sentry-cli") it.appId.set("com.example") it.snapshotsPath.set(File("/path/to/snapshots")) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt index 9f7457d8d..617cc41b6 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt @@ -24,14 +24,13 @@ class UploadSourceBundleTaskTest { val sourceBundleDir = File(project.buildDir, "dummy/folder") val task: TaskProvider = project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sourceBundleDir.set(sourceBundleDir) it.autoUploadSourceContext.set(true) } val args = task.get().computeCommandLineArgs() - assertTrue("sentry-cli" in args) + assertTrue(args.any { it.contains("sentry-cli") }) assertTrue("debug-files" in args) assertTrue("upload" in args) assertTrue("--type=jvm" in args) @@ -50,7 +49,6 @@ class UploadSourceBundleTaskTest { val sourceBundleDir = File(project.buildDir, "dummy/folder") val task: TaskProvider = project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sourceBundleDir.set(sourceBundleDir) it.autoUploadSourceContext.set(false) } @@ -67,7 +65,6 @@ class UploadSourceBundleTaskTest { val sourceBundleDir = File(project.buildDir, "dummy/folder") val task: TaskProvider = project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sourceBundleDir.set(sourceBundleDir) it.autoUploadSourceContext.set(true) it.debug.set(true) @@ -86,7 +83,6 @@ class UploadSourceBundleTaskTest { val task: TaskProvider = project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sourceBundleDir.set(sourceBundleDir) it.autoUploadSourceContext.set(true) it.sentryProperties.set(propertiesFile) @@ -107,7 +103,6 @@ class UploadSourceBundleTaskTest { val task: TaskProvider = project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sourceBundleDir.set(sourceBundleDir) it.autoUploadSourceContext.set(true) it.sentryAuthToken.set("") @@ -125,7 +120,6 @@ class UploadSourceBundleTaskTest { val task: TaskProvider = project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sourceBundleDir.set(sourceBundleDir) it.autoUploadSourceContext.set(true) it.sentryUrl.set("https://some-host.sentry.io") @@ -144,7 +138,6 @@ class UploadSourceBundleTaskTest { val task: TaskProvider = project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sourceBundleDir.set(sourceBundleDir) it.autoUploadSourceContext.set(true) } @@ -161,7 +154,6 @@ class UploadSourceBundleTaskTest { val task: TaskProvider = project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sourceBundleDir.set(sourceBundleDir) it.autoUploadSourceContext.set(true) it.sentryOrganization.set("dummy-org") @@ -180,7 +172,6 @@ class UploadSourceBundleTaskTest { val task: TaskProvider = project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { - it.cliExecutable.set("sentry-cli") it.sourceBundleDir.set(sourceBundleDir) it.autoUploadSourceContext.set(true) it.sentryProject.set("dummy-proj") diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryServiceTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryServiceTest.kt index e0c462921..61e7a3f1c 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryServiceTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryServiceTest.kt @@ -1,7 +1,9 @@ package io.sentry.android.gradle.telemetry -import io.sentry.android.gradle.SentryCliProvider +import io.sentry.BuildConfig +import io.sentry.android.gradle.extensions.SentryPluginExtension import kotlin.test.assertEquals +import kotlin.test.assertTrue import org.gradle.testfixtures.ProjectBuilder import org.junit.Rule import org.junit.Test @@ -11,24 +13,55 @@ class SentryTelemetryServiceTest { @get:Rule val testProjectDir = TemporaryFolder() - @Suppress("UnstableApiUsage") @Test - fun `SentryCliInfoValueSource returns empty string when no auth token is present`() { + fun `createParameters uses BuildConfig CliVersion`() { val project = ProjectBuilder.builder().withProjectDir(testProjectDir.root).build() + val extension = project.extensions.create("sentry", SentryPluginExtension::class.java) - val cliPath = - SentryCliProvider.getCliResourcesExtractionPath(project.layout.buildDirectory).get().asFile + val params = + SentryTelemetryService.createParameters( + project, + null, + extension, + null, + "test", + ) - val infoOutput = - project.providers - .of(SentryCliInfoValueSource::class.java) { cliVS -> - cliVS.parameters.buildDirectory.set(project.layout.buildDirectory) - cliVS.parameters.cliExecutable.set(cliPath.absolutePath) - // sets an empty/invalid auth token - cliVS.parameters.authToken.set("") - } - .get() + assertEquals(BuildConfig.CliVersion, params.cliVersion) + } + + @Test + fun `createParameters detects SaaS when no URL is set`() { + val project = ProjectBuilder.builder().withProjectDir(testProjectDir.root).build() + val extension = project.extensions.create("sentry", SentryPluginExtension::class.java) + + val params = + SentryTelemetryService.createParameters( + project, + null, + extension, + null, + "test", + ) + + assertTrue(params.saas == true) + } + + @Test + fun `createParameters detects self-hosted when URL is set`() { + val project = ProjectBuilder.builder().withProjectDir(testProjectDir.root).build() + val extension = project.extensions.create("sentry", SentryPluginExtension::class.java) + extension.url.set("https://sentry.example.com") + + val params = + SentryTelemetryService.createParameters( + project, + null, + extension, + null, + "test", + ) - assertEquals("", infoOutput) + assertTrue(params.saas == false) } } From 723505a7cbb4510e923d915188fe8d8749950bf5 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 22 May 2026 15:18:07 +0200 Subject: [PATCH 04/17] fix(config-cache): Use abstract DirectoryProperty for CLI paths Replace private val fields that captured project.projectDir and project.rootDir with abstract DirectoryProperty fields annotated with @Internal. Properties are wired during configuration via asSentryCliExec() and resolved during execution, following Gradle best practices for configuration cache compatibility. Also removes duplicate buildDir property from SentryUploadNativeSymbolsTask in favor of the base class property. Co-Authored-By: Claude Opus 4.6 --- .../android/gradle/tasks/SentryCliExecTask.kt | 20 ++++++++++++------- .../tasks/SentryUploadNativeSymbolsTask.kt | 5 ++--- .../android/gradle/util/SentryCliExec.kt | 8 ++++++-- .../gradle/tasks/BundleSourcesTaskTest.kt | 11 ++++++++++ .../gradle/tasks/SentryCliExecTaskTest.kt | 14 ++++++++++++- .../tasks/SentryUploadAppArtifactTaskTest.kt | 19 ++++++++++++++++++ .../SentryUploadNativeSymbolsTaskTest.kt | 7 ++++++- .../SentryUploadProguardMappingTaskTest.kt | 14 +++++++++++++ .../tasks/SentryUploadSnapshotsTaskTest.kt | 7 ++++++- .../tasks/UploadSourceBundleTaskTest.kt | 12 +++++++++++ 10 files changed, 102 insertions(+), 15 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt index c81d74503..686b5aec7 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt @@ -4,11 +4,10 @@ import io.sentry.android.gradle.SentryCliProvider import io.sentry.android.gradle.telemetry.SentryTelemetryService import io.sentry.android.gradle.util.info import io.sentry.android.gradle.util.setSentryPipelineEnv -import java.io.File import org.apache.tools.ant.taskdefs.condition.Os +import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property -import org.gradle.api.provider.Provider import org.gradle.api.tasks.Exec import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFile @@ -38,9 +37,11 @@ abstract class SentryCliExecTask : Exec() { @get:Internal abstract val sentryTelemetryService: Property - private val sentryProjectDir: File = project.projectDir - private val sentryRootDir: File = project.rootDir - private val buildDirectory: Provider = project.layout.buildDirectory.asFile + @get:Internal abstract val sentryProjectDir: DirectoryProperty + + @get:Internal abstract val sentryRootDir: DirectoryProperty + + @get:Internal abstract val buildDirectory: DirectoryProperty override fun exec() { computeCommandLineArgs().let { @@ -93,9 +94,14 @@ abstract class SentryCliExecTask : Exec() { args.add(1, "/c") } + val buildDir = buildDirectory.get().asFile val cliPath = - SentryCliProvider.getSentryCliPath(sentryProjectDir, buildDirectory.get(), sentryRootDir) - args.add(SentryCliProvider.maybeExtractFromResources(buildDirectory.get(), cliPath)) + SentryCliProvider.getSentryCliPath( + sentryProjectDir.get().asFile, + buildDir, + sentryRootDir.get().asFile, + ) + args.add(SentryCliProvider.maybeExtractFromResources(buildDir, cliPath)) args.addAll(preArgs()) getArguments(args) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt index d823ff196..503776f24 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt @@ -31,8 +31,6 @@ abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { @get:Internal abstract val variantName: Property - private val buildDir: Provider = project.layout.buildDirectory.asFile - override fun getArguments(args: MutableList) { args.add("debug-files") args.add("upload") @@ -44,7 +42,8 @@ abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { // eg absoluteProjectFolderPath/build/intermediates/merged_native_libs/{variantName} // where {variantName} could be debug/release... args.add( - File(buildDir.get(), "intermediates/merged_native_libs/${variantName.get()}").absolutePath + File(buildDirectory.get().asFile, "intermediates/merged_native_libs/${variantName.get()}") + .absolutePath ) // Only include sources if includeNativeSources is enabled, as this is opt-in feature diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt index 55a25fecd..1bfb2ea14 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt @@ -1,10 +1,10 @@ package io.sentry.android.gradle.util import io.sentry.BuildConfig +import io.sentry.android.gradle.tasks.SentryCliExecTask import io.sentry.android.gradle.util.CliFailureReason.OUTDATED import java.io.ByteArrayOutputStream import org.gradle.api.GradleException -import org.gradle.api.tasks.Exec import org.gradle.process.ExecSpec fun ExecSpec.setSentryPipelineEnv() { @@ -15,7 +15,11 @@ fun ExecSpec.setSentryPipelineEnv() { * An ext function for tasks that wrap sentry-cli, which provides common error handling. Must be * called at configuration phase (=when registering a task). */ -fun Exec.asSentryCliExec() { +fun SentryCliExecTask.asSentryCliExec() { + sentryProjectDir.set(project.layout.projectDirectory) + sentryRootDir.fileValue(project.rootDir) + buildDirectory.set(project.layout.buildDirectory) + isIgnoreExitValue = true // this is a workaround, otherwise doFirst is not needed // https://github.com/gradle/gradle/issues/16535 diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt index ff9136855..49232b912 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt @@ -29,6 +29,7 @@ class BundleSourcesTaskTest { val outDir = File(project.buildDir, "dummy/out") val task: TaskProvider = project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { + it.configureCliPaths(project) it.sourceDir.set(sourceDir) it.bundleIdFile.set(debugMetaPropertiesFile) it.output.set(outDir) @@ -56,6 +57,7 @@ class BundleSourcesTaskTest { val outDir = File(project.buildDir, "dummy/out") val task: TaskProvider = project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { + it.configureCliPaths(project) it.sourceDir.set(sourceDir) it.bundleIdFile.set(debugMetaPropertiesFile) it.output.set(outDir) @@ -137,6 +139,7 @@ class BundleSourcesTaskTest { val outDir = File(project.buildDir, "dummy/out") val task: TaskProvider = project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { + it.configureCliPaths(project) it.sourceDir.set(sourceDir) it.bundleIdFile.set(debugMetaPropertiesFile) it.output.set(outDir) @@ -158,6 +161,7 @@ class BundleSourcesTaskTest { val outDir = File(project.buildDir, "dummy/out") val task: TaskProvider = project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { + it.configureCliPaths(project) it.sourceDir.set(sourceDir) it.bundleIdFile.set(debugMetaPropertiesFile) it.output.set(outDir) @@ -217,6 +221,7 @@ class BundleSourcesTaskTest { val outDir = File(project.buildDir, "dummy/out") val task: TaskProvider = project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { + it.configureCliPaths(project) it.sourceDir.set(sourceDir) it.bundleIdFile.set(debugMetaPropertiesFile) it.output.set(outDir) @@ -236,6 +241,12 @@ class BundleSourcesTaskTest { } } + private fun SentryCliExecTask.configureCliPaths(project: Project) { + sentryProjectDir.set(project.layout.projectDirectory) + sentryRootDir.fileValue(project.rootDir) + buildDirectory.set(project.layout.buildDirectory) + } + private fun createDebugMetaProperties( project: Project, uuid: UUID = UUID.randomUUID(), diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt index 504cb4193..778f838a4 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt @@ -25,7 +25,9 @@ class SentryCliExecTaskTest { assertTrue(!cliPath.exists()) val task: TaskProvider = - project.tasks.register("testTask", TestTask::class.java) + project.tasks.register("testTask", TestTask::class.java) { + it.configureCliPaths(project) + } val args = task.get().computeCommandLineArgs() @@ -39,6 +41,7 @@ class SentryCliExecTaskTest { val task: TaskProvider = project.tasks.register("testTask", TestTask::class.java) { + it.configureCliPaths(project) it.debug.set(true) } @@ -93,6 +96,7 @@ class SentryCliExecTaskTest { val project = createProject() val task: TaskProvider = project.tasks.register("testTask", TestTask::class.java) { + it.configureCliPaths(project) it.sentryOrganization.set("dummy-org") } @@ -107,6 +111,7 @@ class SentryCliExecTaskTest { val project = createProject() val task: TaskProvider = project.tasks.register("testTask", TestTask::class.java) { + it.configureCliPaths(project) it.sentryProject.set("dummy-proj") } @@ -121,6 +126,7 @@ class SentryCliExecTaskTest { val project = createProject() val task: TaskProvider = project.tasks.register("testTask", TestTask::class.java) { + it.configureCliPaths(project) it.sentryUrl.set("https://some-host.sentry.io") } @@ -137,6 +143,12 @@ class SentryCliExecTaskTest { } } + private fun SentryCliExecTask.configureCliPaths(project: Project) { + sentryProjectDir.set(project.layout.projectDirectory) + sentryRootDir.fileValue(project.rootDir) + buildDirectory.set(project.layout.buildDirectory) + } + abstract class TestTask : SentryCliExecTask() { override fun getArguments(args: MutableList) { // no-op diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTaskTest.kt index 0413c722b..3a6dc9e7e 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTaskTest.kt @@ -30,6 +30,7 @@ class SentryUploadAppArtifactTaskTest { val apkFile = project.apkDirProvider(dummyApkName) val task: TaskProvider = project.tasks.register("testUploadAppArtifact", SentryUploadAppArtifactTask::class.java) { + it.configureCliPaths(project) it.apk.set(apkFile) } @@ -48,6 +49,7 @@ class SentryUploadAppArtifactTaskTest { val aabFile = project.aabFileProvider(dummyAabName) val task: TaskProvider = project.tasks.register("testUploadAppArtifact", SentryUploadAppArtifactTask::class.java) { + it.configureCliPaths(project) it.bundle.set(aabFile) } @@ -66,6 +68,7 @@ class SentryUploadAppArtifactTaskTest { val apkDir = project.apkDirProvider(dummyApkName) val task: TaskProvider = project.tasks.register("testUploadProguardMapping", SentryUploadAppArtifactTask::class.java) { + it.configureCliPaths(project) it.apk.set(apkDir) it.debug.set(true) } @@ -123,6 +126,7 @@ class SentryUploadAppArtifactTaskTest { val apkDir = project.apkDirProvider(dummyApkName) val task: TaskProvider = project.tasks.register("testUploadProguardMapping", SentryUploadAppArtifactTask::class.java) { + it.configureCliPaths(project) it.sentryUrl.set("https://some-host.sentry.io") it.apk.set(apkDir) } @@ -141,6 +145,7 @@ class SentryUploadAppArtifactTaskTest { val apkDir = project.apkDirProvider(dummyApkName) val task: TaskProvider = project.tasks.register("testUploadProguardMapping", SentryUploadAppArtifactTask::class.java) { + it.configureCliPaths(project) it.apk.set(apkDir) it.sentryOrganization.set("dummy-org") } @@ -158,6 +163,7 @@ class SentryUploadAppArtifactTaskTest { val apkDir = project.apkDirProvider(dummyApkName) val task: TaskProvider = project.tasks.register("testUploadProguardMapping", SentryUploadAppArtifactTask::class.java) { + it.configureCliPaths(project) it.apk.set(apkDir) it.sentryProject.set("dummy-proj") } @@ -174,6 +180,7 @@ class SentryUploadAppArtifactTaskTest { val nonExistentBundle = project.layout.buildDirectory.file("nonexistent/bundle.aab") val task: TaskProvider = project.tasks.register("testUploadAppArtifact", SentryUploadAppArtifactTask::class.java) { + it.configureCliPaths(project) it.bundle.set(nonExistentBundle) } @@ -188,6 +195,7 @@ class SentryUploadAppArtifactTaskTest { val nonExistentApkDir = project.layout.buildDirectory.dir("nonexistent/apk") val task: TaskProvider = project.tasks.register("testUploadAppArtifact", SentryUploadAppArtifactTask::class.java) { + it.configureCliPaths(project) it.apk.set(nonExistentApkDir) } @@ -204,6 +212,7 @@ class SentryUploadAppArtifactTaskTest { emptyApkDir.get().asFile.mkdirs() val task: TaskProvider = project.tasks.register("testUploadAppArtifact", SentryUploadAppArtifactTask::class.java) { + it.configureCliPaths(project) it.apk.set(emptyApkDir) } @@ -217,6 +226,7 @@ class SentryUploadAppArtifactTaskTest { val project = createProject() val task: TaskProvider = project.tasks.register("testUploadAppArtifact", SentryUploadAppArtifactTask::class.java) { + it.configureCliPaths(project) } val exception = assertFailsWith { task.get().computeCommandLineArgs() } @@ -230,6 +240,7 @@ class SentryUploadAppArtifactTaskTest { val apkDir = project.apkDirProvider(dummyApkName) val task: TaskProvider = project.tasks.register("testUploadAppArtifact", SentryUploadAppArtifactTask::class.java) { + it.configureCliPaths(project) it.apk.set(apkDir) it.vcsHeadSha.set("abc123def456") it.vcsBaseSha.set("def456abc123") @@ -265,6 +276,7 @@ class SentryUploadAppArtifactTaskTest { val apkDir = project.apkDirProvider(dummyApkName) val task: TaskProvider = project.tasks.register("testUploadAppArtifact", SentryUploadAppArtifactTask::class.java) { + it.configureCliPaths(project) it.apk.set(apkDir) it.installGroups.set(setOf("internal", "beta", "alpha")) } @@ -283,6 +295,7 @@ class SentryUploadAppArtifactTaskTest { val apkDir = project.apkDirProvider(dummyApkName) val task: TaskProvider = project.tasks.register("testUploadAppArtifact", SentryUploadAppArtifactTask::class.java) { + it.configureCliPaths(project) it.apk.set(apkDir) it.installGroups.set(emptySet()) } @@ -300,6 +313,12 @@ class SentryUploadAppArtifactTaskTest { } } + private fun SentryCliExecTask.configureCliPaths(project: Project) { + sentryProjectDir.set(project.layout.projectDirectory) + sentryRootDir.fileValue(project.rootDir) + buildDirectory.set(project.layout.buildDirectory) + } + private fun Project.apkDirProvider(path: String): Provider { val apkFileProvider = project.layout.buildDirectory.dir(path) apkFileProvider.get().asFile.parentFile.mkdirs() diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt index e1a1af5d1..9f903a34d 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt @@ -178,6 +178,11 @@ class SentryUploadNativeSymbolsTaskTest { block: (SentryUploadNativeSymbolsTask) -> Unit = {}, ): SentryUploadNativeSymbolsTask = project.tasks - .register("testUploadNativeSymbols", SentryUploadNativeSymbolsTask::class.java) { block(it) } + .register("testUploadNativeSymbols", SentryUploadNativeSymbolsTask::class.java) { + it.sentryProjectDir.set(project.layout.projectDirectory) + it.sentryRootDir.fileValue(project.rootDir) + it.buildDirectory.set(project.layout.buildDirectory) + block(it) + } .get() } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt index b365dfc62..49eb5c4cb 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt @@ -33,6 +33,7 @@ class SentryUploadProguardMappingTaskTest { "testUploadProguardMapping", SentryUploadProguardMappingsTask::class.java, ) { + it.configureCliPaths(project) it.uuidFile.set(uuidFileProvider) it.mappingsFiles = mappingFile it.autoUploadProguardMapping.set(true) @@ -60,6 +61,7 @@ class SentryUploadProguardMappingTaskTest { "testUploadProguardMapping", SentryUploadProguardMappingsTask::class.java, ) { + it.configureCliPaths(project) it.uuidFile.set(uuidFileProvider) it.mappingsFiles = mappingFile it.autoUploadProguardMapping.set(true) @@ -99,6 +101,7 @@ class SentryUploadProguardMappingTaskTest { "testUploadProguardMapping", SentryUploadProguardMappingsTask::class.java, ) { + it.configureCliPaths(project) it.uuidFile.set(uuidFileProvider) it.mappingsFiles = mappingFiles it.autoUploadProguardMapping.set(true) @@ -120,6 +123,7 @@ class SentryUploadProguardMappingTaskTest { "testUploadProguardMapping", SentryUploadProguardMappingsTask::class.java, ) { + it.configureCliPaths(project) it.uuidFile.set(uuidFileProvider) it.mappingsFiles = mappingFile it.autoUploadProguardMapping.set(false) @@ -141,6 +145,7 @@ class SentryUploadProguardMappingTaskTest { "testUploadProguardMapping", SentryUploadProguardMappingsTask::class.java, ) { + it.configureCliPaths(project) it.uuidFile.set(uuidFileProvider) it.mappingsFiles = mappingFile it.autoUploadProguardMapping.set(false) @@ -212,6 +217,7 @@ class SentryUploadProguardMappingTaskTest { "testUploadProguardMapping", SentryUploadProguardMappingsTask::class.java, ) { + it.configureCliPaths(project) it.sentryUrl.set("https://some-host.sentry.io") it.uuidFile.set(uuidFileProvider) it.mappingsFiles = mappingFile @@ -235,6 +241,7 @@ class SentryUploadProguardMappingTaskTest { "testUploadProguardMapping", SentryUploadProguardMappingsTask::class.java, ) { + it.configureCliPaths(project) it.uuidFile.set(uuidFileProvider) it.mappingsFiles = mappingFile it.autoUploadProguardMapping.set(false) @@ -258,6 +265,7 @@ class SentryUploadProguardMappingTaskTest { "testUploadProguardMapping", SentryUploadProguardMappingsTask::class.java, ) { + it.configureCliPaths(project) it.uuidFile.set(uuidFileProvider) it.mappingsFiles = mappingFile it.autoUploadProguardMapping.set(false) @@ -315,6 +323,12 @@ class SentryUploadProguardMappingTaskTest { } } + private fun SentryCliExecTask.configureCliPaths(project: Project) { + sentryProjectDir.set(project.layout.projectDirectory) + sentryRootDir.fileValue(project.rootDir) + buildDirectory.set(project.layout.buildDirectory) + } + private fun createFakeUuid( project: Project, uuid: UUID = UUID.randomUUID(), diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTaskTest.kt index ef19513e3..f52d981c5 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTaskTest.kt @@ -218,6 +218,11 @@ class SentryUploadSnapshotsTaskTest { block: (SentryUploadSnapshotsTask) -> Unit = {}, ): SentryUploadSnapshotsTask = project.tasks - .register("testUploadSnapshots", SentryUploadSnapshotsTask::class.java) { block(it) } + .register("testUploadSnapshots", SentryUploadSnapshotsTask::class.java) { + it.sentryProjectDir.set(project.layout.projectDirectory) + it.sentryRootDir.fileValue(project.rootDir) + it.buildDirectory.set(project.layout.buildDirectory) + block(it) + } .get() } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt index 617cc41b6..122097290 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt @@ -24,6 +24,7 @@ class UploadSourceBundleTaskTest { val sourceBundleDir = File(project.buildDir, "dummy/folder") val task: TaskProvider = project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { + it.configureCliPaths(project) it.sourceBundleDir.set(sourceBundleDir) it.autoUploadSourceContext.set(true) } @@ -49,6 +50,7 @@ class UploadSourceBundleTaskTest { val sourceBundleDir = File(project.buildDir, "dummy/folder") val task: TaskProvider = project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { + it.configureCliPaths(project) it.sourceBundleDir.set(sourceBundleDir) it.autoUploadSourceContext.set(false) } @@ -65,6 +67,7 @@ class UploadSourceBundleTaskTest { val sourceBundleDir = File(project.buildDir, "dummy/folder") val task: TaskProvider = project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { + it.configureCliPaths(project) it.sourceBundleDir.set(sourceBundleDir) it.autoUploadSourceContext.set(true) it.debug.set(true) @@ -120,6 +123,7 @@ class UploadSourceBundleTaskTest { val task: TaskProvider = project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { + it.configureCliPaths(project) it.sourceBundleDir.set(sourceBundleDir) it.autoUploadSourceContext.set(true) it.sentryUrl.set("https://some-host.sentry.io") @@ -154,6 +158,7 @@ class UploadSourceBundleTaskTest { val task: TaskProvider = project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { + it.configureCliPaths(project) it.sourceBundleDir.set(sourceBundleDir) it.autoUploadSourceContext.set(true) it.sentryOrganization.set("dummy-org") @@ -172,6 +177,7 @@ class UploadSourceBundleTaskTest { val task: TaskProvider = project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { + it.configureCliPaths(project) it.sourceBundleDir.set(sourceBundleDir) it.autoUploadSourceContext.set(true) it.sentryProject.set("dummy-proj") @@ -189,4 +195,10 @@ class UploadSourceBundleTaskTest { return this } } + + private fun SentryCliExecTask.configureCliPaths(project: Project) { + sentryProjectDir.set(project.layout.projectDirectory) + sentryRootDir.fileValue(project.rootDir) + buildDirectory.set(project.layout.buildDirectory) + } } From 2166560757754d3b89c423a20ee918cfc7b4cb50 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 22 May 2026 15:20:29 +0200 Subject: [PATCH 05/17] chore: Apply spotless formatting Co-Authored-By: Claude Opus 4.6 --- .../android/gradle/AndroidComponentsConfig.kt | 8 +----- .../gradle/tasks/SentryCliExecTaskTest.kt | 12 +++------ .../telemetry/SentryTelemetryServiceTest.kt | 27 +++---------------- 3 files changed, 7 insertions(+), 40 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt index 93a51ecb4..f13811ea7 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt @@ -271,13 +271,7 @@ private fun Variant.configureTelemetry( val sentryTelemetryProvider = SentryTelemetryService.register(project) project.gradle.taskGraph.whenReady { sentryTelemetryProvider.get().start { - SentryTelemetryService.createParameters( - project, - variant, - extension, - sentryOrg, - "Android", - ) + SentryTelemetryService.createParameters(project, variant, extension, sentryOrg, "Android") } buildEvents.onOperationCompletion(sentryTelemetryProvider) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt index 778f838a4..a5da14519 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt @@ -3,7 +3,6 @@ package io.sentry.android.gradle.tasks import io.sentry.android.gradle.SentryCliProvider import java.io.File import kotlin.test.assertEquals -import kotlin.test.assertFalse import kotlin.test.assertNull import kotlin.test.assertTrue import org.gradle.api.Project @@ -25,9 +24,7 @@ class SentryCliExecTaskTest { assertTrue(!cliPath.exists()) val task: TaskProvider = - project.tasks.register("testTask", TestTask::class.java) { - it.configureCliPaths(project) - } + project.tasks.register("testTask", TestTask::class.java) { it.configureCliPaths(project) } val args = task.get().computeCommandLineArgs() @@ -71,9 +68,7 @@ class SentryCliExecTaskTest { fun `with sentryAuthToken env variable is set correctly`() { val project = createProject() val task: TaskProvider = - project.tasks.register("testTask", TestTask::class.java) { - it.sentryAuthToken.set("") - } + project.tasks.register("testTask", TestTask::class.java) { it.sentryAuthToken.set("") } task.get().setSentryAuthTokenEnv() @@ -83,8 +78,7 @@ class SentryCliExecTaskTest { @Test fun `without sentryProperties file SENTRY_PROPERTIES is not set`() { val project = createProject() - val task: TaskProvider = - project.tasks.register("testTask", TestTask::class.java) + val task: TaskProvider = project.tasks.register("testTask", TestTask::class.java) task.get().setSentryPropertiesEnv() diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryServiceTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryServiceTest.kt index 61e7a3f1c..fae886683 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryServiceTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryServiceTest.kt @@ -18,14 +18,7 @@ class SentryTelemetryServiceTest { val project = ProjectBuilder.builder().withProjectDir(testProjectDir.root).build() val extension = project.extensions.create("sentry", SentryPluginExtension::class.java) - val params = - SentryTelemetryService.createParameters( - project, - null, - extension, - null, - "test", - ) + val params = SentryTelemetryService.createParameters(project, null, extension, null, "test") assertEquals(BuildConfig.CliVersion, params.cliVersion) } @@ -35,14 +28,7 @@ class SentryTelemetryServiceTest { val project = ProjectBuilder.builder().withProjectDir(testProjectDir.root).build() val extension = project.extensions.create("sentry", SentryPluginExtension::class.java) - val params = - SentryTelemetryService.createParameters( - project, - null, - extension, - null, - "test", - ) + val params = SentryTelemetryService.createParameters(project, null, extension, null, "test") assertTrue(params.saas == true) } @@ -53,14 +39,7 @@ class SentryTelemetryServiceTest { val extension = project.extensions.create("sentry", SentryPluginExtension::class.java) extension.url.set("https://sentry.example.com") - val params = - SentryTelemetryService.createParameters( - project, - null, - extension, - null, - "test", - ) + val params = SentryTelemetryService.createParameters(project, null, extension, null, "test") assertTrue(params.saas == false) } From 0dc9c5c403a86659a0fa43e0d383b89006328187 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 22 May 2026 15:24:56 +0200 Subject: [PATCH 06/17] fix(config-cache): Use layout API for root dir property Replace fileValue(project.rootDir) with project.rootProject.layout.projectDirectory so the property is serialized as a project-relative reference by the config cache instead of an absolute path. Co-Authored-By: Claude Opus 4.6 --- .../main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt | 2 +- .../io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt | 2 +- .../io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt | 2 +- .../android/gradle/tasks/SentryUploadAppArtifactTaskTest.kt | 2 +- .../android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt | 2 +- .../android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt | 2 +- .../android/gradle/tasks/SentryUploadSnapshotsTaskTest.kt | 2 +- .../sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt index 1bfb2ea14..fb2d4ac56 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt @@ -17,7 +17,7 @@ fun ExecSpec.setSentryPipelineEnv() { */ fun SentryCliExecTask.asSentryCliExec() { sentryProjectDir.set(project.layout.projectDirectory) - sentryRootDir.fileValue(project.rootDir) + sentryRootDir.set(project.rootProject.layout.projectDirectory) buildDirectory.set(project.layout.buildDirectory) isIgnoreExitValue = true diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt index 49232b912..5b1acd65d 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt @@ -243,7 +243,7 @@ class BundleSourcesTaskTest { private fun SentryCliExecTask.configureCliPaths(project: Project) { sentryProjectDir.set(project.layout.projectDirectory) - sentryRootDir.fileValue(project.rootDir) + sentryRootDir.set(project.rootProject.layout.projectDirectory) buildDirectory.set(project.layout.buildDirectory) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt index a5da14519..bc14e7e23 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt @@ -139,7 +139,7 @@ class SentryCliExecTaskTest { private fun SentryCliExecTask.configureCliPaths(project: Project) { sentryProjectDir.set(project.layout.projectDirectory) - sentryRootDir.fileValue(project.rootDir) + sentryRootDir.set(project.rootProject.layout.projectDirectory) buildDirectory.set(project.layout.buildDirectory) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTaskTest.kt index 3a6dc9e7e..715d55de4 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTaskTest.kt @@ -315,7 +315,7 @@ class SentryUploadAppArtifactTaskTest { private fun SentryCliExecTask.configureCliPaths(project: Project) { sentryProjectDir.set(project.layout.projectDirectory) - sentryRootDir.fileValue(project.rootDir) + sentryRootDir.set(project.rootProject.layout.projectDirectory) buildDirectory.set(project.layout.buildDirectory) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt index 9f903a34d..422d54d35 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt @@ -180,7 +180,7 @@ class SentryUploadNativeSymbolsTaskTest { project.tasks .register("testUploadNativeSymbols", SentryUploadNativeSymbolsTask::class.java) { it.sentryProjectDir.set(project.layout.projectDirectory) - it.sentryRootDir.fileValue(project.rootDir) + it.sentryRootDir.set(project.rootProject.layout.projectDirectory) it.buildDirectory.set(project.layout.buildDirectory) block(it) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt index 49eb5c4cb..9ffaa04b5 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt @@ -325,7 +325,7 @@ class SentryUploadProguardMappingTaskTest { private fun SentryCliExecTask.configureCliPaths(project: Project) { sentryProjectDir.set(project.layout.projectDirectory) - sentryRootDir.fileValue(project.rootDir) + sentryRootDir.set(project.rootProject.layout.projectDirectory) buildDirectory.set(project.layout.buildDirectory) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTaskTest.kt index f52d981c5..fe429448e 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTaskTest.kt @@ -220,7 +220,7 @@ class SentryUploadSnapshotsTaskTest { project.tasks .register("testUploadSnapshots", SentryUploadSnapshotsTask::class.java) { it.sentryProjectDir.set(project.layout.projectDirectory) - it.sentryRootDir.fileValue(project.rootDir) + it.sentryRootDir.set(project.rootProject.layout.projectDirectory) it.buildDirectory.set(project.layout.buildDirectory) block(it) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt index 122097290..d1ba163ce 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt @@ -198,7 +198,7 @@ class UploadSourceBundleTaskTest { private fun SentryCliExecTask.configureCliPaths(project: Project) { sentryProjectDir.set(project.layout.projectDirectory) - sentryRootDir.fileValue(project.rootDir) + sentryRootDir.set(project.rootProject.layout.projectDirectory) buildDirectory.set(project.layout.buildDirectory) } } From 0aee71dbe6d3cd66ffc473e6b6a90e3fcf6f69f8 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 22 May 2026 15:59:09 +0200 Subject: [PATCH 07/17] fix(telemetry): Restore async default org lookup The synchronous SentryCliInfoValueSource was removed because it ran sentry-cli during configuration phase, which is incompatible with Gradle's configuration cache. This restores the default organization lookup by running sentry-cli info in a background daemon thread after telemetry is initialized. If the process times out or fails, the scope is left unchanged. Co-Authored-By: Claude Opus 4.6 --- .../telemetry/SentryTelemetryService.kt | 86 +++++++++++++++++-- 1 file changed, 77 insertions(+), 9 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt index ae3776ff9..749837f9d 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt @@ -13,8 +13,10 @@ import io.sentry.SentryEvent import io.sentry.SentryLevel import io.sentry.SpanStatus import io.sentry.TransactionOptions +import io.sentry.android.gradle.SentryCliProvider import io.sentry.android.gradle.SentryPlugin import io.sentry.android.gradle.SentryPlugin.Companion.logger +import io.sentry.android.gradle.SentryPropertiesFileProvider import io.sentry.android.gradle.extensions.SentryPluginExtension import io.sentry.android.gradle.util.AgpVersions import io.sentry.android.gradle.util.SentryCliException @@ -25,6 +27,8 @@ import io.sentry.exception.ExceptionMechanismException import io.sentry.gradle.common.SentryVariant import io.sentry.protocol.Mechanism import io.sentry.protocol.User +import java.io.File +import java.util.concurrent.TimeUnit import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.internal.tasks.execution.ExecuteTaskBuildOperationDetails @@ -94,16 +98,11 @@ abstract class SentryTelemetryService : BuildService, BuildOperationListen hub.configureScope { scope -> scope.user = - User().also { user -> - startParameters.defaultSentryOrganization?.let { org -> - if (org != "-") { - user.id = org - } - } - startParameters.sentryOrganization?.let { user.id = it } - } + User().also { user -> startParameters.sentryOrganization?.let { user.id = it } } } + fetchDefaultOrgInBackground(startParameters) + started = true } } catch (t: Throwable) { @@ -213,6 +212,61 @@ abstract class SentryTelemetryService : BuildService, BuildOperationListen } } + private fun fetchDefaultOrgInBackground(params: SentryTelemetryServiceParams) { + val buildDir = params.cliBuildDir ?: return + val projectDir = params.cliProjectDir ?: return + val rootDir = params.cliRootDir ?: return + + Thread { + try { + val cliPath = SentryCliProvider.getSentryCliPath(projectDir, buildDir, rootDir) + val resolvedCli = SentryCliProvider.maybeExtractFromResources(buildDir, cliPath) + + val args = mutableListOf(resolvedCli) + params.cliUrl?.let { url -> + args.add("--url") + args.add(url) + } + args.add("--log-level=error") + args.add("info") + + val processBuilder = ProcessBuilder(args).redirectErrorStream(true) + params.cliPropertiesFilePath?.let { + processBuilder.environment()["SENTRY_PROPERTIES"] = it + } + params.cliAuthToken?.let { processBuilder.environment()["SENTRY_AUTH_TOKEN"] = it } + processBuilder.environment()["SENTRY_PIPELINE"] = + "sentry-gradle-plugin/${BuildConfig.Version}" + + val process = processBuilder.start() + val output = process.inputStream.bufferedReader().readText() + if (!process.waitFor(CLI_INFO_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + process.destroyForcibly() + return@Thread + } + + if (process.exitValue() == 0) { + ORG_REGEX.find(output)?.groupValues?.getOrNull(1)?.let { org -> + if (org != "-") { + hub.configureScope { scope -> + if (scope.user?.id == null) { + scope.user = User().also { it.id = org } + } + } + } + } + } + } catch (t: Throwable) { + SentryPlugin.logger.info { "Failed to fetch default org from sentry-cli: ${t.message}" } + } + } + .apply { + isDaemon = true + name = "sentry-cli-info" + start() + } + } + override fun close() { if (transaction?.isFinished == false) { endRun() @@ -224,6 +278,8 @@ abstract class SentryTelemetryService : BuildService, BuildOperationListen val SENTRY_SAAS_DSN: String = "https://000e5dea9770b4537055f8a6d28c021e@o1.ingest.sentry.io/4506241308295168" val MECHANISM_TYPE: String = "GradleTelemetry" + private val ORG_REGEX = Regex("""(?m)Default Organization: (.*)$""") + private const val CLI_INFO_TIMEOUT_SECONDS = 5L fun createParameters( project: Project, @@ -244,6 +300,13 @@ abstract class SentryTelemetryService : BuildService, BuildOperationListen extension.debug.get(), saas = extension.url.orNull == null, cliVersion = BuildConfig.CliVersion, + cliProjectDir = project.projectDir, + cliBuildDir = project.buildDir, + cliRootDir = project.rootDir, + cliAuthToken = extension.authToken.orNull, + cliUrl = extension.url.orNull, + cliPropertiesFilePath = + variant?.let { SentryPropertiesFileProvider.getPropertiesFilePath(project, it) }, ) } @@ -332,7 +395,12 @@ data class SentryTelemetryServiceParams( val buildType: String, val extraTags: Map, val isDebug: Boolean, - val defaultSentryOrganization: String? = null, val saas: Boolean? = null, val cliVersion: String? = null, + val cliProjectDir: File? = null, + val cliBuildDir: File? = null, + val cliRootDir: File? = null, + val cliAuthToken: String? = null, + val cliUrl: String? = null, + val cliPropertiesFilePath: String? = null, ) From 30f669245742b1280d613f97874add452f3fc3ef Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Tue, 26 May 2026 13:10:46 +0200 Subject: [PATCH 08/17] fix(telemetry): Defer org lookup to execution phase Move fetchDefaultOrgInBackground() from start() (called during configuration via taskGraph.whenReady) to the started() callback of BuildOperationListener which fires during execution phase. Gradle's config cache instrumentation detects ProcessBuilder.start() calls during configuration regardless of which thread they run on. Co-Authored-By: Claude Opus 4.6 --- .../android/gradle/telemetry/SentryTelemetryService.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt index 749837f9d..ac436fd79 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt @@ -50,6 +50,7 @@ abstract class SentryTelemetryService : BuildService, BuildOperationListen private var transaction: ITransaction? = null private var didAddChildSpans: Boolean = false private var started: Boolean = false + private var pendingOrgLookupParams: SentryTelemetryServiceParams? = null @Synchronized fun start(paramsCallback: () -> SentryTelemetryServiceParams) { @@ -101,7 +102,7 @@ abstract class SentryTelemetryService : BuildService, BuildOperationListen User().also { user -> startParameters.sentryOrganization?.let { user.id = it } } } - fetchDefaultOrgInBackground(startParameters) + pendingOrgLookupParams = startParameters started = true } @@ -110,7 +111,12 @@ abstract class SentryTelemetryService : BuildService, BuildOperationListen } } - override fun started(descriptor: BuildOperationDescriptor, event: OperationStartEvent) {} + override fun started(descriptor: BuildOperationDescriptor, event: OperationStartEvent) { + pendingOrgLookupParams?.let { params -> + pendingOrgLookupParams = null + fetchDefaultOrgInBackground(params) + } + } override fun progress(identifier: OperationIdentifier, event: OperationProgressEvent) {} From 66d27094f8d9ab8ef3711301e3c5405124170c2f Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 28 May 2026 13:27:01 +0200 Subject: [PATCH 09/17] fix(config-cache): Restore DirectoryProperty overloads in SentryCliProvider Add DirectoryProperty overloads for getSentryCliPath, getSentryPropertiesPath, searchCliInPropertiesFile, getCliResourcesExtractionPath, and maybeExtractFromResources to match existing test expectations. Keep File-based methods for the telemetry background thread. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../android/gradle/SentryCliProvider.kt | 53 ++++++++++++++++++- .../android/gradle/tasks/SentryCliExecTask.kt | 9 +--- .../gradle/tasks/SentryCliExecTaskTest.kt | 2 +- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt index d33051039..a46724821 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt @@ -9,6 +9,9 @@ import java.io.FileInputStream import java.io.FileOutputStream import java.util.Locale import java.util.Properties +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFile +import org.gradle.api.provider.Provider internal object SentryCliProvider { @@ -19,6 +22,19 @@ internal object SentryCliProvider { * installation, and a matching cli is packaged in the resources it will provide a temporary path, * without actually extracting it. */ + @JvmStatic + fun getSentryCliPath( + projectDir: DirectoryProperty, + projectBuildDir: DirectoryProperty, + rootDir: DirectoryProperty, + ): String { + return getSentryCliPath( + projectDir.get().asFile, + projectBuildDir.get().asFile, + rootDir.get().asFile, + ) + } + @JvmStatic fun getSentryCliPath(projectDir: File, projectBuildDir: File, rootDir: File): String { // If a path is provided explicitly use that first. @@ -61,11 +77,27 @@ internal object SentryCliProvider { return null } - internal fun getSentryPropertiesPath(projectDir: File, rootDir: File): String? = - listOf(File(projectDir, "sentry.properties"), File(rootDir, "sentry.properties")) + internal fun getSentryPropertiesPath( + projectDir: DirectoryProperty, + rootDir: DirectoryProperty, + ): String? = + listOf(projectDir.file("sentry.properties"), rootDir.file("sentry.properties")) + .map { it.get().asFile } .firstOrNull(File::exists) ?.path + internal fun searchCliInPropertiesFile( + projectDir: DirectoryProperty, + rootDir: DirectoryProperty, + ): String? { + return getSentryPropertiesPath(projectDir, rootDir)?.let { propertiesFile -> + runCatching { + Properties().apply { load(FileInputStream(propertiesFile)) }.getProperty("cli.executable") + } + .getOrNull() + } + } + internal fun searchCliInPropertiesFile(projectDir: File, rootDir: File): String? { return getSentryPropertiesPath(projectDir, rootDir)?.let { propertiesFile -> runCatching { @@ -75,9 +107,21 @@ internal object SentryCliProvider { } } + internal fun getSentryPropertiesPath(projectDir: File, rootDir: File): String? = + listOf(File(projectDir, "sentry.properties"), File(rootDir, "sentry.properties")) + .firstOrNull(File::exists) + ?.path + internal fun getResourceUrl(resourcePath: String): String? = javaClass.getResource(resourcePath)?.toString() + internal fun getCliResourcesExtractionPath( + projectBuildDir: DirectoryProperty + ): Provider { + // usually /build/tmp/ + return projectBuildDir.dir("tmp").map { it.file("sentry-cli-${BuildConfig.CliVersion}.exe") } + } + internal fun getCliResourcesExtractionPath(projectBuildDir: File): File { // usually /build/tmp/ return File(projectBuildDir, "tmp/sentry-cli-${BuildConfig.CliVersion}.exe") @@ -119,6 +163,11 @@ internal object SentryCliProvider { } /** Tries to extract the sentry-cli from resources if the computedCliPath does not exist. */ + @Synchronized + internal fun maybeExtractFromResources(buildDir: DirectoryProperty, cliPath: String): String { + return maybeExtractFromResources(buildDir.get().asFile, cliPath) + } + @Synchronized internal fun maybeExtractFromResources(buildDir: File, cliPath: String): String { val cli = File(cliPath) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt index 686b5aec7..5c275220c 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt @@ -94,14 +94,9 @@ abstract class SentryCliExecTask : Exec() { args.add(1, "/c") } - val buildDir = buildDirectory.get().asFile val cliPath = - SentryCliProvider.getSentryCliPath( - sentryProjectDir.get().asFile, - buildDir, - sentryRootDir.get().asFile, - ) - args.add(SentryCliProvider.maybeExtractFromResources(buildDir, cliPath)) + SentryCliProvider.getSentryCliPath(sentryProjectDir, buildDirectory, sentryRootDir) + args.add(SentryCliProvider.maybeExtractFromResources(buildDirectory, cliPath)) args.addAll(preArgs()) getArguments(args) diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt index bc14e7e23..779f8e1df 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt @@ -19,7 +19,7 @@ class SentryCliExecTaskTest { @Test fun `cli path is resolved and extracted from resources`() { val project = createProject() - val cliPath = SentryCliProvider.getCliResourcesExtractionPath(project.buildDir) + val cliPath = SentryCliProvider.getCliResourcesExtractionPath(project.layout.buildDirectory).get().asFile assertTrue(!cliPath.exists()) From d1509cf08bf182db989a8121090b2ed7387c5258 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 28 May 2026 13:46:46 +0200 Subject: [PATCH 10/17] fix(config-cache): Restore getIsolatedRootProjectDir for Gradle 8.8+ Use isolated.rootProject.projectDirectory on Gradle 8.8+ to avoid deprecated project.rootProject access that will break when project isolation is enforced. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../io/sentry/android/gradle/util/SentryCliExec.kt | 12 +++++++++++- .../android/gradle/tasks/SentryCliExecTaskTest.kt | 3 ++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt index fb2d4ac56..1c3aab028 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt @@ -5,19 +5,29 @@ import io.sentry.android.gradle.tasks.SentryCliExecTask import io.sentry.android.gradle.util.CliFailureReason.OUTDATED import java.io.ByteArrayOutputStream import org.gradle.api.GradleException +import org.gradle.api.Project +import org.gradle.api.file.Directory import org.gradle.process.ExecSpec fun ExecSpec.setSentryPipelineEnv() { environment("SENTRY_PIPELINE", "sentry-gradle-plugin/${BuildConfig.Version}") } +private fun Project.getIsolatedRootProjectDir(): Directory { + return if (GradleVersions.CURRENT >= GradleVersions.VERSION_8_8) { + isolated.rootProject.projectDirectory + } else { + rootProject.layout.projectDirectory + } +} + /** * An ext function for tasks that wrap sentry-cli, which provides common error handling. Must be * called at configuration phase (=when registering a task). */ fun SentryCliExecTask.asSentryCliExec() { sentryProjectDir.set(project.layout.projectDirectory) - sentryRootDir.set(project.rootProject.layout.projectDirectory) + sentryRootDir.set(project.getIsolatedRootProjectDir()) buildDirectory.set(project.layout.buildDirectory) isIgnoreExitValue = true diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt index 779f8e1df..979cc21c8 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt @@ -19,7 +19,8 @@ class SentryCliExecTaskTest { @Test fun `cli path is resolved and extracted from resources`() { val project = createProject() - val cliPath = SentryCliProvider.getCliResourcesExtractionPath(project.layout.buildDirectory).get().asFile + val cliPath = + SentryCliProvider.getCliResourcesExtractionPath(project.layout.buildDirectory).get().asFile assertTrue(!cliPath.exists()) From 355529497b5a28e60bb2ae1e4e398c7fe8b4b845 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 28 May 2026 14:45:23 +0200 Subject: [PATCH 11/17] fix(config-cache): Consolidate maybeExtractFromResources overloads Remove the File overload and keep only the DirectoryProperty signature. Update SentryTelemetryService to pass DirectoryProperty instead of File. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../io/sentry/android/gradle/SentryCliProvider.kt | 10 +++------- .../android/gradle/telemetry/SentryTelemetryService.kt | 8 +++++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt index a46724821..a89735b47 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt @@ -165,23 +165,19 @@ internal object SentryCliProvider { /** Tries to extract the sentry-cli from resources if the computedCliPath does not exist. */ @Synchronized internal fun maybeExtractFromResources(buildDir: DirectoryProperty, cliPath: String): String { - return maybeExtractFromResources(buildDir.get().asFile, cliPath) - } - - @Synchronized - internal fun maybeExtractFromResources(buildDir: File, cliPath: String): String { + val buildDirFile = buildDir.get().asFile val cli = File(cliPath) if (cli.exists()) { return cliPath } - val currentExtractionPath = getCliResourcesExtractionPath(buildDir) + val currentExtractionPath = getCliResourcesExtractionPath(buildDirFile) if (currentExtractionPath.exists()) { return currentExtractionPath.absolutePath } // Only auto-extract for paths that look like previous resource extractions - val buildTmpDir = File(buildDir, "tmp") + val buildTmpDir = File(buildDirFile, "tmp") if (cli.absolutePath.startsWith(buildTmpDir.absolutePath)) { val cliResPath = getCliLocationInResources() if (!cliResPath.isNullOrBlank()) { diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt index ac436fd79..4127fd781 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt @@ -31,6 +31,7 @@ import java.io.File import java.util.concurrent.TimeUnit import org.gradle.api.Project import org.gradle.api.Task +import org.gradle.api.file.DirectoryProperty import org.gradle.api.internal.tasks.execution.ExecuteTaskBuildOperationDetails import org.gradle.api.provider.Provider import org.gradle.api.services.BuildService @@ -225,7 +226,8 @@ abstract class SentryTelemetryService : BuildService, BuildOperationListen Thread { try { - val cliPath = SentryCliProvider.getSentryCliPath(projectDir, buildDir, rootDir) + val cliPath = + SentryCliProvider.getSentryCliPath(projectDir, buildDir.get().asFile, rootDir) val resolvedCli = SentryCliProvider.maybeExtractFromResources(buildDir, cliPath) val args = mutableListOf(resolvedCli) @@ -307,7 +309,7 @@ abstract class SentryTelemetryService : BuildService, BuildOperationListen saas = extension.url.orNull == null, cliVersion = BuildConfig.CliVersion, cliProjectDir = project.projectDir, - cliBuildDir = project.buildDir, + cliBuildDir = project.layout.buildDirectory, cliRootDir = project.rootDir, cliAuthToken = extension.authToken.orNull, cliUrl = extension.url.orNull, @@ -404,7 +406,7 @@ data class SentryTelemetryServiceParams( val saas: Boolean? = null, val cliVersion: String? = null, val cliProjectDir: File? = null, - val cliBuildDir: File? = null, + val cliBuildDir: DirectoryProperty? = null, val cliRootDir: File? = null, val cliAuthToken: String? = null, val cliUrl: String? = null, From ebdc2f80de017096dcaf3876f5566c530a8911c6 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 28 May 2026 14:51:54 +0200 Subject: [PATCH 12/17] fix(config-cache): Restore SentryCliProvider to match main Revert SentryCliProvider to its main branch state, preserving memoization, SentryCliValueSource, cliExecutableProvider, and getIsolatedRootProjectDir. Only add File overloads for getSentryCliPath, searchCliInPropertiesFile, getSentryPropertiesPath, and getCliResourcesExtractionPath needed by the telemetry service's execution-phase CLI resolution. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../android/gradle/SentryCliProvider.kt | 133 +++++++++++++----- 1 file changed, 101 insertions(+), 32 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt index a89735b47..f337f5e00 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt @@ -1,7 +1,11 @@ +@file:Suppress("UnstableApiUsage") + package io.sentry.android.gradle import io.sentry.BuildConfig +import io.sentry.android.gradle.SentryCliValueSource.Params import io.sentry.android.gradle.SentryPlugin.Companion.logger +import io.sentry.android.gradle.util.GradleVersions import io.sentry.android.gradle.util.error import io.sentry.android.gradle.util.info import java.io.File @@ -9,12 +13,19 @@ import java.io.FileInputStream import java.io.FileOutputStream import java.util.Locale import java.util.Properties +import org.gradle.api.Project +import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFile import org.gradle.api.provider.Provider +import org.gradle.api.provider.ValueSource +import org.gradle.api.provider.ValueSourceParameters +import org.gradle.api.tasks.Input internal object SentryCliProvider { + @field:Volatile private var memoizedCliPath: String? = null + /** * Return the correct sentry-cli executable path to use for the given project. This will look for * a sentry-cli executable in a local node_modules in case it was put there by sentry-react-native @@ -23,37 +34,67 @@ internal object SentryCliProvider { * without actually extracting it. */ @JvmStatic + @Synchronized fun getSentryCliPath( projectDir: DirectoryProperty, projectBuildDir: DirectoryProperty, rootDir: DirectoryProperty, ): String { - return getSentryCliPath( - projectDir.get().asFile, - projectBuildDir.get().asFile, - rootDir.get().asFile, - ) + val cliPath = memoizedCliPath + if (!cliPath.isNullOrEmpty() && File(cliPath).exists()) { + logger.info { "Using memoized cli path: $cliPath" } + return cliPath + } + // If a path is provided explicitly use that first. + logger.info { "Searching cli from sentry.properties file..." } + + searchCliInPropertiesFile(projectDir, rootDir)?.let { + logger.info { "cli Found: $it" } + memoizedCliPath = it + return@getSentryCliPath it + } ?: logger.info { "sentry-cli not found in sentry.properties file" } + + // next up try a packaged version of sentry-cli + val cliResLocation = getCliLocationInResources() + if (!cliResLocation.isNullOrBlank()) { + logger.info { "cli present in resources: $cliResLocation" } + // just provide the target extraction path + // actual extraction will be done prior to task execution + val extractedResourcePath = + getCliResourcesExtractionPath(projectBuildDir).get().asFile.absolutePath + memoizedCliPath = extractedResourcePath + return extractedResourcePath + } + + logger.error { "Falling back to invoking `sentry-cli` from shell" } + return "sentry-cli".also { memoizedCliPath = it } } @JvmStatic fun getSentryCliPath(projectDir: File, projectBuildDir: File, rootDir: File): String { - // If a path is provided explicitly use that first. + val cliPath = memoizedCliPath + if (!cliPath.isNullOrEmpty() && File(cliPath).exists()) { + logger.info { "Using memoized cli path: $cliPath" } + return cliPath + } logger.info { "Searching cli from sentry.properties file..." } searchCliInPropertiesFile(projectDir, rootDir)?.let { logger.info { "cli Found: $it" } + memoizedCliPath = it return@getSentryCliPath it } ?: logger.info { "sentry-cli not found in sentry.properties file" } - // next up try a packaged version of sentry-cli val cliResLocation = getCliLocationInResources() if (!cliResLocation.isNullOrBlank()) { logger.info { "cli present in resources: $cliResLocation" } - return getCliResourcesExtractionPath(projectBuildDir).absolutePath + val extractedResourcePath = getCliResourcesExtractionPath(projectBuildDir).absolutePath + memoizedCliPath = extractedResourcePath + return extractedResourcePath } logger.error { "Falling back to invoking `sentry-cli` from shell" } - return "sentry-cli" + return "sentry-cli".also { memoizedCliPath = it } } private fun getCliLocationInResources(): String? { @@ -86,6 +127,11 @@ internal object SentryCliProvider { .firstOrNull(File::exists) ?.path + internal fun getSentryPropertiesPath(projectDir: File, rootDir: File): String? = + listOf(File(projectDir, "sentry.properties"), File(rootDir, "sentry.properties")) + .firstOrNull(File::exists) + ?.path + internal fun searchCliInPropertiesFile( projectDir: DirectoryProperty, rootDir: DirectoryProperty, @@ -107,11 +153,6 @@ internal object SentryCliProvider { } } - internal fun getSentryPropertiesPath(projectDir: File, rootDir: File): String? = - listOf(File(projectDir, "sentry.properties"), File(rootDir, "sentry.properties")) - .firstOrNull(File::exists) - ?.path - internal fun getResourceUrl(resourcePath: String): String? = javaClass.getResource(resourcePath)?.toString() @@ -123,7 +164,6 @@ internal object SentryCliProvider { } internal fun getCliResourcesExtractionPath(projectBuildDir: File): File { - // usually /build/tmp/ return File(projectBuildDir, "tmp/sentry-cli-${BuildConfig.CliVersion}.exe") } @@ -165,27 +205,56 @@ internal object SentryCliProvider { /** Tries to extract the sentry-cli from resources if the computedCliPath does not exist. */ @Synchronized internal fun maybeExtractFromResources(buildDir: DirectoryProperty, cliPath: String): String { - val buildDirFile = buildDir.get().asFile val cli = File(cliPath) - if (cli.exists()) { - return cliPath + if (!cli.exists()) { + // we only want to auto-extract if the path matches the pre-computed one + if ( + File(cliPath) + .absolutePath + .equals(getCliResourcesExtractionPath(buildDir).get().asFile.absolutePath) + ) { + val cliResPath = getCliLocationInResources() + if (!cliResPath.isNullOrBlank()) { + return extractCliFromResources(cliResPath, cli) ?: cliPath + } + } } + return cliPath + } +} - val currentExtractionPath = getCliResourcesExtractionPath(buildDirFile) - if (currentExtractionPath.exists()) { - return currentExtractionPath.absolutePath - } +abstract class SentryCliValueSource : ValueSource { + interface Params : ValueSourceParameters { + @get:Input val projectDir: DirectoryProperty - // Only auto-extract for paths that look like previous resource extractions - val buildTmpDir = File(buildDirFile, "tmp") - if (cli.absolutePath.startsWith(buildTmpDir.absolutePath)) { - val cliResPath = getCliLocationInResources() - if (!cliResPath.isNullOrBlank()) { - return extractCliFromResources(cliResPath, currentExtractionPath) - ?: currentExtractionPath.absolutePath - } - } + @get:Input val projectBuildDir: DirectoryProperty - return cliPath + @get:Input val rootProjDir: DirectoryProperty + } + + override fun obtain(): String? { + return SentryCliProvider.getSentryCliPath( + parameters.projectDir, + parameters.projectBuildDir, + parameters.rootProjDir, + ) + } +} + +fun Project.cliExecutableProvider(): Provider { + // config-cache compatible way to retrieve the cli path, it properly gets invalidated when + // e.g. switching branches + return providers.of(SentryCliValueSource::class.java) { + it.parameters.projectDir.set(layout.projectDirectory) + it.parameters.projectBuildDir.set(layout.buildDirectory) + it.parameters.rootProjDir.set(getIsolatedRootProjectDir()) + } +} + +private fun Project.getIsolatedRootProjectDir(): Directory { + return if (GradleVersions.CURRENT >= GradleVersions.VERSION_8_8) { + isolated.rootProject.projectDirectory + } else { + rootProject.layout.projectDirectory } } From ad14939b7ac6df63040feb6baa71cc67fa3daa52 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 28 May 2026 15:03:24 +0200 Subject: [PATCH 13/17] fix(config-cache): Remove File overloads from SentryCliProvider SentryCliProvider now matches main exactly. Updated SentryTelemetryService to use DirectoryProperty for all CLI dir params so it can call getSentryCliPath(DirectoryProperty) directly. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../android/gradle/SentryCliProvider.kt | 45 ------------------- .../telemetry/SentryTelemetryService.kt | 12 +++-- 2 files changed, 5 insertions(+), 52 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt index f337f5e00..bd7a44d7c 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt @@ -70,33 +70,6 @@ internal object SentryCliProvider { return "sentry-cli".also { memoizedCliPath = it } } - @JvmStatic - fun getSentryCliPath(projectDir: File, projectBuildDir: File, rootDir: File): String { - val cliPath = memoizedCliPath - if (!cliPath.isNullOrEmpty() && File(cliPath).exists()) { - logger.info { "Using memoized cli path: $cliPath" } - return cliPath - } - logger.info { "Searching cli from sentry.properties file..." } - - searchCliInPropertiesFile(projectDir, rootDir)?.let { - logger.info { "cli Found: $it" } - memoizedCliPath = it - return@getSentryCliPath it - } ?: logger.info { "sentry-cli not found in sentry.properties file" } - - val cliResLocation = getCliLocationInResources() - if (!cliResLocation.isNullOrBlank()) { - logger.info { "cli present in resources: $cliResLocation" } - val extractedResourcePath = getCliResourcesExtractionPath(projectBuildDir).absolutePath - memoizedCliPath = extractedResourcePath - return extractedResourcePath - } - - logger.error { "Falling back to invoking `sentry-cli` from shell" } - return "sentry-cli".also { memoizedCliPath = it } - } - private fun getCliLocationInResources(): String? { val cliSuffix = getCliSuffix() logger.info { "cliSuffix is $cliSuffix" } @@ -127,11 +100,6 @@ internal object SentryCliProvider { .firstOrNull(File::exists) ?.path - internal fun getSentryPropertiesPath(projectDir: File, rootDir: File): String? = - listOf(File(projectDir, "sentry.properties"), File(rootDir, "sentry.properties")) - .firstOrNull(File::exists) - ?.path - internal fun searchCliInPropertiesFile( projectDir: DirectoryProperty, rootDir: DirectoryProperty, @@ -144,15 +112,6 @@ internal object SentryCliProvider { } } - internal fun searchCliInPropertiesFile(projectDir: File, rootDir: File): String? { - return getSentryPropertiesPath(projectDir, rootDir)?.let { propertiesFile -> - runCatching { - Properties().apply { load(FileInputStream(propertiesFile)) }.getProperty("cli.executable") - } - .getOrNull() - } - } - internal fun getResourceUrl(resourcePath: String): String? = javaClass.getResource(resourcePath)?.toString() @@ -163,10 +122,6 @@ internal object SentryCliProvider { return projectBuildDir.dir("tmp").map { it.file("sentry-cli-${BuildConfig.CliVersion}.exe") } } - internal fun getCliResourcesExtractionPath(projectBuildDir: File): File { - return File(projectBuildDir, "tmp/sentry-cli-${BuildConfig.CliVersion}.exe") - } - internal fun extractCliFromResources(resourcePath: String, outputPath: File): String? { val resourceStream = javaClass.getResourceAsStream(resourcePath) return if (resourceStream != null) { diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt index 4127fd781..b479f1838 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt @@ -27,7 +27,6 @@ import io.sentry.exception.ExceptionMechanismException import io.sentry.gradle.common.SentryVariant import io.sentry.protocol.Mechanism import io.sentry.protocol.User -import java.io.File import java.util.concurrent.TimeUnit import org.gradle.api.Project import org.gradle.api.Task @@ -226,8 +225,7 @@ abstract class SentryTelemetryService : BuildService, BuildOperationListen Thread { try { - val cliPath = - SentryCliProvider.getSentryCliPath(projectDir, buildDir.get().asFile, rootDir) + val cliPath = SentryCliProvider.getSentryCliPath(projectDir, buildDir, rootDir) val resolvedCli = SentryCliProvider.maybeExtractFromResources(buildDir, cliPath) val args = mutableListOf(resolvedCli) @@ -308,9 +306,9 @@ abstract class SentryTelemetryService : BuildService, BuildOperationListen extension.debug.get(), saas = extension.url.orNull == null, cliVersion = BuildConfig.CliVersion, - cliProjectDir = project.projectDir, + cliProjectDir = project.objects.directoryProperty().apply { set(project.projectDir) }, cliBuildDir = project.layout.buildDirectory, - cliRootDir = project.rootDir, + cliRootDir = project.objects.directoryProperty().apply { set(project.rootDir) }, cliAuthToken = extension.authToken.orNull, cliUrl = extension.url.orNull, cliPropertiesFilePath = @@ -405,9 +403,9 @@ data class SentryTelemetryServiceParams( val isDebug: Boolean, val saas: Boolean? = null, val cliVersion: String? = null, - val cliProjectDir: File? = null, + val cliProjectDir: DirectoryProperty? = null, val cliBuildDir: DirectoryProperty? = null, - val cliRootDir: File? = null, + val cliRootDir: DirectoryProperty? = null, val cliAuthToken: String? = null, val cliUrl: String? = null, val cliPropertiesFilePath: String? = null, From 1cc1147b4ea3f068d629d28d238b250deba18b09 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 28 May 2026 15:26:53 +0200 Subject: [PATCH 14/17] fix(test): Reset memoized CLI path between tests Add clearMemoizedCliPath() to SentryCliProvider and call it in SentryCliExecTaskTest @Before to avoid stale memoized paths from other tests interfering with CLI resolution. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../kotlin/io/sentry/android/gradle/SentryCliProvider.kt | 4 ++++ .../io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt index bd7a44d7c..a212273dc 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt @@ -26,6 +26,10 @@ internal object SentryCliProvider { @field:Volatile private var memoizedCliPath: String? = null + internal fun clearMemoizedCliPath() { + memoizedCliPath = null + } + /** * Return the correct sentry-cli executable path to use for the given project. This will look for * a sentry-cli executable in a local node_modules in case it was put there by sentry-react-native diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt index 979cc21c8..196d6265e 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt @@ -8,6 +8,7 @@ import kotlin.test.assertTrue import org.gradle.api.Project import org.gradle.api.tasks.TaskProvider import org.gradle.testfixtures.ProjectBuilder +import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -16,6 +17,11 @@ class SentryCliExecTaskTest { @get:Rule val tempDir = TemporaryFolder() + @Before + fun setup() { + SentryCliProvider.clearMemoizedCliPath() + } + @Test fun `cli path is resolved and extracted from resources`() { val project = createProject() From 91d6f3158049527e061f08b724326c7fb12293a7 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 28 May 2026 15:45:01 +0200 Subject: [PATCH 15/17] fix(config-cache): Remove memoized CLI path from SentryCliProvider The memoizedCliPath field persisted for the lifetime of the Gradle daemon, which could return a stale CLI path after plugin upgrades or build dir cleanups. Since tasks now resolve the CLI path at execution time, the memoization is unnecessary and harmful. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../android/gradle/SentryCliProvider.kt | 24 ++----------------- .../gradle/tasks/SentryCliExecTaskTest.kt | 6 ----- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt index a212273dc..44e96fc21 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt @@ -24,12 +24,6 @@ import org.gradle.api.tasks.Input internal object SentryCliProvider { - @field:Volatile private var memoizedCliPath: String? = null - - internal fun clearMemoizedCliPath() { - memoizedCliPath = null - } - /** * Return the correct sentry-cli executable path to use for the given project. This will look for * a sentry-cli executable in a local node_modules in case it was put there by sentry-react-native @@ -38,40 +32,26 @@ internal object SentryCliProvider { * without actually extracting it. */ @JvmStatic - @Synchronized fun getSentryCliPath( projectDir: DirectoryProperty, projectBuildDir: DirectoryProperty, rootDir: DirectoryProperty, ): String { - val cliPath = memoizedCliPath - if (!cliPath.isNullOrEmpty() && File(cliPath).exists()) { - logger.info { "Using memoized cli path: $cliPath" } - return cliPath - } - // If a path is provided explicitly use that first. logger.info { "Searching cli from sentry.properties file..." } searchCliInPropertiesFile(projectDir, rootDir)?.let { logger.info { "cli Found: $it" } - memoizedCliPath = it return@getSentryCliPath it } ?: logger.info { "sentry-cli not found in sentry.properties file" } - // next up try a packaged version of sentry-cli val cliResLocation = getCliLocationInResources() if (!cliResLocation.isNullOrBlank()) { logger.info { "cli present in resources: $cliResLocation" } - // just provide the target extraction path - // actual extraction will be done prior to task execution - val extractedResourcePath = - getCliResourcesExtractionPath(projectBuildDir).get().asFile.absolutePath - memoizedCliPath = extractedResourcePath - return extractedResourcePath + return getCliResourcesExtractionPath(projectBuildDir).get().asFile.absolutePath } logger.error { "Falling back to invoking `sentry-cli` from shell" } - return "sentry-cli".also { memoizedCliPath = it } + return "sentry-cli" } private fun getCliLocationInResources(): String? { diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt index 196d6265e..979cc21c8 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt @@ -8,7 +8,6 @@ import kotlin.test.assertTrue import org.gradle.api.Project import org.gradle.api.tasks.TaskProvider import org.gradle.testfixtures.ProjectBuilder -import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -17,11 +16,6 @@ class SentryCliExecTaskTest { @get:Rule val tempDir = TemporaryFolder() - @Before - fun setup() { - SentryCliProvider.clearMemoizedCliPath() - } - @Test fun `cli path is resolved and extracted from resources`() { val project = createProject() From e4cc66ea2e1d56e49230d241f402ee802b53c503 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 29 May 2026 09:25:38 +0200 Subject: [PATCH 16/17] fix(config-cache): Resolve CLI path via ValueSource task input SentryCliExecTask resolved the sentry-cli path inside its action by calling getSentryCliPath() at execution time, so the path was never a declared task input. Wire the existing SentryCliValueSource into a new @Input cliExecutable property via cliExecutableProvider(), so the path is a config-cache-safe, lazily resolved task input. The now-unused sentryProjectDir/sentryRootDir task properties (and the duplicate getIsolatedRootProjectDir helper) are removed; the value source resolves those from the project itself. buildDirectory stays, since extraction from resources still needs it. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../android/gradle/tasks/SentryCliExecTask.kt | 8 ++------ .../io/sentry/android/gradle/util/SentryCliExec.kt | 14 ++------------ .../android/gradle/tasks/BundleSourcesTaskTest.kt | 4 ++-- .../android/gradle/tasks/SentryCliExecTaskTest.kt | 4 ++-- .../tasks/SentryUploadAppArtifactTaskTest.kt | 4 ++-- .../tasks/SentryUploadNativeSymbolsTaskTest.kt | 4 ++-- .../tasks/SentryUploadProguardMappingTaskTest.kt | 4 ++-- .../gradle/tasks/SentryUploadSnapshotsTaskTest.kt | 4 ++-- .../gradle/tasks/UploadSourceBundleTaskTest.kt | 4 ++-- 9 files changed, 18 insertions(+), 32 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt index 5c275220c..ff75e1ffb 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt @@ -37,9 +37,7 @@ abstract class SentryCliExecTask : Exec() { @get:Internal abstract val sentryTelemetryService: Property - @get:Internal abstract val sentryProjectDir: DirectoryProperty - - @get:Internal abstract val sentryRootDir: DirectoryProperty + @get:Input abstract val cliExecutable: Property @get:Internal abstract val buildDirectory: DirectoryProperty @@ -94,9 +92,7 @@ abstract class SentryCliExecTask : Exec() { args.add(1, "/c") } - val cliPath = - SentryCliProvider.getSentryCliPath(sentryProjectDir, buildDirectory, sentryRootDir) - args.add(SentryCliProvider.maybeExtractFromResources(buildDirectory, cliPath)) + args.add(SentryCliProvider.maybeExtractFromResources(buildDirectory, cliExecutable.get())) args.addAll(preArgs()) getArguments(args) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt index 1c3aab028..6ec141789 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt @@ -1,33 +1,23 @@ package io.sentry.android.gradle.util import io.sentry.BuildConfig +import io.sentry.android.gradle.cliExecutableProvider import io.sentry.android.gradle.tasks.SentryCliExecTask import io.sentry.android.gradle.util.CliFailureReason.OUTDATED import java.io.ByteArrayOutputStream import org.gradle.api.GradleException -import org.gradle.api.Project -import org.gradle.api.file.Directory import org.gradle.process.ExecSpec fun ExecSpec.setSentryPipelineEnv() { environment("SENTRY_PIPELINE", "sentry-gradle-plugin/${BuildConfig.Version}") } -private fun Project.getIsolatedRootProjectDir(): Directory { - return if (GradleVersions.CURRENT >= GradleVersions.VERSION_8_8) { - isolated.rootProject.projectDirectory - } else { - rootProject.layout.projectDirectory - } -} - /** * An ext function for tasks that wrap sentry-cli, which provides common error handling. Must be * called at configuration phase (=when registering a task). */ fun SentryCliExecTask.asSentryCliExec() { - sentryProjectDir.set(project.layout.projectDirectory) - sentryRootDir.set(project.getIsolatedRootProjectDir()) + cliExecutable.set(project.cliExecutableProvider()) buildDirectory.set(project.layout.buildDirectory) isIgnoreExitValue = true diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt index 5b1acd65d..785d441b6 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt @@ -1,6 +1,7 @@ package io.sentry.android.gradle.tasks import com.google.common.truth.Truth.assertThat +import io.sentry.android.gradle.cliExecutableProvider import io.sentry.android.gradle.sourcecontext.BundleSourcesTask import io.sentry.android.gradle.sourcecontext.GenerateBundleIdTask.Companion.SENTRY_BUNDLE_ID_PROPERTY import java.io.File @@ -242,8 +243,7 @@ class BundleSourcesTaskTest { } private fun SentryCliExecTask.configureCliPaths(project: Project) { - sentryProjectDir.set(project.layout.projectDirectory) - sentryRootDir.set(project.rootProject.layout.projectDirectory) + cliExecutable.set(project.cliExecutableProvider()) buildDirectory.set(project.layout.buildDirectory) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt index 979cc21c8..b679b10cf 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt @@ -1,6 +1,7 @@ package io.sentry.android.gradle.tasks import io.sentry.android.gradle.SentryCliProvider +import io.sentry.android.gradle.cliExecutableProvider import java.io.File import kotlin.test.assertEquals import kotlin.test.assertNull @@ -139,8 +140,7 @@ class SentryCliExecTaskTest { } private fun SentryCliExecTask.configureCliPaths(project: Project) { - sentryProjectDir.set(project.layout.projectDirectory) - sentryRootDir.set(project.rootProject.layout.projectDirectory) + cliExecutable.set(project.cliExecutableProvider()) buildDirectory.set(project.layout.buildDirectory) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTaskTest.kt index 715d55de4..e3d4d916d 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadAppArtifactTaskTest.kt @@ -4,6 +4,7 @@ import com.google.common.truth.FailureMetadata import com.google.common.truth.Subject import com.google.common.truth.Truth.assertAbout import com.google.common.truth.Truth.assertThat +import io.sentry.android.gradle.cliExecutableProvider import kotlin.test.assertEquals import kotlin.test.assertFailsWith import org.gradle.api.Project @@ -314,8 +315,7 @@ class SentryUploadAppArtifactTaskTest { } private fun SentryCliExecTask.configureCliPaths(project: Project) { - sentryProjectDir.set(project.layout.projectDirectory) - sentryRootDir.set(project.rootProject.layout.projectDirectory) + cliExecutable.set(project.cliExecutableProvider()) buildDirectory.set(project.layout.buildDirectory) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt index 422d54d35..e62729f87 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt @@ -1,5 +1,6 @@ package io.sentry.android.gradle.tasks +import io.sentry.android.gradle.cliExecutableProvider import java.io.File import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -179,8 +180,7 @@ class SentryUploadNativeSymbolsTaskTest { ): SentryUploadNativeSymbolsTask = project.tasks .register("testUploadNativeSymbols", SentryUploadNativeSymbolsTask::class.java) { - it.sentryProjectDir.set(project.layout.projectDirectory) - it.sentryRootDir.set(project.rootProject.layout.projectDirectory) + it.cliExecutable.set(project.cliExecutableProvider()) it.buildDirectory.set(project.layout.buildDirectory) block(it) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt index 9ffaa04b5..bff11c8c9 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt @@ -1,5 +1,6 @@ package io.sentry.android.gradle.tasks +import io.sentry.android.gradle.cliExecutableProvider import java.io.File import java.util.UUID import kotlin.test.assertEquals @@ -324,8 +325,7 @@ class SentryUploadProguardMappingTaskTest { } private fun SentryCliExecTask.configureCliPaths(project: Project) { - sentryProjectDir.set(project.layout.projectDirectory) - sentryRootDir.set(project.rootProject.layout.projectDirectory) + cliExecutable.set(project.cliExecutableProvider()) buildDirectory.set(project.layout.buildDirectory) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTaskTest.kt index fe429448e..0af083031 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTaskTest.kt @@ -1,6 +1,7 @@ package io.sentry.android.gradle.tasks import com.google.common.truth.Truth.assertThat +import io.sentry.android.gradle.cliExecutableProvider import java.io.File import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -219,8 +220,7 @@ class SentryUploadSnapshotsTaskTest { ): SentryUploadSnapshotsTask = project.tasks .register("testUploadSnapshots", SentryUploadSnapshotsTask::class.java) { - it.sentryProjectDir.set(project.layout.projectDirectory) - it.sentryRootDir.set(project.rootProject.layout.projectDirectory) + it.cliExecutable.set(project.cliExecutableProvider()) it.buildDirectory.set(project.layout.buildDirectory) block(it) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt index d1ba163ce..c0267b1d7 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt @@ -1,5 +1,6 @@ package io.sentry.android.gradle.tasks +import io.sentry.android.gradle.cliExecutableProvider import io.sentry.android.gradle.sourcecontext.UploadSourceBundleTask import java.io.File import kotlin.test.assertEquals @@ -197,8 +198,7 @@ class UploadSourceBundleTaskTest { } private fun SentryCliExecTask.configureCliPaths(project: Project) { - sentryProjectDir.set(project.layout.projectDirectory) - sentryRootDir.set(project.rootProject.layout.projectDirectory) + cliExecutable.set(project.cliExecutableProvider()) buildDirectory.set(project.layout.buildDirectory) } } From f15b88f043136c42d62bcb92274e0e1808fac3a9 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 29 May 2026 09:46:41 +0200 Subject: [PATCH 17/17] ref(telemetry): Use kotlin.concurrent.thread for org lookup Replace the manual Thread { }.apply { isDaemon = true; name = ...; start() } with the kotlin.concurrent.thread helper, which creates, configures, and starts the daemon thread in a single call. Behavior is unchanged: the sentry-cli org lookup still runs as a named daemon thread so it never blocks or outlives the build. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../telemetry/SentryTelemetryService.kt | 74 +++++++++---------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt index b479f1838..bef59f221 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt @@ -28,6 +28,7 @@ import io.sentry.gradle.common.SentryVariant import io.sentry.protocol.Mechanism import io.sentry.protocol.User import java.util.concurrent.TimeUnit +import kotlin.concurrent.thread import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.file.DirectoryProperty @@ -223,54 +224,47 @@ abstract class SentryTelemetryService : BuildService, BuildOperationListen val projectDir = params.cliProjectDir ?: return val rootDir = params.cliRootDir ?: return - Thread { - try { - val cliPath = SentryCliProvider.getSentryCliPath(projectDir, buildDir, rootDir) - val resolvedCli = SentryCliProvider.maybeExtractFromResources(buildDir, cliPath) - - val args = mutableListOf(resolvedCli) - params.cliUrl?.let { url -> - args.add("--url") - args.add(url) - } - args.add("--log-level=error") - args.add("info") + thread(isDaemon = true, name = "sentry-cli-info") { + try { + val cliPath = SentryCliProvider.getSentryCliPath(projectDir, buildDir, rootDir) + val resolvedCli = SentryCliProvider.maybeExtractFromResources(buildDir, cliPath) - val processBuilder = ProcessBuilder(args).redirectErrorStream(true) - params.cliPropertiesFilePath?.let { - processBuilder.environment()["SENTRY_PROPERTIES"] = it - } - params.cliAuthToken?.let { processBuilder.environment()["SENTRY_AUTH_TOKEN"] = it } - processBuilder.environment()["SENTRY_PIPELINE"] = - "sentry-gradle-plugin/${BuildConfig.Version}" - - val process = processBuilder.start() - val output = process.inputStream.bufferedReader().readText() - if (!process.waitFor(CLI_INFO_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { - process.destroyForcibly() - return@Thread - } + val args = mutableListOf(resolvedCli) + params.cliUrl?.let { url -> + args.add("--url") + args.add(url) + } + args.add("--log-level=error") + args.add("info") + + val processBuilder = ProcessBuilder(args).redirectErrorStream(true) + params.cliPropertiesFilePath?.let { processBuilder.environment()["SENTRY_PROPERTIES"] = it } + params.cliAuthToken?.let { processBuilder.environment()["SENTRY_AUTH_TOKEN"] = it } + processBuilder.environment()["SENTRY_PIPELINE"] = + "sentry-gradle-plugin/${BuildConfig.Version}" + + val process = processBuilder.start() + val output = process.inputStream.bufferedReader().readText() + if (!process.waitFor(CLI_INFO_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + process.destroyForcibly() + return@thread + } - if (process.exitValue() == 0) { - ORG_REGEX.find(output)?.groupValues?.getOrNull(1)?.let { org -> - if (org != "-") { - hub.configureScope { scope -> - if (scope.user?.id == null) { - scope.user = User().also { it.id = org } - } + if (process.exitValue() == 0) { + ORG_REGEX.find(output)?.groupValues?.getOrNull(1)?.let { org -> + if (org != "-") { + hub.configureScope { scope -> + if (scope.user?.id == null) { + scope.user = User().also { it.id = org } } } } } - } catch (t: Throwable) { - SentryPlugin.logger.info { "Failed to fetch default org from sentry-cli: ${t.message}" } } + } catch (t: Throwable) { + SentryPlugin.logger.info { "Failed to fetch default org from sentry-cli: ${t.message}" } } - .apply { - isDaemon = true - name = "sentry-cli-info" - start() - } + } } override fun close() {