☰
从零搭建repo编译环境:AOSP源码同步与构建避坑指南
2026/10/7 3:27:28 网站建设 项目流程

做Android系统开发、ROM定制、甚至只是单纯想把AOSP源码拉到本地研究的人,迟早都要过“配置repo编译环境”这一关。repo这个命令行工具是Google用来管理Android源码仓库的管家,它不替代Git,而是把几百上千个Git仓库按照一份manifest清单文件组合成一套完整、可编译的源码树。这篇文章是我个人从零搭建repo编译环境的完整记录,覆盖repo安装、仓库初始化、源码同步、编译依赖自检,顺带把社区里流传的repo汉化补丁也讲清楚怎么装、值不值得装。无论你是第一次碰AOSP的新手,还是要给团队搭一套内部构建环境的老工程师,这套流程都适用。

1. 项目全貌:一套可用的repo编译环境由什么组成

1.1 repo的定位:不只是“多仓库版Git”

很多人刚听到repo的反应是:“这不就是Git多仓库管理工具吗?”严格来说这个理解不准确。Git解决的是单个项目里的版本控制问题,而repo解决的是“一堆Git仓库如何按照指定版本组合在一起”的问题。Android源码树由几百上千个独立Git仓库组成,每个仓库都有自己的提交历史、分支和标签,但发布某个系统版本时,必须把所有这些仓库锁定到互相兼容的commit上。这个“锁定组合”的工作,就是repo的核心职责。

打个比方,Git是你手里的工具箱,repo则是整个工地的施工总调度。你负责的每一个独立模块可以各自迭代,但到了打地基、盖楼的时候,调度员会按图纸把每个模块调到指定的工程版本。这里的“图纸”就是manifest文件,它明明白白写着每个仓库该指向哪个分支、哪个提交,甚至哪个远程地址。理解了这一点,就明白为什么pull一个AOSP工程时,第一步永远是repo init——它在拉取那张“图纸”。

1.2 一套完整编译环境的组成清单

配置repo编译环境,远远不止“装好repo命令”这么简单。我在实际项目里见过太多人卡在后面的编译阶段,回头排查才发现是基础环境有隐患。一套完整的、能跑通AOSP编译的环境,至少要包含下面几块:

组成部分建议配置说明
操作系统Ubuntu 20.04 / 22.04 64位Google官方以Ubuntu/Debian系为基准,依赖包最顺手
CPU8核以上并行编译和repo sync都吃多核,核心越多用时越短
内存16GB起步,建议32GB低于8GB编译时极容易OOM,加swap都救不回来
磁盘至少200GB可用空间,SSD源码加编译产物轻松超过100GB,机械硬盘同步时想哭
文件系统ext4,大小写敏感Android构建依赖大小写敏感,NTFS/exFAT挂载盘直接劝退
基础工具Git、Python 3.6+、curlrepo 2.x依赖Python 3,老教程里Python 2早就过时了
JDK版本随源码版本走后面会专门讲对应关系,装错版本报错到你怀疑人生
repo工具2.x最新版建议从官方渠道下载,安装步骤见下文

很多人忽视文件系统这一点。Android编译系统假设文件系统区分大小写,所以同目录下a.txt和A.txt是两个文件。ext4默认大小写敏感没问题,但如果你把源码放在NTFS移动硬盘或exFAT的U盘上,编译时会出现各种莫名其妙“file not found”或头文件找不到的报错。这一条属于“隐藏炸弹”,等到构建中期才炸。

1.3 动手前的环境自检

正式配置之前,我习惯先跑一轮快速自检,把基础项一次过一遍,省得后面抓瞎:

# 系统版本 lsb_release -a # Python版本,repo 2.x要求3.6以上 python3 --version # Git版本 git --version # 磁盘空间 df -h # 内存 free -h # 文件句柄限制,太小会引发莫名其妙的IO报错 ulimit -n

这一轮检查的意义不是走形式,而是把“环境问题”和“操作问题”隔离开。后面repo init、repo sync出错时,你至少可以确认基础面是干净的,排查范围一下就缩小了。我在给别人远程排障时,第一句话永远是:“先跑一遍这个自检,把输出发我。”

2. 安装repo与初始化仓库:4个步骤搞定基础配置

2.1 安装repo:一条命令的事,但细节藏坑

repo本质上是一个Python脚本,官方推荐把它放到用户目录下的~/bin里。安装命令本身非常短:

mkdir -p ~/bin curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo chmod +x ~/bin/repo export PATH=~/bin:$PATH echo 'export PATH=~/bin:$PATH' >> ~/.bashrc source ~/.bashrc

把repo放进~/bin而不是直接丢到/usr/local/bin,是我个人比较推荐的做法。用户级工具就该待在自己家里,系统级升级不会误覆盖,换机器迁移时整个~/bin拷走就行。当然,直接放/usr/local/bin也完全没问题,纯粹看个人偏好。

这里有一个隐蔽的坑:repo脚本的第一行shebang默认是#!/usr/bin/env python(老版本)或#!/usr/bin/env python3(新版本)。如果你的系统里还存在Python 2,并且python这个命令还指向它,那么执行repo时会直接崩,报错往往是SyntaxError: invalid syntax。遇到这种情况,先查一下repo脚本第一行,把它改成#!/usr/bin/env python3,或者干脆用python3 ~/bin/repo显式调用。这个坑在新手群里出现频率非常高。

2.2 repo init:初始化清单仓库

装好repo后,下一步就是拉取manifest清单。我把操作目录固定为~/aosp,你也可以换成任意路径,但注意别放在有中文和空格的路径下,后面构建脚本对路径敏感:

mkdir -p ~/aosp cd ~/aosp repo init -u https://android.googlesource.com/platform/manifest -b android-13.0.0_r1

这里三个参数各有用意:-u指定manifest仓库的Git地址,Android官方清单仓库就放在这个地址下;-b指定分支或标签,android-13.0.0_r1是Android 13的release标签之一,具体版本号可以到官方release分支列表里查,选一个你目标设备对应的版本;如果不加-b,默认拉取master主线,那是给贡献代码的人用的,天天在变,不适合做稳定构建。

repo init执行完后,目录下会生成一个.repo隐藏目录,里面保存了manifest仓库的本地副本、符号链接manifest.xml以及后续同步Git对象用的元数据。这时候源码目录还是空的,因为你只是拿到了“图纸”,还没开始“施工”。repo init本身很快,几秒钟到十几秒,除非网络环境异常。

2.3 读懂manifest文件:为什么local_manifests是定制神器

.repo/manifests目录里放着清单仓库的完整内容,其中default.xml就是那张核心图纸。我截取一个简化片段帮助理解:

<manifest> <remote name="aosp" fetch="https://android.googlesource.com" /> <default revision="refs/tags/android-13.0.0_r1" remote="aosp" sync-j="4" /> <project path="build/make" name="platform/build" groups="pdk" /> <project path="frameworks/base" name="platform/frameworks/base" groups="pdk-cw-fs" /> <project path="packages/apps/Settings" name="platform/packages/apps/Settings" /> </manifest>

每个<project>标签里的path决定源码在本地目录树中的位置,name决定从远程Git服务器拉取的仓库名。init -b指定的分支会被写进<default>的revision属性。所以说,repo init -b android-13.0.0_r1实际上做的事情是:拉下manifest仓库本身,然后让default.xml里的所有project都指向这个release标签对应的提交。

真正的进阶玩法是local_manifests。很多人不知道,.repo目录下可以建一个local_manifests文件夹,里面放自定义xml文件,repo sync时自动合并到整体清单里。做ROM定制、加私有vendor仓库、替换某个仓库的远程地址,都靠它。比如你要加入自己公司的配套仓库,只需要新建.repo/local_manifests/local_manifest.xml:

<?xml version="1.0" encoding="UTF-8"?> <manifest> <remote name="company" fetch="https://git.example.com/" /> <project path="vendor/company" name="vendor_company" remote="company" revision="main" /> </manifest>

下次repo sync就会自动把vendor/company拉下来。这是最干净的做法,千万不要去改官方default.xml——下次repo sync就可能被覆盖或者产生冲突,维护起来全是眼泪。

3. 源码同步实战:repo sync参数详解与编译环境检查

3.1 repo sync的常用参数:别傻傻地全默认

初始化完成后,核心操作就是同步源码。我常用的完整命令长这样:

repo sync -j8 -c -d --no-tags

逐个说参数:-j8表示同时启动8个并发同步任务,这个数字不是越大越好。同步瓶颈通常在磁盘IO和网络带宽,开到二三十的时候,经常出现一堆子进程排队等待,反而更容易触发个别仓库超时失败。我个人经验是8到16之间比较稳。-c表示只同步当前清单中指定的分支历史,不把每个仓库的所有分支都拉下来,能省大量时间和磁盘空间。-d强制切回清单指定的revision,防止本地残留的检出状态污染整体构建。--no-tags跳过分发各仓库的标签对象,对普通构建没人关心标签,省下的时间和网络流量很可观。

首次全量同步AOSP android-13,在千兆有线网络加SSD的环境下,大概需要一到两个小时。如果你用机械盘或网络波动大,跑三五个小时也不稀奇。所以我的建议是:第一次同步时,用tmux或nohup把进程挂到后台,人不用死盯终端。同步中途断了也没关系,直接再执行一次repo sync,它会从断点继续,这个设计在早期版本里不完善,但现在已经很成熟了。

3.2 编译前系统依赖安装:一个apt命令解决大部分问题

源码同步完之后,别急着编译,先检查系统依赖包。Ubuntu 22.04上,AOSP构建官方要求的那堆依赖包,一条命令基本能装齐:

sudo apt-get update sudo apt-get install git-core gnupg flex bison build-essential zip curl zlib1g-dev \ libc6-dev-i386 libncurses5-dev lib32z1-dev x11proto-core-dev libx11-dev \ libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig

这里有几个细节值得说。第一,build-essential必须装,它里面包含gcc、g++、make这些编译工具链,很多新人漏了这个就跑去跑make,然后被幺蛾子报错淹没。第二,老版本Ubuntu上能直接装libncurses5-dev,但22.04的默认源里可能已经移除了这个老包,如果报“Unable to locate package”,就用apt-cache search ncurses确认当前源里可用版本,装libncurses dev的最新兼容包即可,不必硬磕老版本号。第三,libc6-dev-i386也常被遗漏,它是32位兼容库的一部分,某些构建工具链需要它。

至于JDK版本,我用一张表总结对应关系,具体的官方要求以source.android.com上的构建要求页面为准:

源码版本JDK要求示例repo分支
Android 8.0及以下OpenJDK 8android-8.1.0_rXX
Android 9 ~ Android 13OpenJDK 11android-13.0.0_rXX
Android 14及以上OpenJDK 17android-14.0.0_rXX

装错JDK版本的典型症状是:编译到某个阶段,javac或jack报出一串完全看不懂的类型错误、Unsupported class file version之类。这时候别去查代码,先检查java -version,大概率是JDK版本和源码要求不匹配。

3.3 ccache:让增量编译快出一个数量级

规模稍大一点的编译工程,ccache是必须配的。它对C/C++编译结果做缓存,第二次构建时如果源文件没变,直接命中缓存跳过编译步骤。AOSP本身在prebuilts/misc/linux-x86/ccache/目录下带了ccache可执行文件,所以不需要额外装,只需要在编译前设置环境变量:

export USE_CCACHE=1 export CCACHE_EXEC=/usr/bin/ccache prebuilts/misc/linux-x86/ccache/ccache -M 50G source build/envsetup.sh lunch <target> make -j$(nproc)

ccache -M 50G是设置缓存上限50GB,这个量级对AOSP的增量构建比较合理。初次完整编译还是要等几个小时到十几个小时,视机器性能而定,但编译过一次之后,哪怕只改动一小块代码再重新构建,速度快得会让你怀疑是不是编译出错了。强烈建议把这个环境变量写进~/.bashrc,免得每次开新终端忘了导出。

3.4 不只手机:repo环境在各类定制系统里的通用性

这套环境配置好之后,不只是Google官方AOSP能用。很多基于Android或Linux的定制系统工程项目,只要源码是用repo管理的,走的流程一模一样。比如某些类Switch掌机、电视盒子、平板、车载方案的项目,其manifest通常由方案商提供,你只需要把repo init里的-u换成它们给的manifest仓库地址,后面sync、lunch、make的套路完全相同。这类项目里,vendor和device相关仓库往往闭源,由硬件方案商单独提供,你在local_manifests里把它们引进来就行。所以“配置repo编译环境”这个技能是通用的,掌握一次,后续换项目只是换URL和分支号的事。

4. 高频报错实录:从repo init到make的排查手册

4.1 repo: command not found

这个报错几乎每天都有人在群里问。排查顺序很固定:先echo $PATH看~/bin在不在路径里;再ls -l ~/bin/repo确认文件存在且有-rwxr-xr-x权限;最后source ~/.bashrc让刚写入的PATH生效。还有一个低级的坑:新开的终端窗口不会自动加载刚才改的.bashrc,必须重新打开一个终端或手动source。如果都正常还报not found,检查一下repo文件内容是否完整,curl下载中断会留下一个几KB的残缺文件,执行时就会报语法错误。这种情况重新下载覆盖就好。

4.2 repo init阶段就报错:多半是Python或证书问题

repo init一执行就报SyntaxError,九成是repo脚本被Python 2解释执行了。按前文方法改shebang或显式用python3调用即可。另一个常见报错是repo: error: SSL certificate problem: unable to get local issuer certificate,这通常不是网络问题,而是系统CA证书过期或时间不同步。先跑sudo apt-get install ca-certificates更新证书,再用date确认系统时间正确。证书和时间这类基础项出问题,不只是repo,后面git拉取、curl下载都会连环爆。

还有一个需要留意的情况:repo init提示RuntimeError: Python version X.Y is too old。这说明repo版本太新,而你系统的Python版本不满足最低要求。升级Python到3.6以上通常能解决。如果不想动系统默认Python,也可以用虚拟环境装一个高版本,把~/bin/repo的shebang指过去,灵活度更高。

4.3 repo sync阶段中断、对象损坏的处理

同步过程中最常见的崩溃是磁盘满了。很多人只在df -h里扫一眼,其实根目录满和家目录所在分区满不是一回事。我见过最离谱的情况是/分区只剩几百MB,源码放在/home但缓存和临时文件全往/tmp写,同步到一半直接报No space left on device。先确认源码所在分区的剩余空间,再确认~/.cache和/tmp所在分区,该清理清理,该换盘换盘。

磁盘没满但某些仓库报error: object file is empty或fatal: Not a git repository,这说明单个仓库的本地git对象损坏了。常规手段是只针对出错仓库重同步:

# 删除损坏的源码目录(比如frameworks/base) rm -rf frameworks/base # 单独重新同步这个仓库 repo sync frameworks/base -j4

rm -rf frameworks/base只会删除工作树,.repo里的元数据还在,所以repo sync能把它重新拉回来,不会影响其他仓库。如果.repo/projects/frameworks/base里的对象也坏了,那才需要连.repo里的对应项目目录一起删。这一招是保底手段,正常情况别乱动.repo。

另外,如果本地改过某些源码文件导致repo sync失败,加--force-sync可以强制重置到清单状态。这个参数有破坏性,会把本地未提交的修改丢弃,用之前一定确认自己没在改重要代码。

4.4 编译阶段的四个经典坑

第一个坑:权限。源码目录如果之前用sudo操作过,属主变成root,普通用户编译时就会疯狂报Permission denied。解决办法是把整个源码目录chown回当前用户,全程不要用sudo make。

第二个坑:内存不足。编译中突然提示Killed,或者cc1: out of memory,说明物理内存被吃满了。优先降并发,比如make -j4而不是make -j$(nproc)。如果机器只有8GB内存,硬扛AOSP编译体验极差,老老实实加内存或者加swap,但swap只能救急,救不了长期。

第三个坑:磁盘满。编译产物比源码还肥,out/目录轻松几十GB。编译前就规划好空间,编译中定期df -h看一眼。ccache也会占地方,用ccache -M限制上限,别让它无限膨胀。

第四个坑:JDK版本不匹配。前文提过,旧源码配新JDK会报Unsupported class file major version之类的错。很多人一看到class version就以为是jar包损坏,其实是Java版本对不上。按官方要求装对应JDK,比硬调参数靠谱得多。

5. 锦上添花:repo汉化补丁到底值不值得装

5.1 repo汉化补丁的本质:一场输出翻译

repo平时在终端里输出的全是英文,像Fetching project: 12%、Repo command succeeded、warning: uncommitted changes这些。对英语不好的新手来说,本来就被各种编译日志搞得头大,这些英文提示更加重心理负担。社区里流传的repo汉化补丁、repo汉化包,本质就是把repo这个Python脚本里的输出字符串替换成中文,让你看到的是中文提示。

它确实有存在价值。给非英语背景的新员工做培训时,汉化后的输出一眼能看懂流程走到哪了,教学效率高不少。对纯英文环境无感的老手,装不装都无所谓。需要明确的是,汉化补丁是社区爱好者维护的,不是Google官方出品。它通常基于某个特定版本的repo脚本制作,所以你升级repo后,汉化可能失效,严重时还会因为字符串处理逻辑不匹配导致脚本报错。

5.2 安装repo汉化包的步骤与回滚

如果你确实想体验一下,我的建议是严格按这个流程来,避免翻车:

# 第一步:先备份原版repo,这是回滚的唯一凭证 cp ~/bin/repo ~/bin/repo.bak # 第二步:查看当前repo版本 repo --version # 第三步:从可信任的社区源下载与你版本匹配的汉化包 # 替换或patch到~/bin/repo脚本上 # 第四步:验证还能正常运行 repo --version repo help

这里必须多说一句安全底线。repo是一个有完整执行能力的Python脚本,拿到任何来路不明的repo版本或补丁,一定要先打开看看内容,或者用diff ~/bin/repo ~/bin/repo.bak对比改动范围,确认只涉及输出字符串的翻译,而不是被植入乱七八糟的逻辑。这个原则适用于所有开发工具,不只是repo。版本不匹配导致脚本报错时,回滚就很简单:mv ~/bin/repo.bak ~/bin/repo,一条命令回到解放前。

5.3 我的实际使用建议

实际项目里,我个人更倾向用官方原版repo,不装汉化。原因很简单:官方repo报错时,任何一个错误信息都可以粘到搜索框里找到成百上千条讨论帖,汉化后的报错反而没人认识。汉化包更适合教学演示、适合刚入门的同学在看懂流程的阶段使用,不适合成为长期生产环境的依赖。真要提升日常效率,与其依赖汉化,不如把repo start、repo sync、repo abandon、repo forall这几个高频子命令的英文本身用熟,配合repo help查阅,用不了几天就顺了。

配置repo编译环境这件事,说难不难,说简单也不简单。我自己这些年折腾下来最大的体会是:八成问题出在基础环境没对齐,Python版本、JDK版本、磁盘空间、文件系统大小写,这些检查项一次性过掉,后面能少踩九成坑。真正跑通一次repo sync和完整编译之后,你对Android这套构建体系的理解会上一个台阶。建议新人第一次别贪大版本,挑一个成熟的release分支先跑起来,哪怕从repo help开始一行行读。最后再分享一个小技巧:每次改完local_manifests或者切换分支,先用repo sync -c -d跑一遍再动手改代码,能帮你省掉很多“为什么我改的没生效”的烦恼。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询