Chromium 144 的源码我前前后后拉了两遍,第一次因为磁盘规划没做对,编译到一半直接把系统分区撑爆了,Xcode 版本不匹配的报错也折腾了一晚上。所以这一篇环境准备,我决定把该踩的坑提前铺开讲清楚,给准备在 macOS 上编译 Chromium 的朋友一条相对顺的路。
先说清楚这东西能做什么:Chromium 是 Google Chrome 背后的开源项目,自己编译一份 144 分支的源码,意味着你可以定制功能、去掉不需要的模块、打自己的 patch、在本地带着符号表调试,也能顺手研究一下这个千万行级别的 C++ 项目是怎么组织和构建的。适合谁看?想深入浏览器内核的客户端开发者、想定制自己的浏览器的独立开发者,以及任何对大型开源项目构建流程感兴趣的人。
这篇是第一篇,聚焦在环境准备。后面我计划接着写源码同步、GN 参数调优、编译执行和产物打包,一步一步往下推。
1. 动手之前先想清楚:为什么值得在 macOS 上折腾 Chromium 144
1.1 官方 Chrome 和自编译 Chromium 的差别
很多第一次接触的人会问:Chrome 不就能直接下载吗,为什么要自己编一份?这里面的差别其实挺大。Chromium 是开源浏览器骨架,Chrome 是在这个骨架上做了二次加工的商业产品,额外带上了 Google 品牌、自动更新组件、专有编解码器授权、Flash 时代的遗留模块(虽然已经移除得差不多了)以及一些 Google 服务集成。你拿到的 Chrome 二进制是别人编译好的,里面启用了什么特性、裁剪了什么模块,你只能通过 chrome://flags 这类入口去调整,改不了事实上的编译期行为。
自己编译 Chromium 就不一样了。你可以通过 GN 参数开启或关闭特定功能,比如 proprietary_codecs 决定是否引入 H.264/AAC 这类有专利授权的编解码器,enable_nacl 决定是否保留 Native Client 模块(现在默认趋势是关闭),enable_widevine 控制 CDM 组件。你要是想给 password manager 或者渲染管线打 patch,也必须走自编译这条路。
另外就是版本号的问题。Chromium 的版本节奏大概每 4 周一个大版本,144 这个编号在主线推进序列里已经属于比较新的分支,带着一大堆新 API 和渲染层面的改动。如果你要跟 Web 平台的特性落地进度保持同步、或者围绕某个新特性做开发,那就得跟这种新版本分支。
1.2 编译一套浏览器下来,你能得到什么
这个工程量对个人开发者来说不算小,但收获也实在。我自己编译完最大的感受是,过去对浏览器"输入 URL 到页面显示"的认知是黑盒式的,编译一次你就会被迫弄明白几个点:GN 和 Ninja 是怎么描述和调度这个上千目标的大工程的;third_party 下面几百个第三方库是如何通过 DEPS 文件被组织起来的;链接阶段为什么会那么消耗内存和磁盘;以及为什么一个浏览器的构建产物可以跑到几十 GB。
从实用角度看,如果你是做前端或者客户端的,本地有一份带源码和调试符号的 Chromium,排查浏览器行为相关的问题会直接很多。比如怀疑某个渲染行为是不是 bug,你可以直接定位到 blink 层的源码,打上断点看调用栈,比黑盒猜测强得多。如果你后续想往浏览器定制方向走,比如做一个以 Chromium 为内核的桌面应用、套壳浏览器、或者类似 Electron 但更底层的定制化方案,这套编译能力就是基本功。
1.3 这个 macOS 系列准备怎么组织
我打算把整个流程拆成几篇:环境准备(也就是本篇)、源码同步与版本管理、GN 参数详解、编译执行与增量构建、产物打包与真机部署。这样每一篇控制在可操作范围内,不至于一篇塞太多东西,读者也不用一次吸收太多信息。
这一篇的核心任务非常明确:把硬件/软件环境、工具链、目录规划、基础命令跑通,最后到你能够生成 out 目录、跑通 gn gen 为止。编译动作本身下一篇再展开。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备的整体设计与方案选型
2.1 硬件要求:磁盘、内存和 CPU 的真实底线
先说大多数人最关心的硬件配置。我自己踩过的最痛的坑就是磁盘。Chromium 的源码仓库用 git 管理,拉下完整历史的情况下 .git 目录就非常占空间,source code 本体在 10~20GB 量级,再加上 gclient sync 从 CIPD 拉下来的各类预编译工具链、测试数据、资源文件,首次同步完成后的 src 目录轻轻松松三四十 GB。这还只是源码。到了构建阶段,Debug 模式下 out 目录里生成的中间文件、符号文件、动态库,动辄七八十 GB,如果你把 symbol_level 开到 2 保留完整调试符号,一百多 GB 都挡不住。
所以我的建议是:给这个项目留出至少 200GB 的可用磁盘空间。最好是一个独立的 APFS 卷宗,或者至少单独一个目录,避免和其他工作文件混在一起。用 macOS 自带的分区工具单独建一个卷宗会有个额外好处:APFS 是动态分配空间的,不会因为预分配把其他空间卡死,配合 Time Machine 排除规则还能避免备份工具整天去读你几十 GB 的构建缓存。
内存方面,16GB 是底线,但说实话非常勉强。链接阶段 ld64 或者 lld 吃内存很厉害,尤其是生成 chrome 这个最终可执行文件时,多个大目标文件同时参与链接,内存不足会直接触发 OOM 或者 swap 风暴,编译速度断崖式下降。我实测 32GB 内存的机器在 M1 Pro 上跑 Debug 构建比较舒服,如果你用 Intel Mac,建议内存再往上加。CC 方面,核心数越多编译越快,但也不是线性关系,因为 Chromium 的构建有大量串行依赖,实际加速比大概在中低并发时就饱和了。
2.2 macOS 版本与 Xcode 版本的匹配逻辑
Chromium 项目对 macOS SDK 和 Xcode 版本是有明确要求的,不是说你装个最新版 Xcode 就一定行,也不是说你系统版本越高越稳。Chromium 官方文档里会为每个发布分支指定一个"当前可用的 Xcode 版本",这个版本通常和构建机器上实际使用的 Command Line Tools 版本挂钩,因为 macOS SDK 里很多头文件会影响编译结果。
第一次尝试时我的 Xcode 装的是比较新的 16.x,系统是较新的 macOS,按理说工具链绰绰有余,结果编译过程中出现一个和 SDK 中某些 API 标注相关的语法错误,一查才发现 Chromium 144 分支要求的 SDK baseline 和我本机 SDK 版本之间的某些头文件声明有变化。所以正确的做法是:先去 Chromium 官方文档查一下当前分支建议的 Xcode 版本,尽量匹配,不要盲目追求最新。
Command Line Tools 和完整版 Xcode 的分工也要搞清楚。Chromium 的构建脚本其实不依赖 Xcode.app 里那套 IDE,真正用的是 clang、ld、SDK 这些命令行工具,也就是 Xcode Command Line Tools 提供的部分。但实际操作中还是建议把完整版 Xcode 装上,因为 build 过程中有一步会调用 xcodebuild 来获取 SDK 路径,而且后面如果要用 Xcode 工程方式调试 Chromium,也需要完整版。只装 CLT 的情况下可能编译本身能过,但某些工具脚本跑不起来,与其卡在半路再补,不如一步到位。
2.3 为什么必须有 depot_tools
这是新人最容易疏忽的一点。Chromium 源码不是简单地 git clone 一个仓库就能编译的。它由主仓库加几百个 third_party 子仓库组成,这些子仓库的版本号统一记录在主仓库根目录的 DEPS 文件里。直接 clone 主仓库你只会拿到一份残缺的源码,而且依赖版本完全不对。
Chromium 团队为此提供了 depot_tools,这是一套 Python 写的工具集,核心就是 gclient、gn 和 ninja。gclient 负责读取 DEPS 文件,把依赖仓库同步到指定 commit,并触发 hooks 去下载 CIPD 中的预编译工具;gn 是元构建系统,你给它提供参数,它生成 Ninja 文件;Ninja 再根据构建图执行真正的编译和链接。这套流程里任何一环缺失或者版本不对,后面都会以莫名其妙的方式报错。
所以我建议把 depot_tools 理解成整个编译流程的"入口总管",它本身是个 git 仓库,需要单独 clone 到一个路径,然后把它的目录加到 PATH 里。这一步有一个看起来小但实际影响很大的细节:路径中不要有空格。自己在 ~/depot_tools 这种干净路径下,后面所有脚本都省心。
3. 实操:搭建环境的关键步骤
3.1 安装并验证 Xcode 工具链
如果你还没装过 Xcode,先去 App Store 下载并完成安装,这个过程根据网络情况可能要花一段时间。装完后不建议直接开始拉代码,先把工具链验证一遍:
bash复制xcode-select --install
xcodebuild -version
clang --version
xcode-select --install 会补齐或安装 Command Line Tools。这里要注意,即使你装了完整版 Xcode,这一步也有可能触发系统授权弹窗,需要手动确认。
然后确认 Xcode 的 license 已经被接受,否则构建脚本调用 xcodebuild 时会报 license 未接受:
bash复制sudo xcodebuild -license accept
我实际碰到过一个情况:命令行工具能正常执行,但编译到中间某一步报 xcode-select: error: tool 'xcodebuild' requires Xcode,原因就是系统里只有 Command Line Tools,没有完整 Xcode。如果你确定只做命令行编译,理论上 CLT 可能够用,但如果后续想用 Xcode 工程调试、或者跑某些需要 SDK 版本的脚本,完整版是最稳的选择。
3.2 拉取并配置 depot_tools
在用户目录下建一个专门放工具链的目录,把 depot_tools clone 下来:
bash复制cd ~
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
然后把这个目录加到 PATH 里。因为我用的是 zsh,要写入 ~/.zshrc:
bash复制export PATH="$HOME/depot_tools:$PATH"
配置完后重新打开终端,或者执行 source ~/.zshrc,然后验证:
bash复制which fetch
which gclient
能正常输出版本信息就说明 depot_tools 基本就绪。这里还建议顺手设置一个环境变量,让 Ninja 在编译结束时输出耗时统计,对判断构建瓶颈很有帮助:
bash复制export NINJA_SUMMARIZE_BUILD=1
同样是写入 ~/.zshrc。这个变量不是必须的,但我强烈建议加上,之后每次编译结束你能看到每个编译阶段的时间分布,方便排查是编译慢、链接慢还是某一步 IO 瓶颈。
3.3 初始化 Chromium 源码目录
接下来是整个流程中最耗时的一步。建议单独建一个目录放源码,注意不要和 depot_tools 混在一起:
bash复制mkdir ~/chromium
cd ~/chromium
fetch --nohooks --no-history chromium
这里两个参数我解释一下。--nohooks 表示只拉代码,暂时不执行 DEPS 里的 hooks,因为 hooks 会触发 CIPD 下载大量预编译工具,首次执行需要很长时间,分开跑更方便排查问题。--no-history 是关键,它让 git 只拉取最新的 commit 快照,不拉完整提交历史。Chromium 仓库的提交历史极其庞大,如果你的目的只是编译某个里程碑分支而不是做 git 代码考古,这个参数能把拉取时间和磁盘占用降一个量级。
fetch 执行完之后,你会得到 ~/chromium/src 目录和一个 .gclient 文件。.gclient 是 gclient 顶层配置,fetch 会在早期阶段自动生成它。不要手贱去删这个文件,后续同步依赖全靠它。
这一步的实际耗时差异非常大。它取决于网络质量、磁盘读写速度,以及 Chromium 源码仓库在你的网络环境下的连通速度。快的半小时左右,慢的可能一两个小时。这个阶段卡住不要慌,先观察输出日志,一般会停留在某个 submodule 的 clone 阶段,可以等,不要随便 ctrl-c 中断,中途反复重启反而更容易坏。
3.4 用 gclient sync 同步依赖
源码目录拉取完成后,接着同步依赖:
bash复制cd ~/chromium
gclient sync --with_branch_heads
--with_branch_heads 这个参数建议带上。它会让 git 仓库保留所有分支头引用,后面如果你想从 144 切换到其他里程碑分支做 cherry-pick 或者打补丁,这个参数能省很多事。不带的话,虽然也能切分支,但获取远端分支引用的流程会麻烦一点。
gclient sync 会做几件事:按 DEPS 文件把 third_party 下的依赖仓库同步到指定 commit;执行 hooks,从 CIPD 下载 Python、Node、各类构建工具;生成一些必要的符号链接和配置文件。如果之前 fetch 时用了 --nohooks,这一步就是真正刷工具链的时机。
这个阶段同样很慢,而且对网络稳定性的要求高。如果中途失败,不要急着删目录重建,直接重新执行 gclient sync,一般会断点续传,已经下载好的依赖不会再重新拉。我自己遇到几次 sync 失败,基本都是网络波动导致的,重复执行几次就过了。
3.5 生成第一个构建目录
依赖同步完成后,配置构建参数。先建一个 out 目录并生成 Ninja 文件:
bash复制cd ~/chromium/src
gn gen out/Debug --ide=xcode
--ide=xcode 不是必须的,但如果你之后想在 Xcode 里看代码、打断点,这个参数会生成一个 out/Debug/all.xcworkspace,可以用 Xcode 打开整个工程。注意,这个工作区文件非常大,首次用 Xcode 打开时索引很慢,如果不是要调试 ignore 掉也行。
gn gen 结束后,会生成 out/Debug/args.gn 文件。这个文件就是所有构建参数的总入口,直接编辑它然后重新 gn gen 就能应用新参数。下一篇我会重点展开 args.gn 的调优,这一篇先给一个能跑通的最小配置。
4. 构建参数配置:找到适合自己的 gn 参数
4.1 GN 与 Ninja 在编译流程里扮演什么角色
先花点时间把概念理顺。Chromium 不用传统 Makefile,也不用 CMake,而是自己搞了一套 GN(Generate Ninja)元构建系统。你写 args.gn 描述构建目标,GN 根据 BUILD.gn 文件里的声明解析整个依赖图,最终生成 Ninja 文件。Ninja 是一个极致追求增量构建速度的构建工具,它读 GN 生成的构建图,精确知道哪个文件依赖哪个文件,改一个 .cc 文件只重编受影响的目标,链接阶段也尽可能复用缓存。
这套设计对大型 C++ 项目来说非常合理。Chrome 全量构建的目标数量级在几万个,如果每次改动都全量重编,开发效率没法看。理解 GN 和 Ninja 的分工,后面排查构建问题才不糊涂:GN 阶段的报错主要是参数错误、依赖目标缺失,Ninja 阶段的报错主要是源码编译错误、链接错误、资源文件缺失。
4.2 两套我验证过的参数方案
编辑 out/Debug/args.gn,先用一套保守配置把流程跑通:
gn复制is_debug = true
is_component_build = true
symbol_level = 1
enable_nacl = false
我来逐个解释这几个参数的意义。
is_debug = true 表示构建 Debug 版,关闭大部分编译优化,保留完整运行时检查,编译速度和链接速度明显快于 Release,非常适合日常开发和调试。代价是浏览器运行性能很差,页面加载卡顿是正常的,别怀疑自己机器有问题。
is_component_build = true 在 Debug 模式下几乎是强制选项。它把所有模块拆分成几十个动态库而非单个巨大可执行文件,链接时不再需要一次链接几百 MB 的二进制,链接时间从可能几十分钟降到几分钟,对迭代调试极其重要。但它的产物不适合作为正式发行版本,因为文件数量庞大且依赖路径关系复杂。
symbol_level = 1 控制调试符号的详细程度。2 是完整符号,便于在调试器里查看所有局部变量和类型信息;1 只保留函数名和少量信息,兼顾调试和体积;0 是不要符号,体积最小。我日常用 1,除非遇到需要深入调试的疑难 bug,才临时切回 2。
enable_nacl = false 就是前面说的,关闭 Native Client 模块。Chrome 已经在大步淘汰 NaCl/PNaCl,144 分支默认也不建议开启,关掉能节省不少编译时间和产物体积。
如果你偏向出生产可用版本,也就是类似 Chrome 日常使用的优化版本,用另一套配置:
gn复制is_debug = false
is_official_build = true
symbol_level = 0
enable_nacl = false
这里 is_official_build = true 会启用一系列面向发行的优化选项,包括 LTO、PGO 策略相关的东西,编译时间会显著拉长,如果是第一次全量编译,在主流 M 系列芯片上大概要三四个小时以上。所以我的建议是从 Debug 方案开始,先把流程跑通,再根据自己的需求切换 Release 方案。
4.3 磁盘、内存与耗时的估算
构建产物体积的估算很重要,很多人的磁盘就是在这里爆掉的。Debug + component_build + symbol_level=1 的组合,out 目录在完整构建后大约 40~60GB;如果把 component_build 改为 false,加上 symbol_level=2,光是 out 目录就可能上百 GB。Release + official_build 会小一点,但编译中间文件、LTO 临时文件、dsym 符号文件也不少,建议按 30~50GB 预估。
内存方面的经验是,Debug 模式 16GB 内存比较紧张,链接 phase 可能触发 swap,32GB 就舒服了。如果发现链接阶段 OOM,可以手动降低并发:ninja -C out/Debug -j 4 chrome,用牺牲速度换稳定性。不要一上来就 -j 32,在内存不是特别充裕的机器上反而会拖垮整个系统。
耗时上,我可以给个大致的量级参考:M1 Pro 32GB 内存,首次 Debug 全量构建大概 1.5 到 2 小时;Intel Mac 会明显更长;Release 版本翻倍是大概率事件。这个时间受磁盘读写速度影响很大,如果用的是机械硬盘或者外接 USB 2.0 盘,可能还要再加一半甚至更多。
5. 常见问题与排查技巧实录
5.1 源码下载阶段的疑难杂症
拉取源码是新手重灾区。最常见的是 fetch 阶段卡住不动,或者报某个 repository 无法访问。这个阶段的失败大多数不是代码问题,而是网络波动、连接超时导致的,处理方式很简单:重新执行 gclient sync,它会尝试从断点继续。
还有一种情况值得注意:git 缓存膨胀。如果多次中断后 .git 目录异常占空间,可以谨慎执行 git gc 清理松散对象,但这属于最后手段,平时不建议频繁操作。
另外一个容易忽略的点是证书与 git 配置。如果报 SSL 证书验证失败的错误,检查一下全局 git config 是否设置了代理相关的证书设置,这类环境残留很坑。确保仓库是原生 HTTPS 访问方式,不要夹带其他仓库的配置干扰。
注意:Chromium 的 fet 过程会下载大量资源,对网络环境要求高,必须保证网络稳定,不要频繁中断。下载速度慢的时候不建议反复重启同步,连续几小时甚至隔夜的下载都是正常现象。
5.2 Xcode 和系统环境引发的报错
xcode-select: error: tool 'xcodebuild' requires Xcode, but system integration was not configured 是我碰到最多的一类错误。原因很简单,系统只装了 Command Line Tools,没装完整 Xcode,或者装完没设置命令行工具路径。解决办法是:
bash复制sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
如果你装的是 Xcode beta 版,路径要改成对应的 beta 目录。
还有一类是 SDK 版本不匹配的编译错误。这种情况通常表现为某个系统头文件里的定义和 Chromium 源码里的声明冲突,或者找不到某个 SDK 符号。处理思路是去查当前分支的官方文档,确认该分支的要求,而不是盲目升级 Xcode。Chromium 对 SDK 的适配有滞后性,最新版 SDK 不一定兼容你的目标分支。
5.3 编译阶段的典型失败与对应处理
我把实际操作中遇到的、以及身边朋友经常问的几类编译问题整理成一张速查表,方便对号入座:
| 错误现象 | 常见原因 | 排查方向 |
|---|---|---|
编译中断,提示 fatal error: file not found |
gclient sync 未完成,依赖不完整 | 重新执行 gclient sync,确认无报错 |
| 链接阶段内存溢出或系统卡死 | 并发过高、内存不足 | ninja -j 4 降低并发,或临时增加 swap |
| 构建提示 Xcode 版本不对 | SDK 版本与分支要求不匹配 | 对照官方文档调整 Xcode 版本 |
| 生成的浏览器启动闪退 | Debug 下符号/参数配置问题 | 先确认 args.gn 参数组合合法,必要时临时改 symbol_level=0 测试 |
ninja: error: loading 'build.ninja' |
GN 参数变化后未重新生成 | 修改 args.gn 后必须重新 gn gen,不要直接 ninja |
| 磁盘空间不足 | out 目录 + 符号文件过大 | 清理旧 out 目录,降低 symbol_level,检查 .git 目录占用 |
有一个比较典型的坑,就是改了 args.gn 之后忘了重新执行 gn gen,直接跑 ninja。Ninja 不知道参数变了,还在用旧的构建图,结果就是你改了参数但行为没变化,或者编译报一些来源不明的错。记住这个顺序:改 args.gn 后必须先 gn gen,再 ninja。
还有编码和 locale 相关的报错也偶尔出现,通常是某些源文件在特定 locale 环境下解析不一致。如果你用的是非英文系统,可以试一下设置 LC_ALL=C 再编译,能避免一部分环境相关的诡异问题。
5.4 源码目录和构建产物维护的小技巧
用了一段时间之后,你可能会发现磁盘空间越来越紧张。Chromium 的 out 目录是可以安全删除的,删了下一次会重新全量构建,所以不用怕。如果只是要释放空间,也可以只删中间的 .o 文件,但建议直接删掉目录重新生成,更干净。
另外建议在开始编译前,把整个源码目录添加到文件监控排除列表里,比如 IDE 的索引目录、杀毒软件实时扫描、云盘同步目录都要排除。Chromium 源码里文件和目录数量极多,每次文件变更触发这些监听服务会白白消耗 CPU 和磁盘 IO,严重影响构建性能。
6. 环境就绪检查清单与时间成本
6.1 动手编译前,按这个清单自检
环境准备做得到不到位,不用等编译开始才知道。我总结了一套自检清单,每项都过一遍,再进入下一步也不迟:
| 检查项 | 确认方式 |
|---|---|
| Xcode 完整安装并接受许可 | xcodebuild -version 正常输出 |
| Command Line Tools 正常 | clang --version 正常输出 |
| depot_tools 在 PATH 中 | which fetch 返回路径 |
| 源码目录存在且 DEPS 已同步 | ~/chromium/src/AUTHORS 文件存在 |
| .gclient 文件存在 | ls ~/chromium/.gclient |
| 磁盘可用空间足够 | df -h / 确认剩余 > 60GB(保守) |
| args.gn 已配置并成功生成 | gn gen out/Debug 无报错 |
| Ninja 文件已生成 | ls out/Debug/build.ninja |
这个清单看起来琐碎,但每一项都对应着我踩过的坑。尤其是第一项,很多编译问题追根溯源就是工具链没就位,早点确认能省下大量排查时间。
6.2 关于时间成本的坦诚说明
首次跑 Chromium 编译,请务必做好心理准备。以我实测的 M1 Pro 32GB 机器为例,从零开始拉源码、同步依赖、Debug 全量编译,到最终跑起自定义构建的浏览器,整体消耗大约是半天到一天,其中大头在网络下载和首次编译。
如果你的目标是做深度定制,或者编译 Release 版本,时间成本还要再往上加。所以第一次操作时建议两个策略:一是先用 Debug 方案跑通全链路,不要一上来就追求 Release 优化版本;二是编译过程中不要盯着进度条焦虑,该干嘛干嘛,让它去跑。
我个人的体会是,环境准备这笔投入非常值得。后面每一次改动源码、跑增量构建、调试问题,前期准备得越扎实,过程就越顺。我这台机器现在增量编译一个模块只要几十秒,全量构建也稳定在一小时左右,这都得益于最开始把工具链和参数调到了合适状态。下一篇我准备接着写源码同步与版本切换的细节,到时候再详聊。
