Android gradle dependency tree change(依赖树变化)监控实现,sdk version 变化一目了然
@
目錄- 前言
- 基本原理
- 執行流程
-
diff 報告
- 不同分支 merge 過來的 diff 報告
- 同個分支產生的 merge 報告
- 同個分支提交的 diff 報告
-
具體實現原理
- 我們需要監控怎樣的 Dendenpency 變化
-
怎樣獲取 dependency Tree
project.configurations方式- ./gradlew dependencies
- AsciiDependencyReportRenderer
- 方案選擇
-
怎樣對 dependency Tree 進行 diff 計算
- 傳統 diff 方案
- 自定義的 diff 方案
- 如何找到一個基準點,進行 diff 計算
- 怎樣集合 Gialab CI 進行計算
- 總結
- 參考文章
前言
這篇文章,其實在一年之前的時候就已經寫好了。當時是在公司內部分享的,作為一個監控框架。當時是想著過一段時間之后,分享到技術論壇上面的,沒想到計劃趕不上變化,過完國慶被裁了。
當時忙著找工作,就一直沒有更新了,放在筆記里面吃灰。
最近,發現好久沒有分享技術文章了,從筆記里面找了一下,就拿來分享了。
在項目開發中,會有很多第三方依賴,通過 gradle 引入進來的。比如 androidxDesignVersion、androidxSupportVersion、 rxjava2Version、 okhttpVersion 等第三方庫。有時候第三方庫改到了或者升級了,我們并不能及時發現,往往需要等到出問題的時候,去排查的時候,才發現是某個依賴版本改動導致的。
這時候其實是有點晚了,如果能夠提前暴露,那么我們能夠大大地減少風險,因此我們希望能夠監控起來。
基本原理
- 代碼 merge 到 dev 分支的時候,借助 gitlab ci,促發 gradle task 任務,自動分析 dependency 鏈表
- 對比上一次打包的 dependency 鏈表,如果發現變更了,會通過 機器人進行通知。并附上最新的 commit,提交作者信息,需要 author 確認一下
執行流程
目前主要對 dev 分支進行監控,以下幾種場景會促發 diff 檢查
- MR 合并進 dev 分支的時候
- 在 dev 分支直接提交代碼的時候
diff 報告
diff 報告主要包括以下幾種信息
- 作者,當前 commitId 的 author
- branch 分支名
- commitId 當前的 commitId, baseCommitId:基準 id
- 變動依賴,這里最多顯示 6 行,超過會截斷,具體變動可以見詳情
- 提交:如果是 MR 合并進來的,會顯示 MR 鏈接,否則,會顯示 commit 鏈接
不同分支 merge 過來的 diff 報告
檢測到 Dependency 變化
分支: 573029_test
作者: 徐俊
commitId: 4844590b baseCommitId: bed4cb64
變動依賴:
+\--- project :component-matrix
+ \--- com.google.code.gson:gson:2.8.2 -> 2.8.9
詳情: {url}
提交:{url}/merge_requests/4425/diffs
同個分支產生的 merge 報告
檢測到 Dependency 變化
分支: 573029_dep_diff
作者: xujun
commitId: 16145365 baseCommitId: 4844590b
變動依賴:
+\--- project :component-matrix
+ \--- com.squareup.retrofit2:converter-gson:2.4.0 (*)
詳情: {url}
提交: {url)/commit/16145365
同個分支提交的 diff 報告
檢測到 Dependency 變化
分支: 573029_dep_diff
作者: xujun
commitId: 19f22516 baseCommitId: 8c90d512
變動依賴:
+\--- project :component-tcpcache
+ \--- com.google.code.gson:gson:2.8.2 -> 2.8.9
詳情: {url}
提交: {url)/commit/16145365
我們主要講述以下幾點
- 我們需要監控怎樣的 Dendenpency 變化
- 怎樣獲取 dependency Tree
- dependency Tree 怎樣做 diff
- 如何找到基準點,進行 diff 計算
- 怎樣結合 CI 進行計算
具體實現原理
我們需要監控怎樣的 Dendenpency 變化
眾所周知,Android 的 Dependency 是通過 gradle 進行配置的,如果我們在 build.gradle 下面配置了這樣,證明了我們依賴 recyclerview 這個庫。
dependencies {
implementation androidx.recyclerview:recyclerview:1.1.0 ”
}
那一行代碼會給我們的 Dendenpency 帶來怎樣的變化呢?
有人說,它是新增了 recyclerview 這個庫。
這個說法對嘛?
不全對。
因為 gradle 依賴默認是有傳遞性的。他還會同時引入 recyclerview 自身所依賴的庫。
+--- androidx.recyclerview:recyclerview:1.1.0
| +--- androidx.annotation:annotation:1.1.0
| +--- androidx.core:core:1.1.0
| +--- androidx.customview:customview:1.1.0
| \--- androidx.collection:collection:1.0.0 -> 1.1.0 (*)
- 如果項目當中當前沒有這些庫的,會同時導入這些庫。
- 如果項目中有這些庫了,庫的版本比較低,會升級到相應的版本。比如 collection 會從 1.0.0 升級到 1.1.0
然而這些情況就是我們往往所忽略的,即使有代碼 review,有時候也會漏了。即使 review 待了,可能下意識也只以為只引入了這個庫,卻很難看到它背后的變化。
而這些如果帶到線上去,有時候會發生一些難以預測的結果,因此,我們需要有專門的手段來監控這些變化。能夠監測到整條鏈路的變化,而不僅僅只是 implementation androidx.recyclerview:recyclerview:1.1.0 ” 這行代碼的變化
至于如果依賴的傳遞性,可以通過 transitive、exclude 等用法做到。 可以看這些文章,這里不再一一展開。
解決 Android Gradle 依賴的各種版本問題
build.gradle管理依賴的版本(傳遞(transitive)\排除(exclude)\強制(force)\動態版本(+))
怎樣獲取 dependency Tree
獲取 dependency Tree 的話,有多種方式
- 通過
project.configurations這種方式獲取 - 通過
gradlew :app:dependenciestask - 通過
AsciiDependencyReportRenderer獲取,需要適配不同版本的 gradle 版本
project.configurations 方式
通過這種方式獲取的,他是能夠獲取到所有的 dependencies,但是并不能看到 dependencies 的樹形關系。
偽代碼如下
def configuration = project.configurations.getByName("debugCompileClasspath")
configuration.resolvedConfiguration.lenientConfiguration.allModuleDependencies.each {
def identifer = it.module.id
depList.add(identifer)
}
./gradlew dependencies
./gradlew dependencies 會輸出所有 configuration 的 Dependcency Tree。包括 testDebugImplementation、testDebugProvided、testDebugRuntimeOnly 等等
事實上,我們只關心打進 APK 包里面的 dependencies。因此我們可以指定更詳細的 configuration 。即
gradlew :app:dependencies --configuration releaseRuntimeClasspath
這樣,就只會輸出 Release 包 runtimeClasspath 相關的東西。
RuntimeClasspath 跟我們常用的 implementation,關系大概如下
在輸出的 dependencies tree 報告中,我們看到的格式一般是這樣的
** 這里有幾個格式需要說明一下**
- x.x.x (*), 比如圖中的 4.2.2(*), 該依賴已經有了,將不再重復依賴,
- x.x.x -> x.x.x 該依賴的版本被箭頭所指的版本代替
- x.x.x -> x.x.x(*) 該依賴的版本被箭頭所指的版本代替,并且該依賴已經有了,不再重復依賴
AsciiDependencyReportRenderer
AsciiDependencyReportRenderer 這個東東,在不同的 gradle 版本有不同的差異,需要適配一下。
如果要這種方案,建議將某個版本的代碼剝離出來,偽代碼一般如下,單獨集成一個庫。
project.afterEvaluate {
Log.i(TAG, "afterEvaluate")
val renderer = AsciiDependencyReportRenderer()
val sb = StringBuilder()
val f = StreamingStyledTextOutputFactory(sb)
renderer.setOutput(f.create(javaClass, LogLevel.INFO))
val projectDetails = ProjectDetails.of(project)
renderer.startProject(projectDetails)
// sort all dependencies
val configuration: org.gradle.api.artifacts.Configuration =
project.configurations.getByName("releaseRuntimeClasspath")
renderer.startConfiguration(configuration)
renderer.render(configuration)
renderer.completeConfiguration(configuration)
// finish the whole processing
renderer.completeProject(projectDetails)
val textOutput = renderer.textOutput
textOutput.println()
Log.i(TAG, "end sb is $sb")
}
方案選擇
從上面闡述可知,第一種方案 project.configurations, 通過這種方式獲取的,他是能夠獲取到所有的 dependencies,但是并不能看到 dependencies 的樹形關系。
第二種方案 ./gradlew dependencies 的優點是簡單,直接采用 gradle 原生 Task,輸出特定格式的文本。然后根據規律將所有的 dependency tree 提出出來。
可能有人擔心 ./gradlew dependencies 的輸出格式會變化。
其實還好,看了幾個 gradle 版本的輸出格式,基本都是一樣的。
第三種方案 AsciiDependencyReportRenderer 的優點是可定制性高,缺點是麻煩,需要適配不同版本的 gradle。
最終我選擇的方案是方案二。
怎樣對 dependency Tree 進行 diff 計算
傳統 diff 方案
可能很多人想到的方案是使用 Git diff 進行 diff 計算。但是這種方式有局限性。
- 當有多個修改的時候,key -value 可能無法一一對應。
- 他的 diff 類型 add、remove、 change 并不能一一對應我們 dependency add、remove、 change 的類型。
這無法達到我們想要的結果。因此,我們需要整合自己的 diff 算法。
自定義的 diff 方案
這里的方案是借鑒了 JakeWharton 大神的方案,在其基礎之上進行了改造。
原理大概如下
- 分別計算當前,上一次的 dependency tree,用 Set<List
> 儲存,分別表示為 oldPaths,newPaths - 接著根據 oldPaths 和 newPaths 計算出 removedTree, addedTree, changedTree
- 最后,根據 removedTree, addedTree 計算出 diff
第一步
對于這里的依賴,我們會使用 Set<List<String>> 的數據結構儲存
轉換之后的數據結構
這樣的好處就是可以看到每一個 dependency 的全路徑,如果 dependency 的全路徑不一樣,那么可以 diff 出來。
第二步 計算 remove 樹 和 add 樹
有了第一步的基礎,其實很簡單,直接調用 kotlin 的擴展方法 Set<T>.minus
如何找到一個基準點,進行 diff 計算
其實,這個說到底,就是找到上一個 commit 提交的 diff 文件。
- 看是不是 MR,如果是 MR,我們應該找到 MR 合并前的一個 commit
- 不是 MR 合并進來的,我們直接找到上一個 commit 即可
因此,我們可以借助 git 命令來處理。對于 merge request,目前主要有幾種情況會產生 merge request。
- 直接 MR 合并進來的,這時候 parent 會產生兩個點,我們去 parent[0] 即可
- 當前本地分支落后遠程分支, 且 local 分支有 commit 的時候,pull 或者 push 的時候,會產生一個 merge 節點,這時候 parent 會產生兩個點,我們去 parent[1] 即可
原理圖如下:
怎樣集合 Gialab CI 進行計算
Gialab push 或者 merge 的時候,我們需要感知到,接著執行特定的 task,進行計算。 每個公司的 CI 可能不太一樣,具體可以修改一下
gradlew :{appName}:checkDepDiff
總結
dependency diff 監控的原理其實不難,主要是涉及到挺多方面的,有興趣的可以看一下。如果覺得對你有所幫助的話,希望可以一鍵三連。
參考文章
https://wajahatkarim.com/2020/03/gradle-dependency-tree/
https://tomgregory.com/gradle-dependency-tree/
https://github.com/jfrog/gradle-dep-tree
http://muydev.top/2018/08/21/Analyze-Android-Dependency-Tree/
總結
以上是生活随笔為你收集整理的Android gradle dependency tree change(依赖树变化)监控实现,sdk version 变化一目了然的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: utu2440 gdbserver 搭建
- 下一篇: 如何关掉硬盘自检