百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Android gradle 依赖树变化监控实现(gradle dependency tree change

toyiye 2024-08-09 10:25 31 浏览 0 评论

前言

这篇文章,其实在一年之前的时候就已经写好了。当时是在公司内部分享的,作为一个监控框架。当时是想着过一段时间之后,分享到技术论坛上面的,没想到计划赶不上变化,过完国庆被裁了

当时忙着找工作,就一直没有更新了,放在笔记里面吃灰

最近,发现好久没有分享技术文章了,从笔记里面找了一下,就拿来分享了

在项目开发中,会有很多第三方依赖,通过 gradle 引入进来的。比如 androidxDesignVersion、androidxSupportVersion、 rxjava2Version、 okhttpVersion 等第三方库。有时候第三方库改到了或者升级了,我们并不能及时发现,往往需要等到出问题的时候,去排查的时候,才发现是某个依赖版本改动导致的。

这时候其实是有点晚了,如果能够提前暴露,那么我们能够大大地减少风险,因此我们希望能够监控起来。

基本原理

  1. 代码 merge 到 dev 分支的时候,借助 gitlab ci,促发 gradle task 任务,自动分析 dependency 链表
  2. 对比上一次打包的 dependency 链表,如果发现变更了,会通过 机器人进行通知。并附上最新的 commit,提交作者信息,需要 author 确认一下

执行流程

目前主要对 dev 分支进行监控,以下几种场景会促发 diff 检查

  • MR 合并进 dev 分支的时候
  • 在 dev 分支直接提交代码的时候

diff 报告

diff 报告主要包括以下几种信息

  • 作者,当前 commitId 的 author
  • branch 分支名
  • commitId 当前的 commitId, baseCommitId:基准 id
  • 变动依赖,这里最多显示 6 行,超过会截断,具体变动可以见详情
  • 提交:如果是 MR 合并进来的,会显示 MR 链接,否则,会显示 commit 链接

不同分支 merge 过来的 diff 报告

makefile

复制代码

检测到 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 报告

makefile

复制代码

检测到 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 报告

makefile

复制代码

检测到 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 这个库。

css

复制代码

dependencies { implementation androidx.recyclerview:recyclerview:1.1.0 ” }

那一行代码会给我们的 Dendenpency 带来怎样的变化呢?

有人说,它是新增了 recyclerview 这个库。

这个说法对嘛?

不全对。

因为 gradle 依赖默认是有传递性的。他还会同时引入 recyclerview 自身所依赖的库。

lua

复制代码

+--- 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 (*)

  1. 如果项目当中当前没有这些库的,会同时导入这些库。
  2. 如果项目中有这些库了,库的版本比较低,会升级到相应的版本。比如 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 的话,有多种方式

  1. 通过 project.configurations 这种方式获取
  2. 通过 gradlew :app:dependencies task
  3. 通过 AsciiDependencyReportRenderer 获取,需要适配不同版本的 gradle 版本

project.configurations 方式

通过这种方式获取的,他是能够获取到所有的 dependencies,但是并不能看到 dependencies 的树形关系。

伪代码如下

ini

复制代码

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 。即

ruby

复制代码

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 版本有不同的差异,需要适配一下。

如果要这种方案,建议将某个版本的代码剥离出来,伪代码一般如下,单独集成一个库。

scss

复制代码

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 大神的方案,在其基础之上进行了改造。

原理大概如下

  1. 分别计算当前,上一次的 dependency tree,用 Set<List> 储存,分别表示为 oldPaths,newPaths
  2. 接着根据 oldPaths 和 newPaths 计算出 removedTree, addedTree, changedTree
  3. 最后,根据 removedTree, addedTree 计算出 diff

第一步

对于这里的依赖,我们会使用 Set<List<String>> 的数据结构储存

转换之后的数据结构

这样的好处就是可以看到每一个 dependency 的全路径,如果 dependency 的全路径不一样,那么可以 diff 出来。

第二步 计算 remove 树 和 add 树

有了第一步的基础,其实很简单,直接调用 kotlin 的扩展方法 Set<T>.minus

如何找到一个基准点,进行 diff 计算

其实,这个说到底,就是找到上一个 commit 提交的 diff 文件。

  1. 看是不是 MR,如果是 MR,我们应该找到 MR 合并前的一个 commit
  2. 不是 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 可能不太一样,具体可以修改一下

css

复制代码

gradlew :{appName}:checkDepDiff

总结

dependency diff 监控的原理其实不难,主要是涉及到挺多方面的,有兴趣的可以看一下。如果觉得对你有所帮助的话,希望可以一键三连。

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码