buildSrc + gradle插件:多项目共享gradle依赖管理

news2024/10/4 11:19:43

自定义gradle 插件,配合 buildSrc 形式的组件库版本管理,

用于实现多 project 项目共享一套版本管理信息

前言

随着组件化越来越常见,module数量越来越多,依赖管理的混乱问题大家想必是都遇到过甚至正在经历着。

对于依赖管理的优化从手动到 ext ,到 buildSrc,越来越优雅,越高效和高级,确实解决了依赖管理方面的问题。但是这些都是解决同一个 project 项目下的依赖管理问题的。

当一个团队有多个 project 时,各 project 之间是感知不到对方的 buildSrc 配置信息的,也就是说每个 project 都是要建立一个 buildSrc,并配置相同的依赖信息。

本文主要基于 buildSrc 形式,介绍一种自定 gradle 插件,使 buildSrc 实现多项目间可以共享一套版本依赖管理信息。

默认你已了解 buildSrc 实现依赖管理。

对于 buildSrc 的介绍有很多文章,搜一下即可,这里推荐一篇

Kotlin + buildSrc:更好的管理Gadle依赖! - 腾讯云开发者社区-腾讯云

依赖管理的方式

下面简单介绍一下依赖管理:
 

手动管理

moduleA 的 gradle 中:

implementation "com.android.support:appcompat-v7:27.0.2"
implementation "com.haodf.lib:toast:1.2.3"

moduleB 的 gradle 中:

implementation "com.android.support:appcompat-v7:27.0.2"
implementation "com.haodf.lib:toast:1.2.3"

可以看到,相同的代码重复的写在 gradle 中。一旦 toast 有更新(这个其实很常见,不同的人维护不同的组件库,经常有功能升级,bug修改),就得把所有引用的地方都手动 由 1.2.3 更新到 1.2.4。久而久之,忘了这个 module ,漏了那个 module,于是一个项目里对toast的引用竟然有三四个版本同时存在。

ext管理

即形如下面这种:

ext {
  versions = [
    toast: "27.0.2",
  ]
  libs = [
    toast: "com.haodf.lib:${versions.toast}",
  ]
}


//使用
implementation libs.toast

这种就是没有代码提示

于是 buildSrc 出现了:

buildSrc

形如:

代码:

object Librarys {

    //toast
    const val toast= "com.haodf.lib:${Versions.toast}"
    

 使用:

implementation Librarys.toast

这里的调用是有代码提示的,并且可以像代码调用一样点击查看都有哪些地方在引用了这个库。

多项目共享依赖信息的方案

那么,如果我们团队有三个 project 项目,该怎么解决依赖管理问题呢?

多项目是很常见的,即使只服务于一个app,也会有包含app模块的主project,然后是其他的工具类组件库模块,业务模块等等。每一个都是以project形式进行开发的,他们都以 aar的形式被发布、依赖,开发时也基本都需要依赖其他的 aar 库。

这时候,我们需要为 projectA、projectB、projectC 分别建立一个 buildSrc 模块,这是必须的,因为buildSrc 必须显示的在项目目录下创建。然后在每一个 buildSrc 模块下创建依赖管理的类 dependencies.kt 文件。然后内容是一模一样的。

这即是问题所在。

一是代码重复,本来就是被维护的信息,却要同时维护多份同样的信息。

二是当 toast 组件有更新时,必须到这个 project 中去对三个dependencies.kt进行源码级的修改。

我们是否能对依赖相关的代码只维护一份呢,当更新时是否能只修改一处源码呢?

原理

我们想到通过自定义 gradle 插件来实现这个功能
在 buildSrc 模块被自动编译以供 gradle 调用前,自动的去某个地方把版本依赖相关的源码下载到 buildSrc 中。这样如果有版本更新,就会把最新的依赖相关的代码下载到 buildSrc,然后再编译 buildSrc,,就实现了自动更新。

这个能保存依赖相关代码的某个地方,可以是某个url,下载某个rar文件,也可以是某个maven管理的库。但是从开发的角度,最好的就是maven仓库了,同时,我们在维护这份依赖信息时最好也能和正常敲代码一样,敲的是什么,最后更新到本地项目中的就是什么。

最终确定依赖相关的信息直接写在 gradle 插件里,每次有依赖信息更新,就发布新的gradle插件。

 

自定义 gradle插件

同样这个也有很多文章介绍,就不过多介绍如何自定义了,只讲一下依赖管理相关的具体的代码

下面是 gradle插件的主要结构:

app是一个空的项目,plugin-universion模块是自定义gradle的代码。

因为 gradle使用 groovy语言编写的,而我们实际项目中是用kotlin开发的,所以创建了两个源码目录:

src/main/groovy:用来编写插件代码

src/main/java:用来像在实际项目中编写代码一样,编写依赖管理相关的代码。

首先,我们在src\main\java\com\haodf\universion\Dependencies.kt文件中编写kotlin代码:

注意这是在自定义gradle插件项目中编写的kotlin代码

object Versions {

    const val activity= "1.4.10"
    const val toast= "1.3.9"

}

object Librarys {

    const val activity= "com.xx.lib:activity:${Versions.activity}"
    const val toast= "com.xx.lib:toast:${Versions.toast}"


}

最终,以上的代码将以源码的形式被全部复制到具体项目的 buildSrc 模块下的 Dependencies.kt中。

提取源码
怎么样在gradle运行时拿到上面这堆源码呢?在gradle插件运行时,上面的代码早就被编译了。所以我们想到在自定义gradle项目编译前,能不能把Dependencies.kt中的源码内容转成一个类的变量的值,这样在gradle代码运行时,就能读取到了。

所以,在src/main/groovy 代码目录下新建 Code.groovy类:
 

public class Code {
    public String code = """ 这里使用了三引号,保留此处的字符串的格式 """
}

接下来,就靠 gradle任务来把Dependencies.kt内的kotlin代码内容赋值到 code变量里了。

在 plugin-universion 的gradle中直接编码:

File f = new File( "plugin-universion", "src/main/java/com/haodf/universion/Dependencies.kt")
if (!f.exists()) {
    f = new File( "src/main/java/com/haodf/universion/Dependencies.kt")
}

String codeStr = f.readLines().join("\n")

String c1 ="package com.haodf.universion\n" +
        "\n" +
        "public class Code {\n" +
        "    public String code = \"\"\""
String c2 = "\"\"\"\n" +
        "}"

f = new File( "plugin-universion", "src/main/groovy/com/haodf/universion/Code.groovy")
if (!f.exists()) {
    f = new File( "src/main/groovy/com/haodf/universion/Code.groovy")
}

println codeStr
println("----------------ok--------------------")
f.write(c1+codeStr.replace("\$", "\\\$")+c2)

f = new File( "plugin-universion", "src/main/groovy/com/haodf/universion/Version.groovy")
if (!f.exists()) {
    f = new File( "src/main/groovy/com/haodf/universion/Version.groovy")
}
f.write("package com.haodf.universion\n" +
        "\n" +
        "public class Version {\n" +
        "    public String version = \"" +
        project.ext.gradle_version +
        "\"\n" +
        "\n" +
        "}")

其实逻辑很简单,全部是文件操作。config阶段,gradle读取Dependencies.kt文件内容,然后按代码格式组装字符串,将字符串内容覆盖写入 Code.groovy文件中。

所以,在编译之前,Code.groovy的内容就会变成:

注意哦,看起来的 kotlin 代码是code变量的值

public class Code {
    public String code = """/**
 *  Created by zhaoruixuan1 on 2023/3/24
 *  CopyRight (c) haodf.com
 *  功能:统一依赖管理
 */
object Versions {

    const val activity = "1.4.10"
    const val toast = "1.3.9"

}

object Librarys {

    const val activity = "com.xx.lib:activity:\${Versions.activity}"
    const val toast = "com.xx.lib:toast:\${Versions.toast}"


}"""
}

同时,也覆写了Version.groovy文件,保存了当前的gradle插件版本。用来在项目运行时判断是否需要执行更新操作。

然后就是自定义gradle插件的主要代码:
UniversionPlugin.groovy

public class UniVersionPlugin implements Plugin<Project> {

    String version = new Version().version
    String info = "val name = \"kotlin\""
    UniVersionExtension uniVersionExtension  = new UniVersionExtension()
    @Override
    void apply(Project target) {
        println "universion plugin running"

        target.extensions.add("universion", UniVersionExtension)//配置的插件扩展

        //默认项:实际项目的buildSrc模块下,保存版本号的文件
        uniVersionExtension.versionFile = "src/main/java/version"
        //默认项:实际项目中,承载依赖代码的kotlin类
        uniVersionExtension.codeFile = "src/main/java/Dependencies.kt"
        uniVersionExtension.ignoreError = false

        target.afterEvaluate {
            update(target)
        }
    }

    private void update(Project target) {
        try {
            //读取自定义的扩展配置信息
            UniVersionExtension extension = target.extensions.getByName("universion")
            if (extension != null) {
                if (extension.versionFile != null && extension.versionFile != "") {
                    uniVersionExtension.versionFile = extension.versionFile
                }
                if (extension.codeFile != null && extension.codeFile != "") {
                    uniVersionExtension.codeFile = extension.codeFile
                }
                if (extension.ignoreError != null) {
                    uniVersionExtension.ignoreError = extension.ignoreError
                }

            }

            println("versionFile = " + uniVersionExtension.versionFile + " codeFile = " + uniVersionExtension.codeFile + " ignoreError = " + uniVersionExtension.ignoreError)
            File versionNumFile = target.file(uniVersionExtension.versionFile)
            versionNumFile.setReadable(true)
            List<String> lines = versionNumFile.readLines("utf-8")
            String moduleVersion = ""
            if (lines != null && lines.size() > 0) {
                moduleVersion = lines.get(0)
            }

            if (moduleVersion == "" || moduleVersion == null) {
                println "universion: 未找到目前 universion 版本,请确认src/main/java/version文件存在"
            }
            println("universion: 项目版本:" + moduleVersion + "   最新版本:" + version)
            if (moduleVersion.equals(version)) {
                println "universion 已是最新版"
                return
            }
            println "universion: 开始更新"

            //依然是文件操作
            File versionInfoFile = target.file(uniVersionExtension.codeFile)
            versionNumFile.write(version)
            versionInfoFile.setWritable(true)
            versionInfoFile.write(new Code().code)

            println "universion:更新成功"
        } catch (Exception e) {
            if (uniVersionExtension.ignoreError) {
                println("universion: 更新失败\n error:" + e.message+"\n已设置忽略此异常,可能导致统一版本管理信息未更新")
            } else {
                throw new RuntimeException("universion: 更新失败\n " + e.message + "\n如果想忽略此异常,请在gradle中配置universion { ignoreError = true }")
            }
        }
    }
}

插件运行时,把 Code.code的值写入到具体项目的Dependencies.kt文件中

插件的扩展:

public class UniVersionExtension {
    String versionFile;//指定具体项目中,保存插件版本号的文件
    String codeFile; //指定具体项目中,保存依赖管理代码的文件
    boolean ignoreError;//指定当此插件更新失败时,是否忽略,继续往下编译

}

最后,发布gradle插件即可。

注意:

本插件为 buildSrc 模块开发,以使 buildSrc 模块被用来进行版本管理时,在多 project 项目开发时有更好更高效的使用体验。
 但是本插件并不局限于 buildSrc,任何 project 均可引用。

使用gradle插件

在具体的project中(默认是配合 buildSrc使用):

新建 buildSrc 模块后,建立空的包目录,buildSrc/src/main/java/。
然后新建一个空的代码类 比如名 Dependencies.kt,无需编写实际代码。
再新建一个文本文件,比如名 version.

引用插件:
在 buildSrc 模块的 gradle 中:
dependencies 下增加:

dependencies {
    ......
        classpath "com.haodf.universion:universion:+"
    }

gradle 文件顶部增加:

apply plugin: 'com.haodf.universion'

至此,项目引用部分完成,点击同步或编译,之前在插件中编写的版本管理的代码会以源码格式写入 Dependencies.kt 文件,
插件版本号会写入 version 文件中, 用于对比下次编译是否需要执行更新操作。


扩展:

插件提供三个参数,以对代码、版本文件、更新操作进行自定义:

比如你的想project中新建的buildSrc模块下,你想取的保存依赖信息的代码文件是YiLai.kt

你想保存版本号的文件是 gradle.txt

那么扩展配置如下:

 universion {
    versionFile = "src/main/java/gradle.txt"    //修改记录插件版本号的文件
    codeFile= "src/main/java/YiLai.kt"   //修改源码文件
    ignoreError = false     //当此插件进行版本更新失败时,是否忽略,继续往下编译。
 }

插件的不足

 版本信息的源码只在 gradle 插件项目中维护一份即可。
 N 个 project 依然需要 N 个 buildSrc 模块,
 当有组件版本号变更时,只需修改 gradle 插件项目一处。
 另外在 N 个项目中,依然需要对插件的引用处修改插件版本号
 同步后,新代码就自动更新到对应的 buildSrc 下的类文件中了。


 存在的缺憾:
 1、多项目已共享一份版本信息源码,但是更新时依然需要手动修改 gradle 版本号:
 classpath "com.haodf.universion:universion:1.2.3"

2、使用 + 替换具体的版本号可以实现多 project 自动更新统一版本信息
classpath "com.haodf.universion:universion:+"

组件管理者只管发布新版本,任何一个project在下一次编译前都会自动更新下来最新的依赖版本
但是这又引入了 + 引用的风险。虽然目前看对此插件的 classpath 依赖没什么风险

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/421849.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

iOS - 接入 Live2D

1.安装 Cmake 1.1 从官方下载 https://cmake.org/download/ 下载成功以后,在终端输入 sudo "/Applications/CMake.app/Contents/bin/cmake-gui" --install校验是否成功 cmake --version1.2 从 Homebrew 安装 (这个方法没有成功) brew install cmake如果提示 co…

简单的配置Sawgger+knife4j完成API测试功能

目的&#xff1a;减少postman的使用&#xff0c;以及生成对应的接口文档 1、添加依赖 基于自身spring boot 版本2.7.X 我选择的是&#xff1a; <dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId>…

网络中的一些基本概念

组建网络的重要设备 集线器,交换机(组建局域网,不能跨局域网组建网络),路由器(wifi本质上是无线路由器,路由器的本质的把俩个局域网给连起来) 网络通信的一些基础概念 IP地址 标识了网络设备所在的位置 端口号 标识了一个具体的应用程序 协议 协议是网络通信的概念,约定好…

校园安全AI视频行为分析系统 yolov7

校园安全AI视频行为分析系统以yolov7网络模型算法为核心&#xff0c;校园安全AI视频行为分析算法模型对现场画面中学生打架、异常跌倒、攀爬翻墙、违规闯入、明火烟雾、睡岗离岗、抽烟打电话等行为主动识别预警存档。YOLOv7 在 5 FPS 到 160 FPS 范围内&#xff0c;速度和精度都…

计算机系统-存储器层次结构

本篇不是学习课程时的笔记&#xff0c;是重看这本书时的简记。对于学习本课程的同学&#xff0c;未涉及的内容不代表考试不涉及&#xff0c;部分省略的部分是在该课程的讨论课中学习的(存储器山&#xff0c;矩阵乘法)&#xff0c;对于核心内容的掌握&#xff0c;需要学习相关实…

还在crud?快来学习架构设计啦---微服务下的依赖管理(maven篇)

文章目录一、前言二、实战2.1 创建父工程统一依赖的版本管理2.2 创建公共使用的 common工程2.3 创建子工程并引入父工程的依赖以及公共工程2.4 搭建启动环境2.5 启动程序开始验证三、总结一、前言 2023年口罩放开的第一年&#xff0c;大多数人都是想着重新开始&#xff0c;抓住…

Python assert实现软件测试

PythonPythonPython 对于测试非常看重&#xff0c;例如测试中最常见的操作——断言 assertassertassert&#xff0c;其在 PythonPythonPython 中就是一个关键字而不是一个函数。而在 CCC 语言中&#xff0c;assertassertassert 只是一个普通的函数。从这点也可以看出&#xff0…

TCP套接字编程

文章目录前言一、TCP套接字1.简单认识TCP协议2.listen函数3.accept函数4.通用TCP服务器二、大小写转换服务1.服务端2.客户端三、多进程版本TCP服务器四、多线程版本TCP服务器前言 这篇文章是紧接着上一篇《UDP套接字编程》文章的&#xff0c;里面详细介绍了套接字编程的一些基…

在Vue项目中使用tinymce富文本编辑器

TinyMC编辑器简介 TinyMCE是一款易用、且功能强大的所见即所得的富文本编辑器。跟其他富文本编辑器相比&#xff0c;有着丰富的插件&#xff0c;支持多种语言&#xff0c;能够满足日常的业务需求并且免费。 TinyMCE的优势&#xff1a; 开源可商用&#xff0c;基于LGPL2.1 插…

虚拟机安装 Ubuntu 桌面版

目录 1、下载系统镜像 2、新建虚拟机 3、配置虚拟机 1、下载系统镜像 Ubuntu桌面版最新版本下载地址&#xff1a;Download | Ubuntu 桌面版 Ubuntu桌面版历史版本下载地址&#xff1a;Download | Ubuntu 桌面版&#xff08;历史版本&#xff09; 以下载18.04 版本为例&am…

【三位重建】NeRF原理+代码讲解

文章目录一、技术原理1.概览2.基于神经辐射场&#xff08;Neural Radiance Field&#xff09;的体素渲染算法3.体素渲染算法4.位置信息编码&#xff08;Positional encoding&#xff09;5.多层级体素采样二、代码讲解1.数据读入2.创建nerf1.计算焦距focal与其他设置2.get_embed…

CSS学习|这一篇就够了|笔记|总结|(超详细讲解)

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;老茶icon &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开兴好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;计…

误删除文件怎么找回 数据恢复用这些方法

误删除文件是很多人都会遇到的问题&#xff0c;尤其是在Windows 10系统中&#xff0c;有时候我们不小心按了ShiftDelete或者清空了回收站&#xff0c;就会导致文件永久消失。那么&#xff0c;误删除文件怎么找回呢?本文将介绍四种数据恢复的方法&#xff0c;帮助你轻松将误删除…

【PCIE体系结构五】PCIE配置和地址空间

&#x1f449;个人主页&#xff1a;highman110 &#x1f449;作者简介&#xff1a;一名硬件工程师&#xff0c;持续学习&#xff0c;不断记录&#xff0c;保持思考&#xff0c;输出干货内容 参考书籍&#xff1a; PCI_Express体系结构导读、 深入浅出SSD&#xff1a;固态存储…

【Java版oj】day30最难的问题、因子个数

目录 一、最难的问题 &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 二、因子个数 &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 三、DFS深度优…

Python实现批量图片下载及去重处理

背景 在爬虫应用开发中&#xff0c;常常需要批量下载图片&#xff0c;并对图片进行去重处理。Python 是一种非常流行的编程语言&#xff0c;也是开发爬虫应用的首选&#xff0c;本文将介绍如何使用 Python 下载图片&#xff0c;并对下载的图片进行去重处理。 内容 首先&…

win10彻底永久关闭自动更新【亲测有效】

一、禁用Windows Update服务 1、同时按下键盘 Win R&#xff0c;打开运行对话框&#xff0c;然后输入命令 services.msc &#xff0c;点击下方的“确定”打开服务&#xff0c;如下图所示。 2、找到 Windows Update 这一项&#xff0c;并双击打开&#xff0c;如图所示。 3、右击…

【行为型模式】责任链模式

文章目录1、简介2、结构3、实现方式3.1、案例引入3.2、结构分析3.3、具体实现4、责任链优缺点5、应用场景1、简介 责任链模式(Chain of Responsibility)是一种行为型设计模式&#xff0c;它允许对象在链上依次处理请求&#xff0c;用户只需要将请求发送到责任链上即可&#xf…

CocosCreator实战篇 | 实现刮刮卡和橡皮擦 | 擦除效果

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/dxt19980308 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由肩匣与橘编写&#xff0c;首发于CSDN&#x1f649; &#x1f4e2;生活依旧是美好而…

【SSM框架】spring的创建与使用

spring的创建与使用Spring项目的创建创建一个maven项目添加Spring依赖添加启动类将bean存储到Spring 中创建bean对象将bean对象存储到Spring容器中从Spring中获取bean创建Spring(上下文)对象从Spring中获取到bean对象使用Bean&#xff08;非必须&#xff09;从spring中获取Bean…