Skip to content

Commit 42ff7d7

Browse files
runningcodeclaude
andauthored
fix(telemetry): Resolve default org via ValueSource at execution (GRADLE-82) (#1263)
* fix(telemetry): Resolve default org via ValueSource at execution (GRADLE-82) When telemetry is enabled (the default), the plugin resolved the default Sentry organization and CLI version at configuration time via two ValueSources that each spawned a sentry-cli process. Because they were `.get()`-ed during configuration, Gradle tracked them as configuration inputs and re-ran both processes on every configuration-cache-hit build, adding significant overhead even when no Sentry task executed. Replace them with a single SentryOrgValueSource that is queried lazily on the first tracked task, so the sentry-cli process runs during execution and stays off the configuration phase. The CLI version now comes straight from BuildConfig. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(changelog): Add entry for telemetry org lookup fix (GRADLE-82) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ref(telemetry): Guard default-org attach with AtomicBoolean attachDefaultOrg() runs on the first tracked task and may be invoked concurrently from parallel task threads. Replace the plain Boolean flag and its check-then-set with an AtomicBoolean compareAndSet so the attach body runs exactly once and the flag's visibility is guaranteed across threads. This is a robustness tightening, not a fix for duplicate sentry-cli processes: the process is spawned inside the SentryOrgValueSource, which Gradle evaluates at most once per build regardless of this flag. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 04a10bf commit 42ff7d7

6 files changed

Lines changed: 154 additions & 228 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- Resolve the sentry-cli path as a task input instead of memoizing it in a static field, fixing stale-path build failures when switching branches with the configuration cache enabled ([#1264](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1264))
88
- This fixed the issue where sentry-cli could not be found (`A problem occurred starting process 'command ../sentry-cliXXX.exe'`)
9+
- Defer the telemetry default-org lookup to execution time so the configuration cache no longer re-runs `sentry-cli` on every build ([#1263](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1263))
910

1011
## 6.10.0
1112

plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ fun ApplicationAndroidComponentsExtension.configure(
8383
if (isVariantAllowed(extension, variant.name, variant.flavorName, variant.buildType)) {
8484
val paths = OutputPaths(project, variant.name)
8585
val sentryTelemetryProvider =
86-
variant.configureTelemetry(project, extension, cliExecutable, sentryOrg, buildEvents)
86+
variant.configureTelemetry(project, extension, sentryOrg, buildEvents)
8787

8888
variant.configureDependenciesTask(project, extension, sentryTelemetryProvider)
8989

@@ -265,22 +265,14 @@ fun ApplicationAndroidComponentsExtension.configure(
265265
private fun Variant.configureTelemetry(
266266
project: Project,
267267
extension: SentryPluginExtension,
268-
cliExecutable: Provider<String>,
269268
sentryOrg: String?,
270269
buildEvents: BuildEventListenerRegistryInternal,
271270
): Provider<SentryTelemetryService> {
272271
val variant = AndroidVariant74(this)
273272
val sentryTelemetryProvider = SentryTelemetryService.register(project)
274273
project.gradle.taskGraph.whenReady {
275274
sentryTelemetryProvider.get().start {
276-
SentryTelemetryService.createParameters(
277-
project,
278-
variant,
279-
extension,
280-
cliExecutable,
281-
sentryOrg,
282-
"Android",
283-
)
275+
SentryTelemetryService.createParameters(project, variant, extension, sentryOrg, "Android")
284276
}
285277
buildEvents.onOperationCompletion(sentryTelemetryProvider)
286278
}

plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,17 @@ import java.io.FileInputStream
1313
import java.io.FileOutputStream
1414
import java.util.Locale
1515
import java.util.Properties
16+
import java.util.concurrent.TimeUnit
1617
import org.gradle.api.Project
1718
import org.gradle.api.file.Directory
1819
import org.gradle.api.file.DirectoryProperty
1920
import org.gradle.api.file.RegularFile
21+
import org.gradle.api.provider.Property
2022
import org.gradle.api.provider.Provider
2123
import org.gradle.api.provider.ValueSource
2224
import org.gradle.api.provider.ValueSourceParameters
2325
import org.gradle.api.tasks.Input
26+
import org.gradle.api.tasks.Optional
2427

2528
internal object SentryCliProvider {
2629

@@ -196,3 +199,90 @@ private fun Project.getIsolatedRootProjectDir(): Directory {
196199
rootProject.layout.projectDirectory
197200
}
198201
}
202+
203+
/**
204+
* Resolves the default Sentry organization by running `sentry-cli info`. Implemented as a
205+
* ValueSource so the external process is started in a configuration-cache compatible way; querying
206+
* the returned provider during task execution keeps the process off the configuration phase.
207+
*/
208+
abstract class SentryOrgValueSource : ValueSource<String, SentryOrgValueSource.Params> {
209+
interface Params : ValueSourceParameters {
210+
@get:Input val projectDir: DirectoryProperty
211+
212+
@get:Input val projectBuildDir: DirectoryProperty
213+
214+
@get:Input val rootProjDir: DirectoryProperty
215+
216+
@get:Input @get:Optional val url: Property<String>
217+
218+
@get:Input @get:Optional val authToken: Property<String>
219+
220+
@get:Input @get:Optional val propertiesFilePath: Property<String>
221+
}
222+
223+
override fun obtain(): String? {
224+
return try {
225+
val cliPath =
226+
SentryCliProvider.getSentryCliPath(
227+
parameters.projectDir,
228+
parameters.projectBuildDir,
229+
parameters.rootProjDir,
230+
)
231+
val resolvedCli =
232+
SentryCliProvider.maybeExtractFromResources(parameters.projectBuildDir, cliPath)
233+
234+
val args = mutableListOf(resolvedCli)
235+
parameters.url.orNull?.let {
236+
args.add("--url")
237+
args.add(it)
238+
}
239+
args.add("--log-level=error")
240+
args.add("info")
241+
242+
val processBuilder = ProcessBuilder(args).redirectErrorStream(true)
243+
parameters.propertiesFilePath.orNull?.let {
244+
processBuilder.environment()["SENTRY_PROPERTIES"] = it
245+
}
246+
parameters.authToken.orNull?.let { processBuilder.environment()["SENTRY_AUTH_TOKEN"] = it }
247+
processBuilder.environment()["SENTRY_PIPELINE"] =
248+
"sentry-gradle-plugin/${BuildConfig.Version}"
249+
250+
val process = processBuilder.start()
251+
// Wait with the timeout before reading the output: reading first would block indefinitely if
252+
// the process hangs (e.g. a network stall), bypassing the timeout. `info` output is small and
253+
// bounded, so it cannot fill the pipe buffer and stall the process before we read it here.
254+
if (!process.waitFor(CLI_INFO_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
255+
process.destroyForcibly()
256+
return null
257+
}
258+
if (process.exitValue() != 0) {
259+
return null
260+
}
261+
val output = process.inputStream.bufferedReader().readText()
262+
ORG_REGEX.find(output)?.groupValues?.getOrNull(1)?.takeUnless { it == "-" }
263+
} catch (t: Throwable) {
264+
logger.info { "Failed to fetch default org from sentry-cli: ${t.message}" }
265+
null
266+
}
267+
}
268+
269+
companion object {
270+
private val ORG_REGEX = Regex("""(?m)Default Organization: (.*)$""")
271+
private const val CLI_INFO_TIMEOUT_SECONDS = 5L
272+
}
273+
}
274+
275+
fun Project.defaultOrgProvider(
276+
url: String?,
277+
authToken: String?,
278+
propertiesFilePath: String?,
279+
): Provider<String> {
280+
return providers.of(SentryOrgValueSource::class.java) {
281+
it.parameters.projectDir.set(layout.projectDirectory)
282+
it.parameters.projectBuildDir.set(layout.buildDirectory)
283+
it.parameters.rootProjDir.set(getIsolatedRootProjectDir())
284+
url?.let { value -> it.parameters.url.set(value) }
285+
authToken?.let { value -> it.parameters.authToken.set(value) }
286+
propertiesFilePath?.let { value -> it.parameters.propertiesFilePath.set(value) }
287+
}
288+
}

0 commit comments

Comments
 (0)