00-01:AOSP 源码仓库结构与 repo 工作流源码剖析(Android 16 / aosp-main)

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 镜像来自清华 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。

调用链路

graph TD A[repo启动器] --> B{已有.repo/repo?} B -->|否且命令为init| C[克隆git-repo到.repo/repo] B -->|是| D[exec main.py] C --> D D --> E[_Repo._Run分发子命令] E --> F[Init.Execute] E --> G[Sync.Execute] F --> H[_SyncManifest拉Manifest] G --> I[_UpdateAllManifestProjects] G --> J[_SyncPhased] J --> K[_FetchMain网络抓取] J --> L[_UpdateManifestLists] J --> M[_Checkout检出工作树] L --> N[UpdateCopyLinkfileList]

纯文本调用链路:

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。

边界场景与异常分支

  1. 空目录执行非 init 命令

    启动器 _FindRepo 失败且 cmd != init → _NoCommands,提示先 repo init。

  2. init 克隆 git-repo 失败

    CloneFailure → 删除 .repo/repo 与 .tmp,避免半残状态残留。

  3. 在已有 checkout 里再次 init

    existing_checkout 为真时打印 reuse 提示;repo 不支持嵌套 checkout 。若目录选错,提示删掉该树 .repo 后重来(见 _DisplayResult)。

  4. sync 网络失败 + --fail-fast

    _SyncPhased 在 fetch 阶段报错并明确:本地 checkout 未 更新;可用 repo sync -l 对已有对象做本地更新。

  5. 仅网络 / 仅本地

    -n:_FetchMain 提前返回。-l:跳过 _FetchMain,只走列表更新与 _Checkout。

  6. 部分同步

    LocalSyncState.IsPartiallySynced() 为真时打 warning:partial sync 不被许多项目支持,应尽量整树 sync。

  7. 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 等是 Manifest linkfile 的结果,不是手工装饰。
  • 选分支用 repo init -b,再用 repo sync 对齐;学习发行版行为应选冻结 tag/release 线,而不是默认以为 main 等于某个 Android 正式版本号。

下一篇(序章 02)将基于已下载树,搭建阅读环境与检索方法。

相关推荐
用户92817267390161 小时前
Android Compose版本的AI组件库来了。
android·kotlin
知昂七昂1 小时前
00-02:AOSP 源码阅读环境与检索方法论源码剖析(Android 16 / aosp-main)
android
跨境数据猎手1 小时前
Android Native 层黑盒调用实战:unidbg 模拟执行 so 的原理
android
镜象科技2 小时前
中小学AI心理健康解决方案服务商怎么选?2026年选型参考
android·java·人工智能
脚踏实地,坚持不懈!3 小时前
从 AOSP 源码角度分析 Android 定位性能问题
android
悟道子HD3 小时前
网络安全基本功——MySQL基础
android·mysql·web安全
纪念 2293 小时前
C++ string(三)
android·c++
纪念 2293 小时前
c++ string(最终篇)
android·c++
aqi0012 小时前
鸿蒙版本的电子书阅读APP开放源码啦
android·华为·ai编程·harmonyos·鸿蒙