Skip to content

Commit 69fad0d

Browse files
authored
fix(proguard): Do not generate empty debug-meta.properties file (#1047)
* fix(proguard): Do not generate empty debug-meta.properties file * Changelog
1 parent 8821cae commit 69fad0d

7 files changed

Lines changed: 173 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Fixes
6+
7+
- Fix reproducible builds by writing `sentry-debug-meta.properties` without timestamps ([#876](https://github.com/getsentry/sentry-android-gradle-plugin/pull/876))
8+
- Skip generating `sentry-debug-meta.properties` when `includeProguardMapping` and `includeSourceContext` are disabled ([#1047](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1047))
9+
310
## 6.0.0-rc.1
411

512
### Fixes

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,23 @@ abstract class InjectSentryMetaPropertiesIntoAssetsTask : DefaultTask() {
8080

8181
input.copyRecursively(output, overwrite = true)
8282

83+
// skip writing the properties file if there are no input property files
84+
// this avoids generating an empty properties file when all Sentry features are disabled
85+
if (inputPropertyFiles.isEmpty) {
86+
return
87+
}
88+
8389
// merge props
8490
val props = Properties()
8591
props.setProperty("io.sentry.build-tool", "gradle")
8692
inputPropertyFiles.forEach { inputFile ->
8793
PropertiesUtil.loadMaybe(inputFile)?.let { props.putAll(it) }
8894
}
8995

90-
// write props
9196
val propsFile = File(output, SENTRY_DEBUG_META_PROPERTIES_OUTPUT)
92-
propsFile.writer().use { props.store(it, "Generated by sentry-android-gradle-plugin") }
97+
propsFile.writer().use {
98+
PropertiesUtil.store(props, it, "Generated by sentry-android-gradle-plugin")
99+
}
93100
}
94101

95102
companion object {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ abstract class SentryGenerateDebugMetaPropertiesTask : DirectoryOutputTask() {
4444
PropertiesUtil.loadMaybe(inputFile)?.let { props.putAll(it) }
4545
}
4646
debugMetaPropertiesFile.writer().use {
47-
props.store(it, "Generated by sentry-android-gradle-plugin")
47+
PropertiesUtil.store(props, it, "Generated by sentry-android-gradle-plugin")
4848
}
4949
}
5050

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.sentry.android.gradle.util
22

33
import java.io.File
4+
import java.io.Writer
45
import java.util.Properties
56

67
class PropertiesUtil {
@@ -18,5 +19,19 @@ class PropertiesUtil {
1819

1920
return load(file)
2021
}
22+
23+
/**
24+
* Stores properties to a writer without timestamps, ensuring reproducible builds.
25+
*
26+
* @param props The properties to store
27+
* @param writer The writer to write to
28+
* @param comment Optional comment to include at the top of the file (without the # prefix)
29+
*/
30+
fun store(props: Properties, writer: Writer, comment: String? = null) {
31+
comment?.let { writer.write("# $it\n") }
32+
props.stringPropertyNames().sorted().forEach { key ->
33+
writer.write("$key=${props.getProperty(key)}\n")
34+
}
35+
}
2136
}
2237
}

plugin-build/src/test/kotlin/io/sentry/android/gradle/TestUtils.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@ private val ASSET_PATTERN_SOURCE_CONTEXT =
3232
.trimMargin()
3333
)
3434

35+
internal fun verifyDebugMetaPropertiesNotInApk(
36+
rootFile: File,
37+
variant: String = "release",
38+
signed: Boolean = true,
39+
) {
40+
val signedStr = if (signed) "-unsigned" else ""
41+
val apk = rootFile.resolve("app/build/outputs/apk/$variant/app-$variant$signedStr.apk")
42+
val sentryProperties = extractZip(apk, "assets/sentry-debug-meta.properties")
43+
44+
assertTrue("Properties file should NOT be in the APK but was found") {
45+
sentryProperties.isBlank()
46+
}
47+
}
48+
3549
internal fun verifyProguardUuid(
3650
rootFile: File,
3751
variant: String = "release",

plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ import io.sentry.BuildConfig
44
import io.sentry.android.gradle.extensions.InstrumentationFeature
55
import io.sentry.android.gradle.util.AgpVersions
66
import io.sentry.android.gradle.util.SemVer
7+
import io.sentry.android.gradle.verifyDebugMetaPropertiesNotInApk
78
import io.sentry.android.gradle.verifyDependenciesReportAndroid
89
import io.sentry.android.gradle.verifyIntegrationList
910
import io.sentry.android.gradle.verifyProguardUuid
11+
import io.sentry.android.gradle.verifySourceContextId
12+
import io.sentry.android.gradle.withDummyKtFile
1013
import java.io.File
1114
import java.util.EnumSet
1215
import kotlin.test.assertEquals
@@ -360,7 +363,51 @@ class SentryPluginTest :
360363

361364
runner.appendArguments(":app:assembleRelease").build()
362365

363-
assertThrows(AssertionError::class.java) { verifyProguardUuid(testProjectDir.root) }
366+
verifyDebugMetaPropertiesNotInApk(testProjectDir.root)
367+
}
368+
369+
@Test
370+
fun `includes source context bundle ID in APK when proguard mapping is disabled but source context is enabled`() {
371+
appBuildFile.writeText(
372+
// language=Groovy
373+
"""
374+
plugins {
375+
id "com.android.application"
376+
id "io.sentry.android.gradle"
377+
}
378+
379+
android {
380+
namespace 'com.example'
381+
382+
buildTypes {
383+
release {
384+
minifyEnabled true
385+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
386+
}
387+
}
388+
}
389+
390+
sentry {
391+
includeProguardMapping = false
392+
includeSourceContext = true
393+
autoUploadSourceContext = false
394+
autoUploadProguardMapping = false
395+
org = "sentry-sdks"
396+
projectName = "sentry-android"
397+
}
398+
"""
399+
.trimIndent()
400+
)
401+
402+
sentryPropertiesFile.writeText("")
403+
testProjectDir.withDummyKtFile()
404+
405+
runner
406+
// run source context tasks but skip upload task (requires auth token)
407+
.appendArguments(":app:assembleRelease", "-x", "sentryUploadSourceBundleRelease")
408+
.build()
409+
410+
verifySourceContextId(testProjectDir.root)
364411
}
365412

366413
@Test
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package io.sentry.android.gradle.util
2+
3+
import java.io.StringWriter
4+
import java.util.Properties
5+
import kotlin.test.assertEquals
6+
import kotlin.test.assertFalse
7+
import kotlin.test.assertTrue
8+
import org.junit.Test
9+
10+
class PropertiesUtilTest {
11+
12+
@Test
13+
fun `store writes properties without timestamp`() {
14+
val props = Properties()
15+
props.setProperty("key1", "value1")
16+
props.setProperty("key2", "value2")
17+
18+
val writer = StringWriter()
19+
PropertiesUtil.store(props, writer, "Test comment")
20+
val result = writer.toString()
21+
22+
// should not contain date patterns that Java Properties.store() adds
23+
assertFalse(result.contains("Mon ") || result.contains("Tue ") || result.contains("Wed "))
24+
assertFalse(result.contains("Thu ") || result.contains("Fri ") || result.contains("Sat "))
25+
assertFalse(result.contains("Sun "))
26+
// should contain properties
27+
assertTrue(result.contains("key1=value1"))
28+
assertTrue(result.contains("key2=value2"))
29+
// should contain comment
30+
assertTrue(result.contains("# Test comment"))
31+
}
32+
33+
@Test
34+
fun `store writes properties in sorted order`() {
35+
val props = Properties()
36+
props.setProperty("zebra", "last")
37+
props.setProperty("apple", "first")
38+
props.setProperty("middle", "mid")
39+
40+
val writer = StringWriter()
41+
PropertiesUtil.store(props, writer)
42+
val result = writer.toString()
43+
44+
val appleIndex = result.indexOf("apple=first")
45+
val middleIndex = result.indexOf("middle=mid")
46+
val zebraIndex = result.indexOf("zebra=last")
47+
48+
assertTrue(appleIndex < middleIndex)
49+
assertTrue(middleIndex < zebraIndex)
50+
}
51+
52+
@Test
53+
fun `store works without comment`() {
54+
val props = Properties()
55+
props.setProperty("key", "value")
56+
57+
val writer = StringWriter()
58+
PropertiesUtil.store(props, writer)
59+
val result = writer.toString()
60+
61+
assertFalse(result.contains("#"))
62+
assertEquals("key=value\n", result)
63+
}
64+
65+
@Test
66+
fun `store produces identical output for same properties`() {
67+
val props = Properties()
68+
props.setProperty("io.sentry.build-tool", "gradle")
69+
props.setProperty("io.sentry.ProguardUuids", "test-uuid")
70+
71+
val writer1 = StringWriter()
72+
PropertiesUtil.store(props, writer1, "Generated by sentry-android-gradle-plugin")
73+
74+
val writer2 = StringWriter()
75+
PropertiesUtil.store(props, writer2, "Generated by sentry-android-gradle-plugin")
76+
77+
assertEquals(writer1.toString(), writer2.toString())
78+
}
79+
}

0 commit comments

Comments
 (0)