Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- (Experimental) Add snapshot upload support via `sentryUploadSnapshots` task ([#1091](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1091))

### Dependencies

- Bump CLI from v3.2.0 to v3.2.3 ([#1084](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1084), [#1090](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1090))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import io.sentry.BuildConfig
import io.sentry.android.gradle.autoinstall.installDependencies
import io.sentry.android.gradle.extensions.SentryPluginExtension
import io.sentry.android.gradle.tasks.SentryUploadSnapshotsTask
import io.sentry.android.gradle.util.AgpVersions
import java.io.File
import javax.inject.Inject
Expand Down Expand Up @@ -65,6 +66,14 @@ constructor(private val buildEvents: BuildEventListenerRegistryInternal) : Plugi
sentryProjectParameter,
)

SentryUploadSnapshotsTask.register(
Comment thread
runningcode marked this conversation as resolved.
project,
extension,
cliExecutable,
sentryOrgParameter,
sentryProjectParameter,
)
Comment thread
sentry[bot] marked this conversation as resolved.

project.installDependencies(extension, true)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,41 +18,53 @@ internal object SentryPropertiesFileProvider {
* @return A [String] for the path if sentry.properties is found or null otherwise
*/
@JvmStatic
fun getPropertiesFilePath(project: Project, variant: SentryVariant): String? {
val flavorName = variant.flavorName.orEmpty()
val buildTypeName = variant.buildTypeName.orEmpty()
fun getPropertiesFilePath(project: Project, variant: SentryVariant? = null): String? {
val flavorName = variant?.flavorName.orEmpty()
val buildTypeName = variant?.buildTypeName.orEmpty()
Comment thread
cursor[bot] marked this conversation as resolved.

val projDir = project.projectDir
val rootDir = project.rootDir

val sep = File.separator

// Local Project dirs
val possibleFiles = mutableListOf("${projDir}${sep}src${sep}${buildTypeName}${sep}$FILENAME")
val possibleFiles = mutableListOf<String>()
if (buildTypeName.isNotBlank()) {
possibleFiles.add("${projDir}${sep}src${sep}${buildTypeName}${sep}$FILENAME")
}
if (flavorName.isNotBlank()) {
possibleFiles.add("${projDir}${sep}src${sep}${buildTypeName}${sep}$flavorName${sep}$FILENAME")
possibleFiles.add(
"${projDir}${sep}src${sep}${flavorName}${sep}${buildTypeName}${sep}$FILENAME"
)
if (buildTypeName.isNotBlank()) {
possibleFiles.add(
"${projDir}${sep}src${sep}${buildTypeName}${sep}$flavorName${sep}$FILENAME"
)
possibleFiles.add(
"${projDir}${sep}src${sep}${flavorName}${sep}${buildTypeName}${sep}$FILENAME"
)
}
possibleFiles.add("${projDir}${sep}src${sep}${flavorName}${sep}$FILENAME")
}
possibleFiles.add("${projDir}${sep}$FILENAME")

// Other flavors dirs
possibleFiles.addAll(
variant.productFlavors.map { "${projDir}${sep}src${sep}${it}${sep}$FILENAME" }
variant?.productFlavors?.map { "${projDir}${sep}src${sep}${it}${sep}$FILENAME" }
?: emptyList()
)

// Root project dirs
possibleFiles.add("${rootDir}${sep}src${sep}${buildTypeName}${sep}$FILENAME")
if (buildTypeName.isNotBlank()) {
possibleFiles.add("${rootDir}${sep}src${sep}${buildTypeName}${sep}$FILENAME")
}
if (flavorName.isNotBlank()) {
possibleFiles.add("${rootDir}${sep}src${sep}${flavorName}${sep}$FILENAME")
possibleFiles.add(
"${rootDir}${sep}src${sep}${buildTypeName}${sep}${flavorName}${sep}$FILENAME"
)
possibleFiles.add(
"${rootDir}${sep}src${sep}${flavorName}${sep}${buildTypeName}${sep}$FILENAME"
)
if (buildTypeName.isNotBlank()) {
possibleFiles.add(
"${rootDir}${sep}src${sep}${buildTypeName}${sep}${flavorName}${sep}$FILENAME"
)
possibleFiles.add(
"${rootDir}${sep}src${sep}${flavorName}${sep}${buildTypeName}${sep}$FILENAME"
)
}
}
possibleFiles.add("${rootDir}${sep}$FILENAME")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ abstract class SentryPluginExtension @Inject constructor(project: Project) {
vcsInfoAction.execute(vcsInfo)
}

val snapshots: SnapshotsExtension = objects.newInstance(SnapshotsExtension::class.java)

/** Configure the snapshots upload. */
@Experimental
fun snapshots(snapshotsAction: Action<SnapshotsExtension>) {
snapshotsAction.execute(snapshots)
}

/**
* Disables or enables the reporting of dependencies metadata for Sentry. If enabled the plugin
* will collect external dependencies and will take care of uploading them to Sentry as part of
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.sentry.android.gradle.extensions

import javax.inject.Inject
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.jetbrains.annotations.ApiStatus.Experimental

@Experimental
open class SnapshotsExtension @Inject constructor(objects: ObjectFactory) {

/** The application identifier used to associate snapshots with an app. */
val appId: Property<String> = objects.property(String::class.java)
Comment thread
runningcode marked this conversation as resolved.

/** The path to the folder containing snapshots to upload. */
val path: DirectoryProperty = objects.directoryProperty()
Comment thread
runningcode marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package io.sentry.android.gradle.tasks

import io.sentry.android.gradle.SentryPropertiesFileProvider.getPropertiesFilePath
import io.sentry.android.gradle.autoinstall.SENTRY_GROUP
import io.sentry.android.gradle.extensions.SentryPluginExtension
import io.sentry.android.gradle.util.asSentryCliExec
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskProvider
import org.gradle.work.DisableCachingByDefault

@DisableCachingByDefault(because = "Uploads should not be cached")
abstract class SentryUploadSnapshotsTask : SentryCliExecTask() {

init {
group = SENTRY_GROUP
description = "Uploads snapshots to Sentry"
}

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

@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val snapshotsPath: DirectoryProperty
Comment thread
sentry[bot] marked this conversation as resolved.

override fun getArguments(args: MutableList<String>) {
args.add("build")
args.add("snapshots")
args.add("--app-id")
args.add(appId.get())
args.add(snapshotsPath.get().asFile.absolutePath)
}

companion object {
fun register(
project: Project,
extension: SentryPluginExtension,
cliExecutable: Provider<String>,
sentryOrgOverride: String?,
sentryProjectOverride: String?,
): TaskProvider<SentryUploadSnapshotsTask> {
return project.tasks.register(
"sentryUploadSnapshots",
SentryUploadSnapshotsTask::class.java,
) { task ->
task.workingDir(project.rootDir)
task.debug.set(extension.debug)
task.cliExecutable.set(cliExecutable)
task.sentryProperties.set(
getPropertiesFilePath(project)?.let { file -> project.file(file) }
)
task.sentryOrganization.set(
sentryOrgOverride?.let { project.provider { it } } ?: extension.org
)
task.sentryProject.set(
sentryProjectOverride?.let { project.provider { it } } ?: extension.projectName
)
task.sentryAuthToken.set(extension.authToken)
task.sentryUrl.set(extension.url)
task.appId.set(extension.snapshots.appId)
task.snapshotsPath.set(extension.snapshots.path)
task.asSentryCliExec()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,28 @@ class SentryPropertiesFileProviderTest(private val agpVersion: SemVer) {
assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText())
}

@Test
fun `getPropertiesFilePath with null variant finds file inside project folder`() {
val (project, _) = createTestAndroidProject(forceEvaluate = !AgpVersions.isAGP74(agpVersion))
createTestFile(project.projectDir, "sentry.properties")

assertEquals("42", File(getPropertiesFilePath(project)!!).readText())
}

@Test
fun `getPropertiesFilePath with null variant skips buildType paths`() {
val rootProject = ProjectBuilder.builder().build()
val (project, _) =
createTestAndroidProject(
parent = rootProject,
forceEvaluate = !AgpVersions.isAGP74(agpVersion),
)
// Only place the file under src/debug/ — with null variant this should not be found
createTestFile(project.projectDir, "src${sep}debug${sep}sentry.properties")

assertEquals(null, getPropertiesFilePath(project))
}

@Test
fun `getPropertiesFilePath finds file inside root buildType flavor folder`() {
val rootProject = ProjectBuilder.builder().build()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package io.sentry.android.gradle.tasks

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
import org.gradle.testfixtures.ProjectBuilder
import org.junit.Test

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("build" in args)
assertTrue("snapshots" in args)
assertTrue("--app-id" in args)
assertTrue("com.example" in args)
assertTrue("/path/to/snapshots" in args)
assertFalse("--log-level=debug" in args)
}

@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)
}

val args = task.computeCommandLineArgs()

assertTrue("--log-level=debug" in args)
}

@Test
fun `with sentryProperties file SENTRY_PROPERTIES is set correctly`() {
val project = createProject()
val propertiesFile = project.file("dummy/folder/sentry.properties")
val task = createTestTask(project) { it.sentryProperties.set(propertiesFile) }

task.setSentryPropertiesEnv()

assertEquals(propertiesFile.absolutePath, task.environment["SENTRY_PROPERTIES"].toString())
}

@Test
fun `without sentryProperties file SENTRY_PROPERTIES is not set`() {
val task = createTestTask()

task.setSentryPropertiesEnv()

assertNull(task.environment["SENTRY_PROPERTIES"])
}

@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"))
}

val args = task.computeCommandLineArgs()

assertTrue("--org" in args)
assertTrue("dummy-org" in args)
}

@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"))
}

val args = task.computeCommandLineArgs()

assertTrue("--project" in args)
assertTrue("dummy-proj" in args)
}

@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"))
}

val args = task.computeCommandLineArgs()

assertTrue("--url" in args)
assertTrue("https://some-host.sentry.io" in args)
}

@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"))
}

val args = task.computeCommandLineArgs()

assertEquals(1, args.indexOf("--url"))
}

private fun createProject(): Project {
with(ProjectBuilder.builder().build()) {
plugins.apply("io.sentry.android.gradle")
return this
}
}

private fun createTestTask(
project: Project = createProject(),
block: (SentryUploadSnapshotsTask) -> Unit = {},
): SentryUploadSnapshotsTask =
project.tasks
.register("testUploadSnapshots", SentryUploadSnapshotsTask::class.java) { block(it) }
.get()
}
Loading