Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
40fffae
ci(benchmark): Add help task configuration benchmark on PRs
runningcode May 29, 2026
30ae4ba
fix(benchmark): Compute help benchmark mean from measured builds
runningcode May 29, 2026
bf72162
fix(config-cache): Defer CLI path resolution to execution phase
runningcode May 22, 2026
723505a
fix(config-cache): Use abstract DirectoryProperty for CLI paths
runningcode May 22, 2026
2166560
chore: Apply spotless formatting
runningcode May 22, 2026
0dc9c5c
fix(config-cache): Use layout API for root dir property
runningcode May 22, 2026
0aee71d
fix(telemetry): Restore async default org lookup
runningcode May 22, 2026
30f6692
fix(telemetry): Defer org lookup to execution phase
runningcode May 26, 2026
66d2709
fix(config-cache): Restore DirectoryProperty overloads in SentryCliPr…
runningcode May 28, 2026
d1509cf
fix(config-cache): Restore getIsolatedRootProjectDir for Gradle 8.8+
runningcode May 28, 2026
3555294
fix(config-cache): Consolidate maybeExtractFromResources overloads
runningcode May 28, 2026
ebdc2f8
fix(config-cache): Restore SentryCliProvider to match main
runningcode May 28, 2026
ad14939
fix(config-cache): Remove File overloads from SentryCliProvider
runningcode May 28, 2026
1cc1147
fix(test): Reset memoized CLI path between tests
runningcode May 28, 2026
91d6f31
fix(config-cache): Remove memoized CLI path from SentryCliProvider
runningcode May 28, 2026
e4cc66e
fix(config-cache): Resolve CLI path via ValueSource task input
runningcode May 29, 2026
f15b88f
ref(telemetry): Use kotlin.concurrent.thread for org lookup
runningcode May 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/workflows/benchmark-help-config.yml
Original file line number Diff line number Diff line change
@@ -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 = '<!-- help-config-benchmark -->';
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 });
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ fun ApplicationAndroidComponentsExtension.configure(
project: Project,
extension: SentryPluginExtension,
buildEvents: BuildEventListenerRegistryInternal,
cliExecutable: Provider<String>,
sentryOrg: String?,
sentryProject: String?,
) {
Expand All @@ -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)

Expand All @@ -106,7 +105,6 @@ fun ApplicationAndroidComponentsExtension.configure(
sentryTelemetryProvider,
paths,
sourceFiles,
cliExecutable,
sentryOrg,
sentryProject,
)
Expand All @@ -118,7 +116,6 @@ fun ApplicationAndroidComponentsExtension.configure(
extension,
sentryTelemetryProvider,
paths,
cliExecutable,
sentryOrg,
sentryProject,
)
Expand All @@ -139,7 +136,6 @@ fun ApplicationAndroidComponentsExtension.configure(
project,
extension,
sentryTelemetryProvider,
cliExecutable,
sentryOrg,
sentryProject,
)
Expand Down Expand Up @@ -257,7 +253,6 @@ fun ApplicationAndroidComponentsExtension.configure(
project,
extension,
sentryTelemetryProvider,
cliExecutable,
sentryOrg,
sentryProject,
)
Expand All @@ -269,22 +264,14 @@ fun ApplicationAndroidComponentsExtension.configure(
private fun Variant.configureTelemetry(
project: Project,
extension: SentryPluginExtension,
cliExecutable: Provider<String>,
sentryOrg: String?,
buildEvents: BuildEventListenerRegistryInternal,
): Provider<SentryTelemetryService> {
val variant = AndroidVariant74(this)
val sentryTelemetryProvider = SentryTelemetryService.register(project)
project.gradle.taskGraph.whenReady {
sentryTelemetryProvider.get().start {
SentryTelemetryService.createParameters(
project,
variant,
extension,
cliExecutable,
sentryOrg,
"Android",
)
SentryTelemetryService.createParameters(project, variant, extension, sentryOrg, "Android")
}
buildEvents.onOperationCompletion(sentryTelemetryProvider)
Comment on lines 275 to 276
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The unsynchronized started() method in SentryTelemetryService creates a race condition when accessing pendingOrgLookupParams, which is set by the synchronized start() method.
Severity: MEDIUM

Suggested Fix

Add the @Synchronized annotation to the started() method in SentryTelemetryService to ensure thread-safe access to the pendingOrgLookupParams field. Alternatively, annotate the pendingOrgLookupParams field with @Volatile to ensure memory visibility across threads.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location:
plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt#L275-L276

Potential issue: A race condition exists in `SentryTelemetryService`. The `start()`
method is `@Synchronized` and sets the `pendingOrgLookupParams` field. However, the
`started()` method, which reads and nullifies this field, is not synchronized. During
parallel Gradle builds, multiple threads can call `started()` concurrently. This can
lead to a race condition where the write to `pendingOrgLookupParams` during the
configuration phase may not be visible to threads reading it in the execution phase, due
to a lack of memory visibility guarantees.

Also affects:

  • plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt:115~119

}
Expand All @@ -297,7 +284,6 @@ private fun Variant.configureSourceBundleTasks(
sentryTelemetryProvider: Provider<SentryTelemetryService>,
paths: OutputPaths,
sourceFiles: Provider<out Collection<Directory>>?,
cliExecutable: Provider<String>,
sentryOrg: String?,
sentryProject: String?,
): SourceContext.SourceContextTasks? {
Expand All @@ -313,7 +299,6 @@ private fun Variant.configureSourceBundleTasks(
variant,
paths,
sourceFiles,
cliExecutable,
sentryOrg,
sentryProject,
taskSuffix,
Expand Down Expand Up @@ -354,7 +339,6 @@ private fun ApplicationVariant.configureProguardMappingsTasks(
extension: SentryPluginExtension,
sentryTelemetryProvider: Provider<SentryTelemetryService>,
paths: OutputPaths,
cliExecutable: Provider<String>,
sentryOrg: String?,
sentryProject: String?,
): TaskProvider<SentryGenerateProguardUuidTask>? {
Expand Down Expand Up @@ -386,7 +370,6 @@ private fun ApplicationVariant.configureProguardMappingsTasks(
extension,
sentryTelemetryProvider,
debug = extension.debug,
cliExecutable = cliExecutable,
generateUuidTask = generateUuidTask,
sentryProperties = sentryProps,
mappingFiles = mappings,
Expand Down Expand Up @@ -460,7 +443,6 @@ private fun ApplicationVariant.configureDistributionPropertiesTask(
private fun ApplicationVariant.configureSnapshotsTasks(
project: Project,
extension: SentryPluginExtension,
cliExecutable: Provider<String>,
sentryOrg: String?,
sentryProject: String?,
) {
Expand All @@ -478,7 +460,6 @@ private fun ApplicationVariant.configureSnapshotsTasks(
project = project,
extension = extension,
sentryTelemetryProvider = null,
cliExecutable = cliExecutable,
sentryOrgOverride = sentryOrg,
sentryProjectOverride = sentryProject,
applicationId = applicationId,
Expand Down Expand Up @@ -557,7 +538,6 @@ fun Variant.configureUploadAppTasks(
project: Project,
extension: SentryPluginExtension,
sentryTelemetryProvider: Provider<SentryTelemetryService>,
cliExecutable: Provider<String>,
sentryOrg: String?,
sentryProject: String?,
): Pair<TaskProvider<SentryUploadAppArtifactTask>, TaskProvider<SentryUploadAppArtifactTask>> {
Expand All @@ -570,7 +550,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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ 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
Expand All @@ -34,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? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -59,7 +57,6 @@ constructor(private val buildEvents: BuildEventListenerRegistryInternal) : Plugi
project,
extension,
buildEvents,
cliExecutable,
sentryOrgParameter,
sentryProjectParameter,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ abstract class BundleSourcesTask : SentryCliExecTask() {
collectSourcesTask: TaskProvider<CollectSourcesTask>,
output: Provider<Directory>,
debug: Property<Boolean>,
cliExecutable: Provider<String>,
sentryOrg: Provider<String>,
sentryProject: Provider<String>,
sentryAuthToken: Property<String>,
Expand All @@ -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))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ class SourceContext {
variant: SentryVariant,
paths: OutputPaths,
sourceFiles: Provider<out Collection<Directory>>?,
cliExecutable: Provider<String>,
sentryOrg: String?,
sentryProject: String?,
taskSuffix: String,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ abstract class UploadSourceBundleTask : SentryCliExecTask() {
variant: SentryVariant,
bundleSourcesTask: TaskProvider<BundleSourcesTask>,
debug: Property<Boolean>,
cliExecutable: Provider<String>,
autoUploadSourceContext: Property<Boolean>,
sentryOrg: Provider<String>,
sentryProject: Provider<String>,
Expand All @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ abstract class SentryCliExecTask : Exec() {

@get:Input @get:Optional abstract val debug: Property<Boolean>

@get:Input abstract val cliExecutable: Property<String>

@get:InputFile
@get:Optional
@get:PathSensitive(PathSensitivity.RELATIVE)
Expand All @@ -39,7 +37,9 @@ abstract class SentryCliExecTask : Exec() {

@get:Internal abstract val sentryTelemetryService: Property<SentryTelemetryService>

private val buildDirectory: DirectoryProperty = project.layout.buildDirectory
@get:Input abstract val cliExecutable: Property<String>

@get:Internal abstract val buildDirectory: DirectoryProperty

override fun exec() {
computeCommandLineArgs().let {
Expand Down Expand Up @@ -92,8 +92,7 @@ abstract class SentryCliExecTask : Exec() {
args.add(1, "/c")
}

val cliPath = SentryCliProvider.maybeExtractFromResources(buildDirectory, cliExecutable.get())
args.add(cliPath)
args.add(SentryCliProvider.maybeExtractFromResources(buildDirectory, cliExecutable.get()))
args.addAll(preArgs())

getArguments(args)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ abstract class SentryUploadAppArtifactTask @Inject constructor(objectFactory: Ob
debug: Property<Boolean>,
appBundle: Provider<RegularFile>,
apk: Provider<Directory>,
cliExecutable: Provider<String>,
sentryProperties: String?,
sentryOrg: Provider<String>,
sentryProject: Provider<String>,
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Loading
Loading