AOSP 源码仓库结构与 repo 工作流源码剖析(Android 16 / aosp-main)
概述
AOSP 不是单一 Git 仓库。完整平台由上千个独立 Git 工程拼成一棵工作树。拼装工具是 repo:它先拉一份 Manifest(工程清单),再按清单批量 fetch / checkout,最后用 linkfile / copyfile 把关键入口文件挂到源码树顶层。
本文基于repo v2.61.1,走读一次完整下载链路:
repo 启动器 → repo init → repo sync → 工作树与 .repo/ 内部布局。
本篇只覆盖仓库结构、分支选择与下载工作流。
前置知识
- 会用基础 Git:
clone/fetch/checkout/ 远程分支。 - 知道 XML结构化配置文件。
- 区分两套"版本":
- 平台分支 :Manifest 的
default revision(本树为main)。 - repo 工具版本 :
.repo/repo自身的 git-repo 检出(本机为 v2.61.1)。
- 平台分支 :Manifest 的
- 本树 Manifest 镜像来自清华 Tuna:
http://aosp.tuna.tsinghua.edu.cn/platform/manifest,与官方android.googlesource.com内容对应,仅下载入口不同。
核心数据结构
1. Manifest 工程模型
Manifest 是一份(可嵌套 include 的)XML。核心元素:
| 元素 | 作用 |
|---|---|
<remote> |
远程名、fetch 基址、review 地址 |
<default> |
缺省 revision / remote / sync-j |
<project> |
一个 Git 工程:name(远端路径)+ path(本地目录) |
<linkfile> / <copyfile> |
把工程内文件链/拷到工作树其他位置 |
<include> |
引入另一份 XML |
本树顶层入口不是直接编辑的工程列表,而是生成文件:
xml
<!-- AOSP aosp-main / repo 生成文件 -->
<!-- 文件:.repo/manifest.xml -->
<manifest>
<include name="default.xml" />
</manifest>
真正的工程表在 .repo/manifests/default.xml:
xml
<!-- AOSP aosp-main -->
<!-- 文件:.repo/manifests/default.xml -->
<remote name="aosp"
fetch=".."
review="https://android-review.googlesource.com/" />
<default revision="main"
remote="aosp"
sync-j="4" />
解读:
remote.fetch="..":相对 Manifest 仓库 URL 的上一级,作为各project name的拼接基址。default revision="main":未单独写revision的工程,一律跟main。- 同文件内的
<notice>明确提醒:自 2025-03-27 起,官方更推荐用android-latest-release做可构建发行分支;aosp-main是主干开发线。本树的代码当前就是main分支的,读者可以自行下载其他分支,可以参考我的其他博客文章。
build/make 一条典型工程声明示例(含 linkfile):
xml
<!-- AOSP aosp-main -->
<!-- 文件:.repo/manifests/default.xml -->
<project path="build/make" name="platform/build" groups="pdk,sysui-studio" >
<linkfile src="CleanSpec.mk" dest="build/CleanSpec.mk" />
<linkfile src="buildspec.mk.default" dest="build/buildspec.mk.default" />
<linkfile src="core" dest="build/core" />
<linkfile src="envsetup.sh" dest="build/envsetup.sh" />
<linkfile src="target" dest="build/target" />
<linkfile src="tools" dest="build/tools" />
</project>
path 决定工作树目录;name 决定远端仓库名。linkfile 把 build/make 仓库内的 envsetup.sh 等挂到 build/ 下,供后续 source build/envsetup.sh 使用。
2. .repo/ 内部布局
.repo/ 是 repo 私有状态区。本树可以看到的关键目录如下:
| 路径 | 含义 |
|---|---|
.repo/repo/ |
git-repo 工具自身检出;真正执行逻辑在 main.py |
.repo/manifests/ |
Manifest 工作树;本地分支名固定为 default |
.repo/manifests.git/ |
Manifest 裸库 |
.repo/manifest.xml |
当前生效入口(常 include default.xml) |
.repo/project.list |
已同步工程相对路径列表 |
.repo/projects/ / project-objects/ |
各工程的 git 对象/元数据存放 |
.repo/copy-link-files.json |
当前 linkfile/copyfile 目标清单 |
本树 copy-link-files.json 实况:
json
{"linkfile": ["WORKSPACE", "BUILD", "build/CleanSpec.mk", "build/buildspec.mk.default", "build/core", "build/envsetup.sh", "build/target", "build/tools", "Android.bp", "bootstrap.bash", "trusty/WORKSPACE.bazel", "trusty/.bazelrc"], "copyfile": ["lk_inc.mk"]}
因此源码树顶层可见:
text
Android.bp -> build/soong/root.bp
bootstrap.bash -> build/soong/bootstrap.bash
这不是手工建的符号链接,而是 sync 阶段按 Manifest 维护的结果。
3. 工作树顶层目录(本机实况)
与 Framework / 运行时相关的常见顶层目录:
| 目录 | 内容概览 |
|---|---|
build/ |
构建系统(make / soong / bazel / release) |
frameworks/ |
Framework(含 base) |
system/ |
系统原生守护进程与库 |
packages/ |
系统应用与模块 |
hardware/ |
HAL 相关接口与参考实现 |
device/ |
设备配置(含 generic / google 机型树) |
external/ |
大量第三方开源组件 |
art/ / bionic/ / libcore/ |
运行时与 C 库 |
prebuilts/ |
预编译工具链与二进制 |
kernel/ |
内核相关检出(视 Manifest 而定) |
平台版本标识(本树):
make
# AOSP aosp-main
# 文件:build/make/core/build_id.mk
BUILD_ID=MAIN
BUILD_ID=MAIN 与 Manifest revision="main" 一致,说明这是主干开发树,不是某个 android-15.0.0_r* 冻结 tag。
调用链路
纯文本调用链路:
text
/root/bin/repo(启动器)
└─ _FindRepo() 向上查找 .repo/repo/main.py
├─ 未找到且 cmd==init → _Init() 克隆 git-repo → 再 _FindRepo()
└─ exec: python main.py --repo-dir=... <subcommand>
└─ _Main() → _Repo._ParseArgs() → _Repo._Run()
├─ init → Init.Execute()
│ └─ _SyncManifest() → manifestProject.Sync(...)
└─ sync → Sync.Execute() → _ExecuteHelper()
├─ _UpdateAllManifestProjects()
├─ GetProjects()
└─ _SyncPhased()
├─ _FetchMain() # 网络阶段
├─ _UpdateManifestLists()
│ ├─ UpdateProjectList()
│ └─ UpdateCopyLinkfileList()
└─ _Checkout() # 本地检出
源码逐段分析
1. 启动器:先找本地 .repo,再 exec 真正入口
启动器脚本常量定义了私有目录名与入口文件名:
python
# AOSP 工作树内 git-repo v2.61.1
# 文件:.repo/repo/repo
repodir = ".repo" # name of repo's private directory
S_repo = "repo" # special repo repository
S_manifests = "manifests" # special manifest repository
REPO_MAIN = S_repo + "/main.py" # main script
查找逻辑:从当前目录向上爬,直到找到 .repo/repo/main.py:
python
# AOSP 工作树内 git-repo v2.61.1
# 文件:.repo/repo/repo
def _FindRepo():
"""Look for a repo installation, starting at the current directory."""
curdir = os.getcwd()
repo = None
olddir = None
while curdir != olddir and not repo:
repo = os.path.join(curdir, repodir, REPO_MAIN)
if not os.path.isfile(repo):
repo = None
olddir = curdir
curdir = os.path.dirname(curdir)
return (repo, os.path.join(curdir, repodir))
main() 里对"尚未 init"和"已 init"分流:
python
# AOSP 工作树内 git-repo v2.61.1
# 文件:.repo/repo/repo
def main(orig_args):
cmd, opt, args = _ParseArguments(orig_args)
repo_main, rel_repo_dir = _FindRepo()
# ...
if not repo_main:
# ...
if cmd == "init":
try:
_Init(args)
except CloneFailure:
# 失败则清理 .repo/repo 残留
shutil.rmtree(path, ignore_errors=True)
sys.exit(1)
repo_main, rel_repo_dir = _FindRepo()
else:
_NoCommands(cmd)
# ...
me = [
sys.executable,
repo_main,
"--repo-dir=%s" % rel_repo_dir,
"--wrapper-version=%s" % ver_str,
# ... 后续把原 argv 拼上再 exec
]
要点:
- 第一次在空目录执行,只有
init合法;其他命令直接_NoCommands。 _Init负责把 git-repo 克隆进.repo/repo(可用--repo-url/ 环境变量REPO_URL改源;本机 launcher 来自清华镜像)。- 之后所有子命令都转给
.repo/repo/main.py,启动器自身不再解析复杂业务。
2. main.py:解析全局参数并分发子命令
python
# AOSP 工作树内 git-repo v2.61.1
# 文件:.repo/repo/main.py
def _Main(argv):
# 解析 --repo-dir / --wrapper-version 等包装参数
opt, argv = opt.parse_args(argv)
_CheckWrapperVersion(opt.wrapper_version, opt.wrapper_path)
_CheckRepoDir(opt.repodir)
repo = _Repo(opt.repodir)
try:
init_http()
name, gopts, argv = repo._ParseArgs(argv)
result = repo._Run(name, gopts, argv) or 0
except RepoChangedException as rce:
# repo 自更新后强制重启
os.execv(sys.executable, [sys.executable, __file__] + argv)
sys.exit(result)
_Run 在子命令名为空时打印帮助并返回 1;--version 会改写成 version 子命令。正常路径按 all_commands 表实例化 Init / Sync 等并调用 Execute。
3. repo init:只同步 Manifest,不拉全树
Init.Execute 主流程(入口级):
python
# AOSP 工作树内 git-repo v2.61.1
# 文件:.repo/repo/subcmds/init.py
def Execute(self, opt, args):
wrapper = Wrapper()
# 校验 git 最低版本
reqs = wrapper.Requirements.from_dir(WrapperDir())
git_require(reqs.get_hard_ver("git"), fail=True)
# 可选:改 repo 工具自身的 url / rev
if opt.repo_url:
remote = rp.GetRemote("origin")
remote.url = opt.repo_url
remote.Save()
if opt.repo_rev:
# check_repo_rev + hard reset 到指定 rev
...
existing_checkout = self.manifest.manifestProject.Exists
if not opt.quiet and existing_checkout:
print("repo: reusing existing repo client checkout in",
self.manifest.topdir)
self._SyncManifest(opt)
if os.isatty(0) and os.isatty(1) and not self.manifest.IsMirror:
if opt.config_name or self._ShouldConfigureUser(opt, existing_checkout):
self._ConfigureUser(opt)
self._ConfigureColor()
if not opt.quiet:
self._DisplayResult()
_SyncManifest 把 CLI 选项交给 Manifest 工程的 Sync:
python
# AOSP 工作树内 git-repo v2.61.1
# 文件:.repo/repo/subcmds/init.py
def _SyncManifest(self, opt):
self.manifest.manifestProject.clone_depth = opt.manifest_depth
self.manifest.manifestProject.upstream = opt.manifest_upstream_branch
if not self.manifest.manifestProject.Sync(
manifest_url=opt.manifest_url,
manifest_branch=opt.manifest_branch,
standalone_manifest=opt.standalone_manifest,
groups=opt.groups,
platform=opt.platform,
# ... mirror/reference/partial_clone 等
manifest_name=opt.manifest_name,
):
raise UpdateManifestError(
f"Unable to sync manifest {manifest_name}"
)
参数含义(与下载选型直接相关):
| 选项 | 作用 |
|---|---|
-u / --manifest-url |
Manifest 仓库地址(可位置参数传入) |
-b / --manifest-branch |
Manifest 分支或 revision(选 main / android-15.0.0_r2 等) |
-m / --manifest-name |
用哪份 XML,默认 default.xml |
-g / --groups |
按 group 裁剪工程集合 |
--depth / --partial-clone |
浅克隆 / 部分克隆,省空间与流量 |
--reference |
复用本地 mirror,加速二次检出 |
--mirror |
建镜像仓,不是普通客户端工作树 |
ValidateOptions 对冲突组合直接报错,例如:
--mirror与--archive不能同用;--standalone-manifest不能再配-b/ 非默认-m;- URL 不能同时用
-u和位置参数。
对应本机一次典型 init(示意,镜像可换):
bash
repo init -u http://aosp.tuna.tsinghua.edu.cn/platform/manifest -b main
init 结束后:.repo/manifests 可用,project.list 与上千工程工作树尚未齐------那是 sync 的职责。
4. repo sync:更新 Manifest → 抓取 → 维护列表 → 检出
Sync.Execute 包一层错误聚合,真正工作在 _ExecuteHelper:
python
# AOSP 工作树内 git-repo v2.61.1
# 文件:.repo/repo/subcmds/sync.py
def Execute(self, opt, args):
errors = []
try:
self._ExecuteHelper(opt, args, errors)
except (RepoExitError, RepoChangedException):
raise
except (KeyboardInterrupt, Exception) as e:
raise RepoUnhandledExceptionError(e, aggregate_errors=errors)
self._RunPostSyncHook(opt)
_ExecuteHelper 入口级顺序:
python
# AOSP 工作树内 git-repo v2.61.1
# 文件:.repo/repo/subcmds/sync.py
def _ExecuteHelper(self, opt, args, errors):
manifest = self.outer_manifest
if opt.manifest_name:
manifest.Override(opt.manifest_name)
# 1) 更新 Manifest 工程(可用 --no-manifest-update 跳过)
if opt.mp_update:
self._UpdateAllManifestProjects(opt, mp, manifest_name, errors)
self._ValidateOptionsWithManifest(opt, mp)
self._UpdateRepoProject(opt, manifest, errors) # 检查 repo 工具自身升级
self._UpdateProjectsRevisionId(...) # superproject 相关 revision
all_projects = self.GetProjects(..., missing_ok=True, ...)
if opt.interleaved:
sync_method = self._SyncInterleaved
else:
sync_method = self._SyncPhased
sync_method(opt, args, errors, manifest, mp, all_projects, ...)
默认分相模式 _SyncPhased:
python
# AOSP 工作树内 git-repo v2.61.1
# 文件:.repo/repo/subcmds/sync.py
def _SyncPhased(...):
if not opt.local_only:
result = self._FetchMain(...) # 先网络
all_projects = result.all_projects
if opt.network_only:
return
if err_event.is_set() and opt.fail_fast:
# 网络失败且 fail-fast:本地工作树不更新
raise SyncFailFastError(...)
err_update_projects, err_update_linkfiles = self._UpdateManifestLists(...)
err_checkout = not self._Checkout(all_projects, opt, err_results, errors)
_FetchMain 在 network_only 时直接返回空检出列表,保证"只下载、不改工作树":
python
# AOSP 工作树内 git-repo v2.61.1
# 文件:.repo/repo/subcmds/sync.py
if opt.network_only:
if err_event.is_set():
raise SyncError("error: Exited sync due to fetch errors.", ...)
return _FetchMainResult([])
列表与链接维护:
python
# AOSP 工作树内 git-repo v2.61.1
# 文件:.repo/repo/subcmds/sync.py
def _UpdateManifestLists(...):
for m in self.ManifestList(opt):
if m.IsMirror or m.IsArchive:
continue
self.UpdateProjectList(opt, m) # 写 project.list,清理已删除工程
self.UpdateCopyLinkfileList(m) # 维护 copy-link-files.json 与失效链接
UpdateCopyLinkfileList 会对比旧 JSON 与新 dest 集合,删除不再存在的 link/copy 目标,再写回 JSON。这解释了本树顶层 Android.bp、build/envsetup.sh 等符号链接的来源与生命周期。
常用 sync 参数(与现象对应):
| 选项 | 行为 |
|---|---|
-j N |
并行度(Manifest default 也可写 sync-j) |
-c |
只抓当前分支 |
-d |
强制回到 Manifest 指定 revision(丢弃主题分支 tip 对齐) |
-l / --local-only |
不访问网络,只做本地 checkout |
-n / --network-only |
只 fetch,不改工作树 |
--fail-fast |
首错即停;分相模式下网络失败可不碰本地树 |
5. 分支选择:选什么,树就是什么
Manifest 分支决定整树"对齐哪条线"。本树:
text
.repo/manifests.git remote = tuna platform/manifest
branch default merge = refs/heads/main
BUILD_ID = MAIN
project 数量 ≈ 1042
常见选择对照:
| 目标 | 典型 init | 说明 |
|---|---|---|
| 跟主干开发 | -b main |
本树现状;API 可能仍是 CUR_DEVELOPMENT |
| 跟最新可发布线 | -b android-latest-release(或以对应 manifest 名) |
Manifest notice 推荐给平台开发者 |
| 冻结学习/编译 | -b android-15.0.0_rXX 一类 tag/分支 |
版本稳定,适合对照发行版行为 |
切换分支不是改某个工程的 git checkout 完事,而是:
bash
repo init -b <otherbranch>
repo sync # 或 repo sync -d
Init 头部注释写明:换 Manifest 分支后,需要后续 repo sync(或 repo sync -d)才能把各工程拉到新 revision。
边界场景与异常分支
-
空目录执行非 init 命令
启动器
_FindRepo失败且cmd != init→_NoCommands,提示先repo init。 -
init 克隆 git-repo 失败
CloneFailure→ 删除.repo/repo与.tmp,避免半残状态残留。 -
在已有 checkout 里再次 init
existing_checkout为真时打印 reuse 提示;repo 不支持嵌套 checkout 。若目录选错,提示删掉该树.repo后重来(见_DisplayResult)。 -
sync 网络失败 +
--fail-fast_SyncPhased在 fetch 阶段报错并明确:本地 checkout 未 更新;可用repo sync -l对已有对象做本地更新。 -
仅网络 / 仅本地
-n:_FetchMain提前返回。-l:跳过_FetchMain,只走列表更新与_Checkout。 -
部分同步
LocalSyncState.IsPartiallySynced()为真时打 warning:partial sync 不被许多项目支持,应尽量整树 sync。 -
Manifest 缺失
main.py在需要读 Manifest 却读不到时,报manifest missing or unreadable -- please run init。
AOSP 原生 vs 厂商可定制点
| 类别 | 说明 |
|---|---|
| AOSP 原生 | repo 工具、官方/镜像 Manifest、default.xml 工程表、linkfile 机制、.repo/ 布局 |
| 合法本地扩展 | .repo/local_manifests/ 覆盖/追加工程(官方支持的定制入口) |
| 厂商扩展 | 厂商私有 Manifest 仓、额外 device/<vendor>/ 闭源树、预置的 vendor HAL 工程列表、对公司内 mirror 地址与强制 group 裁剪策略 |
厂商若把私有工程写进自维护 Manifest,下载流程仍走同一套 repo init/sync,但工程集合与签名/编译产品定义已超出 AOSP 原生范围。
小结
- AOSP 工作树 = Manifest 描述的多 Git 工程集合;本机
main线约 1042 个工程,BUILD_ID=MAIN。 repo启动器只负责定位/引导;业务在.repo/repo/main.py。init同步 Manifest(及 repo 工具自身),sync才批量 fetch/checkout,并维护project.list与copy-link-files.json。- 顶层
Android.bp、build/envsetup.sh等是 Manifestlinkfile的结果,不是手工装饰。 - 选分支用
repo init -b,再用repo sync对齐;学习发行版行为应选冻结 tag/release 线,而不是默认以为main等于某个 Android 正式版本号。
下一篇(序章 02)将基于已下载树,搭建阅读环境与检索方法。