说明:本工具基于vivo互联网客户端团队内部开源编译管理工具开发。一、背景现在客户端业务越来越多,大部分客户端项目采用模块化开发模式,即根据业务分成多个模块进行开发,以提高团队效率。比如我们vivo官网目前的整体架构如下图,分为13个模块,每个模块都是一个独立的代码仓库。(注:为什么这么划分可以参考之前的一篇文章《??Android模块化开发实践???》)其次,完全隔离痛点的代码仓库让各个模块更加独立,更容易进行代码管理,但也带来了一些问题。1、开发阶段,分仓开发和集成开发调试,操作繁琐,容易出错,难以跟踪和追溯。需要开多个AndroidStudio进行开发;1.2.将子仓库集成到主仓库进行开发调试有两种方式,但是都有比较大的缺点:(1)方式一,子仓库依赖maven,需要不断的发布子仓库的快照仓库,然后更新主仓库的快照,效率低;(2)方法二,子仓库依赖代码,即需要在主仓库的settings.gradle中手动包含本地子仓库代码,然后在build.gradle中配置依赖,配置为繁琐且容易出错;1.3、主仓库对子仓库的依赖,如果是部分maven依赖,部分代码依赖,容易出现代码冲突;1.4、apk集成的子模块aar和代码,没有对应关系,排错时很难追溯。2、版本发布阶段,流程繁琐,重复工作过多。过程如下:2.1.逐一修改子仓库的版本,指定snapshot或者release;2.2.每个子仓库都需要将修改后版本号的代码提交到git;2.3.每个子仓库Repositories必须手动触发maven仓库的发布;2.4、更新主仓库依赖子仓库的版本;2.5、构建Apk;2.6、如果使用持续集成系统CI,每个子仓库需要配置一个项目,然后启动子仓库一个一个子仓库的编译,等到所有子仓库编译完成,然后开始编译主仓库。3、解决方案针对以上问题,我们的优化思路也很明确,就是用自动化的方式解决繁琐重复的操作。最后开发了ModularDevTool,实现了以下功能:1.开发阶段1.1.在主仓库管理所有子仓库代码(拉取代码、分支等git操作),管理子仓库相关信息(代码仓库路径、分支、版本等);1.2.只需要开启一个AS项目,即可开发所有仓库的代码;1.3.一键切换子仓库两种依赖模??式(代码依赖和maven依赖),支持混合依赖(即部分仓库代码依赖,部分仓库maven依赖);1.4、编译时输出子模块的版本和对应的commitid,方便回溯和跟踪代码。2.版本发布阶段2.1.只需要在主仓库修改子仓库的版本号,子仓库不需要修改,省去了子仓库的代码修改和提交过程;2.2.只需在CI上配置一个主仓库工程即可实现一键编译。包括分库编译aar(按依赖顺序编译)、上传maven、编译apk;2.3、CI支持3种编译模式:OnlyApp:即只编译主仓库代码生成apk(前提是子模块已经maven发布);publishSnapshot:即子仓库编译上传快照版本,再编译主仓库生成apk;publishRelease:表示子仓库编译上传release版本,再编译主仓库生成apk。4.ModularDevTool概览工具采用shell脚本+gradle插件实现。先看项目目录概览1.submodules目录用于存放子仓库代码,是正常的项目结构。submodules目录如下图所示:2.repositories.xml文件用于配置子仓库信息,包括模块名称、代码仓库、分支、版本等,具体内容如下:lib模块com.vivo.space.libvivospace_lib5.9.8.0-SNAPSHOT0模块>ssh://$user@smartgit:xxxx/VivoCode/xxxx_libfeature_5.9.0.0_xxx_devcbd4xxxxxx69d1...3.vsub.sh脚本是工具各项功能的入口,例如:./vsub.shsync:拉取所有子模块代码,代码存放在主工程./vsub.sh下的submodules目录中publish:一键编译所有子仓库,并发布aar到maven4,subbuild目录用于输出子仓库的git提交记录,subError目录用于输出子仓库时的log5编译异常,关键功能的实现ModularDevTool的主要功能分为两大类,一类是代码管理,用于批处理git操作;二是工程构建,实现子模块依赖动态配置、子模块发布等功能。5.1代码管理常用的git命令封装在vsub.sh脚本中,用于批量处理子仓库的git操作。实现逻辑比较简单,git命令用shell脚本封装。比如./vsub.sh-pull的实现逻辑是先cd到submodules目录下(submodules目录存放所有子仓库代码),然后遍历到子仓库目录下执行gitpull--rebase命令,从而实现一条命令完成所有子仓库的同一个git操作,实现逻辑如下:cdsubmodulespath=$currPathfiles=$(ls$path)forfileNamein$filesdoif[!-d$fileName]thencontinueficd$fileNameecho-e"\033[33mEntering$fileName\033[0m"gitpull--rebasecd..done5.2工程构建(一)./vsub执行sync函数.shsync命令将所有子模块的代码拉到主项目的子模块目录中。Sync命令有3个作用:1)如果没有拉取子仓库代码,则拉取代码并切换到repositories.xml中配置的devbranch;2)如果子仓库代码已经拉取,则切换到repositories.xml配置的devbranch;3)考虑到在某些场景下(比如jenkinsbuild),使用branchcheckout代码时可能会出现异常,在sync命令后加上-c参数,会使用repositories.xml中配置的commitid来检出指定的分行代码。Sync流程如下:(2)子模块依赖处理之前我们依赖不同子仓库的代码,需要手动修改settings.gradle导入子模块,然后在build.gradle中修改依赖。gradle,如下图所示。include':app',':module_name_1',':module_name_2',':module_name_3'...project(':module_name_1').projectDir=newFile('E:/AndroidCode/module_name_1/code/')project(':module_name_2').projectDir=newFile('E:/AndroidCode/module_name_2/code/')project(':module_name_3').projectDir=newFile('E:/AndroidCode/module_name_3/code/')...dependencies{apifileTree(dir:'libs',include:['*.jar'])//业务子模块开始api项目(':module_name_1')apiproject(':module_name_2')apiproject(':module_name_3')//业务子模块结束}...团队中每个人的代码存放位置不同,新版本拉取后代码需要手动配置,比较麻烦。基于sync功能,所有子仓库代码都已经拉入submodules目录。现在我们只需要在构建项目时简单配置local.properties(local.properties配置如下图所示)即可确定哪些子模块是代码依赖,哪些子模块是maven依赖。module_name_1=0module_name_2=0module_name_3=1module_name_4=1module_name_5=1module_name_6=1子模块依赖处理过程如下:(3)publish函数执行./vsub.shpublish命令实现一键编译所有子模块aar并上传maven。publish命令主要有四个作用:1)如果没有拉取子仓库代码,则自动拉取子仓库代码;2)如果发布了snapshot版本,切换到devbranch分支的最新代码,版本包含snapshotstringModule的子仓库,编译生成aar上传到maven;否则直接跳过,不编译;3)如果发布的是release版本(即指定-a参数),切换到commitid对应的代码,编译生成release版本的aar,并上传maven;4)编译上传子库的顺序按照配置的优先级执行。注意:上面的devbranch、version、commitid、priority等都是repositories.xml中的配置项。发布子模块的流程如下:6.ModularDevTool接入接入本方案的前提是项目采用多代码仓库的方式进行模块化开发。具体接入步骤比较简单。第一步,主仓库依赖gradle插件modular_dev_plugin;(该插件包括settings、tools、base、publish四个子插件,其中settings、tools、base插件协同实现分库代码管理和动态依赖处理,publish插件实现分仓库aar发布)第二步,将settings插件应用到主仓库的settings.gradle中,将tools和base插件应用到主仓库的appbuild.gradle中;第三步,在主仓库根目录添加repositories.xml配置文件和vsub脚本;第四步,分库依赖modular_dev_plugin,应用publish插件;第五步,中间层的子库(如App→Shop→Lib,则Shop为中间层子库)根据Placeholder的下一层子库的版本号,会构建项目时自动替换为repositories.xml中的版本号。如下图所示:dependencies{//对lib仓库的依赖,原来是依赖一个特定的版本号,现在改为“统一”的占位符,会自动替换为repositories.xml中的版本号当项目构建时api"com.vivo.space.lib:vivospace_lib:unified"}至此,ModularDevTool就连接上了。7、目前的开发流程都是基于这个工具。现在我们官网的开发流程是这样的:第一步克隆App主仓库的代码,checkout对应的开发分支,在AndroidStudio中打开工程;第二步,修改repositories.xml配置,需要针对正在开发的子仓库,修改devbranch为对应的开发分支,修改version为对应的版本号;第三步,通过./vsub.shsync命令查看所有子模块代码;第四步,修改local.properties中子仓库依赖模式(maven-dependent或code-dependent),修改完成后点击Sync,即可正常进行代码开发,开发体验与单项目多模块模式。8.总结这个工具已经很成熟了。在vivo钱包、vivo官网等项目中使用多年。通过该工具,开发阶段实现了多业务模块的集成开发,解决了代码仓库分散管理、手动配置依赖等繁琐操作,发布阶段,实现了多种编译方式和一键编译能力,大大提高了团队开发效率,支持官网APP项目3+业务线并行迭代,代码冲突减少50%以上。