Open qingmei2 opened 4 years ago
「反思」 系列是笔者对于 学习归纳 一种新的尝试,其起源与目录请参考 这里 。
随着Android项目 模块化 或 插件化 项目业务的愈发复杂,开发流程中通过版本控制工具(比如Git)管理项目的成本越来越高。
Android
Git
以大名鼎鼎的 Android源代码开源项目 (Android Open-Source Project,下文简称 ASOP)为例,截止2020年初,Android10的源码项目,其模块化分割出的 子项目 已接近800个,而每一个子项目都是一个独立的Git仓库。
Android Open-Source Project
ASOP
Android10
这意味着Git的使用成本究竟有多高?如果开发者希望针对AOSP的一个分支进行开发,就需要手动将每个子项目进行checkout操作,如果本地分支尚未创建,开发者便需要手动地在每一个子项目里面去创建分支。
AOSP
checkout
如此高昂的使用成本显然需要一种更自动化的方式去处理。为此,Google的工程师基于Git进行了一系列的代码补充,推出了名为Repo的代码版本管理工具,其本质是通过Python开发出一系列的脚本命令,便于开发者对复杂的模块化源码项目进行统一的调度和切换。
Google
Repo
Python
即使对于上文说到的AOSP而言,其同样使用了Repo工具进行项目的管理,由此可见,对于 高度模块化 开发的Android项目而言,Repo工具的确有一定的学习和借鉴意义。
本文以AOSP为例,对Repo工具的 使用流程 和 原理 进行系统性的分析,读者需要对Git和Repo工具有一定的了解。
官方文档:Repo入门及基本使用 https://source.android.com/]source/downloading.html
本文大纲如下:
Repo 是以 Git 为基础构建的代码库管理工具。其并非用来取代 Git,只是为了让开发者在多模块的项目中更轻松地使用 Git。Repo 命令是一段可执行的 Python 脚本,开发者可以使用 Repo 执行跨网络操作。例如,借助单个 Repo 命令,将文件从多个代码库下载到本地工作目录。
那么,Repo幕后原理究竟是怎么样的?想要真正的理解Repo,就必须理解Repo最核心的三个要素:Repo仓库、Manifest仓库 以及 项目源码仓库。
这里我们先将三者的关系通过一张图进行概括,该图已经将Repo工具本身的结构描述的淋漓尽致:
对于若干个模块化的子项目,也就是 项目源码仓库 而言,它们是开发者希望的 被统一调度的对象。
比如,通过一个简单的Repo命令,统一完成所有子项目的分支切换、代码提交、代码远端更新等等。
因此,对于Repo工具整个框架的设计而言,项目源码仓库 明显应该处于最底层,它们是被Repo命令执行操作的最基本元素。
Manifest仓库 中最重要的是一个名为manifest.xml的清单文件,其存储了所有子项目仓库的元信息。
manifest.xml
当Repo命令想要对所有子项目进行对应操作的时候,其总是需要知道 要操作的项目的相关信息——比如,我想要clone AOSP所有子项目的代码,首先我需要知道所有子项目仓库的名称和仓库地址;这时,Repo便会从manifest仓库中获取对应所有仓库的元信息,并进行对应的fetch操作。
clone AOSP
manifest
fetch
对于Android应用的开发者而言这很好理解,对于一个APP而言,其对应的组件通过在manifest中声明进行管理。
APP
因此,想要通过Repo对模块化项目进行管理,项目的管理者必须提供一个对应的manifest清单文件,里面存储所有子项目的相关信息,这样,Repo工具才能通过对其进行解析,然后完成子项目的统一管理。
此外,读者应该知道,AOSP也是在迭代过程中不断变化的,因此,其每一个分支版本所包含的子项目信息可能都是不同的,这意味着Manifest仓库同样也是一个Git仓库,以达到AOSP不同分支版本中,该仓库对应存储的子项目元信息不同的目的。
Manifest
Repo工具实际上是由一系列的Python脚本组成的,这些Python脚本通过调用Git命令来完成自己的功能。
Repo仓库的本质就是存储了各种各样的Python脚本,当开发者调用相关的Repo命令时,便会从Repo仓库中运行对应的脚本进行处理,并根据脚本中的代码逻辑,找到manifest中所有项目的元信息,然后将其中包含的子项目进行对应命令的处理——因此,我们可以称 Repo仓库是顶层命令的容器。
此外,和Manifest仓库相同,组成Repo工具的Python脚本本身也是一个Git仓库;每当开发者执行Repo命令的时候,Repo仓库都会对自己进行一次更新。
读者请务必深刻理解这三者的意义,这也是Repo工具内部最核心的三个概念,也是阅读下文内容的基础。
现在,通过Repo工具完成项目模块化的管理需要分步构建以上三个角色,但是在这之前,我们需要先将Repo工具添加到自己的开发环境中。
正如 官方文档 所描述的,通过以下命令安装Repo工具,并确保它可执行:
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo chmod a+x ~/bin/repo
安装成功后,对应的目录下便会存在一个repo脚本文件,通过将其配置到环境中,开发者可以在终端中使用repo的基本命令。
repo
整个流程如下图所示:
Repo脚本初始化完毕,接下来针对Repo仓库创建流程进行简单的分析。
以AOSP项目为例,开发者通过以下命令来安装一个Repo仓库:
repo init -u https://android.googlesource.com/platform/manifest -b master
这个命令实际上是包含了两个操作:初始化 Repo仓库 和 Manifest仓库,其中Repo仓库完成初始化之后,才会继续初始化Manifest仓库。
这很好理解,Repo仓库的本质就是存储了各种各样的Python脚本,若它没有初始化,就不存在所谓的Repo相关命令,更遑论后面的Manifest仓库初始化和子项目代码初始化的流程了。
这一小节我们先分析 Repo仓库 的安装过程,在下一小节再分析 Manifest仓库 的安装过程。
本小节整体流程如下图所示:
上一节我们成功安装了repo脚本文件,这个脚本里面提供了例如version、help、init等最基本的命令:
version
help
init
def main(orig_args): if cmd == 'help': // help命令 _Help(args) if opt.version or cmd == 'version': // version命令 _Version() if not cmd: _NotInstalled() if cmd == 'init' or cmd == 'gitc-init': // init命令 ...
由此可见Repo脚本最初提供的命令确实非常少,前两个命令十分好理解,分别是查看Repo工具相关依赖的版本或者查看帮助,比较重要的是init命令,这个命令的作用便是对本地Repo仓库的初始化。
那么Repo仓库如何才能初始化呢?设计者并没有尝试直接向远端服务器请求拉取代码,而是从当前目录开始 往上遍历直到根目录 ,若在这个过程中找到一个.repo/repo目录,并且该目录本身的确是一个Repo仓库,便尝试从该仓库 克隆一个新的Repo仓库 到执行Repo脚本的目录中。
.repo/repo
反之,若从本地向上直到根目录不存在Repo仓库,则尝试向远端克隆一个新的Repo仓库到本地来。
回到本地克隆Repo仓库的流程中,代码是如何判断本地的.repo/repo目录的确是一个Repo仓库的呢,代码中已经描述的非常清晰了:
def _RunSelf(wrapper_path): my_dir = os.path.dirname(wrapper_path) my_main = os.path.join(my_dir, 'main.py') my_git = os.path.join(my_dir, '.git') if os.path.isfile(my_main) and os.path.isdir(my_git): for name in ['git_config.py', 'project.py', 'subcmds']: if not os.path.exists(os.path.join(my_dir, name)): return None, None return my_main, my_git return None, None
从这里我们就可以看出,判断的依据是对应的需要满足以下条件: 1、存在一个.git目录; 2、存在一个main.py文件; 3、存在一个git_config.py文件; 4、存在一个project.py文件; 5、存在一个subcmds目录。
读到这里,读者可以对Repo仓库进行一个简单的总结了。
从上文的源码中,读者了解了Repo脚本源码中判断是否是Repo仓库的五个依据,从这些判断条件中,我们可以简单对Repo仓库的定位进行一个总结。
首先,从条件1中我们得知,组成Repo工具的Python脚本本身也是一个Git仓库;每当开发者执行Repo命令的时候,Repo仓库都会对自己进行一次更新。
其次,Repo仓库本身作为存储Python脚本的容器,其内部必然存在一个入口的main函数可供运行。
main
对于条件3而言,我们直到Repo工具本质是对Git命令的封装,因此,必须有一个类负责Git相关的配置信息,和提供简单的Git相关工具方法,这便是git_config.py文件的作用。
git_config.py
对于条件4,Repo仓库目录下还需要一个project.py文件,负责Hook相关功能,细心的读者应该注意到,/.repo/repo目录下还有一个/hooks/目录。
project.py
Hook
/.repo/repo
/hooks/
最后也是最重要的,/.repo/repo目录下必须还存在一个subcmds目录,顾名思义,这个目录下存储了绝大多数repo重要的命令,比如sync、checkout、pull、commit等等;这也说明了,如果没有Repo仓库的初始化,使用Repo命令操作子项目代码仓库便是无稽之谈。
subcmds
sync
pull
commit
继续回到上一节我们使用到的命令:
读者已经知道,通过init命令,我们在指定的目录下,成功初始化了Repo仓库。当安装好Repo仓库之后,就会调用该Repo仓库下面的main.py脚本,对应的文件为.repo/repo/main.py。
main.py
.repo/repo/main.py
这样我们便可以通过init后面的-u -b参数,进行Manifest仓库的创建流程,其中-u指的是manifest文件所在仓库对应的Url地址,-b指的是对应仓库的默认分支。
-u -b
-u
Url
-b
上文中我们提到,想要通过Repo对模块化项目进行管理,项目的管理者必须提供一个对应的manifest清单文件,里面存储所有子项目的相关信息,这样,Repo工具才能通过对其进行解析,然后完成子项目的统一管理。
对于公司的业务而言,项目的管理者需要根据自己公司的实际业务模块构造出自己的manifest文件,并放置在某个git仓库内,这样开发者便可以通过指定对应的Url构建Manifest仓库。
git
本文以AOSP项目为例,其项目清单文件所在的Url为:
https://android.googlesource.com/platform/manifest
通过init命令和对应的参数,Repo便可以尝试从远端克隆Manifest仓库,然后从指定的Url克隆对应的manifest文件,切换到对应的分支并进行解析。
这里描述比较简单,实际上内部实现逻辑非常复杂;比如,在向远端克隆对应的Manifest仓库之前,会先进行本地是否存在Manifest仓库的判断,若已经存在,则尝试更新本地的Manifest仓库,而非直接向远程仓库中克隆。此外,当未指定分支时,则会checkout一个default分支。
default
这之后,Repo会根据远端的xml清单文件尝试构建自己本地的Manifest 仓库。
xml
让我们看以下/.repo/目录下文件层级:
/.repo/
上文我们说到,Manifest仓库本身也是一个Git仓库,因此,当我们打开.repo/manifests/目录时,里面会存在一个.git的文件夹,远端的Manifest文件仓库中的所有文件都被克隆到了这个目录下。
.repo/manifests/
.git
这里重点说一下项目的Git仓库目录和工作目录的概念。一般来说,一个项目的Git仓库目录(默认为.git目录)是位于工作目录下面的,但是Git支持将一个项目的Git仓库目录和工作目录分开来存放。
在AOSP中,Repo仓库的Git目录位于工作目录.repo/repo下,Manifest仓库的Git目录有两份拷贝,一份.git位于工作目录.repo/manifests下,另外一份位于.repo/manifests.git目录。
.repo/manifests
.repo/manifests.git
同时,我们看到这里还有一个.repo/manifest.xml文件,这个文件是最终被Repo的文件,它是通过将.repo/manifest文件夹下的文件和local_manifest文件进行合并后生成的,关于local_manifest机制我们后文会讲到,这里仅需将.repo/manifest.xml文件视为最终被使用的配置文件即可。
.repo/manifest.xml
.repo/manifest
local_manifest
回到上图,我们知道名字带有manifest相关的文件和文件夹代表了Manifest仓库,其内部存储了所有子项目仓库的元信息;而repo文件夹中存储了repo相关命令的脚本文件。
读者注意到,除此之外,还有一部分名字带有project的文件和文件夹,它们便是代表了Repo解析Manifest后生成的子项目信息和文件。
project
在Repo中,其管理的所有子项目,每一个子项目都被封装成为了一个Project对象,该对象内部存储了一系列相关的信息。
Project
现在,Manifest仓库被创建并初始化完毕,接下来我们分析Repo的sync流程,看看子项目是如何被统一下载和管理的。
执行完成repo init命令之后,我们就可以继续执行repo sync命令来克隆或者同步子项目了:
repo init
repo sync
当执行repo sync命令时,会默认尝试拉取远程仓库下载更新本地的Manifest 仓库,下载远端对应的default.xml文件。
default.xml
下载完成后,会自动解析default.xml文件中项目管理者配置的所有子项目信息,然后每个子项目信息被解析成为一个Project对象,并整合到一个内存的集合中去。
接下来,根据本地是否已经存在对应的子项目源码,针对每一个子项目,Repo都会进行对应的更新操作或者克隆操作,而这些操作的本质,其实就是内部调用了Git的fetch、rebase或者merge等等命令。
rebase
merge
值得关注的是,和Manifest仓库相似,AOSP子项目的工作目录和Git目录也都是分开存放的,其中,工作目录位于AOSP根目录下,Git目录位于.repo/projects目录下。
.repo/projects
此外,每一个AOSP子项目的工作目录也有一个.git目录,不过这个.git目录是一个符号链接,链接到.repo/repo/projects对应的Git目录。这样,我们就既可以在AOSP子项目的工作目录下执行Git命令,也可以在其对应的Git目录下执行Git命令。
.repo/repo/projects
从上文中读者已经知道了,对于源码来讲,manifest.xml只是一个到.repo/manifests/default.xml的文件链接,真正的清单文件是通过manifests这个Git仓库托管起来的。
.repo/manifests/default.xml
manifests
需要注意的是,在进行Android系统开发时,通常需要对清单文件进行自定义定制。例如,设备厂商会构建自己的manifest库,通常是基于AOSP的default.xml进行定制,去掉AOSP的一些Git库、增加一些自有的Git库。
这意味着,项目的管理者需要手动的对default.xml文件内容进行修改,然而这种方式在一些场景下存在弊端——对于AOSP而言,其本身可能存在几百个不同的分支,而项目的管理者需要修改的内容却基本是相同的。
比如,国内某个手机厂商需要删除AOSP中某个不受中国支持的功能,就需要对每个分支的default.xml文件内容进行相同的修改——删除某个project标签。
因此,Repo工具提出了另外一种本地的支持,这个机制便是LocalManifest机制。
LocalManifest
在repo sync下载代码之前,会将.repo/manifests/default.xml、local_manifest.xml和.repo/local_manifests/目录下存在清单文件进行合并,再根据融合的清单文件进行代码同步。
.repo/manifests/default.xml、local_manifest.xml
.repo/local_manifests/
这样一来,只需要将清单文件的修改项放到.repo/local_manifests/目录下, 就能够在不修改default.xml的前提下,完成对清单的文件的定制。
LocalManifest机制的原理图如下所示:
参考网上的资料,Local Manifests的隐含规则如下:
Local Manifests
local_manifest.xml
local_manifests/
local_manifests
1.《Android源代码仓库及其管理工具Repo分析》 by 罗升阳: https://blog.csdn.net/Luoshengyang/article/details/18195205
罗老师的这篇文章非常经典,文章针对源码进行了非常细致的讲解,本文前四个小节都是参考该文进行的参考总结,强烈建议阅读。
2.《Android Local Manifests机制》 by ZhangJianIsAStark: https://blog.csdn.net/gaugamela/article/details/78593000
针对 LocalManifests机制 进行了非常详细的讲解,本文的第五节内容都是从中截取的,想要仔细了解的可以阅读本文。
3.AOSP Google 官方文档: https://source.android.com/source/developing.html 4.《Google Git-Repo 多仓库项目管理》 by 郑晓鹏-Rocko: https://juejin.im/post/5bf5913fe51d457dd7800a73
3.AOSP Google 官方文档: https://source.android.com/source/developing.html
4.《Google Git-Repo 多仓库项目管理》 by 郑晓鹏-Rocko: https://juejin.im/post/5bf5913fe51d457dd7800a73
一篇非常不错的实践总结,该文并非针对Repo进行系统性的讲述,但是对于实践者而言是一篇不错的参考文章,从基础到集成到jenkins都有讲述。
jenkins
Hello,我是 却把清梅嗅,女儿奴,源码的眷者,观众途径序列1,杀人游戏信徒,大头菜投机者,端茶递水工程师。欢迎关注我的 博客 或者 GitHub。
如果您觉得文章对您有价值,欢迎 ❤️,或通过下方打赏功能,督促我写出更好的文章 :)
反思|Android源码模块化管理工具Repo分析
起源
随着
Android
项目 模块化 或 插件化 项目业务的愈发复杂,开发流程中通过版本控制工具(比如Git
)管理项目的成本越来越高。以大名鼎鼎的 Android源代码开源项目 (
Android Open-Source Project
,下文简称ASOP
)为例,截止2020年初,Android10
的源码项目,其模块化分割出的 子项目 已接近800个,而每一个子项目都是一个独立的Git
仓库。这意味着
Git
的使用成本究竟有多高?如果开发者希望针对AOSP
的一个分支进行开发,就需要手动将每个子项目进行checkout
操作,如果本地分支尚未创建,开发者便需要手动地在每一个子项目里面去创建分支。如此高昂的使用成本显然需要一种更自动化的方式去处理。为此,
Google
的工程师基于Git
进行了一系列的代码补充,推出了名为Repo
的代码版本管理工具,其本质是通过Python
开发出一系列的脚本命令,便于开发者对复杂的模块化源码项目进行统一的调度和切换。即使对于上文说到的
AOSP
而言,其同样使用了Repo
工具进行项目的管理,由此可见,对于 高度模块化 开发的Android
项目而言,Repo
工具的确有一定的学习和借鉴意义。本文以
AOSP
为例,对Repo
工具的 使用流程 和 原理 进行系统性的分析,读者需要对Git
和Repo
工具有一定的了解。本文大纲如下:
核心思想
Repo
是以Git
为基础构建的代码库管理工具。其并非用来取代Git
,只是为了让开发者在多模块的项目中更轻松地使用Git
。Repo
命令是一段可执行的Python
脚本,开发者可以使用Repo
执行跨网络操作。例如,借助单个Repo
命令,将文件从多个代码库下载到本地工作目录。那么,
Repo
幕后原理究竟是怎么样的?想要真正的理解Repo
,就必须理解Repo
最核心的三个要素:Repo仓库、Manifest仓库 以及 项目源码仓库。这里我们先将三者的关系通过一张图进行概括,该图已经将
Repo
工具本身的结构描述的淋漓尽致:1、项目源码仓库:底层的被执行者
对于若干个模块化的子项目,也就是 项目源码仓库 而言,它们是开发者希望的 被统一调度的对象。
因此,对于
Repo
工具整个框架的设计而言,项目源码仓库 明显应该处于最底层,它们是被Repo
命令执行操作的最基本元素。2、Manifest仓库:子项目元信息的容器
Manifest仓库 中最重要的是一个名为
manifest.xml
的清单文件,其存储了所有子项目仓库的元信息。当
Repo
命令想要对所有子项目进行对应操作的时候,其总是需要知道 要操作的项目的相关信息——比如,我想要clone AOSP
所有子项目的代码,首先我需要知道所有子项目仓库的名称和仓库地址;这时,Repo
便会从manifest
仓库中获取对应所有仓库的元信息,并进行对应的fetch
操作。因此,想要通过
Repo
对模块化项目进行管理,项目的管理者必须提供一个对应的manifest
清单文件,里面存储所有子项目的相关信息,这样,Repo
工具才能通过对其进行解析,然后完成子项目的统一管理。此外,读者应该知道,
AOSP
也是在迭代过程中不断变化的,因此,其每一个分支版本所包含的子项目信息可能都是不同的,这意味着Manifest
仓库同样也是一个Git
仓库,以达到AOSP
不同分支版本中,该仓库对应存储的子项目元信息不同的目的。3、Repo仓库:顶层命令的容器
Repo
工具实际上是由一系列的Python
脚本组成的,这些Python
脚本通过调用Git
命令来完成自己的功能。Repo
仓库的本质就是存储了各种各样的Python
脚本,当开发者调用相关的Repo
命令时,便会从Repo
仓库中运行对应的脚本进行处理,并根据脚本中的代码逻辑,找到manifest
中所有项目的元信息,然后将其中包含的子项目进行对应命令的处理——因此,我们可以称 Repo仓库是顶层命令的容器。此外,和
Manifest
仓库相同,组成Repo
工具的Python
脚本本身也是一个Git
仓库;每当开发者执行Repo
命令的时候,Repo
仓库都会对自己进行一次更新。现在,通过
Repo
工具完成项目模块化的管理需要分步构建以上三个角色,但是在这之前,我们需要先将Repo
工具添加到自己的开发环境中。一、Repo脚本初始化流程
正如 官方文档 所描述的,通过以下命令安装
Repo
工具,并确保它可执行:安装成功后,对应的目录下便会存在一个
repo
脚本文件,通过将其配置到环境中,开发者可以在终端中使用repo
的基本命令。整个流程如下图所示:
二、Repo仓库创建流程
Repo
脚本初始化完毕,接下来针对Repo
仓库创建流程进行简单的分析。1、工欲善其事,必先利其器
以
AOSP
项目为例,开发者通过以下命令来安装一个Repo仓库:这个命令实际上是包含了两个操作:初始化 Repo仓库 和 Manifest仓库,其中
Repo
仓库完成初始化之后,才会继续初始化Manifest
仓库。这很好理解,
Repo
仓库的本质就是存储了各种各样的Python
脚本,若它没有初始化,就不存在所谓的Repo
相关命令,更遑论后面的Manifest
仓库初始化和子项目代码初始化的流程了。这一小节我们先分析 Repo仓库 的安装过程,在下一小节再分析 Manifest仓库 的安装过程。
本小节整体流程如下图所示:
2、init命令分析
上一节我们成功安装了
repo
脚本文件,这个脚本里面提供了例如version
、help
、init
等最基本的命令:由此可见
Repo
脚本最初提供的命令确实非常少,前两个命令十分好理解,分别是查看Repo
工具相关依赖的版本或者查看帮助,比较重要的是init
命令,这个命令的作用便是对本地Repo
仓库的初始化。那么
Repo
仓库如何才能初始化呢?设计者并没有尝试直接向远端服务器请求拉取代码,而是从当前目录开始 往上遍历直到根目录 ,若在这个过程中找到一个.repo/repo
目录,并且该目录本身的确是一个Repo
仓库,便尝试从该仓库 克隆一个新的Repo
仓库 到执行Repo
脚本的目录中。反之,若从本地向上直到根目录不存在
Repo
仓库,则尝试向远端克隆一个新的Repo
仓库到本地来。回到本地克隆
Repo
仓库的流程中,代码是如何判断本地的.repo/repo
目录的确是一个Repo
仓库的呢,代码中已经描述的非常清晰了:读到这里,读者可以对
Repo
仓库进行一个简单的总结了。3、Repo仓库到底是什么
从上文的源码中,读者了解了
Repo
脚本源码中判断是否是Repo
仓库的五个依据,从这些判断条件中,我们可以简单对Repo
仓库的定位进行一个总结。首先,从条件1中我们得知,组成
Repo
工具的Python
脚本本身也是一个Git
仓库;每当开发者执行Repo
命令的时候,Repo
仓库都会对自己进行一次更新。其次,
Repo
仓库本身作为存储Python
脚本的容器,其内部必然存在一个入口的main
函数可供运行。对于条件3而言,我们直到
Repo
工具本质是对Git
命令的封装,因此,必须有一个类负责Git
相关的配置信息,和提供简单的Git
相关工具方法,这便是git_config.py
文件的作用。对于条件4,
Repo
仓库目录下还需要一个project.py
文件,负责Hook
相关功能,细心的读者应该注意到,/.repo/repo
目录下还有一个/hooks/
目录。最后也是最重要的,
/.repo/repo
目录下必须还存在一个subcmds
目录,顾名思义,这个目录下存储了绝大多数repo
重要的命令,比如sync
、checkout
、pull
、commit
等等;这也说明了,如果没有Repo
仓库的初始化,使用Repo
命令操作子项目代码仓库便是无稽之谈。三、Manifest仓库创建流程
继续回到上一节我们使用到的命令:
读者已经知道,通过
init
命令,我们在指定的目录下,成功初始化了Repo
仓库。当安装好Repo
仓库之后,就会调用该Repo
仓库下面的main.py
脚本,对应的文件为.repo/repo/main.py
。这样我们便可以通过
init
后面的-u -b
参数,进行Manifest
仓库的创建流程,其中-u
指的是manifest
文件所在仓库对应的Url
地址,-b
指的是对应仓库的默认分支。本小节整体流程如下图所示:
1、定义manifest文件
上文中我们提到,想要通过
Repo
对模块化项目进行管理,项目的管理者必须提供一个对应的manifest
清单文件,里面存储所有子项目的相关信息,这样,Repo
工具才能通过对其进行解析,然后完成子项目的统一管理。对于公司的业务而言,项目的管理者需要根据自己公司的实际业务模块构造出自己的
manifest
文件,并放置在某个git
仓库内,这样开发者便可以通过指定对应的Url
构建Manifest
仓库。本文以
AOSP
项目为例,其项目清单文件所在的Url
为:2、初始化Manifest仓库
通过
init
命令和对应的参数,Repo
便可以尝试从远端克隆Manifest
仓库,然后从指定的Url
克隆对应的manifest
文件,切换到对应的分支并进行解析。这里描述比较简单,实际上内部实现逻辑非常复杂;比如,在向远端克隆对应的
Manifest
仓库之前,会先进行本地是否存在Manifest
仓库的判断,若已经存在,则尝试更新本地的Manifest
仓库,而非直接向远程仓库中克隆。此外,当未指定分支时,则会checkout
一个default
分支。这之后,
Repo
会根据远端的xml
清单文件尝试构建自己本地的Manifest
仓库。3、Manifest仓库的文件层级
让我们看以下
/.repo/
目录下文件层级:上文我们说到,
Manifest
仓库本身也是一个Git
仓库,因此,当我们打开.repo/manifests/
目录时,里面会存在一个.git
的文件夹,远端的Manifest
文件仓库中的所有文件都被克隆到了这个目录下。这里重点说一下项目的
Git
仓库目录和工作目录的概念。一般来说,一个项目的Git
仓库目录(默认为.git
目录)是位于工作目录下面的,但是Git
支持将一个项目的Git
仓库目录和工作目录分开来存放。在
AOSP
中,Repo
仓库的Git
目录位于工作目录.repo/repo
下,Manifest
仓库的Git
目录有两份拷贝,一份.git
位于工作目录.repo/manifests
下,另外一份位于.repo/manifests.git
目录。同时,我们看到这里还有一个
.repo/manifest.xml
文件,这个文件是最终被Repo
的文件,它是通过将.repo/manifest
文件夹下的文件和local_manifest
文件进行合并后生成的,关于local_manifest
机制我们后文会讲到,这里仅需将.repo/manifest.xml
文件视为最终被使用的配置文件即可。4、解析并生成Projects项目
回到上图,我们知道名字带有
manifest
相关的文件和文件夹代表了Manifest
仓库,其内部存储了所有子项目仓库的元信息;而repo
文件夹中存储了repo
相关命令的脚本文件。读者注意到,除此之外,还有一部分名字带有
project
的文件和文件夹,它们便是代表了Repo
解析Manifest
后生成的子项目信息和文件。在
Repo
中,其管理的所有子项目,每一个子项目都被封装成为了一个Project
对象,该对象内部存储了一系列相关的信息。现在,
Manifest
仓库被创建并初始化完毕,接下来我们分析Repo
的sync
流程,看看子项目是如何被统一下载和管理的。四、子项目仓库Sync流程
执行完成
repo init
命令之后,我们就可以继续执行repo sync
命令来克隆或者同步子项目了:当执行
repo sync
命令时,会默认尝试拉取远程仓库下载更新本地的Manifest
仓库,下载远端对应的default.xml
文件。下载完成后,会自动解析
default.xml
文件中项目管理者配置的所有子项目信息,然后每个子项目信息被解析成为一个Project
对象,并整合到一个内存的集合中去。接下来,根据本地是否已经存在对应的子项目源码,针对每一个子项目,
Repo
都会进行对应的更新操作或者克隆操作,而这些操作的本质,其实就是内部调用了Git
的fetch
、rebase
或者merge
等等命令。值得关注的是,和
Manifest
仓库相似,AOSP
子项目的工作目录和Git
目录也都是分开存放的,其中,工作目录位于AOSP
根目录下,Git
目录位于.repo/projects
目录下。此外,每一个
AOSP
子项目的工作目录也有一个.git
目录,不过这个.git
目录是一个符号链接,链接到.repo/repo/projects
对应的Git
目录。这样,我们就既可以在AOSP
子项目的工作目录下执行Git
命令,也可以在其对应的Git
目录下执行Git
命令。本小节整体流程如下图所示:
五、LocalManifest机制
从上文中读者已经知道了,对于源码来讲,
manifest.xml
只是一个到.repo/manifests/default.xml
的文件链接,真正的清单文件是通过manifests
这个Git
仓库托管起来的。需要注意的是,在进行
Android
系统开发时,通常需要对清单文件进行自定义定制。例如,设备厂商会构建自己的manifest
库,通常是基于AOSP
的default.xml
进行定制,去掉AOSP
的一些Git
库、增加一些自有的Git
库。这意味着,项目的管理者需要手动的对
default.xml
文件内容进行修改,然而这种方式在一些场景下存在弊端——对于AOSP
而言,其本身可能存在几百个不同的分支,而项目的管理者需要修改的内容却基本是相同的。因此,
Repo
工具提出了另外一种本地的支持,这个机制便是LocalManifest
机制。在
repo sync
下载代码之前,会将.repo/manifests/default.xml、local_manifest.xml
和.repo/local_manifests/
目录下存在清单文件进行合并,再根据融合的清单文件进行代码同步。这样一来,只需要将清单文件的修改项放到
.repo/local_manifests/
目录下, 就能够在不修改default.xml
的前提下,完成对清单的文件的定制。LocalManifest
机制的原理图如下所示:参考网上的资料,
Local Manifests
的隐含规则如下:local_manifest.xml
,再解析local_manifests/
目录下的清单文件;local_manifests
目录下的清单文件是没有命名限制的,但会按照字母序被解析,即字母序靠后的文件内容会覆盖之前的;repo
定义的格式才能被正确解析。参考 & 感谢
罗老师的这篇文章非常经典,文章针对源码进行了非常细致的讲解,本文前四个小节都是参考该文进行的参考总结,强烈建议阅读。
针对 LocalManifests机制 进行了非常详细的讲解,本文的第五节内容都是从中截取的,想要仔细了解的可以阅读本文。
一篇非常不错的实践总结,该文并非针对
Repo
进行系统性的讲述,但是对于实践者而言是一篇不错的参考文章,从基础到集成到jenkins
都有讲述。关于我
Hello,我是 却把清梅嗅,女儿奴,源码的眷者,观众途径序列1,杀人游戏信徒,大头菜投机者,端茶递水工程师。欢迎关注我的 博客 或者 GitHub。
如果您觉得文章对您有价值,欢迎 ❤️,或通过下方打赏功能,督促我写出更好的文章 :)