为什么要选择VersionCatalog来做依赖管理?

很多人都介绍过Gradle 7.+提供新依赖管理工具VersionCatalog,我就不过多介绍这个了。我们最近也算是成功接入了VersionCatalog,过程也还是有点曲折的,总体来说我觉得确实比我们当前的ext,或者说是用buildSrc的形式进行依赖管理是个更成熟的方案吧。下面是几个介绍的文章,尤其可以看看三七哥哥的。

Android 依赖管理及通用项目配置插件

【Gradle7.0】依赖统一管理的全新方式,了解一下~

之前大部分文章只介绍了技术方案,很少会去横向对比几个技术方案之间的优劣。从我们最近一个月的使用结果上来看吧,接下来我给大家分析下实际的优劣,仅仅只代表个人看法, 上表格了。

因为VersionCatalog使用的文件格式是toml,所以后续可能会用toml进行简称。

ext buildSrc toml
声明域 *.gradle *.java *.kt *.toml
可修改 可修改 不可修改 不可修改
写法 花里胡哨 静态变量 固定写法 xxx.xxx.xxx
校验 随便写 编译时校验 同步时校验

声明域: 指的是我们在哪里声明这些依赖管理。其中ext可以在绝大部分的.gradle中去进行声明,所以就会导致依赖声明的过于零散。而这部分问题就不存在于buildSrc和toml中,他们只能被声明在固定的位置上。

可修改性: 特制声明的依赖能否被修改,ext声明是在内存空间内,而ext的本质其实就是一个Any他可以存放任意的东西,如果出现同名的则会是后面声明的把前面声明的覆盖掉,这就是一个非常不稳定的属性,而buildSrc则是由class来声明的,我们没有办法在gradle中去修改这部分,所以相对来说是稳定的。而toml也类似,基于固定格式反序列化成代码。不具备修改的能力。

写法: ext这方面是真的拉胯,比如支持libs.abc或者libs."abc"或者libs.["abc"]还可以单引号,就非常的随意,而且极为不统一。这也是我们本次改动中碰到问题最多的时候。其他两种写法都相对比较固定,类似java/kt 中的静态常量。

校验: ext就是爱咋写咋写吧,反正也没有很好的校验啥的。而buildSrc则是基于java的代码编译来的,toml因为是一个新的文件格式,所以内置了一套相对比较强的语法校验,如果不合规则会报错,并显示错误行数。

据说buildSrc对于增量编译的适配等其实不太良好,而且我们是一个复杂的巨型复合构建的工程,所以个人并不太推荐buildSrc。

可以参考这篇文章第二章 Stop using Gradle buildSrc. Use composite builds instead

由此可证哦,VersionCatalog雀食是一个非常好的选择,尤其如果你们当前还是在使用的是ext的情况下。

巨型工程最麻烦的事情其实另外一点就是技术栈的切换,因为要改起来的地方可真的就是太多了,首先就是要先解决复合构建的情况下全局只有一份注册的逻辑,其二就是把当前工程的ext全部转移到toml中,然后要最好和之前的方式接近,尽量保证最小改动。最后则是所有工程都改一下!!!!!!!!(要我狗命)

共享配置

GradleSample demo 工程如下,其中plugin-version就是

我们也采取了之前Gradle 奇淫技巧之initscript pluginManagement一样的方式,通过initscript做到复合构建内共享插件的能力。

另外我们把VersionCatalog作为一个extension抛出来在外部完成注册。

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"
            }
        }
    }

}

通过这部分配置就可以把共享的部分注入进工程内。然后就是很沙雕的改改改了,把所有的ext全部迁移到我们新的toml上去,然后注册出多个。

命令行工具

TheNext 虾开发的撒币cli工具 专门解决虾的撒币问题

以前也说过了我们工程的模块数量巨大,然后又因为ext的写法风骚,所以我们基本所有的写依赖的地方都要改,就是真的工作量巨大。

一个优秀的摸鱼工程师最重要的天赋就是要学会转化生产力,把这种简单又繁琐的工作交给命令行来解决。所以这就有了TheNext的一个新能力,基于当前的文件目录修改所有的.gradle文件,然后把非标准的ext的写法全部进行一次替换。

效果如图所示。

代码逻辑如下,我们首先会遍历整个工程的文件目录,然后发现.gradle后缀的文件,之后通过正则匹配出dependencies,然后进行把一些"" '' []等等都删掉,然后把- _更换成.,这样就能完成简单的自动替换了。

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"

代码就基本是这样,如果有正则带佬可以帮忙优化下正则的。

然后这个工具也可以多次复用,因为我这个需求没有办法很快的被合入,需要频繁的rebase master的代码,每次rebase完之后都要进行二次修改,真的吐了。

验收

每个新功能开发最后都是要进行验收的,尤其是技改需求,你到时候把功能搞坏了到时候可是要背黑锅的啊。而且这种需求也没有办法要求测试进行特别系统性的测试,所以还是要开发自己想办法了。

我们拉取了apk包的依赖,然后用HashSet进行了拉平,去除重复依赖,然后通过diff对比前后差异,在基本符合预期的情况下我们就可以进行快速的合入。

结尾

其实本文的核心是给大家分析下几种依赖管理方式的优劣,然后对于还在使用gradle ext的大佬,其实可以逐渐考虑进行替换了。

最后祝大家新年快乐了,兔年大吉吧!

作者:究极逮虾户
链接:https://juejin.cn/post/7190277951614058555

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容