很多人都介紹過Gradle 7.+提供新依賴管理工具VersionCatalog,我就不過多介紹這個(gè)了。我們最近也算是成功接入了VersionCatalog,過程也還是有點(diǎn)曲折的,總體來說我覺得確實(shí)比我們當(dāng)前的ext,或者說是用buildSrc的形式進(jìn)行依賴管理是個(gè)更成熟的方案吧。下面是幾個(gè)介紹的文章,尤其可以看看三七哥哥的。
之前大部分文章只介紹了技術(shù)方案,很少會(huì)去橫向?qū)Ρ葞讉€(gè)技術(shù)方案之間的優(yōu)劣。從我們最近一個(gè)月的使用結(jié)果上來看吧,接下來我給大家分析下實(shí)際的優(yōu)劣,僅僅只代表個(gè)人看法, 上表格了。
因?yàn)?code>VersionCatalog使用的文件格式是
toml,所以后續(xù)可能會(huì)用toml進(jìn)行簡(jiǎn)稱。
| ext | buildSrc | toml | |
|---|---|---|---|
| 聲明域 | *.gradle | *.java *.kt | *.toml |
| 可修改 | 可修改 | 不可修改 | 不可修改 |
| 寫法 | 花里胡哨 | 靜態(tài)變量 | 固定寫法 xxx.xxx.xxx |
| 校驗(yàn) | 隨便寫 | 編譯時(shí)校驗(yàn) | 同步時(shí)校驗(yàn) |
聲明域: 指的是我們?cè)谀睦锫暶鬟@些依賴管理。其中ext可以在絕大部分的.gradle中去進(jìn)行聲明,所以就會(huì)導(dǎo)致依賴聲明的過于零散。而這部分問題就不存在于buildSrc和toml中,他們只能被聲明在固定的位置上。
可修改性: 特制聲明的依賴能否被修改,ext聲明是在內(nèi)存空間內(nèi),而ext的本質(zhì)其實(shí)就是一個(gè)Any他可以存放任意的東西,如果出現(xiàn)同名的則會(huì)是后面聲明的把前面聲明的覆蓋掉,這就是一個(gè)非常不穩(wěn)定的屬性,而buildSrc則是由class來聲明的,我們沒有辦法在gradle中去修改這部分,所以相對(duì)來說是穩(wěn)定的。而toml也類似,基于固定格式反序列化成代碼。不具備修改的能力。
寫法: ext這方面是真的拉胯,比如支持libs.abc或者libs."abc"或者libs.["abc"]還可以單引號(hào),就非常的隨意,而且極為不統(tǒng)一。這也是我們本次改動(dòng)中碰到問題最多的時(shí)候。其他兩種寫法都相對(duì)比較固定,類似java/kt 中的靜態(tài)常量。
校驗(yàn): ext就是愛咋寫咋寫吧,反正也沒有很好的校驗(yàn)啥的。而buildSrc則是基于java的代碼編譯來的,toml因?yàn)槭且粋€(gè)新的文件格式,所以內(nèi)置了一套相對(duì)比較強(qiáng)的語(yǔ)法校驗(yàn),如果不合規(guī)則會(huì)報(bào)錯(cuò),并顯示錯(cuò)誤行數(shù)。
據(jù)說buildSrc對(duì)于增量編譯的適配等其實(shí)不太良好,而且我們是一個(gè)復(fù)雜的巨型復(fù)合構(gòu)建的工程,所以個(gè)人并不太推薦buildSrc。
可以參考這篇文章第二章 Stop using Gradle buildSrc. Use composite builds instead
由此可證哦,VersionCatalog雀食是一個(gè)非常好的選擇,尤其如果你們當(dāng)前還是在使用的是ext的情況下。
巨型工程最麻煩的事情其實(shí)另外一點(diǎn)就是技術(shù)棧的切換,因?yàn)橐钠饋淼牡胤娇烧娴木褪翘嗔耍紫染褪且冉鉀Q復(fù)合構(gòu)建的情況下全局只有一份注冊(cè)的邏輯,其二就是把當(dāng)前工程的ext全部轉(zhuǎn)移到toml中,然后要最好和之前的方式接近,盡量保證最小改動(dòng)。最后則是所有工程都改一下?。。。。。。。。ㄒ夜访?/p>
共享配置
GradleSample demo 工程如下,其中plugin-version就是
我們也采取了之前Gradle 奇淫技巧之initscript pluginManagement一樣的方式,通過initscript做到復(fù)合構(gòu)建內(nèi)共享插件的能力。
另外我們把VersionCatalog作為一個(gè)extension拋出來在外部完成注冊(cè)。
catalogs {
script = new File(rootProjectDir, "depencies.gradle")
versionCatalogs {
create("libs") { from(files("${rootProjectDir.path}/toml/dependencies.versions.toml")) }
create("module") { from(files("${rootProjectDir.path}/toml/module.versions.toml")) }
}
dependencyResolutionManagement {
repositories {
maven { setUrl("https://maven.aliyun.com/repository/central/") }
maven {
setUrl("https://storage.googleapis.com/r8-releases/raw")
}
gradlePluginPortal()
google()
mavenLocal()
maven {
url "https://dl.bintray.com/kotlin/kotlin-eap"
}
}
}
}
通過這部分配置就可以把共享的部分注入進(jìn)工程內(nèi)。然后就是很沙雕的改改改了,把所有的ext全部遷移到我們新的toml上去,然后注冊(cè)出多個(gè)。
命令行工具
TheNext 蝦開發(fā)的撒幣cli工具 專門解決蝦的撒幣問題
以前也說過了我們工程的模塊數(shù)量巨大,然后又因?yàn)閑xt的寫法風(fēng)騷,所以我們基本所有的寫依賴的地方都要改,就是真的工作量巨大。
一個(gè)優(yōu)秀的摸魚工程師最重要的天賦就是要學(xué)會(huì)轉(zhuǎn)化生產(chǎn)力,把這種簡(jiǎn)單又繁瑣的工作交給命令行來解決。所以這就有了TheNext的一個(gè)新能力,基于當(dāng)前的文件目錄修改所有的.gradle文件,然后把非標(biāo)準(zhǔn)的ext的寫法全部進(jìn)行一次替換。

效果如圖所示。
代碼邏輯如下,我們首先會(huì)遍歷整個(gè)工程的文件目錄,然后發(fā)現(xiàn).gradle后綴的文件,之后通過正則匹配出dependencies,然后進(jìn)行把一些"" '' []等等都刪掉,然后把- _更換成.,這樣就能完成簡(jiǎn)單的自動(dòng)替換了。
package com.kronos.mebium.android
import com.beust.jcommander.JCommander
import com.kronos.mebium.action.Handler
import com.kronos.mebium.entity.CommandEntity
import com.kronos.mebium.file.getRootProjectDir
import com.kronos.mebium.utils.green
import com.kronos.mebium.utils.red
import com.kronos.mebium.utils.yellow
import java.io.File
import java.util.Scanner
/**
*
* @Author LiABao
* @Since 2022/12/8
*
*/
class DependenciesHandler : Handler {
val scanner = Scanner(System.`in`)
var isSkip = false
override fun handle(args: Array<String>) {
isSkip = args.contains(skip)
val realArgs = if (isSkip) {
arrayListOf<String>().apply {
args.forEach {
if (it != skip) {
add(it)
}
}
}.toTypedArray()
} else {
args
}
val commandEntity = CommandEntity()
JCommander.newBuilder().addObject(commandEntity).build().parse(*realArgs)
val first = commandEntity.file
val name = commandEntity.name
val root = first
val files = root.walkTopDown().filter {
it.isFile && it.name.contains(".gradle")
}
val overrideList = mutableListOf<Pair<File, File>>()
files.forEach {
onGradleCheck(it)?.apply {
overrideList.add(it to this)
}
}
confirm(overrideList)
}
private fun confirm(overrideList: MutableList<Pair<File, File>>) {
if (overrideList.isEmpty()) {
return
}
println("if you want overwrite all this file ? input y to confirm \r\n".red())
val input = scanner.next()
if (input == "y") {
overrideList.forEach {
it.first.delete()
it.second.renameTo(it.first)
}
print("replace success \r\n ".green())
} else {
print("skip\r\n ".yellow())
}
}
private val pattern =
"(\\D\\S*)(implementation|Implementation|compileOnly|CompileOnly|test|Test|api|Api|kapt|Kapt|Processor)([ (])(\\D\\S*)".toPattern()
private fun onGradleCheck(file: File): File? {
var override = false
val lines = file.readLines()
val newLines = mutableListOf<String>()
lines.forEach { line ->
val matcher = pattern.matcher(line)
if (matcher.find()) {
val libs = matcher.group(4)
if (!libs.contains(":") && !libs.contains("files(")) {
val newLibs =
libs.replace("\'", "").replace("\"", "").replace("-", ".").replace("_", ".")
.replace("kotlin.libs", "kotlinlibs").replace("[", ".").replace("]", "")
if (newLibs == libs) {
newLines.add(line)
return@forEach
}
print("fileName: ${file.name} dependencies : $line \r\n")
if (isSkip) {
override = true
newLines.add(line.replace(libs, newLibs))
print("$libs do you want replace to $newLibs \r\n ".green())
return@forEach
}
print("$libs do you want replace to $newLibs ? input y to replace \r\n ".red())
while (true) {
val input = scanner.next()
if (input == "y") {
print("replace success\r\n".green())
override = true
newLines.add(line.replace(libs, newLibs))
return@forEach
} else {
print("skip\r\n ".yellow())
break
}
}
}
}
newLines.add(line)
}
if (override) {
val newFile = File(file.parent, file.name.removeSuffix(".gradle") + ".temp")
newLines.forEach {
newFile.appendText(it + "\r\n")
}
return newFile
}
return null
}
}
const val skip = "--skip"
代碼就基本是這樣,如果有正則帶佬可以幫忙優(yōu)化下正則的。
然后這個(gè)工具也可以多次復(fù)用,因?yàn)槲疫@個(gè)需求沒有辦法很快的被合入,需要頻繁的rebase master的代碼,每次rebase完之后都要進(jìn)行二次修改,真的吐了。
驗(yàn)收
每個(gè)新功能開發(fā)最后都是要進(jìn)行驗(yàn)收的,尤其是技改需求,你到時(shí)候把功能搞壞了到時(shí)候可是要背黑鍋的啊。而且這種需求也沒有辦法要求測(cè)試進(jìn)行特別系統(tǒng)性的測(cè)試,所以還是要開發(fā)自己想辦法了。
我們拉取了apk包的依賴,然后用HashSet進(jìn)行了拉平,去除重復(fù)依賴,然后通過diff對(duì)比前后差異,在基本符合預(yù)期的情況下我們就可以進(jìn)行快速的合入。
結(jié)尾
其實(shí)本文的核心是給大家分析下幾種依賴管理方式的優(yōu)劣,然后對(duì)于還在使用gradle ext的大佬,其實(shí)可以逐漸考慮進(jìn)行替換了。
最后祝大家新年快樂了,兔年大吉吧!
作者:究極逮蝦戶
鏈接:https://juejin.cn/post/7190277951614058555