為什么要選擇VersionCatalog來做依賴管理?

很多人都介紹過Gradle 7.+提供新依賴管理工具VersionCatalog,我就不過多介紹這個(gè)了。我們最近也算是成功接入了VersionCatalog,過程也還是有點(diǎn)曲折的,總體來說我覺得確實(shí)比我們當(dāng)前的ext,或者說是用buildSrc的形式進(jìn)行依賴管理是個(gè)更成熟的方案吧。下面是幾個(gè)介紹的文章,尤其可以看看三七哥哥的。

Android 依賴管理及通用項(xiàng)目配置插件

【Gradle7.0】依賴統(tǒng)一管理的全新方式,了解一下~

之前大部分文章只介紹了技術(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容