Skip to content

Commit f22eebf

Browse files
committed
fix(build): resolve R8 conflicts, missing test dependencies, and JDK compatibility issues
* Fix R8 "defined multiple times" error by optimizing shadowJar configurations. * Fix WorkManager 2.7.1 unit test compilation and runtime errors. * Enhance reflection tools for JDK 17+ compatibility. * Globally add missing test dependencies (Gson/OkHttp) to fix NoClassDefFoundError in feature and integration modules. * Add Proguard -dontwarn rules for missing classes in reliability tests.
1 parent a66a0d7 commit f22eebf

38 files changed

Lines changed: 364 additions & 38 deletions

File tree

dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/data/upload/UploadWorkerTest.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ import org.mockito.kotlin.whenever
5454
import org.mockito.quality.Strictness
5555
import org.mockito.stubbing.Answer
5656

57+
import com.datadog.android.utils.forge.WorkerParametersForgeryFactory
58+
5759
@Extensions(
5860
ExtendWith(MockitoExtension::class),
5961
ExtendWith(ForgeExtension::class),
@@ -74,7 +76,6 @@ internal class UploadWorkerTest {
7476
@StringForgery
7577
lateinit var fakeInstanceName: String
7678

77-
@Forgery
7879
lateinit var fakeWorkerParameters: WorkerParameters
7980

8081
var fakeFeaturesCount: Int = 0
@@ -93,6 +94,8 @@ internal class UploadWorkerTest {
9394

9495
@BeforeEach
9596
fun `set up`(forge: Forge) {
97+
fakeWorkerParameters = WorkerParametersForgeryFactory().getForgery(forge)
98+
9699
whenever(mockSdkCore.getDatadogContext()) doReturn fakeDatadogContext
97100
Datadog.registry.register(fakeInstanceName, mockSdkCore)
98101

@@ -918,7 +921,6 @@ internal class UploadWorkerTest {
918921
tags,
919922
runtimeExtras,
920923
runAttemptCount,
921-
generation,
922924
backgroundExecutor,
923925
taskExecutor,
924926
workerFactory,

dd-sdk-android-core/src/test/kotlin/com/datadog/android/utils/forge/WorkerParametersForgeryFactory.kt

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ package com.datadog.android.utils.forge
88

99
import android.content.Context
1010
import androidx.work.Data
11+
import androidx.work.ForegroundUpdater
1112
import androidx.work.ListenableWorker
13+
import androidx.work.ProgressUpdater
1214
import androidx.work.WorkerFactory
1315
import androidx.work.WorkerParameters
14-
import androidx.work.impl.utils.taskexecutor.SerialExecutor
1516
import androidx.work.impl.utils.taskexecutor.TaskExecutor
1617
import fr.xgouchet.elmyr.Forge
1718
import fr.xgouchet.elmyr.ForgeryFactory
19+
import org.mockito.kotlin.mock
1820
import java.util.concurrent.Executor
1921

2022
class WorkerParametersForgeryFactory : ForgeryFactory<WorkerParameters> {
@@ -25,34 +27,27 @@ class WorkerParametersForgeryFactory : ForgeryFactory<WorkerParameters> {
2527
val sameThreadExecutor = object : Executor {
2628
override fun execute(command: Runnable) = command.run()
2729
}
30+
31+
// Use Mockito to avoid direct implementation of internal interfaces
32+
val mockTaskExecutor = mock<TaskExecutor>()
33+
2834
return WorkerParameters(
2935
forge.getForgery(),
3036
Data.EMPTY,
3137
forge.aList { anAlphabeticalString() },
3238
WorkerParameters.RuntimeExtras(),
3339
forge.aSmallInt(),
34-
forge.aSmallInt(),
3540
sameThreadExecutor,
36-
object : TaskExecutor {
37-
override fun getMainThreadExecutor(): Executor {
38-
TODO()
39-
}
40-
41-
override fun getSerialTaskExecutor(): SerialExecutor {
42-
TODO("Not yet implemented")
43-
}
44-
},
41+
mockTaskExecutor,
4542
object : WorkerFactory() {
4643
override fun createWorker(
4744
appContext: Context,
4845
workerClassName: String,
4946
workerParameters: WorkerParameters
50-
): ListenableWorker? {
51-
return null
52-
}
47+
): ListenableWorker? = null
5348
},
54-
{ _, _, _ -> forge.getForgery() },
55-
{ _, _, _ -> forge.getForgery() }
49+
mock<ProgressUpdater>(),
50+
mock<ForegroundUpdater>()
5651
)
5752
}
5853

dd-sdk-android-dependencies/build.gradle.kts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,35 @@ dependencies {
1414
implementation(libs.re2j)
1515
}
1616

17+
tasks.named<Jar>("jar") {
18+
// Move the default (empty) jar out of the way to avoid name collision
19+
archiveClassifier.set("raw")
20+
}
21+
1722
tasks.shadowJar {
18-
archiveClassifier.set("all")
23+
// Make shadowJar the primary artifact by removing the 'all' classifier
24+
archiveClassifier.set("")
1925

2026
relocate("org.jctools", "cloud.flashcat.shaded.jctools")
2127
relocate("com.google.re2j", "cloud.flashcat.shaded.re2j")
28+
29+
// Use runtimeClasspath which is resolvable
30+
configurations = listOf(project.configurations.runtimeClasspath.get())
2231
}
2332

24-
tasks.named<Jar>("jar") {
33+
// Force the shadowJar to be the ONLY exported artifact for this module
34+
// This prevents R8 duplicate class errors while ensuring the file exists for KSP
35+
configurations.apiElements {
36+
outgoing.artifacts.clear()
37+
outgoing.artifact(tasks.shadowJar)
38+
}
39+
configurations.runtimeElements {
40+
outgoing.artifacts.clear()
41+
outgoing.artifact(tasks.shadowJar)
42+
}
43+
44+
tasks.assemble {
2545
dependsOn(tasks.shadowJar)
26-
from(zipTree(tasks.shadowJar.flatMap { it.archiveFile })) {
27-
// Exclude manifest from shadowJar to avoid conflicts with main jar's manifest
28-
exclude("META-INF/MANIFEST.MF")
29-
}
3046
}
3147

3248
if (tasks.findByName("assembleDebug") == null) {

dd-sdk-android-internal/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ dependencies {
6868
}
6969
testImplementation(libs.bundles.jUnit5)
7070
testImplementation(libs.bundles.testTools)
71+
testImplementation(libs.okHttp)
72+
testImplementation(libs.gson)
7173
testImplementation(libs.robolectric)
7274
testImplementation(libs.androidXTestCore)
7375
testFixturesImplementation(libs.kotlin)

fc_sdk_rum.md

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
FC SDK (RUM) 三方库冲突解决方案(最终版)
2+
3+
问题背景
4+
5+
宿主 App 接入 FC SDK 时,SDK 内部三方库可能与宿主已有依赖冲突,导致编译失败或运行时崩溃。需同时解决:
6+
7+
1. OkHttp/Gson 等常见库的版本冲突
8+
2. KronosNTP/JCTools/re2j 等冷门库的 Duplicate class
9+
3. **AndroidX 高版本对宿主 compileSdk / Kotlin 版本的要求过高**
10+
11+
\--------------------------------------------------------------------------------
12+
13+
依赖版本约束总表
14+
15+
| 依赖 | 当前版本 | compileSdk 要求 | 冲突风险 | 公开 API 暴露 | 策略 |
16+
| --------------------- | ------------- | --------------- | -------- | ------------- | ------------------------------- |
17+
| OkHttp | 4.12.0 || 🔴 极高 | ✅ 深度暴露 | compileOnly + 降到 4.9.0 |
18+
| Gson | 2.10.1 || 🔴 极高 | ✅ 深度暴露 | compileOnly + 降到 2.8.9 |
19+
| **AndroidX Core** | **1.17.0** | **36** | 🔴 极高 | 部分 | **降到 1.12.0** (compileSdk 34) |
20+
| AndroidX Navigation | 2.7.7 | 34 | 🟡 中 | ✅ 暴露 | compileOnly + 降到 2.5.3 |
21+
| AndroidX WorkManager | 2.8.1 | 33 | 🟡 中 | ✅ 暴露 | compileOnly + 降到 2.7.1 |
22+
| AndroidX Metrics | 1.0.0-beta03 | 34 | 🟡 中 | ❌ 内部 | 降到 1.0.0-alpha04 |
23+
| AndroidX RecyclerView | 1.3.2 | 31 | 🟢 低 | ❌ 内部 | 保持/可降到 1.2.1 |
24+
| AndroidX Fragment | 1.2.4 || 🟢 低 | ❌ 内部 | 保持 |
25+
| AndroidX Collection | 1.4.5 | 28 | 🟢 低 | ❌ 内部 | 保持 |
26+
| AndroidX Annotation | 1.9.1 || 🟢 低 | ❌ 内部 | 保持 |
27+
| KronosNTP | 0.0.1-alpha11 || 🟡 中 | ❌ 内部 | **Shadow relocate** |
28+
| JCTools | 3.3.0 || 🟡 中 | ❌ 内部 | **Shadow relocate** |
29+
| re2j | 1.7 || 🟡 中 | ❌ 内部 | **Shadow relocate** |
30+
| OpenTelemetry API | 1.40.0 || 🟡 中 | ✅ 暴露 | compileOnly |
31+
32+
IMPORTANT
33+
34+
**最大问题**`androidx.core:core:1.17.0` 要求 compileSdk 36、AGP 8.9.1+,绝大多数宿主项目达不到。必须降版本。
35+
36+
\--------------------------------------------------------------------------------
37+
38+
修改方案
39+
40+
一、OkHttp / Gson:`compileOnly` + 降版本
41+
42+
SDK 编译使用低版本,由宿主提供具体版本。
43+
44+
版本变更
45+
46+
```
47+
# gradle/libs.versions.toml
48+
49+
-okHttp = "4.12.0"
50+
51+
+okHttp = "4.9.0"
52+
53+
-gson = "2.10.1"
54+
55+
+gson = "2.8.9"
56+
```
57+
58+
依赖声明变更
59+
60+
需修改以下模块的
61+
62+
build.gradle.kts,将 OkHttp/Gson 从 `implementation` 改为 `compileOnly`
63+
64+
| 模块 | OkHttp | Gson |
65+
| ------------------------ | ------------------ | ------------------ |
66+
| dd-sdk-android-core | impl → compileOnly | impl → compileOnly |
67+
| dd-sdk-android-rum | impl → compileOnly | impl → compileOnly |
68+
| dd-sdk-android-trace || impl → compileOnly |
69+
| dd-sdk-android-webview || impl → compileOnly |
70+
| dd-sdk-android-ndk | impl → compileOnly ||
71+
| dd-sdk-android-profiling | impl → compileOnly | impl → compileOnly |
72+
| dd-sdk-android-okhttp | impl → compileOnly ||
73+
74+
WARNING
75+
76+
**降到 OkHttp 4.9.0 需验证**:SDK 内部使用的 Kotlin 扩展 API(`toMediaTypeOrNull``toRequestBody` 等)在 4.9.0 中存在,但部分新 API 可能不兼容,需要编译验证。
77+
78+
**降到 Gson 2.8.9 需验证**:确认 SDK 内部没有使用 2.9+ 新增的 API。
79+
80+
\--------------------------------------------------------------------------------
81+
82+
二、AndroidX 依赖:降版本 + 部分改 `compileOnly`
83+
84+
目标:兼容 compileSdk 33 的宿主项目
85+
86+
```
87+
# gradle/libs.versions.toml
88+
89+
-androidXCore = "1.17.0"
90+
91+
+androidXCore = "1.12.0" # compileSdk 34, 向下兼容 33
92+
93+
94+
95+
-androidXNavigation = "2.7.7"
96+
97+
+androidXNavigation = "2.5.3" # compileSdk 33
98+
99+
100+
101+
-androidXWorkManager = "2.8.1"
102+
103+
+androidXWorkManager = "2.7.1" # compileSdk 31
104+
105+
106+
107+
-androidXMetrics = "1.0.0-beta03"
108+
109+
+androidXMetrics = "1.0.0-alpha04" # compileSdk 33
110+
111+
112+
113+
-androidXRecyclerView = "1.3.2"
114+
115+
+androidXRecyclerView = "1.2.1" # compileSdk 31
116+
117+
118+
119+
-androidXCollection = "1.4.5"
120+
121+
+androidXCollection = "1.2.0" # compileSdk 28
122+
```
123+
124+
Navigation / WorkManager → `compileOnly`
125+
126+
这两个库在 SDK 公开 API 中有暴露(`NavigationViewTrackingStrategy` 继承了 `NavController.OnDestinationChangedListener``UploadWorker` 继承了 `Worker`),但宿主不一定使用 Navigation 组件:
127+
128+
```
129+
# features/dd-sdk-android-rum/build.gradle.kts
130+
131+
- implementation(libs.bundles.androidXNavigation)
132+
133+
+ compileOnly(libs.bundles.androidXNavigation)
134+
135+
136+
137+
# dd-sdk-android-core/build.gradle.kts
138+
139+
- implementation(libs.androidXWorkManager)
140+
141+
+ compileOnly(libs.androidXWorkManager)
142+
```
143+
144+
NOTE
145+
146+
Navigation 改为 `compileOnly` 后,不使用 `NavigationViewTrackingStrategy` 的宿主无需引入 Navigation 库。WorkManager 改为 `compileOnly` 后,需在文档中说明宿主必须引入 `androidx.work:work-runtime`
147+
148+
SDK 自身 compileSdk 不变
149+
150+
SDK 自身的 `compileSdk = 36` 保持不变(SDK 编译环境由 SDK 开发者控制)。降低的是**依赖库版本**,使得宿主在 `compileSdk 33``34` 时也能正常编译集成。
151+
152+
\--------------------------------------------------------------------------------
153+
154+
三、KronosNTP / JCTools / re2j → Shadow Relocate
155+
156+
这三个库在 SDK 内部使用,不暴露公开 API,适合做 relocate。
157+
158+
重定位映射
159+
160+
```
161+
com.lyft.kronos.* → com.datadog.vendor.kronos.*
162+
163+
org.jctools.* → com.datadog.vendor.jctools.*
164+
165+
com.google.re2j.* → com.datadog.vendor.re2j.*
166+
```
167+
168+
实现方式
169+
170+
使用 [Shadow Gradle Plugin](https://www.google.com/url?sa=E&q=https%3A%2F%2Fgithub.com%2FGradleUp%2Fshadow) 或手动 jar 重打包:
171+
172+
1. 在根项目添加 Shadow 插件
173+
2.`dd-sdk-android-core``features:dd-sdk-android-trace` 配置 relocate 规则
174+
3. 发布时自动将 relocate 后的类打包进 AAR
175+
176+
CAUTION
177+
178+
Shadow Plugin 对 Android AAR 的支持有限,推荐方式是:先将这些库 Shadow 为独立的 jar/aar,再作为本地依赖引入。或者直接将源码 fork 后改包名放入 SDK 项目内部。
179+
180+
\--------------------------------------------------------------------------------
181+
182+
四、OpenTelemetry API → `compileOnly`
183+
184+
仅在 `trace-otel` 模块中通过 `api()` 暴露,改为 `compileOnly`
185+
186+
```
187+
# features/dd-sdk-android-trace-otel/build.gradle.kts
188+
189+
- api(libs.openTelemetryApi)
190+
191+
+ compileOnly(libs.openTelemetryApi)
192+
```
193+
194+
\--------------------------------------------------------------------------------
195+
196+
修改汇总
197+
198+
版本变更 (libs.versions.toml)
199+
200+
| 依赖 | 当前 | 目标 | 原因 |
201+
| -------------------- | ------------ | ------------- | ---------------- |
202+
| okHttp | 4.12.0 | 4.9.0 | 降低版本限制 |
203+
| gson | 2.10.1 | 2.8.9 | 降低版本限制 |
204+
| androidXCore | 1.17.0 | 1.12.0 | compileSdk 36→34 |
205+
| androidXNavigation | 2.7.7 | 2.5.3 | compileSdk 34→33 |
206+
| androidXWorkManager | 2.8.1 | 2.7.1 | compileSdk 33→31 |
207+
| androidXMetrics | 1.0.0-beta03 | 1.0.0-alpha04 | compileSdk 34→33 |
208+
| androidXRecyclerView | 1.3.2 | 1.2.1 | compileSdk 31 |
209+
| androidXCollection | 1.4.5 | 1.2.0 | compileSdk 28 |
210+
211+
依赖声明变更
212+
213+
| 依赖 | 变更 | 涉及模块 |
214+
| ------------- | ------------------- | ------------------------------------ |
215+
| OkHttp | impl → compileOnly | core, rum, ndk, profiling, okhttp |
216+
| Gson | impl → compileOnly | core, rum, trace, webview, profiling |
217+
| Navigation | impl → compileOnly | rum |
218+
| WorkManager | impl → compileOnly | core |
219+
| OpenTelemetry | api → compileOnly | trace-otel |
220+
| KronosNTP | impl → **relocate** | core |
221+
| JCTools | impl → **relocate** | trace (via traceCore bundle) |
222+
| re2j | impl → **relocate** | trace (via traceCore bundle) |
223+
224+
\--------------------------------------------------------------------------------
225+
226+
实施优先级
227+
228+
| 阶段 | 改动 | 工作量 |
229+
| ------ | ------------------------------------------------------- | ------ |
230+
| **P0** | libs.versions.toml 降版本 + 编译验证 | 1 天 |
231+
| **P0** | OkHttp/Gson 改 compileOnly + 运行时检查 | 1 天 |
232+
| **P1** | AndroidX 降版本 + Navigation/WorkManager 改 compileOnly | 2 天 |
233+
| **P1** | OpenTelemetry 改 compileOnly | 0.5 天 |
234+
| **P2** | KronosNTP / JCTools / re2j Shadow relocate | 3-5 天 |
235+
236+
验证计划
237+
238+
1. 降版本后执行 `./gradlew assembleLibrariesRelease` 确认 SDK 编译通过
239+
2. 执行 `./gradlew unitTestRelease` 确认单元测试通过
240+
3.`sample/vendor-lib` 中添加宿主冲突依赖(OkHttp 4.9/4.11/5.0-alpha, Gson 2.8.6/2.10)模拟编译
241+
4. 宿主集成测试:使用 compileSdk 33 的宿主项目接入修改后的 SDK
242+

features/dd-sdk-android-flags-openfeature/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ dependencies {
6565
}
6666
testImplementation(libs.bundles.jUnit5)
6767
testImplementation(libs.bundles.testTools)
68+
testImplementation(libs.okHttp)
69+
testImplementation(libs.gson)
6870
testImplementation(libs.coroutinesTest)
6971
unmock(libs.robolectric)
7072
}

0 commit comments

Comments
 (0)