在Windows上编译Chromium这件事,说难不难,说简单也真不简单。经常有同行跑来问我:为什么gn gen一跑就报错?为什么同一份源码,别人半天就能跑起来,我这边编了一整夜还在链接?最后排查下来,绝大多数问题都不在代码本身,而是栽在了最开始的环境准备和GN参数上。GN和Ninja这套构建体系设计得挺干净,但Windows对工具链、路径和权限的要求比Linux严格得多,一步没对齐,后面就是连环雷。这篇就把我在Windows上从零编译Chromium的完整经验梳理一遍:GN/Ninja怎么配合工作、工具链怎么一次配齐、args.gn里的常用参数到底是什么含义、有哪些Windows特有大坑必须提前绕开,最后附上我现在固定使用的几套参数配置,方便直接抄作业。
1. 必须理解GN与Ninja的分工,才能选对参数
1.1 摆脱Makefile思维的元构建系统
很多刚接触Chromium构建的人最常犯的错,是把GN当成一个“编译工具”,然后拿它和Makefile对比,越比越糊涂。其实GN的全称是Generate Ninja,它的职责只有一个:读取项目里的BUILD.gn文件,加上你传入的参数,最终生成一份给Ninja使用的build.ninja文件。真正负责并行执行编译和链接的是Ninja,它不关心你的源码长什么样,只按DAG依赖关系调度命令,尽量把CPU和内存吃满。
Chromium为什么要放弃Makefile/CMake,自己搞一套GN?原因很现实:整个仓库有上万个BUILD定义、几十万个源文件,传统构建系统要么在解析阶段就慢得离谱,要么依赖关系表达不清楚,根本撑不住这种规模。GN的配置语法更像带类型检查的脚本,表达能力比Makefile强很多,生成Ninja文件的耗时也能控制在秒级。Ninja则是一个“为速度而生”的执行器,它不会帮你做任何智能推导,只负责老老实实按依赖图跑命令并处理增量。
理解这套分工之后,你就明白为什么“gn gen之后再改参数”是一件代价很大的事了:参数变化会影响整个依赖图的编译选项,Ninja检测到build.ninja变了,往往会触发大范围重新编译,而不是聪明地只重编受影响的部分。
1.2 参数为什么重要:一个开关决定你今晚能不能编完
GN参数不是可有可无的优化项,它直接定义了最终产物的形态,也直接决定了你的编译时长。我见过太多人拿到源码后直接执行gn gen out/Default,以为“默认配置就是最稳妥的”,结果默认配置是is_debug=true加is_component_build=false的组合,编出来的浏览器是未优化的Debug版,所有模块被静态链接成一个巨大的二进制。改一行代码,要重新链接一个几GB的主程序,整个开发节奏彻底废掉。
反过来说,如果你把is_component_build=true打开,Chromium会把base、net、ui、blink等模块拆成一个个动态库。Windows下改动某个模块内部逻辑时,往往只需要重建对应的一个DLL再重启浏览器,链接和加载速度都大幅提升。对日常开发和断点调试来说,这是最重要的一个开关。
我的建议是:在跑任何编译命令之前,先花十分钟把下面要讲的参数过一遍,再开编译器。磨刀不误砍柴工,这句话在Chromium这种量级的项目上是字面意义上的真理。
2. 环境准备:把Windows工具链一次配到位
2.1 先装对Visual Studio和SDK
Windows上编译Chromium,Visual Studio是没法绕开的,因为底层链接器还是MSVC的link.exe,资源编译也需要Windows SDK。我踩过最典型的坑是“VS装了但没装Windows SDK”,结果每次gn gen都报找不到rc.exe或者cl.exe,最后排查半天才发现是安装VS时漏勾了组件。
推荐做法:安装Visual Studio 2022,工作负载勾选“使用C++的桌面开发”,右侧详细信息里务必确认包含MSVC v143生成工具和Windows 10/11 SDK。Chromium官方维护的VS版本更新比较频繁,不要在“最新预览版”上折腾,稳定版就够了。装完之后在“单个组件”里也可以确认一下SDK的具体版本,不过一般默认勾选已经满足要求。
如果你机器上同时装了好几个VS版本,GN有自动检测机制,但为了省心,我更建议手动指定。在环境变量里加上VSINSTALLDIR指向你的VS安装根目录,例如C:\Program Files\Microsoft Visual Studio\2022\Community,能避免不少自动检测的随机问题。
2.2 depot_tools与源码同步的几个关键选择
depot_tools是Chromium开发必备的工具集合,里面包含了gclient、gn、ninja、autoninja等一系列脚本。下载后把它解压到一个短路径目录(比如C:\depot_tools),然后将这个目录加到PATH的最前面,然后设置环境变量DEPOT_TOOLS_WIN_TOOLCHAIN=0,含义是不下载Google内部专用工具链,直接使用本机安装的VS。这一步如果不设置,脚本可能会尝试去下载专用工具链,过程又慢又容易失败。
接下来是同步源码,常用的写法是:
mkdir C:\cr cd C:\cr fetch --no-history chromium--no-history的意思是只下载最近一段Git历史,而不是完整提交历史。Chromium的完整Git历史非常庞大,带历史同步不仅慢,还白白占用大量磁盘。除非你要做git blame考古,否则我推荐直接加这个参数。fetch完成后,源码在C:\cr\src目录下,里面已经有顶层BUILD.gn了。
磁盘规划建议至少留出120GB可用空间。源码本身大概20到30GB,编译输出目录通常也要50到80GB,这还不算后续增量构建不断增长的体积。SSD是刚需,机械硬盘编译Chromium的时间可以轻松翻倍以上,我不推荐任何人尝试。
2.3 验证环境是否可用
同步完成后先别急着gn gen,跑几个命令确认环境没问题:在cmd里执行gn --version、ninja --version、gclient --version,如果能正常输出版本号,说明depot_tools工作正常。再执行where python确认Python指向的是depot_tools内部版本,而不是系统里某个乱七八糟的Python。
Windows上还有一个特殊前置条件:开启系统的“开发者模式”。设置路径在“设置”->“隐私和安全性”->“开发者选项”,把开发人员模式打开。Chromium的gclient hooks会在源码目录创建符号链接,不开启开发者模式就会出现各种重解析点错误,后面编译阶段才暴露的话,排查成本高很多。
3. args.gn参数逐个过一遍:每个开关背后的代价
3.1 少数几个决定命运的开关
先列一张表把这几个核心参数看清楚,再展开细说:
| 参数 | 默认值 | 作用 | 建议 |
|---|---|---|---|
is_debug | true | 是否为Debug构建,决定是否带调试信息和关闭优化 | 日常调试true,性能测试false |
is_component_build | false | 是否拆分为多个动态库 | 开发调试强烈建议true |
symbol_level | 1 | 调试符号详细程度,0~2 | 调试断点用2,日常编译用1 |
target_cpu | 跟随宿主机 | 生成目标CPU架构 | x64/arm64按需设置 |
is_official_build | false | 官方发布级构建,开启全套优化 | 除非做发布基准测试,否则不要碰 |
enable_nacl | 视版本而定 | 是否启用Native Client相关构建 | 不需要就显式false |
is_debug是第一个要明确的,因为它决定编译器的优化级别。Debug构建下大量代码不会被优化,生成的PDB文件也更完整,但体积大、运行慢。很多人只是想拿一个能打开网页的Chromium来验证功能,却默认用了Debug配置,白白付出几小时的编译时间。
is_component_build是开发效率的分水岭。它开启后会把模块拆成动态库,改一处代码的重链成本大幅下降。代价是运行时性能稍有损耗,因为跨DLL调用比静态链接多一层间接跳转。我自己的经验是:日常开发和断点调试用true,凡是需要跑性能对比的场景,单独建一个release全静态的输出目录。
symbol_level在Windows上的表现很有意思:0就是完全不生成调试符号,链接最快;1生成能解析函数名的符号,足够应对多数崩溃栈和release调试;2生成完整调试信息,包括局部变量,代价是PDB文件体积和链接时间猛增。如果只改业务逻辑、不需要深入调试,1基本够用;要打断点看变量,老老实实上2,别在1下面浪费时间。
is_official_build是个容易让人手痒的开关。它会把LTO、PGO、CFG等全套优化打开,编出来的浏览器性能接近官方渠道版,但代价是编译时间成倍增长。普通开发场景用不上,我基本只在做性能回归对比时才会单独建一个official输出目录。
3.2 按场景组合出的三套参数
介绍完单个参数,直接给组合方案。我这里给出三套我实际用过的配置,分别对应三种典型需求:
调试开发配置,适合打断点、改代码:
gn gen out/Debug --args="is_debug=true is_component_build=true symbol_level=2 target_cpu=\"x64\" enable_nacl=false"日常验证配置,编译速度较快且运行性能可以接受:
gn gen out/Dev --args="is_debug=false is_component_build=true symbol_level=0 target_cpu=\"x64\" enable_nacl=false"性能基准配置,运行表现最接近正式版:
gn gen out/Release --args="is_debug=false is_component_build=false symbol_level=0 target_cpu=\"x64\" enable_nacl=false"注意Windows下在cmd里写参数时,内部引号要用反斜杠转义,否则GN会把x64解析成别的值。如果用PowerShell,转义规则又略有不同。更省心的办法是后面要讲的gn args编辑方式。
3.3 那些默认值就够、别随便开的参数
有几个参数在网上教程里出现的频率很高,但实际日常开发中根本不需要动它们。
use_goma和use_remoteexec是分布式编译开关,需要配合内部编译集群或厂商服务使用。个人机器上开了只会报错或者白等网络请求,我建议保持默认false。v8_symbol_level和blink_symbol_level是单独控制V8和Blink符号级别的,只有在调试渲染引擎内部时才需要单独调高,一般跟着symbol_level走就够了。
另外老教程里常见的enable_nacl在新版本Chromium中其实已经被逐步移除了,照抄老配置不仅没意义,还可能因为参数不存在导致gn gen报错。遇到这种情况,用下面这个命令查一下参数是否真实存在才是正道。
3.4 用 gn args --list 自己查参数
gn args out/Dev --list可以列出当前GN版本支持的所有参数和默认值,这也是我排查“参数到底存不存在”的首选方式。不确定某个参数能不能写时,先gn args --list out/Dev | findstr 参数名过滤一遍,比在搜索引擎里翻老帖子靠谱得多。
推荐的做法是先用gn args out/Dev打开编辑器写参数,第一次运行会弹出记事本,直接在里面的args.gn文件中按行写入参数即可:
is_debug=true is_component_build=true symbol_level=2 target_cpu="x64"这样能避免cmd转义问题,而且args.gn会长期保留,事后知道自己改过什么,也方便团队之间共享。我所有输出目录的参数都是用这种方式管理的。
4. Windows特有的大坑:路径、权限与杀毒软件
4.1 源码目录别放在深路径里
Chromium源码的目录层级很深,有些头文件路径加上盘符字符数直接逼近Windows经典260字符限制。虽然新版本Windows和较新的工具链已经支持长路径,但根除这个问题最朴素的办法,还是把源码放在短路径根目录下。
我自己用的目录是C:\cr\src,避开“用户名里的中文”、“带空格的长目录”这些潜在雷区。另外Windows 10 1607以上版本可以通过注册表开启长路径支持:HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled设为1,能少很多诡异报错。但我要提醒的是,注册表只对支持长路径的进程有效,depot_tools里有些老Python脚本仍然会翻车,所以短路径目录是更根本的解法。
4.2 开发者模式与符号链接
很多人的gn gen已经成功了,却在编译阶段碰到类似Unable to create reparse point for ...这样的错误,第一反应是“权限不够”,于是右键管理员身份运行cmd,结果照样失败。真正的原因是没有开启开发者模式,或者符号链接创建被策略禁用了。
去系统的“开发者选项”里开启开发人员模式后,这个问题基本不会再出现。还有一个容易忽略的细节:如果之前用非管理员权限跑过部分流程,源码目录里可能已经残留了半成品符号链,开完开发者模式后建议重启一次终端,再执行gclient sync补齐hooks。
4.3 杀毒软件实时扫描是慢的元凶
这是我在Windows编译Chromium时体验最明显的一个坑。编译过程中,cl.exe会频繁创建临时文件、写PDB、生成目标文件,Windows自带的Microsoft Defender实时防护会逐个文件扫描,IO开销非常大。我实测下来,同样的源码、同样的参数配置,把源码目录和输出目录加入Defender排除列表之后,首次全量编译时间能缩短半小时以上。
需要注意的坑是,输出目录千万别放在系统盘临时目录下,也尽量不要放在OneDrive同步目录里,否则编译和文件同步互相打架,轻则速度骤降,重则引发文件占用冲突直接编译失败。
4.4 增量编译是否失效:改参数要付出的代价
Ninja的增量编译逻辑很成熟,但它依赖的是build.ninja文件的稳定性。一旦你修改了args.gn里的参数,GN会重新生成build.ninja,Ninja检测到旧的编译命令已经变化,几乎会触发全量重编。这个代价在Chromium项目上非常可观,所以千万不要在同一个输出目录里反复横跳调试参数。
我的习惯是:一个输出目录只服务一种构建类型。Debug调试、Dev验证、Release性能测试分别用三个独立目录,参数互不干扰,也方便并行使用。改参数前先想清楚,这次构建的目的到底是什么。
5. 编译实测数据与调优思考
5.1 我的机器配置与首次全量编译时间分布
我目前常用的一台Windows编译机器配置是i7-12700K、64GB内存、1TB PCIe 4.0 SSD,Windows 11。源码放在C:\cr\src,输出目录在C:\cr\src\out,Defender排除已经配好。
用第3.2节里的Dev配置,也就是release加component build、symbol_level=0,首次全量编译大概耗时1小时50分钟。换成Release静态配置,也就是release加静态链接、symbol_level=0,首次全量编译耗时3小时20分钟左右。Debug加component build加symbol_level=2的组合,首次编译大约2小时40分钟。
编译阶段耗时分布大致如下:
| 阶段 | 耗时占比 | 说明 |
|---|---|---|
| 依赖同步与hooks | 5%左右 | gclient sync、下载构建工具链 |
| C++编译 | 70%左右 | 大头在third_party和blink相关代码 |
| 链接 | 20%左右 | 组件化明显缩短,静态全量最耗时 |
| 资源生成与最后打包 | 5%左右 | 生成资源文件、最终可执行文件 |
另外值得注意,改一个普通业务cc文件后的增量编译,在Dev配置下大概1分半钟能完成;但如果你改的是base这类基础库的公共头文件,很大概率触发大范围依赖重编,实测跑到13分钟也是常有的事。这不算Ninja失效,而是依赖关系本身导致的重编范围太大。
5.2 并行度与内存的平衡
Ninja默认会根据CPU逻辑核心数并行动工,但Windows上内存才是真正的瓶颈。Chromium的C++编译进程单个峰值内存大约600MB到1GB,64GB内存的机器还不愁,16GB内存的机器如果放任Ninja全核编译,很容易触发内存不足,甚至导致个别编译进程被杀,最终报出一堆莫名其妙的错误。
我的经验公式是:把逻辑核心数记为N,内存容量记为M(单位GB),实际并行度建议取min(N, M/2),同时给链接阶段预留几个GB内存。比如16GB内存、8核机器,建议-j 8;32GB内存、16核机器,建议-j 16;64GB内存、20线程机器,-j 16到-j 20都在合理范围。
使用depot_tools里的autoninja脚本可以省去手动计算,它会自动探测机器配置并设置合适的进程数。我一直用autoninja -C out/Dev chrome来构建,只有链路特别长的时候才手动指定-j。
5.3 让日常迭代更快的小技巧
只验证渲染或内容层逻辑时,不必每次构建完整chrome.exe,autoninja -C out/Dev content_shell可以构建一个精简壳,编译量小很多。完整浏览器跑通之后,很多内部功能验证我都用content_shell来做,效率高一大截。
Windows上链接是另一个绕不开的时间点。即使组件化构建,chrome.dll和最终chrome.exe的链接也要吃掉不少时间。如果你只是调整了某个模块的少量逻辑,尽量保持组件化开启,避免被迫回到静态链接的输出目录。
6. 我现在固定使用的三套配置(直接抄作业版)
说了这么多,最后把我目前Windows机器上实际在用的三套配置原文贴出来,你直接复制到args.gn里就能用。
第一套是日常调试,建立在D:\cr\src下,用于断点分析和改代码:
is_debug=true is_component_build=true symbol_level=2 target_cpu="x64" enable_nacl=false第二套是功能验证,追求编译速度和运行效果平衡,我最常用的一套:
is_debug=false is_component_build=true symbol_level=0 target_cpu="x64" enable_nacl=false第三套是性能复现,模拟接近正式版的运行表现,编译速度慢但数据更有参考价值:
is_debug=false is_component_build=false symbol_level=0 target_cpu="x64" enable_nacl=false如果还想获得更完整的媒体解码能力,比如H.264和AAC,可以考虑额外追加两个参数:
proprietary_codecs=true ffmpeg_branding="Chrome"不过它们会增加构建和运行时的复杂度,我一般只在自己需要测试媒体相关功能时才加。个人体会是:Windows编译Chromium没有魔法,环境干净、参数明确、输出目录隔离、杀毒排除到位,剩下的事情Ninja和你的CPU会自己解决。真正耗时间的往往不是编译本身,而是用错参数之后反复全量重建的那些冤枉路。