背景
最近比较突然被安排接手一个项目,该项目的情况如下
- 原生和RN结合的混合开发模式
- 组件化开发,有很多基础组件以及业务组件
但是在梳理项目依赖时发现了个别组件源码不全的情况,于是写了个cli用于对比两个版本产物文件,生成差异结果以便于快速进行源码找回恢复。
结果如下:
找回前的对比:
找回后对比:
遗失的源代码
在梳理项目时,我发现几个比较核心的组件依赖的版本跟git源码仓库的tag和发布版本不一致。
我尼玛顿时惊出一身冷汗,怕不是有些代码没提交吧!
经过核实,的确是部分代码没提交,判断依据如下:
- 以核心组件A举例说明,它在壳子依赖的版本是2.0.0,但是源码仓库里发布配置的版本是1.0.0,并且找不到2.0.0版本的tag。
- 源码仓库最新分支的最后一次提交时间比在maven私有仓库中2.0.0版本产物上传的时间早
- 壳子对该组件的依赖时间从1.0.0变更为2.0.0的时间跟2.0.0版本产物上传的时间一致
由此可以确认,1.0.0到2.0.0之间的代码变更是没有提交到git仓库的,但是产物是打包并上传到私有仓库里了,所以壳子可以正常依赖使用。
找出问题
出现这种问题的原因是我们的组件打包上传流程并没有规范化,而是大家约定了一个打包上传的规范如下:
- 先提交代码,并打对应版本tag
- 然后再执行gradle打包上传脚本将对应版本的aar或者jar上传到maven私有仓库
但是这种靠人为执行约定流程是不可控的,有时候开发图省事直接自己在项目中执行一个gradle打包上传脚本把产物上传到maven私有仓库,然后壳子工程就可以依赖使用了,但是本地代码忘记提交到git仓库,也不会去打tag,这样就导致了可能出现产物版本是最新的,但是仓库源码并非最新的情况。
解决问题
出现这个问题的根本原因在于没有一个规范的管理流程,解决这个问题也比较简单,就是把开发直接打包上传到maven的权限收回,把打包操作后置例如放到CI上执行。
大致流程如下:
- 提供一个入口例如网页,网页上把git仓库管理起来
- 需要打包就在网页上选择对应的git仓库,网页把打包相关的配置展示出来,点击确定按钮触发CI打包流程
- CI去对应的git仓库执行内部对应的打包上传脚本即可
由于CI是直接去git仓库拉代码打包的,这样就避免出现了代码没提交,但是产物能出来的问题了。
当然,实现这个打包管理的流程设计到前后端以及CI,整体的细节和工作量还是不少的,上面只是一个大致流程。
实际上如何管理打包流程都是后话了,当务之急是要把新版本的代码找回来。
在梳理的时候其实发现了一个更严重的问题是,当时的项目RN升级一个大版本,做法是基于RN的源码做了些修改然后重新打包成aar给壳子工程依赖的,但是,那个基于RN源码定制的git仓库都找不到了,也就是我要先基于RN的源码编译出产物,然后还要找回已有aar和RN源码编译出来的aar之间的代码变更。本来我对RN就不熟悉,又出了这一档子事情,这不是给我加难度吗…
回归之路的探索
上面也说到了,当务之急是先把源码给恢复好,因为后续还需要基于恢复后的代码继续开发迭代,经过沟通过后得知是不可能再找到之前的开发看看能不能把没提交的代码给提交一下了。
那就只剩下一条路,自己尝试着恢复,那怎么恢复呢?
先来梳理一下现有的东西。
- git仓库里有旧版本的源码,RN的话github上找对应版本的源码即可,
- 有新版本的aar文件
思路一
很自然的一个思路就是Android Studio不是能直接看到依赖产物的的代码吗?
例如
那我直接找到对应的产物,跟源码文件一个一个进行内容对比不就可以了。
当然这样理论上是可以实现的,但实际落地下来很难执行,原因如下:
- 在打包成产物后的源码进行处理过后的,一些注释啊,空格啊,变量名啊,代码的顺序啊都会有变更,AS上显示的产物里的代码是对已经处理过的源码编译后的class文件进行反编译显示,那这样一来,跟原本的源代码就会产生很大的差异。
以RN的源码举例说明,下面是在AS上看的RN源码中的一个类
再来看下实际的源码
可以明显的看出从代码行数以及变量名都有变化,这还是一个非常简单的类,这种我们可以肉眼对比出来,RN源码中负责的类有非常多,这种我们如果一个一个对比肉眼去看,那简直是个灾难。
到这里实际上肉眼比较的方案其实已经执行不下去了,原因如下:
- 对于源码数量非常多的组件,工作量巨大,因为我们要对比每一个文件还有资源文件,确保找出增,删,改的文件
- 人工去对比最终的结果质量无法保证,基本可以遇见的是花了一上午对比了几个文件后就开始心情烦躁,情绪波动,最后直接开摆,扫一眼没啥问题就过。
3. 心智负担巨大,这种明显是属于吃力也不讨好的方案,最后人家问你确定恢复的没有问题吗?你真的敢说没问题吗?
思路二
基于思路一我们来看一下有什么问题
- 直接看产物反编译后的代码跟旧版本源码比较差异大,难以确定是否变更
- 对于代码量多的组件,工作量大,人工对比效率太低
- 靠人工判断无法保证最终结果的正确性
代码差异大的问题
对于查看反编译后的产物跟源码比较差异大的问题,想办法让他们俩尽量在状态比较一致的情况下再次对比。
我们可以把源码也打包编译成产物,然后对比两个版本产物反编译后的代码内容确定是否有变更。实际操作下来这种方式确实能够极大的减少代码差异大的情况
效率低的问题
上面也说到了,人工对比的心智负担比较大,例如RN源码有700多个类文件,一个一个对比下来先不说时间问题,准确度也没办法保证,毕竟,我们不能仅仅靠代码行数是否一致就去判断代码有没有变更。
准确度的问题
可以通过程序计算对比文件的md5值,给出最终的结果输出,保证最终还原的正确性
解决方案
可以做一个工具,把新老版本的产物丢进去,程序帮我们进行解压,遍历,反编译等操作,然后对反编译后的java文件进行md5值计算,最后输出结果,把md5值不同的文件输出出来,这样依赖,既可以保证每个文件都能对比到,还能确保最终的结果是稳定可靠的。
大致流程如下
- 将已有源码编译成产物aar
- 设计一个cli工具,用于处理两个版本的产物
- 对产物进行解压,先处理资源文件,计算文件的md5值
- 针对代码文件进行反编译,随后遍历反编译后的java文件,计算md5值
- 新老版本结果进行对比,找出增,删,改的文件
- 输出结果
回归之路的实践
确定好方案之后,就可以动手开发了,这里我选择是做一个cli工具。
初步实现
CLI工具可下载直接使用:下载地址 component-diff-1.0.0.jar
使用方式如下:
java -jar jar包路径 true --old 旧版本产物 --new新版本产物 --output 结果输出目录
参数说明
- jar包路径:就是你下载的jar文件在你的电脑的绝对路径
- true表示打印执行日志
--old
后面跟你的旧版本的产物文件路径--new
后面跟你的新版本的产物文件路径--output
后面跟输出结果目录
示例:
java -jar /Users/yuzhiqiang/Documents/组件对比/component-diff-1.0.0.jar true --old //Users/yuzhiqiang/Documents/组件对比/rn_source/old/react-native-0.68.5.aar --new /Users/yuzhiqiang/Documents/组件对比/rn_source/new/react-native-0.68.513.aar --output /Users/yuzhiqiang/Documents/组件对比/r