ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

Chromium 145 Windows编译与运行实战指南:从GN配置到ninja构建

Chromium 145 Windows编译与运行实战指南:从GN配置到ninja构建

1. 编译前夜:最后一次确认与心理准备

1.1 磁盘、内存与时间的最后清点

按系列节奏走到这篇的读者,前六篇应该已经把系统环境、depot_tools、源码同步和GN配置都打点好了。现在离真正的Chromium 145二进制只差“编译与运行”这最后一步,但千万别急着敲ninja命令,先花十分钟检查三样东西:磁盘、内存、心理预期。

磁盘是第一个坑。Chromium 145的源码目录加上后续构建产物,整体体积比你想象的大得多。仅src目录同步完成后通常在80GB到120GB之间,而构建产物是另一笔账:

构建类型典型产物体积说明
Debug + 动态链接约40GB~60GB适合日常调试,产物体积小,但运行慢
Release + 静态链接约80GB~120GB官方浏览器接近的形态,链接最吃内存
Release + 动态链接约60GB~90GB平衡方案,二次编译快

我自己的习惯是看到剩余空间低于100GB就先停下来,因为链接阶段会一次性写入大量PDB和中间文件,中途磁盘耗尽比编译报错还难受,清理起来牵扯一堆历史产物。建议至少留出150GB的稳定余量,且确认C盘和源码所在分区是NTFS,exFAT在符号链接和文件权限上会给你挖坑。

内存和页文件放在一起说。Chromium官方文档给出16GB内存的最低建议,但以145这个体量,Release全量链接时clang和lld的瞬时内存占用可以到10GB以上,如果你还开着浏览器、编辑器、虚拟机,物理内存很容易触顶。一个务实的做法是把Windows虚拟内存(页面文件)设置为“系统管理”,然后确保C盘至少有20GB的页文件空间。很多看起来莫名其妙的链接崩溃,其实是页文件扩展太慢导致的。

时间预期方面,首次全量编译在8核16线程的机器上大概是3到5小时,16核以上可以压缩到1到2小时,但这只是平均数。受硬盘随机读写速度、散热降频、杀毒软件实时扫描的影响很大。建议把编译安排在一个“没人催你、机器空闲”的时段,比如睡前启动,第二天起来收货。过程中不要并行跑大型软件,HDD用户尤其要注意,否则CPU没吃满、磁盘却成了瓶颈,时间翻倍很正常。

1.2 args.gn配置的最后校准

编译前最值得再花五分钟确认的是out/Default/args.gn。很多第一次走到这一步的人,要么复制了网上老版本的配置,要么自己凭感觉乱填,结果编译到一半发现符号表过大、链接失败,只能从头再调参数。GN配置不需要多花哨,关键是每一项你都清楚它影响了什么。

我的145版本个人构建常用这套:

is_debug = false is_component_build = true symbol_level = 1 blink_symbol_level = 0 is_clang = true target_cpu = "x64" use_jumbo_build = true enable_nacl = false ffmpeg_branding = "Chrome" proprietary_codecs = true

逐个解释几个关键项。is_debug = false并不是“不能调试”,而是更接近正式发布形态,运行速度快、内存占用小,配合symbol_level = 1依然能拿到大部分调用栈信息。如果你主要做Blink或V8层面的代码修改调试,可以考虑is_debug = true,代价是产物体积和运行速度都会明显变差,日常验证功能用Release更舒服。

is_component_build = true这个参数经常被误解。它把庞大的静态库拆成一组DLL,链接时间大幅缩短,增量编译也快很多,代价是运行时需要依赖out/Default目录下的所有DLL。对个人开发来说,组件构建几乎全是优点,我见过因为死活要用静态链接而频繁链接失败的人,最后换成组件构建后一次通过。

use_jumbo_build = true是Ninja能更快的一个关键开关。它的原理是把多个源文件合并到一个编译单元里批量处理,大幅减少编译器进程启动次数和头文件重复解析。代价是内存压力稍微上升,对代码中全局命名冲突更敏感,但Chromium主干对这个模式的适配已经很成熟,个人构建直接开启,实测能省1/4左右的编译时间。

设置完参数后,务必回到src目录重新执行一次:

gn gen out/Default

很多人改完args.gn就直接跑ninja,结果Ninja用的还是旧配置,反复出现“No such file or directory”或者链接时缺符号。GN生成阶段会把参数真正固化到out/Default/build.ninja文件里,这一步跑完才算生效。顺手执行一遍gn args out/Default --list去核对当前生效的参数,特别是确认current_cpu = "x64"和is_component_build = true没有因为后面的配置被覆盖。

2. 正式构建:从ninja到chrome.exe

2.1 启动构建与过程解读

配置确认完毕,接下来就是直观的“点火”时刻。Chromium 145的官方推荐命令是:

autoninja -C out/Default chrome

这里用autoninja而不是直接写ninja,是因为depot_tools里的autoninja会自动根据CPU核数和内存容量换算并行任务数,通常取CPU逻辑线程数再加一部分余量,避免把机器彻底压垮。手动用ninja -j当然也可以,但绝大多数人不清楚自己的机器到底适合开几个任务,交给工具判断更靠谱。

命令敲下去之后,屏幕上会像瀑布一样滚动编译输出。外行人看是乱码,内行人看的是节奏。正常的构建输出大致分三个段落:最开始是大量“ACTION”开头的行,这是在处理构建配置、生成一些版本头文件和资源文件;随后进入漫长的“CXX”阶段,也就是真正把.cc源文件编译成目标文件;最后才是“LINK”阶段,把所有目标文件链接成chrome.exe和DLL。

编译阶段怎么判断是否卡住?看CPU占用率。如果任务管理器里几个进程的CPU占用在明显跳动,说明还在正常推进。如果长时间(超过十分钟)CPU占用掉到0%,且日志停在最后一行没有任何变化,很可能是某个进程被杀毒软件挂起,等着你手动处理。此时不用急着Ctrl+C,先检查Windows Defender的“保护历史记录”里有没有拦截记录。

构建日志也是一门学问。Ninja本身支持:

ninja -C out/Default -d explain chrome

这个命令会输出“为什么重新编译这个文件”的判定逻辑,非常适合排查“我明明改了代码,怎么没生效”的问题。如果全程没加任何参数,ninja也会把每次构建的耗时统计写在一个.ninja_log文件里,后续要分析构建瓶颈时可以直接读它。

首次构建跑完,最后一个链接步骤通常需要几分钟,此时内存会冲到很高的水位。Release + 组件构建的实际经验是32GB物理内存基本够用,16GB则要留意页文件大小。链接期间别乱动机器,尽量别触发Windows Update,曾经遇到一次链接到90%时系统自动重启,直接把整个out目录搞到需要重新编译大半,血泪教训。

2.2 增量编译:日常开发中的“真 · 常用路径”

首次构建成功之后,绝大多数人不会再频繁触发全量编译。日常改代码、调参数时,增量编译才是陪伴你最久的操作。Chromium的GN/Ninja体系在增量构建设计上做得非常精细,Ninja会记录每个源文件的哈希和依赖关系,改了一个.cc文件,只会重新编译这个文件以及直接影响它的下游目标。

增量编译的快慢和几个因素强相关:第一,构建类型。组件构建的增量明显快于静态构建,因为链接时只需要更新对应DLL,而不是重新链接整个exe。第二,是否修改了头文件。改.h文件的影响面很大,尤其是一些被几千个文件include的公共头,改动一次可能触发上千个目标重新编译,所以入行第一课往往是“不要随便改头文件”。第三,use_jumbo_build开启后,源文件合并会让“只改一个小文件”变成“整个合并单元重新编译”,增量效率反而变差。如果频繁做Blink的小改动,可以考虑关掉jumbo。

几个常用的构建目标值得记下来:

# 只构建浏览器主程序 autoninja -C out/Default chrome # 构建所有测试二进制(体积很大,按需使用) autoninja -C out/Default blink_tests # 只构建content模块测试 autoninja -C out/Default content_unittests

命令行里输入目标名时可以利用Tab补全,Ninja会把所有注册过的target按前缀列出来。一个容易踩的坑是:如果你改的是content层代码,却只构建了chrome目标,Ninja不会自动补全依赖,你的改动可能不会被打进最终exe里。稳妥的做法是:不确定改了哪个模块时,直接重新构建chrome目标,Ninja会自动判断依赖是否过期。

增量构建中还有一个经常被忽视的细节:out/Default目录被删除或移动到别的位置之后,Ninja会当作全新构建处理。如果只是想清理临时文件,不要删整个out目录,用ninja -C out/Default -t clean清除构建产物,再执行gn gen out/Default重新生成配置即可。

2.3 链接环节最容易出事的点

整个编译过程中,链接阶段(LINK)是稳定性最差的一环。Chromium 145代码量摆在这里,链接器需要处理数百万级别的符号,内存消耗极其夸张。即使开了组件构建,某些大模块(比如blink_core)的链接仍然可能吃掉6GB以上的内存。真遇到fatal error: out of memory的时候,别急着加内存条,先检查是不是页面文件太小、后台有没有其他大内存进程,把编辑器、浏览器关掉往往就能续命。

杀毒软件是链接阶段的第二个大敌。Windows Defender的实时防护会逐个扫描新生成的obj和pdb文件,每次扫描都拖慢IO速度,极端情况下会出现“文件被占用,无法写入”的错误。最好的解决办法是在Windows安全中心里把整个src/out目录加入排除列表,这个操作能显著提升链接速度和稳定性,属于一次性配置、长期受益的典型。

链接阶段还会偶发这类情况:错误信息是LNK1104: cannot open file 'chrome.dll',但文件明明存在。这通常是上一次的chrome进程没有完全退出,DLL文件被进程锁定。用任务管理器检查是否有残留的chrome.exe进程,全杀掉再重新链接即可。另一个高频报错是LNK1168: cannot open xxx.pdb for writing,原因几乎都是杀毒软件或disk indexing正在读取PDB。把这些实时扫描程序对out目录的干涉降到最低,就能一次性解决大多数链接期“玄学问题”。

3. 运行:把构建产物真正跑起来

3.1 第一次启动的姿势

编译拿到了out/Default/chrome.exe,下一步就是把它跑起来。但强烈不建议直接双击exe——不是因为不能跑,而是双击启动会直接沿用系统默认的User Data目录,也就是你平时装Chrome用的那个配置目录,两边的版本、数据、扩展混在一起,组件构建的二进制和正式版Chrome用同一个profile,极易出现“配置版本不兼容”的提示,甚至连崩溃报告都会被写乱。

更稳的启动方式是命令行指定独立的用户数据目录:

out\Default\chrome.exe --user-data-dir=D:\chrome-dev-profile

--user-data-dir可以指向任意位置,让这份Chromium 145使用一套干净、隔离的配置。第一次跑建议再加上两个flag:

out\Default\chrome.exe --user-data-dir=D:\chrome-dev-profile --no-first-run --no-default-browser-check

--no-first-run会跳过首次启动的欢迎页和默认浏览器引导,--no-default-browser-check则不会弹出“设我为默认浏览器”的提示条。这两个参数对日常测试很有用,尤其是自动化脚本里反复启动退出时,能少很多干扰。

启动后别急着关,观察一下命令行窗口有没有报错输出。Chromium在Windows上的主进程日志默认不输出到控制台,但严重启动错误会在exe启动瞬间弹窗提示。如果进程一闪而过,大概率是缺DLL或者VC运行时组件,后面“常见问题”章节会专门讲。

3.2 验证“这是一个能用的Chromium 145”

启动成功不代表可以收工,花两分钟验证构建产物是否完整。在地址栏输入chrome://version,回车后能看到完整版本信息和编译参数。

这里有几个字段要重点看:第一行“Google Chrome”旁边的版本号必须显示145.0.x.x,与源码版本一致。第二是“命令行”一栏,能看到你刚才传进去的--user-data-dir参数,确认生效;第三是“可执行文件路径”,指向out\Default\chrome.exe而非系统安装目录。如果这些都对,说明运行的是你编译出来的版本。

接下来做一个基础功能巡检。打开chrome://gpu,看GPU加速是否正常,很多自编译版本会因为显卡驱动或Swiftshader配置问题在性能页显示异常;打开chrome://media-internals,检查音频/视频解码器是否加载,特别关注ffmpeg_branding = "Chrome"和proprietary_codecs = true是否生效,否则H.264、AAC等闭源格式会无法播放。最后随便打开一个有点复杂度的网站,滚动页面、播放视频、打开开发者工具,确认渲染主流程不崩。

如果你在未来要做渲染引擎调试,chrome://crash这个测试页也值得记住:它会让当前标签页故意崩溃并生成dump,用来验证你的调试符号和崩溃收集链路是否工作。对没接触过的人来说,这个页面听起来吓人,但它不会影响整个浏览器进程,只是把当前tab杀掉,很适合验证符号服务器配置。

3.3 调试与性能观察

自编译Chromium的最大意义在于能调试、能加日志、能单步执行,Windows上有几种常用的调试切入方式。

一种是通过DevTools协议(CDP)做远程调试。启动时带上:

out\Default\chrome.exe --user-data-dir=D:\chrome-dev-profile --remote-debugging-port=9222

然后在本机浏览器访问http://localhost:9222/json,就能看到当前浏览器实例的所有标签页列表,通过CDP可以操作DOM、模拟网络、捕获性能指标。这个方案不依赖Visual Studio,跨语言、跨平台的自动化都能直接用。

另一种是本地调试器附加。用Visual Studio的“调试 -> 附加到进程”,选择chrome.exe进程,符号文件直接指向out\Default下的PDB即可。Release +symbol_level = 1模式下,函数调用栈基本可用,局部变量信息略少,但定位问题足够。如果你需要深入Blink或V8内部,可以考虑单独编译一个is_debug = true的构建目录,日常调试和性能验证两个目录互不干扰。我实际项目里就是这么维护两个out目录的,平时跑Release,需要看渲染细节时切换到Debug,看似多占了一倍磁盘,但省掉了来回改参数的重编译。

性能观察方面,chrome://tracing已经改名为chrome://trace,可以录制渲染、合成、主线程任务等各类trace事件;配合--enable-logging=stderr --v=1这类日志参数,能直接看到浏览器内部的日志输出。这些工具组合起来,足够支撑大部分桌面端的性能分析和bug定位工作了。

4. 常见问题与排查技巧实录

4.1 运行阶段问题排查

走到“能跑”这一步,依然有不少问题等着你。下面这张速查表,是我在多次编译Chromium版本过程中整理下来最高频的几种情况:

现象可能原因处理方式
0xc000007b应用程序无法正常启动VC++运行库缺失或位数不匹配安装64位VC++ Redistributable,确认exe是x64
进程闪退,无任何提示DLL缺失或配置目录损坏组件构建确认所有DLL在exe同目录下;删除自定义profile重试
页面白屏/黑屏GPU进程崩溃或显存不足加--disable-gpu先排除,再查chrome://gpu
打开视频网站无法播放未启用私有编解码器确认proprietary_codecs = true后重新编译并验证chrome://media-internals
浏览器卡顿严重Debug版本性能过低确认is_debug = false,改用Release目录运行
启动后立刻报“配置已被更新”多版本共用profile使用独立--user-data-dir或删除旧配置后重试

表格里最容易被忽略的是最后一条。Chromium对profile的版本兼容性要求很严格,不同大版本、不同编译类型的二进制如果共用同一个profile,会出现“配置已更新到更高版本,无法再打开”的提示。使用独立的--user-data-dir可以从根上避开这个问题。

GPU相关问题的排查相对简单,--disable-gpu能启动就说明渲染进程的GPU初始化有问题。这时候去chrome://gpu页面看具体是哪一项失败,如果是D3D11相关报错,优先更新显卡驱动;如果驱动没问题,可能是你编译时禁用了某些特性导致后端不一致,回查args.gn里是否有use_d3d11 = false之类的遗留配置。

4.2 构建崩溃类问题速查表

运行期问题大多集中在启动阶段,构建期问题则更五花八门。把构建阶段遇到过的报错整理出来,是这套教程最“值钱”的部分:

报错特征常见原因应急处理
fatal error C1060: compiler is out of heap space编译器进程内存不足关闭后台高内存应用,扩大页面文件
error LNK1104: cannot open file 'chrome.dll'DLL被进程锁定任务管理器结束残留chrome进程,检查杀毒软件
error LNK1168: cannot open xxx.pdb for writingPDB被索引/扫描占用把out目录加入杀毒软件排除列表
ninja: error: rebuilding 'xxx' failedGN或命令环境异常重新执行gn gen out/Default,再跑ninja
disk has insufficient space空间不足清理旧构建产物,检查临时目录占用
unexpected token in expression类脚本错误depot_tools版本过旧更新depot_tools并重新跑gclient sync
链接时undefined symbol参数配置不一致或代码未同步确认args.gn完整,执行gclient sync重新拉取依赖

C1060这类编译期内存问题,很多人第一反应是加内存,但其实多数场景下调整页面文件就能解决。Windows的动作是把物理内存和页文件看作一个整体,只要页文件足够大且所在分区剩余空间充足,编译器进程就能用虚拟内存续命,只是速度会慢一些。

undefined symbol的坑稍微隐蔽。组件构建模式下,模块间的符号导出由各自的.def文件控制,如果你的源码同步和编译用的不是同一份配置,或者中途用git pull更新了代码但没有同步DEPS里的第三方库,很容易出现某几个符号找不到。最简单有效的恢复姿势是回到src目录执行gclient sync,它会重新对齐依赖版本,然后重新gn gen,多数情况下能原地复活。

4.3 我实测过有效的小技巧

排查做得多了,自然会攒出一套“编译更顺”的操作手法。第一条就是给Windows Defender加排除目录。这个操作影响的是全流程,从源码读取、编译中间文件到链接写PDB,每个环节都受益。具体路径控制在Windows安全中心里加两个目录:源码根目录src和depot_tools所在目录,有精力的把out也单独加上。加上之后,链接时间普遍能缩短20%以上,报错率也会肉眼可见地下降。

第二条是构建命令工整化。把所有常用命令写进一个.bat或PowerShell脚本,比如下面这个:

@echo off cd /d D:\chromium\src call gn gen out/Default call autoninja -C out/Default chrome start out\Default\chrome.exe --user-data-dir=D:\chrome-dev-profile

每次编译执行同一个脚本,避免在终端里想半天命令格式。实测下来,这种“脚本化”操作能少碰到很多低级失误,也方便把常用参数固化下来传给同事。

第三条是学会“拆目录”。一个大目录做所有事情当然简单,但不同用途的构建参数相互干扰。我的做法是维护out/Default(Release组件构建,日常运行)和out/Debug(Debug构建,深入调试)两个目录,两个目录共享同一份源码,args.gn各自独立。切换时只需要改一个字符,不用来回调整参数,磁盘空间多花一点,但时间成本直线降低。

5. 从“能跑”到“能用”的额外补充

5.1 编译成果物的验收清单

编译和启动都通过之后,建议在“正式投入使用”前做一轮快速验收,避免后面用到一半才发现某个功能被编译参数关了。

先试基础页面:打开几个不同类型的网站,分别检查渲染、滚动、点击和表单输入是否正常。接着试媒体模块:找一个包含H.264视频的页面,再找一个包含AV1视频的页面,确认解码器是否按预期工作。然后试WebGL:访问chrome://gpu并运行几个基础WebGL示例,如果页面提示WebGL不可用,大概率是GPU进程初始化失败,需要回查显卡驱动状态。最后试一套扩展安装流程:从应用商店装一个扩展再卸载,确认扩展管理链路顺畅。

如果这些全部通过,基本可以认为这个Chromium 145是一个日常可用的浏览器了。如果个别环节不通过,也不用急着重建整套二进制,很多功能可以通过运行时flag临时打开,比如--enable-features=FeatureName这种形式,先判断是配置问题还是功能本身有bug,再决定要不要动args.gn。

5.2 构建系统的维护日常

编译成功不是终点,Chromium代码库每天都在变化,个人维护构建系统时几件事要形成习惯。

第一,定期同步。git fetch只能拿到Chromium主仓的更新,第三方依赖在DEPS文件里管理,所以每次同步代码都要执行gclient sync,它会根据DEPS自动拉取对应版本的依赖。如果只更新主仓而没有同步DEPS,很容易出现“头文件版本和库版本不一致”导致的链接错误。官方推荐是每几天同步一次,而不是攒一个月一次性拉取,那样冲突排查会非常痛苦。

第二,保留配置文件版本。每次大规模更新前,把out/Default/args.gn备份一份,名称加上日期。更新后如果编译失败,可以用二分法判断是不是参数变化导致的问题。这个习惯在Chrome版本跨越较大时尤其有用,因为GN参数名偶尔会调整或废弃,一份历史配置能帮你快速定位。

第三,定期清理过期产物。Chromium构建会不断产生新的obj、pdb和历史版本文件,时间长了out目录体积会膨胀得很厉害。用ninja -C out/Default -t clean清空一次再全量编译,是最彻底的方式,但耗时较长。日常更温和的做法是删除out/Default下那些一周前的PDB文件,或者直接把整个旧out目录改名保留,重新生成一份新的,确认稳定后归档旧的。

5.3 后续可以继续深挖的方向

编译运行走通之后,“Chromium 145 Windows篇”的系列主线算是基本完成,但怎么把这个基础玩出新东西,每个人可以有不同的方向。

如果对浏览器进程本身好奇,可以研究chrome://process-internals里的站点隔离信息,看看每个标签页如何映射到渲染进程;如果对渲染管线感兴趣,可以打开chrome://tracing录制一次页面加载过程,观察主线程任务、合成器输出和GPU raster的协同节奏;如果要做自动化测试,CDP和content_shell都是很好的切入点,尤其是content_shell,体积比chrome.exe小很多,是个理想的“无UI自动化测试骨架”。

还有一个不建议绕过的方向:构建系统本身。把gn desc out/Default chrome这种命令多跑几遍,看看target依赖图是怎么组织起来的,理解GN/Ninja的增量判定机制。很多人编译了一两次就觉得“不过如此”,但真要深入Chromium二次开发,构建系统的理解和调试能力,是比浏览器功能本身更底层的技能。

最后分享一点我的体会

编译和运行Chromium的过程,说白了就是一遍遍和工具链、操作系统、硬件资源打交道的过程。第一次全量构建时,我因为在链接阶段开着两个大型应用,内存不足导致构建失败,重新跑了一遍才明白“关掉一切多余进程”不是一句空话。后来养成了一套自己的习惯:编译前关掉实时云同步、把杀毒软件的排除列表配好、确认页面文件空间充足,然后该干嘛干嘛去。这些准备工作看起来不起眼,但它们决定了你是在“一次通过”还是在“反复排查”之间浪费时间。

如果你正在编译Chromium 145,前面几步都做好了,这篇对应的“编译与运行”就是临门一脚。编译输出滚动起来的那一刻,屏幕上全是报错也不要慌,对着错误信息一条条查,大部分问题都有成熟的解法。等你第一次看到自己编译的浏览器弹出一个干净的欢迎页时,前面熬过的那些编译时长,都值了。

RELATED READING

延伸阅读

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