Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import com.android.build.api.variant.impl.VariantImpl
import io.sentry.android.gradle.SentryTasksProvider.getComposeMappingMergeTask
import io.sentry.android.gradle.SentryTasksProvider.getMinifyTask
import io.sentry.android.gradle.tasks.SentryGenerateProguardUuidTask
import io.sentry.android.gradle.util.AgpVersions
import io.sentry.gradle.common.SentryVariant
import io.sentry.gradle.common.filterBuildConfig
import org.gradle.api.Project
Expand All @@ -32,9 +31,7 @@ data class AndroidVariant74(private val variant: Variant) : SentryVariant {
override val flavorName: String? = variant.flavorName
override val buildTypeName: String? = variant.buildType
override val productFlavors: List<String> = variant.productFlavors.map { it.second }
override val isMinifyEnabled: Boolean =
(variant as? CanMinifyCode)?.isMinifyEnabled == true ||
variant.isApplicationOptimizationEnabled()
override val isMinifyEnabled: Boolean = (variant as? CanMinifyCode)?.isMinifyEnabled == true

// TODO: replace this eventually (when targeting AGP 8.3.0) with
// https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-api/src/main/java/com/android/build/api/variant/Component.kt;l=103-104;bpv=1
Expand Down Expand Up @@ -117,24 +114,6 @@ data class AndroidVariant74(private val variant: Variant) : SentryVariant {
}
}

private fun Variant.isApplicationOptimizationEnabled(): Boolean {
if (!AgpVersions.isAGP93(AgpVersions.CURRENT)) {
return false
}

// AGP 9.3's optimization.enable DSL does not update CanMinifyCode.isMinifyEnabled. The merged
// value is only exposed through an internal creation config, so use reflection to keep this
// plugin binary-compatible with older AGP versions.
return runCatching {
val optimizationCreationConfig =
javaClass.getMethod("getOptimizationCreationConfig").invoke(this)
optimizationCreationConfig.javaClass
.getMethod("getApplicationOptimizationEnabled")
.invoke(optimizationCreationConfig) as Boolean
}
.getOrDefault(false)
}

fun <T : InstrumentationParameters> configureInstrumentationFor74(
variant: Variant,
classVisitorFactoryImplClass: Class<out AsmClassVisitorFactory<T>>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import io.sentry.android.gradle.telemetry.SentryTelemetryService
import io.sentry.android.gradle.util.AgpVersions
import io.sentry.android.gradle.util.GroovyCompat
import io.sentry.android.gradle.util.SentryModules
import io.sentry.android.gradle.util.SentryPluginUtils.isMinificationEnabled
import io.sentry.android.gradle.util.SentryPluginUtils.isVariantAllowed
import io.sentry.android.gradle.util.collectModules
import io.sentry.android.gradle.util.hookWithAssembleTasks
Expand Down Expand Up @@ -351,9 +350,8 @@ private fun ApplicationVariant.configureProguardMappingsTasks(
}
val sentryProps = getPropertiesFilePath(project, variant)
val dexguardEnabled = extension.dexguardEnabled.get()
val isMinifyEnabled = isMinificationEnabled(project, variant, dexguardEnabled)

if (isMinifyEnabled && extension.includeProguardMapping.get()) {
if (extension.includeProguardMapping.get()) {
val mappings = getMappingFileProvider(project, variant, dexguardEnabled)
val generateUuidTask =
SentryGenerateProguardUuidTask.register(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import io.sentry.android.gradle.telemetry.SentryTelemetryService
import io.sentry.android.gradle.telemetry.withSentryTelemetry
import io.sentry.android.gradle.util.contentHash
import io.sentry.android.gradle.util.info
import java.io.File
import java.util.UUID
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.Directory
Expand Down Expand Up @@ -36,24 +38,34 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() {
// Used by AGP 8.3+ with toListenTo API - this property is wired to the mapping artifact
@get:Internal abstract val mappingFile: RegularFileProperty

internal fun findMappingFile(): File? =
try {
mappingFile.orNull?.asFile?.takeIf { it.exists() }
?: fallbackMappingFiles.files.firstOrNull { it.exists() }
} catch (_: GradleException) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we narrow these to the known “provider has no value” / missing-artifact cases (and rethrow anything else)? Worried we might suppress exceptions we shouldn't....

// AGP uses a provider with no value for non-minified variants; treat it as no mapping.
null
} catch (_: IllegalStateException) {
// Gradle may realize a missing artifact provider before wrapping it in a GradleException.
null
}

@TaskAction
fun generateProperties() {
val outputDir = output.get().asFile
outputDir.mkdirs()

// Prefer mappingFile (set via toListenTo on AGP 8.3+) over fallbackMappingFiles
val mappingFile =
if (mappingFile.isPresent) {
mappingFile.get().asFile.takeIf { it.exists() }
} else {
// Fallback for AGP < 8.3: use conventional file paths
fallbackMappingFiles.files.firstOrNull { it.exists() }
}
val mappingFile = findMappingFile()
val outputFile = outputFile.get().asFile
if (mappingFile == null) {
// The task may have produced a UUID in an earlier minified build. Remove it so a later
// non-minified build cannot inject or upload that stale UUID.
outputFile.delete()
return
}
Comment thread
sentry[bot] marked this conversation as resolved.

val uuid =
mappingFile?.let { UUID.nameUUIDFromBytes(it.contentHash().toByteArray()) }
?: UUID.randomUUID()
outputFile.get().asFile.writer().use { writer ->
val uuid = UUID.nameUUIDFromBytes(mappingFile.contentHash().toByteArray())
outputFile.writer().use { writer ->
writer.appendLine("$SENTRY_PROGUARD_MAPPING_UUID_PROPERTY=$uuid")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskProvider
Expand All @@ -36,21 +37,17 @@ abstract class SentryUploadProguardMappingsTask : SentryCliExecTask() {
outputs.upToDateWhen { true }
}

@get:InputFile @get:PathSensitive(PathSensitivity.NONE) abstract val uuidFile: RegularFileProperty
@get:InputFile
@get:Optional
@get:PathSensitive(PathSensitivity.NONE)
abstract val uuidFile: RegularFileProperty

@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract var mappingsFiles: Provider<FileCollection>

@get:Input abstract val autoUploadProguardMapping: Property<Boolean>

override fun exec() {
if (!mappingsFiles.isPresent || mappingsFiles.get().isEmpty) {
error("[sentry] Mapping files are missing!")
}
super.exec()
}

override fun getArguments(args: MutableList<String>) {
val uuid = readUuidFromFile(uuidFile.get().asFile)
val firstExistingFile = mappingsFiles.get().files.firstOrNull { it.exists() }
Expand Down Expand Up @@ -118,6 +115,10 @@ abstract class SentryUploadProguardMappingsTask : SentryCliExecTask() {
sentryTelemetryProvider?.let { task.sentryTelemetryService.set(it) }
task.asSentryCliExec()
task.withSentryTelemetry(extension, sentryTelemetryProvider)
task.onlyIf("a ProGuard mapping file and matching UUID were produced") {
task.uuidFile.asFile.orNull?.exists() == true &&
task.mappingsFiles.orNull?.files?.any { it.exists() } == true
}
}
return uploadSentryProguardMappingsTask
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,9 +374,10 @@ class SentryPluginTest :

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks like we should also update creates proguard mapping tasks when app optimization is enabled() above. (It's still limited to --dry-run task-name presence, so presumably will pass even if execution-time detection never consumes a produced mapping.)

@Test
fun `does not include a UUID in the APK`() {
// isMinifyEnabled is disabled by default in debug builds
runner.appendArguments(":app:assembleDebug").build()
val build = runner.appendArguments(":app:assembleDebug").build()

assertEquals(TaskOutcome.SUCCESS, build.task(":app:generateSentryProguardUuidDebug")?.outcome)
assertEquals(TaskOutcome.SKIPPED, build.task(":app:uploadSentryProguardMappingsDebug")?.outcome)
assertThrows(AssertionError::class.java) {
verifyProguardUuid(testProjectDir.root, variant = "debug", signed = false)
}
Expand Down Expand Up @@ -1119,25 +1120,17 @@ class SentryPluginTest :
)

val result1 = runner.appendArguments(":app:testReleaseUnitTest").build()
val uuid1 = verifyProguardUuid(testProjectDir.root, inGeneratedFolder = true)
assertFalse { "minifyReleaseWithR8" in result1.output }
assertEquals(
TaskOutcome.SUCCESS,
result1.task(":app:generateSentryProguardUuidRelease")?.outcome,
)

val result2 = runner.build()
val uuid2 = verifyProguardUuid(testProjectDir.root, inGeneratedFolder = true)
assertFalse { "minifyReleaseWithR8" in result2.output }

// UUIDs may differ between runs when minify doesn't run, because there's no mapping file
// to generate a deterministic hash from. The important assertion is that minify doesn't run.
// When minify DOES run (in production builds), the UUID will be deterministic.
assertNotEquals(
"00000000-0000-0000-0000-000000000000",
uuid1.toString(),
"UUID should be generated",
)
assertNotEquals(
"00000000-0000-0000-0000-000000000000",
uuid2.toString(),
"UUID should be generated",
assertEquals(
TaskOutcome.SUCCESS,
result2.task(":app:generateSentryProguardUuidRelease")?.outcome,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import java.io.File
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import org.gradle.testfixtures.ProjectBuilder
import org.junit.Test
Expand Down Expand Up @@ -36,7 +38,7 @@ class SentryGenerateDebugMetaPropertiesTaskTest {
project.extensions.findByName("sentry") as SentryPluginExtension,
null,
project.layout.buildDirectory.dir("dummy/folder/"),
null,
createMappingFileProvider(project),
"test",
)
val idGenerationTasks = listOf(bundleIdTask, proguardIdTask)
Expand Down Expand Up @@ -81,7 +83,7 @@ class SentryGenerateDebugMetaPropertiesTaskTest {
project.extensions.findByName("sentry") as SentryPluginExtension,
null,
project.layout.buildDirectory.dir("dummy/folder/"),
null,
createMappingFileProvider(project),
"test",
)
val idGenerationTasks = listOf(bundleIdTask, proguardIdTask)
Expand Down Expand Up @@ -121,6 +123,16 @@ class SentryGenerateDebugMetaPropertiesTaskTest {
assertNotNull(bundleId2)
}

private fun createMappingFileProvider(project: Project): Provider<FileCollection> =
project.provider {
project.files(
File(project.buildDir, "mapping.txt").also {
it.parentFile.mkdirs()
it.writeText("mapping")
}
)
}

private fun createProject(): Project {
with(ProjectBuilder.builder().build()) {
plugins.apply("io.sentry.android.gradle")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.sentry.android.gradle.tasks.SentryGenerateProguardUuidTask.Companion.S
import io.sentry.android.gradle.util.PropertiesUtil
import java.io.File
import java.util.UUID
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import org.gradle.api.Project
Expand All @@ -22,6 +23,12 @@ class SentryGenerateProguardUuidTaskTest {
SentryGenerateProguardUuidTask::class.java,
) {
it.output.set(project.layout.buildDirectory.dir("dummy/folder/"))
it.fallbackMappingFiles.from(
File(project.buildDir, "mapping.txt").also { file ->
file.parentFile.mkdirs()
file.writeText("mapping")
}
)
}

task.get().generateProperties()
Expand All @@ -35,6 +42,27 @@ class SentryGenerateProguardUuidTaskTest {
UUID.fromString(uuid)
}

@Test
fun `generate proguard UUID removes stale output when no mapping exists`() {
val project = createProject()
val task =
project.tasks.register(
"testRemoveStaleProguardUuid",
SentryGenerateProguardUuidTask::class.java,
) {
it.output.set(project.layout.buildDirectory.dir("dummy/folder/"))
}
val staleOutput =
File(project.buildDir, "dummy/folder/sentry-proguard-uuid.properties").also {
it.parentFile.mkdirs()
it.writeText("$SENTRY_PROGUARD_MAPPING_UUID_PROPERTY=stale")
}

task.get().generateProperties()

assertFalse(staleOutput.exists())
}

private fun createProject(): Project {
with(ProjectBuilder.builder().build()) {
plugins.apply("io.sentry.android.gradle")
Expand Down
Loading