ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

macOS编译Chromium 144:源码获取全流程与踩坑实录

macOS编译Chromium 144:源码获取全流程与踩坑实录

在 macOS 上编译 Chromium 144,很多人以为最煎熬的是最后那一个多小时的编译过程,实际上我一路做下来,真正劝退一半人的是更靠前的“获取源代码”这一步。Chromium 不是git clone完就能就地开编的普通开源仓库,它的源码分散在几百个依赖仓库里,拿到一份版本匹配、结构完整、工具链就绪的源码树,本身就是一门手艺活。这篇文章是 Chromium 144 编译指南 macOS 篇的第四篇,预期读者已经看完了前面几篇的环境准备和工具链规划,接下来我们要解决的核心问题只有一个:把 144 对应版本的真源码,稳妥地放到你的 Mac 上。

这篇文章会按我实际操作的顺序展开:先讲为什么 Chromium 源码不能简单 clone,再讲 macOS 上必须确认的前置条件,然后是 depot_tools 的安装、fetch 完整流程、切到 144 版本的具体命令,最后是我自己在多台 Mac 上反复拉取时踩过的坑和验证手段。这篇偏实操,每个命令你都可以直接抄,但我也尽量把“为什么这么做”讲清楚,毕竟这条链路里任何一步报错,背后都是某个机制在起作用。

1. 为什么 Chromium 源码不能靠一条 git clone 拿全

1.1 你 clone 到的只是一个壳

Chromium 主仓库chromium/src.git里放的是核心代码框架和一部分源码,但真正的完整工程远不止这些。打开初始化的源码树,你会发现大量third_party目录,里面是各种第三方库、测试资源、预编译工具。这些内容并不都托管在主仓库里,而是分散在数量庞大的独立 git 仓库中。

那系统怎么知道该拿哪些仓库、拿哪个版本?答案在根目录的DEPS文件里。它像一张购物清单,声明了每个依赖的仓库地址、版本哈希、需要执行的钩子命令。Chromium 的构建是极度版本锁定的:主仓代码和依赖代码必须严格匹配,哪怕一个第三方库的提交哈希差了几十个 commit,构建结果都可能千奇百怪。所以“获取源代码”这个动作,本质上是“按照 DEPS 清单,把主仓和所有依赖一起同步到同一时刻的状态”。

你可以把整个过程理解成装一台品牌整机:git clone只是拿到了机箱,DEPS是配置单,而 gclient 才是那个按照配置单把主板、内存、硬盘逐件装进机箱的人。只看机箱,你什么都装不了。

1.2 gclient 是组装这一切的编排器

Chromium 官方工具集 depot_tools 里,负责同步的就是gclient。它不是简单的下载器,而是一个编排器:解析 DEPS,按依赖关系递归同步所有子仓库,再执行 DEPS 里声明的 hooks(钩子)。hooks 通常负责下载外部预编译产物、生成某些数据文件、准备工具链,比如后续编译要用的 clang。

这就是为什么正确姿势是fetch chromium或gclient sync,而不是自己跑去 git clone 主仓。直接 clone 主仓再编,你缺的依赖数量会多到怀疑人生。我第一次接触 Chromium 时就犯过这个错,clone 完主仓直接跑编译脚本,报错信息里全是“找不到这个头文件、找不到那个库”,后来才明白是依赖树根本没组装起来。

顺带说一句,fetch本身也只是 gclient 的一层封装:它负责创建.gclient配置文件,然后调用gclient sync做真正的同步。理解了这层关系,后面排查问题时你就知道该查谁了。

2. macOS 前置环境:四件事没确认,别急着 fetch

很多人在“获取源代码”这步翻车,不是因为命令不对,而是环境里埋着雷。以下四件事我在不同机器上都遇到过,拉取源码前值得花十分钟确认。

2.1 Xcode 与 Command Line Tools:版本太低会直接报错

Chromium 每个里程碑都在提高对工具链的要求。144 这个版本已经不是随便一个旧 Xcode 能编的了,建议直接把 macOS 上最新稳定版 Xcode 装好,并确认 Command Line Tools 也存在。装完 Xcode 后,在终端里运行:

xcode-select -p xcodebuild -version

xcode-select -p应该输出 Xcode 的 Developer 目录路径,如果没有输出或者提示找不到,就用xcode-select --install安装命令行工具。这里的关键是 Xcode 自带的 SDK 和xcrun工具链,Chromium 构建脚本会依赖 SDKROOT 找系统库,SDK 版本不对或者路径没切换对,后面 gn gen 阶段就会先崩一波。

我的建议是:不要用单独的Command Line Tools顶着不装完整 Xcode。某些场景下只装命令行工具也能跑,但 Chromium 这种体量的项目,对 Xcode 私有框架和 SDK 布局的依赖很深,缺一个完整 Xcode 会带来很多隐性风险。

2.2 磁盘空间与文件系统:默认卷可能让你白忙一场

先给数值:源码加依赖本身就占 20-30 GB,如果你选了完整 git 历史,.git目录还会再膨胀几 GB。这还只是开始,后续编译产物、缓存、中间文件会再吃掉几十 GB。我个人的建议是预留 120 GB 以上的可用空间,低于这个数,拉取或编译到一半磁盘满的风险非常高。

比空间更隐蔽的是文件系统的大小写设置。macOS 默认的 APFS 卷是大小写不敏感的,但 Chromium 源码树里存在一些名字只用大小写区分的文件,大小写不敏感的文件系统会让它们互相覆盖。这种问题在拉取阶段几乎不会暴露,等编译到一半才会以“文件找不到、符号冲突”等诡异方式炸开,排查成本极高。

我吃过这个亏之后,现在每台机器都先建一个“区分大小写”的专用 APFS 卷,再把源码放在这个卷上。操作很简单:打开 macOS 自带的“磁盘工具”,选中现有系统磁盘的容器,点“添加宗卷”,格式选择“APFS(区分大小写)”,名字比如ChromiumDev。新建的卷会挂载在/Volumes/ChromiumDev,后续所有工作都在这下面做。不要嫌麻烦,这一步能帮你省掉未来几十个小时的排错时间。

# 确认新卷可用 df -h /Volumes/ChromiumDev

2.3 Git、Python 与 PATH 卫生

Chromium 构建链路对 git 和 Python 版本有基本要求。git 不能太老,建议至少 2.39 以上;Python 方面,depot_tools 会自带一套引导工具链,不强依赖系统 Python,但如果你机器上装了其他包管理器维护的 Python,PATH 顺序不对时会干扰它的vpython虚拟环境。

我的习惯是把 depot_tools 所在目录放到 PATH 最前面,同时在~/.zshrc里不要用它之后安装的其他开发工具目录。见过不少案例,问题根源就是机器上某个旧 Python 或旧 git 被提前塞进了 PATH,depot_tools 内部的脚本调用到了错误版本,报错信息还特别像“源码损坏”。

# 检查当前生效版本 git --version python3 --version

如果机器上是 zsh,记得改完 PATH 后source ~/.zshrc或者新开一个终端窗口,避免“我明明配了为什么不行”的乌龙。

3. 安装 depot_tools:第一次接触 Chromium 的“包管理器”

3.1 depot_tools 里到底有什么

depot_tools 是 Chromium 开发工具集的统称,里面包括后面会用到的几个核心命令:gclient(依赖同步编排)、gn(构建文件生成)、ninja(构建执行)、cipd(包分发)、fetch(拉取入口)等。它不是一个单一工具,而是一整套配合 Chromium 工程体系的工具链。

很多人误以为只有拉源码才需要 depot_tools,其实编译阶段同样离不开gn和ninja。所以这一套工具装好后,后面几篇博客会反复用到,值得一次性把它弄明白。

3.2 克隆与 PATH 配置

安装 depot_tools 本身很简单,官方就是一个 git 仓库:

git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git ~/depot_tools

克隆完成后,把它的目录加入 PATH。以 zsh 为例:

echo 'export PATH="$HOME/depot_tools:$PATH"' >> ~/.zshrc source ~/.zshrc

这里有两个注意点:第一,克隆目录路径里不要有空格、中文、特殊符号,否则后面很多脚本拼接路径时会出幺蛾子;第二,尽量把~/depot_tools放在 PATH 靠前位置,保证呼叫系统里的gclient时用到的是 depot_tools 自带的版本,而不是什么别的东西。

3.3 验证工具链是否就绪

装完先快速验证:

which fetch which gclient fetch --version 2>/dev/null || true

第一次运行gclient时,它会自动下载 CIPD 客户端和自带的 Python 引导环境,这个阶段会需要一点时间,也会在终端输出大量日志。初次看到别慌,这不是报错,是在自举。如果这个阶段卡住或者报写入权限错误,多半是~/depot_tools所在目录的权限有问题,检查一下用户对目录有写权限即可。

等工具就绪后,就可以进入正题了。

4. 正式拉取:从 fetch 到同步依赖的完整命令序列

4.1 选择 fetch chromium 还是 fetch --nohooks chromium

拿到 depot_tools 后,第一步是创建工作目录并执行 fetch。最常用的两条命令是:

mkdir ~/chromium && cd ~/chromium fetch chromium

和:

fetch --nohooks chromium

区别在于后者只同步代码和依赖仓库,不执行 DEPS 里定义的 hooks。我强烈建议第一次拉取用--nohooks,把“拿代码”和“跑钩子”两件事拆开。原因很简单:这条链路任何一个环节出错,分开跑能让你迅速定位问题是在网络同步还是工具下载,而不是混在一起互相干扰。

对比项fetch chromiumfetch --nohooks chromium
同步依赖仓库是是
执行 hooks(下载工具链等)是否,需之后手动 runhooks
出问题时定位难度较高,日志混杂较低,阶段清晰
适合场景网络极稳、想一步到位首次拉取、网络一般、想稳一点

4.2 完整历史还是浅克隆

fetch 还支持--no-history参数,也就是浅克隆。浅克隆只拉取最新的提交快照,不保留完整历史,下载量和时间都会明显减少。如果你只是想把 Chromium 144 编译出来跑一跑,或者只是初步了解构建流程,完全可以用浅克隆。

但要注意,浅克隆之后切换具体版本 tag 时会受限。Chromium 的 tag 散布在历史里,浅克隆没有完整历史,git fetch --tags origin可能拉不全。如果你确定要切到 144 的某个具体版本,建议要么直接保留完整历史,要么等 fetch 完成后再git fetch --unshallow。别把浅克隆当成唯一真理,它的省时是有代价的。

我的倾向是:只要你磁盘空间不是特别紧张,第一次拉取就保留完整历史。后面你想研究某个历史提交、对照不同版本之间的差异、或者以后升级到新版本,完整历史能省下很多重新拉取的功夫。

# 浅克隆方式(磁盘紧张或网络慢时使用) fetch --nohooks --no-history chromium

4.3 切换到 Chromium 144 这个版本

fetch 完成后,默认源码树在主分支(滚动版)上。要编 Chromium 144,就得切到对应里程碑。这里有一个关键认知:Chromium 的 144 不是主分支,而是一个功能冻结后的发布分支或 tag。滚动版代码每天都在变,适合追新但不适合稳定复现。

切换到 144 的标准做法是找到对应的 tag 或分支 ref。我实际操作时一般这样:

cd ~/chromium/src # 拉取远端标签 git fetch --tags origin # 看看 144 开头的 tag 有哪些 git tag -l "144.0.*" # 如果本地 tag 列表为空,直接查远端 git ls-remote --tags origin | grep "144.0"

输出里会看到类似144.0.xxxx.x这样的 tag。不同小版本号代表不同的修复级别,通常越新的补丁越多。选一个你需要的,然后基于它建本地分支:

git checkout -b build_144 144.0.xxxx.x

注意:上面命令里的144.0.xxxx.x要换成你实际查询到的 tag 全名,以git ls-remote的输出为准,不要硬抄网上的数字。切完主仓库后,别急着高兴,必须再跑一次依赖同步:

gclient sync -D --with_branch_heads

-D的作用是清理不属于当前分支的游离目录,避免上一分支残留的文件污染新版本;--with_branch_heads会让 gclient 为依赖仓库也抓取分支引用,切版本时更稳定。这一步会把所有第三方依赖锁定到与 144 分支匹配的版本,是整个版本切换里最不可省略的一环。

4.4 hooks 的作用与手动触发

之前如果用了--nohooks,现在要手动执行钩子。钩子是 DEPS 文件里定义的命令,主要用来下载 clang 工具链、准备 macOS 构建所需的辅助工具、生成一些必要的脚本和数据文件。命令是:

gclient runhooks

这一步同样会下载不少内容,耗时视网络而定。看到这一步说明源码和依赖已经齐了,只剩外部工具链的最后准备。跑完runhooks后,理论上源码获取阶段就算完整走通了,可以进入编译前的构建配置阶段。

5. 我在 macOS 上拉取源码时踩过的坑:完整排查链路

这一节都是真金白银的教训,按出现频率排序。每一条我都尽量还原现象、根因和解决步骤,遇到同类问题的读者可以直接对照。

5.1 fetch 中途失败:别反复 fetch,用 gclient sync 续传

现象:fetch 进行到一半,终端输出停住不动,或者直接报Failed to fetch退出。重试时发现目录已经存在,fetch 再次运行直接拒绝执行。

根因:Chromium 的同步是按项目分批进行的,gclient 会记录已完成的部分。再次同步时它会跳过已完成项目,只补没完成的,所以天然具备续传能力。很多人看到失败就删掉整个目录重新 fetch,白白浪费前面几小时的下载量。

正确处理:进入已经存在的src目录,直接跑:

cd ~/chromium/src gclient sync --nohooks

它会继续同步剩下的依赖,而不是从头再来。如果还失败,就再试一次。网络波动导致的单次失败非常常见,gclient 的重试机制基本能扛过去,关键是不要手贱删目录。

5.2 hooks 阶段必踩的坑:工具下载半路失败

现象:代码同步全部完成,最后gclient sync在 “Running hooks” 部分报错,日志里经常出现类似download_from_google_storage returned non-zero exit status或 clang 相关下载超时的字样。

根因:hooks 会拉取预编译的编译器和其他外部文件。这些文件体积大,对网络要求高,稍微抖动就会中断,而 gclient 把 hooks 放在同步的最后,失败后整个sync退出码就不是 0,容易误以为源码坏了。

处理方式:源码本身没问题,缺的只是 hooks 要下载的工具。单独重跑:

gclient runhooks

如果反复失败,优先检查三类原因:一是磁盘空间,hooks 下载的内容会落到缓存目录;二是目标目录权限;三是 PATH 环境是否干净,hook 脚本执行时会继承当前 shell 环境。还有一个小技巧:hooks 下载完成的工具会缓存在本地,失败重跑时不会重新下载已经成功的部分,所以多试几次并不会无限翻倍耗时。

5.3 大小写不敏感文件系统引发的“幽灵问题”

现象:拉取阶段一切正常,编译到一半突然报“文件找不到”,但你用 Finder 看,文件明明就在那个路径下。有时候是头文件冲突,有时候是符号重定义,错误信息千奇百怪,没有统一规律。

根因:macOS 默认卷大小写不敏感,而 Chromium 依赖树里存在文件名只靠大小写区分的文件。在大小写不敏感卷上,这些文件会互相覆盖,编到哪一步被影响完全随机,所以叫“幽灵问题”。最致命的是它不在拉取阶段暴露,等你在编译上投入几十分钟后才炸。

解决:提前用“磁盘工具”创建区分大小写的 APFS 卷,把所有源码放在该卷上。不要在建在默认卷上的~/chromium目录里将就,真出问题再迁移,几十 GB 的搬动成本会让你后悔。卷建好后,把源码目录挂到新卷:

cd /Volumes/ChromiumDev fetch --nohooks chromium

后续所有章节的命令都基于/Volumes/ChromiumDev下的源码树,不再使用默认目录。

5.4 磁盘空间误判与“no space left”

现象:df -h看到还有几十 GB 空闲,但 fetch 或 runhooks 中途报磁盘写满。

根因:APFS 是容器式管理,空闲空间要在整个容器内共享;本地快照、系统缓存、镜像文件都会悄悄占掉连续写入需要的空间。加上编译阶段产物特别大,空间计算绝不能只看单目录大小。

排查链路:

df -h /Volumes/ChromiumDev du -sh /Volumes/ChromiumDev/src

如果src已经吃掉 30 GB,而卷的总空间只剩 40 GB,那基本可以断定后面编译会卡壳。我的经验值是:拉取完成时,卷剩余空间最好还有 80 GB 以上。空间不够的,先清理缓存、旧版本源码,再考虑扩容卷,不要带着隐患往下走。

6. 源码就位后:怎么确认你拿到的是 Chromium 144

6.1 三条验证命令,缺一不可

源码和依赖都同步完,我一般用三条命令确认版本,而不是只凭感觉:

cat /Volumes/ChromiumDev/src/chrome/VERSION

chrome/VERSION文件是 Chrome 版本的源头,内容是一个四段版本号。如果切的是 144 的 tag,MAJOR=144是铁证。接着看当前分支和最近提交:

git -C /Volumes/ChromiumDev/src rev-parse --abbrev-ref HEAD git -C /Volumes/ChromiumDev/src log --oneline -1

第一条确认你在独立的build_144分支,第二条看提交哈希,方便以后对照官方 Release 信息。三条命令输出一致合理,才说明版本切换真正成功。检查chrome/VERSION时要注意,主分支上的 MAJOR 可能已经滚动到更高数字了,别一看不是 144 就以为失败了,先确认自己 checkout 的是哪个 ref。

6.2 目录速览:知道每个关键目录是干嘛的

拿到源码后,我建议花十分钟大致了解目录结构,后面查编译问题会快很多。常用目录的对应关系如下:

目录作用
src/chrome/浏览器壳层、UI 相关代码
src/content/多进程架构、渲染与进程管理核心
src/net/网络栈实现
src/third_party/第三方依赖树,由 gclient 按 DEPS 管理
src/build/构建脚本、工具链配置
src/out/编译输出目录,默认不存在,gn gen 后生成

third_party是最容易让人迷惑的目录,表面上是一堆第三方库,实际上每个子目录都对应 DEPS 里的一个条目,它们的版本由 gclient 锁定,手动去改里头的文件没有意义。out目录现在不存在是正常的,它是编译阶段的产物目录。

6.3 下一步之前建议先固化环境

源码就位后,不要急着直接开编。我会先做两件事:第一,把当前分支名、tag 名、depot_tools 路径、PATH 配置写进一个笔记文件,后续任何一步报错,都能快速对照是不是环境漂移;第二,再跑一遍gclient runhooks,确认 hooks 干净通过,这一步相当于给工具链做个体检。

我自己习惯把git ls-remote --tags origin | grep 144.0的输出存成一个文本文件,放回src目录旁边。后面一旦需要换小版本、重新对齐依赖,直接查这份记录,比重新翻网络快得多。

等这些都确认完毕,下一步就是构建系统登场了:用gn gen生成out/目录下的构建配置,再通过ninja执行编译。我在几台机器上反复拉过多个版本的源码,最深的体会是:源码获取这步看着简单,实际上整套 Chromium 工程的稳定性和可复现性都押在它上面。环境洁净、版本明确、hooks 干净,后面一切都是顺水推舟;这三样缺一样,后面每一分钟都是在为前面的偷懒还债。

具体gn gen的参数怎么配、args.gn里哪些开关最常用、不同 Mac 型号的编译设置有什么区别,就是下一篇要聊的内容了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进