00-02:AOSP 源码阅读环境与检索方法论源码剖析(Android 16 / aosp-main)

AOSP 源码阅读环境与检索方法论源码剖析(Android 16 / aosp-main)

概述

AOSP 树体量非常大。直接用 grep -r 会扫进 .repo、.git、out,非常慢。官方的做法是:先 source build/envsetup.sh,把 build/soong/bin 等目录挂进 PATH,再用按后缀裁剪过的检索脚本(cgrep / jgrep / mgrep / sgrep 等)定位符号与调用点。

本文基于A16走读:

  1. 理清环境如何被 envsetup.sh 装配;
  2. cgrep 族工具的实现与用法;
  3. godir / afind / hmm / IDEGen 等辅助手段;
  4. 会提到一点 ctags / cscope

前置知识

  • 已阅读并理解我发布的博客序章 01:工作树由 Manifest 拼装,顶层存在 build/envsetup.sh 等 linkfile。
  • 会在 Linux/WSL shell 里执行 . script.sh(注意点号,把函数导入当前 shell)。
  • 知道 find 的 -prune 可跳过目录,-exec ... {} + 可批量喂给 grep。
  • 区分两类工具:
    • 树内脚本 (随 AOSP 检出):build/soong/bin/cgrep 等。
    • 宿主机工具 (需自行安装):ctags、cscope、IDE 等。

核心数据结构

1. 源码树"顶"的判定锚点

环境脚本用固定相对路径识别树根:

bash 复制代码
# AOSP aosp-main
# 文件:build/make/shell_utils.sh
function gettop
{
    local TOPFILE=build/make/core/envsetup.mk
    if [ -n "${TOP:-}" -a -f "${TOP:-}/$TOPFILE" ] ; then
        (cd "$TOP"; PWD= /bin/pwd)
    else
        if [ -f $TOPFILE ] ; then
            PWD= /bin/pwd
        else
            local HERE=$PWD
            local T=
            while [ \( ! \( -f $TOPFILE \) \) -a \( "$PWD" != "/" \) ]; do
                \cd ..
                T=`PWD= /bin/pwd -P`
            done
            \cd "$HERE"
            if [ -f "$T/$TOPFILE" ]; then
                echo "$T"
            fi
        fi
    fi
}

含义:

  • 优先采用环境变量 TOP(且其下确有 build/make/core/envsetup.mk);
  • 否则从当前目录向上爬,直到找到该文件;
  • 用 PWD= /bin/pwd -P 消解符号链接,得到真实物理路径。

build/envsetup.sh 开头用 _gettop_once 做同样判定;失败则直接返回,并提示必须从树根 source。

2. 全局 PATH 注入列表

set_global_paths 维护 ANDROID_GLOBAL_BUILD_PATHS,把检索与辅助脚本目录配置到 PATH环境变量,配置过java环境变量的应该都懂:

追加路径 作用
$T/build/soong/bin cgrep / jgrep / hmm / m 等
$T/build/bazel/bin Bazel 相关入口
$T/development/scripts 开发辅助脚本
$T/prebuilts/devtools/tools 预编译开发工具
可选 emulator / dtc 等 视宿主 OS 与目录是否存在

3. 按语言/文件类型裁剪的 grep 族

hmm 列出的检索入口(节选)与脚本对应关系:

命令 脚本 检索范围(后缀/模式)
cgrep build/soong/bin/cgrep *.c *.cc *.cpp *.h *.hpp
jgrep build/soong/bin/jgrep *.java *.kt
mgrep build/soong/bin/mgrep Makefile / *.mk / *.bp 等
sgrep build/soong/bin/sgrep C/Java/XML/mk/bp/aidl/proto 等"源码面"
resgrep build/soong/bin/resgrep res/ 下 *.xml
mangrep build/soong/bin/mangrep AndroidManifest.xml
sepgrep build/soong/bin/sepgrep 名为 sepolicy 的目录树
ggrep build/soong/bin/ggrep *.gradle

共性:一律 find 时 -prune 掉 .repo、.git、out。

4. 目录索引与 IDE 工程

机制 位置 用途
godir 仍在 build/envsetup.sh(shell 函数) 基于 filelist 按路径正则跳转目录
afind build/soong/bin/afind 带 prune 的 find 包装
IDEGen development/tools/idegen/ 生成 IntelliJ / Eclipse 工程文件

调用链路

graph TD A[source build/envsetup.sh] --> B[_gettop_once定位树根] B --> C[source shell_utils.sh] C --> D[set_global_paths] D --> E[PATH前置soong/bin] A --> F[unset旧shell函数名] E --> G[cgrep等脚本可直接执行] G --> H[find剪枝后grep] A --> I[godir读filelist] A --> J[hmm打印帮助]

纯文本调用链路:

text 复制代码
. build/envsetup.sh
  ├─ _gettop_once()                    # 找树根;失败则 return 1
  ├─ source build/make/shell_utils.sh  # 提供 gettop 等
  ├─ ...(lunch 等,本篇不展开)
  ├─ unset cgrep jgrep hmm ...         # 丢掉历史同名 shell 函数
  ├─ validate_current_shell
  ├─ set_global_paths
  │     └─ PATH=$T/build/soong/bin:$T/build/bazel/bin:...:$PATH
  ├─ source_vendorsetup                # 可选加载 device/vendor 的 vendorsetup.sh
  └─ addcompletions

之后用户在任意子目录:
  cgrep / jgrep / mgrep / sgrep ...
    └─ find . -name .repo -prune -o ... -exec grep --color -n "$@" {} +

  godir <regex>
    └─ 维护/读取 filelist → 选目录 → cd

  hmm
    └─ 打印 envsetup 能力说明 + 枚举 function 名

源码逐段分析

1. 装配入口:必须从树根 source

bash 复制代码
# AOSP aosp-main
# 文件:build/envsetup.sh
function _gettop_once
{
    local TOPFILE=build/make/core/envsetup.mk
    if [ -n "$TOP" -a -f "$TOP/$TOPFILE" ] ; then
        (cd "$TOP"; PWD= /bin/pwd)
    else
        if [ -f $TOPFILE ] ; then
            PWD= /bin/pwd
        else
            local HERE=$PWD
            local T=
            while [ \( ! \( -f $TOPFILE \) \) -a \( "$PWD" != "/" \) ]; do
                \cd ..
                T=`PWD= /bin/pwd -P`
            done
            \cd "$HERE"
            if [ -f "$T/$TOPFILE" ]; then
                echo "$T"
            fi
        fi
    fi
}
T=$(_gettop_once)
if [ ! "$T" ]; then
    echo "Couldn't locate the top of the tree. Always source build/envsetup.sh from the root of the tree." >&2
    return 1
fi
IMPORTING_ENVSETUP=true source $T/build/make/shell_utils.sh

解读:

  • 找不到树根就 return 1,后续函数不会被装载上,也就不会生效。
  • IMPORTING_ENVSETUP=true 让 shell_utils.sh 跳过部分"独立脚本模式"断言,避免嵌套冲突。
  • 实际操作:在源码根目录执行 . build/envsetup.sh(或 source build/envsetup.sh)。

文件末尾按固定顺序收尾:

bash 复制代码
# AOSP aosp-main
# 文件:build/envsetup.sh
validate_current_shell
set_global_paths
source_vendorsetup
addcompletions

2. 把检索脚本挂进 PATH

bash 复制代码
# AOSP aosp-main
# 文件:build/envsetup.sh
function set_global_paths()
{
    local T=$(gettop)
    if [ ! "$T" ]; then
        echo "Couldn't locate the top of the tree.  Try setting TOP."
        return
    fi

    if [ -n "$ANDROID_GLOBAL_BUILD_PATHS" ] ; then
        export PATH=${PATH/$ANDROID_GLOBAL_BUILD_PATHS/}
    fi

    ANDROID_GLOBAL_BUILD_PATHS=$T/build/soong/bin
    ANDROID_GLOBAL_BUILD_PATHS+=:$T/build/bazel/bin
    ANDROID_GLOBAL_BUILD_PATHS+=:$T/development/scripts
    ANDROID_GLOBAL_BUILD_PATHS+=:$T/prebuilts/devtools/tools
    # ... Linux 下追加 dtc/libufdt;可选 emulator ...
    export PATH=$ANDROID_GLOBAL_BUILD_PATHS:$PATH
}

要点:

  • 先将旧值从 PATH 去掉,再写入新值,支持反复 source 而不无限拉长 PATH。
  • cgrep 等能直接敲命令,靠的是 $T/build/soong/bin 被前置,而不是 shell alias。

3. 历史同名函数必须 unset

本树 envsetup.sh 明确把旧实现unset掉了:

bash 复制代码
# AOSP aosp-main
# 文件:build/envsetup.sh
# These functions used to be here but are now standalone scripts
# in build/soong/bin.  Unset these for the time being so the real
# script is picked up.
# TODO: Remove this some time after a suitable delay (maybe 2025?)
unset allmod
unset aninja
unset cgrep
unset ggrep
unset hmm
unset jgrep
unset mangrep
unset mgrep
unset resgrep
unset sepgrep
unset sgrep
# ... 以及 mm/mmm 等构建相关入口 ...

若仍残留同名 shell 函数,会遮蔽 PATH 里的脚本。unset 之后,type cgrep 应指向 .../build/soong/bin/cgrep。

4. cgrep:C/C++ 面检索

bash 复制代码
# AOSP aosp-main
# 文件:build/soong/bin/cgrep
find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( \
        -name '*.c' \
        -o -name '*.cc' \
        -o -name '*.cpp' \
        -o -name '*.h' \
        -o -name '*.hpp' \
    \) -exec grep --color -n "$@" {} +
exit $?

执行语义:

  1. 从当前工作目录 起向下找(不是强制从树根);读 Framework 时通常先 cd frameworks/base 再 jgrep,缩小范围。
  2. .repo / .git / out 被 prune,不进入。
  3. 仅匹配列出的 C/C++ 后缀;参数原样传给 grep(可用 -n 以外的 grep 选项,脚本已带 --color -n)。
  4. exit $? 透传 grep/find 状态,便于脚本判断"有无命中"。

同类脚本只改后缀集合。例如 jgrep:

bash 复制代码
# AOSP aosp-main
# 文件:build/soong/bin/jgrep
find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( \
        -name '*.java' \
        -o -name '*.kt' \
    \) -exec grep --color -n "$@" {} +
exit $?

mgrep 面向构建描述:

bash 复制代码
# AOSP aosp-main
# 文件:build/soong/bin/mgrep
find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( \
        -name 'Makefile' \
        -o -name 'Makefile.*' \
        -o -name '*.make' \
        -o -name '*.mak' \
        -o -name '*.mk' \
        -o -name '*.bp' \
    \) -exec grep --color -n "$@" {} +
exit $?

sgrep 覆盖更广的"源码后缀并集",适合不确定语言边界时的第一枪;代价是更慢、噪声更大。

5. resgrep / mangrep / sepgrep:目录语义不同

resgrep 先找名为 res 的目录,再在其下搜 *.xml:

bash 复制代码
# AOSP aosp-main
# 文件:build/soong/bin/resgrep
for dir in `find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -name res -type d`; do
    find $dir -type f -name '*\.xml' -exec grep --color -n "$@" {} +
done

mangrep 只盯 AndroidManifest.xml。sepgrep 进入名为 sepolicy 的目录做递归 grep,并排除其中的 .git。

6. afind:带剪枝的 find

bash 复制代码
# AOSP aosp-main
# 文件:build/soong/bin/afind
set -f
dir=${1:-.}
shift
args=( $@ )
if [[ ${#args[@]} -eq 0 ]] ; then
    args=( -print )
fi
find "$dir" -name .repo -prune -o -name .git -prune -o -name out -prune -o ${args[@]}
exit $?

用途:按文件名/路径枚举时,避免踩进 .repo 与 out。set -f 关闭 glob,防止参数被 shell 提前展开。

7. godir:文件路径索引跳转

bash 复制代码
# AOSP aosp-main
# 文件:build/envsetup.sh
function godir () {
    if [[ -z "$1" ]]; then
        echo "Usage: godir <regex>"
        return
    fi
    local T=$(gettop)
    local FILELIST
    if [ ! "$OUT_DIR" = "" ]; then
        mkdir -p $OUT_DIR
        FILELIST=$OUT_DIR/filelist
    else
        FILELIST=$T/filelist
    fi
    if [[ ! -f $FILELIST ]]; then
        echo -n "Creating index..."
        (\cd $T; find . -wholename ./out -prune -o -wholename ./.repo -prune -o -type f > $FILELIST)
        echo " Done"
    fi
    local lines
    lines=($(\grep "$1" $FILELIST | sed -e 's/\/[^/]*$//' | sort | uniq))
    # 0 条 → Not found;多条 → 交互选择;1 条 → 直接 cd
    \cd $T/$pathname
}

要点:

  • 索引是全树文件路径列表,不是 tags 数据库。
  • 同步后若大量增删文件,需删掉 filelist(或 $OUT_DIR/filelist)重建。
  • 匹配的是路径字符串;sed 去掉最后一段文件名,跳转的是目录。

croot 则简单回到树根或树根下相对路径:

bash 复制代码
# AOSP aosp-main
# 文件:build/envsetup.sh
function croot()
{
    local T=$(gettop)
    if [ "$T" ]; then
        if [ "$1" ]; then
            \cd $(gettop)/$1
        else
            \cd $(gettop)
        fi
    else
        echo "Couldn't locate the top of the tree.  Try setting TOP."
    fi
}

8. hmm:能力清单

bash 复制代码
# AOSP aosp-main
# 文件:build/soong/bin/hmm
# ... 打印 lunch/croot/m/mm 以及 cgrep/jgrep/... 说明 ...
T=$(gettop)
A=""
for i in `cat $T/build/envsetup.sh | sed -n "/^[[:blank:]]*function /s/function \([a-z_]*\).*/\1/p" | sort | uniq`; do
  A="$A $i"
done
echo $A

前半是固定帮助文本;后半扫描 envsetup.sh 里仍以 function 形式存在的名字。注意:已迁到 soong/bin 的 cgrep 等不会出现在后半枚举里,但会出现在前半说明中。

9. IDEGen:用 IDE 读代码树,比较适合做框架开发的,方便跳转代码

text 复制代码
# AOSP aosp-main
# 文件:development/tools/idegen/README
From the project's root directory...
    Repeat these steps after each sync...
    1) make (to produce generated .java source)
    2) development/tools/idegen/idegen.sh
    3) Open android.ipr in IntelliJ.

阅读向用法:

  • 需要跳转生成代码时,至少要有一次能产出 gen 源的构建(完整产品编译属后续篇章)。
  • IDEA 需加大堆内存与 idea.max.intellisense.filesize,README 写明 Android 树体积带来的限制。
  • 每次 repo sync 后建议重跑 idegen.sh。

10. 阅读方法论

推荐固定节奏(均可用本树脚本落地):

  1. 定模块目录 :godir ActivityManagerService 或 afind frameworks/base -name '*AMS*' 一类;不确定时先 mgrep 模块名。
  2. 定符号定义/引用 :Java/Kotlin 用 jgrep;Native 用 cgrep;跨语言用 sgrep;构建归属用 mgrep。
  3. 收窄 cwd :在 frameworks/base 下搜,而不是每次从整树顶扫。
  4. 顺着调用链 :命中文件后,在同目录继续 jgrep/cgrep 追调用方;跨进程入口再换目录。
  5. 清单类文件 :权限/组件查 mangrep;资源 id/布局查 resgrep;SELinux 查 sepgrep。

11. ctags / cscope:宿主机索引

与 AOSP 树对接时,常见用法是(示意,需自行 apt install 等安装主机包):

bash 复制代码
# 在树根生成 tags(示例;具体选项随 universal-ctags/cscope 版本变化)
afind . -name '*.c' -o -name '*.h' -o -name '*.cpp' -o -name '*.java' \
  > cscope.files
# 再交给本机 ctags/cscope 读该文件列表

定位与跳转仍建议优先用官方 cgrep/jgrep;ctags/cscope 适合编辑器内"跳到定义"的索引加速,二者互补,不是替代关系。

边界场景与异常分支

  1. 未从树根 source

    _gettop_once 失败 → return 1,cgrep 不在 PATH,表现为 command not found。

  2. source 了但旧函数未 unset(极老本地改动)

    同名 shell 函数会压过 soong/bin 脚本。本树通过批量 unset 规避;若自行改回 function 实现,行为以 function 为准。

  3. 在错误 cwd 下检索

    脚本从 . 开始 find。在很深的小目录搜"全树符号"会漏;在树根搜又会慢。应用 croot/cd 主动选范围。

  4. godir 无索引或索引过期

    无 filelist 时现场 find 建索引;sync 后路径变更可能导致"假命中",删索引文件即可重建。

  5. godir 多命中

    进入交互选择;输入越界打印 Invalid choice 并继续循环;0 命中打印 Not found。

  6. resgrep 目录名恰好叫 res 的非资源树

    凡目录名匹配 res 都会进入,可能带进无关 XML;结果需人工甄别。

  7. source_vendorsetup 加载厂商脚本

    会扫描 device/vendor/product 下 vendorsetup.sh。若存在 allowed-vendorsetup_sh-files 白名单则只加载名单内文件;多个白名单文件则全部不加载并告警。【不属于 AOSP 原生,是厂商扩展】的逻辑常藏在这些脚本里,阅读时注意区分。

  8. 非 bash/zsh

    validate_current_shell 对其他 shell 仅警告;补全与部分行为可能异常。

AOSP 原生 vs 厂商可定制点

类别 说明
AOSP 原生 envsetup.sh、shell_utils.sh、build/soong/bin/*grep、godir、afind、hmm、development/tools/idegen
合法本地习惯 自装 ctags/cscope/rg、编辑器插件、个人 alias(勿与 unset 列表冲突)
厂商扩展 vendorsetup.sh 里追加的产品/路径/私有检索函数;厂商 IDE 插件与闭源索引服务

小结

  • 阅读环境的第一动作是在树根执行 . build/envsetup.sh,由 _gettop_once + set_global_paths 把 build/soong/bin 挂进 PATH。
  • 本树中 cgrep/jgrep/mgrep/sgrep 等已是独立脚本;envsetup.sh 用 unset 防止旧函数遮蔽。
  • 这类脚本的核心价值是:统一 prune .repo/.git/out,并按后缀收窄扫描面。
  • godir/afind/croot/hmm/idegen 分别解决跳转、枚举、回根、帮助与 IDE 工程生成。
  • ctags/cscope 可与 afind 文件列表配合做编辑器索引。

下一篇进入模块 1 第 1 篇:build/envsetup.sh 与 lunch 的产品选择机制

相关推荐
vilya1 小时前
把 Python 塞进 APK:Chaquopy 打包实践
android·python
用户92817267390161 小时前
Android Compose版本的AI组件库来了。
android·kotlin
知昂七昂1 小时前
00-01:AOSP 源码仓库结构与 repo 工作流源码剖析(Android 16 / aosp-main)
android·google
跨境数据猎手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++