1. Ninja 构建系统基础
Ninja 是一个专注于速度的小型构建系统。它与其他构建系统的主要区别在于其设计目标和核心理念,旨在通过简洁性和底层方法实现尽可能快的构建速度,尤其是在大型项目中。
1.1 设计目标:速度与简洁性
Ninja 最核心的设计目标是 速度。它致力于实现极快的增量构建(即在少量代码修改后重新构建),即使对于包含数万个文件的大型项目(如 Google Chrome 或 LLVM),也能在极短时间内完成构建检查。这种对速度的极致追求源于缩短开发者“编辑-编译-调试”周期的需求,减少等待时间,提升开发效率。Ninja 通过只执行确定构建目标是否过时所必需的最少工作来实现这一目标。
为了实现速度,Ninja 采用了 简洁性 作为其另一个关键设计原则。它刻意避免了其他构建系统(如 Make)中常见的高级语言特性,例如条件语句、循环、模式匹配或内置函数。Ninja 的目标是成为构建领域的“汇编程序”,而非高级语言。这种极简设计使得 Ninja 的构建文件(.ninja
文件)虽然人类可读,但并不适合手写。这种约束使得 Ninja 能够非常快速地解析和评估构建图,因为它无需在构建时进行复杂的决策。当速度与便利性冲突时,Ninja 总是优先选择速度。
1.2 核心概念
Ninja 的简洁性体现在其几个核心概念上:
- 变量 (Variables): Ninja 支持简单的键值对变量定义,用于存储字符串,如编译器标志或路径。变量通过
$variable
或${variable}
的形式在规则或构建语句中引用。需要注意的是,Ninja 中的变量在其定义的作用域内是不可变的,后续的定义只会覆盖(shadow)之前的定义,而不是修改。 - 规则 (Rules): 规则是长命令的简写名称。例如,可以定义一个名为
cc
的规则来表示 C 编译器的调用。规则定义通常包含command
变量(指定要执行的命令行)、description
变量(在构建过程中显示的描述性文本)、以及用于依赖处理的变量如depfile
或deps
。规则内部可以使用内置变量,如$in
(输入文件列表)和$out
(输出文件列表)。 - 构建语句 (Build Statements): 构建语句是 Ninja 文件的核心,用于声明输入文件、输出文件和用于生成输出的规则之间的关系。其基本语法是
build output(s): rule input(s)
。这些语句共同构成了项目的依赖关系图。Ninja 根据文件修改时间戳判断输出是否过时,如果过时或缺失,则执行相应的规则来重新生成。构建语句支持显式依赖、隐式依赖(使用|
分隔,确保依赖项先构建,但不传入$in
)和顺序依赖(使用||
分隔,确保依赖项先构建,但其更改不触发重新构建)。 - 依赖处理 (Dependency Handling): 正确处理依赖关系是 Ninja 的一个关键优势,尤其是在 Make 等工具难以处理的场景下。
- 显式/隐式/顺序依赖: 如上所述,通过构建语句直接指定。
- 编译器生成的依赖 (
depfile
,deps
): 这对于 C/C++ 头文件依赖至关重要。depfile
属性指定一个由编译器(例如gcc -MD -MF file.d
)生成的文件,该文件包含 Makefile 语法的依赖关系。Ninja 会在命令执行后解析此文件。deps
属性(如deps = gcc
或deps = msvc
)允许 Ninja 直接解析编译器的输出(如gcc -M
的输出或 MSVC 的/showIncludes
输出)或使用内部数据库来跟踪依赖,这通常比处理单独的.d
文件更高效。这种机制确保了当头文件发生变化时,所有包含它的源文件都能被正确地重新编译。 - 命令依赖: Ninja 还会跟踪构建规则中
command
变量本身的变化。如果用于生成输出文件的命令发生更改(例如编译器标志变化),即使输入文件没有变化,Ninja 也会重新构建输出文件,以确保构建的正确性。
- 其他特性 (Other Features):
default
语句指定在命令行未提供目标时默认构建的目标。subninja
和include
允许将大型构建拆分成多个文件,便于管理。pool
允许为一组规则或构建边限制并发作业数,这对于控制资源密集型任务(如链接)的并行度非常有用。phony
规则用于创建符号目标,类似于 Make 中的.PHONY
。
Ninja 对依赖关系的处理,特别是通过 depfile
和 deps
机制处理 C/C++ 头文件依赖,以及跟踪命令行变化的能力,直接解决了传统构建系统(如 Make)中的常见痛点和潜在错误来源。Make 处理头文件依赖通常需要复杂的手动配置,并且在处理大量 .d
文件时可能遇到性能瓶颈。Ninja 将这些机制标准化,提高了构建的鲁棒性和效率。同时,pool
特性虽然简单,却为管理大型并行构建(如 LLVM)中的资源竞争(尤其是链接阶段的内存消耗)提供了关键的细粒度控制机制。
1.3 为何设计为生成而非手写
Ninja 从一开始就被设计为由更高级别的“元构建系统”(meta-build system)如 CMake、Meson 或 gn 生成其输入文件(build.ninja
),而不是由开发者直接手写。这一设计决策与其核心目标紧密相关:
- 避免策略决策: Ninja 的极简语法和缺乏内置规则意味着它本身不包含关于项目应如何构建的策略(例如,构建输出是放在源目录旁边还是单独的目录)。这些决策由生成
.ninja
文件的元构建系统负责。生成器可以分析项目的具体需求、系统环境、依赖关系和配置选项(如 Debug vs. Release),然后将这些高级逻辑转换为 Ninja 理解的精确、低级的指令集。 - 管理复杂性: 对于大型项目,手动编写和维护包含复杂依赖关系的构建脚本既耗时又容易出错。元构建系统可以自动化这个过程,分析项目结构并生成必要的 Ninja 文件,让开发者专注于代码本身。
- 优化速度: 元构建系统可以在 Ninja 运行之前预先进行大量的决策和计算。这种预计算将复杂的逻辑处理移到了生成阶段,使得 Ninja 在执行构建时只需遵循
.ninja
文件中明确的指令,无需进行耗时的判断或查找,从而最大限度地减少了构建时的开销。其他构建系统中可能存在的减慢速度的特性,如条件判断或文件搜索(globbing),在 Ninja 本身中被有意省略了。
本质上,Ninja 扮演了一个高效、快速的构建执行者的角色,而元构建系统则负责处理项目分析、依赖解析和生成精确构建指令等更高级别的任务。
2. Ninja 与 Make 对比分析
Ninja 和 GNU Make 是两种广泛使用的构建系统,但它们在设计理念、性能和适用场景上存在显著差异。
2.1 设计理念差异
- Ninja: 如前所述,Ninja 的设计理念是成为一个低级的构建“汇编程序”。它专注于执行速度,特别是增量构建的速度,为此牺牲了语法的便利性和内置功能。它假设其输入文件是由其他程序生成的,因此其语法被设计得尽可能简单,以便快速解析和执行。
- Make: Make 则更像是一种高级的构建描述语言。它拥有丰富的功能,包括变量、函数、条件语句、模式规则和内置规则,旨在为用户提供强大的构建逻辑表达能力。Makefiles 通常(尽管不总是)被设计为可由人工编写和维护。Make 试图将构建逻辑的定义和执行结合在一起。然而,这种灵活性和功能丰富性也可能导致 Makefiles 变得复杂、难以理解和维护,并且在大型项目中解析和执行速度较慢。
2.2 性能对比
性能是 Ninja 相对于 Make 最突出的优势所在,尤其体现在增量构建方面。
- 完整构建 (Full Builds): 在从头开始构建项目时(即清理后的第一次构建),Ninja 和 Make 的性能通常没有显著差异。这是因为在这种情况下,构建时间主要由编译器和链接器执行实际工作所花费的时间决定,构建系统本身的开销相对较小。一些基准测试显示两者之间存在微小差异,这可能归因于任务调度顺序或构建系统自身的少量开销。
- 增量构建/空构建 (Incremental/No-Op Builds): 这是 Ninja 真正胜出的地方。当只有少量文件被修改,或者没有文件被修改(空构建)时,Ninja 的启动和依赖检查速度远超 Make,尤其是在大型项目中。Ninja 完成空构建通常只需要不到一秒的时间,而 Make 在同样的情况下可能需要数秒甚至数分钟来分析依赖关系图并确定无需执行任何操作。对于需要频繁编译和测试的大型项目(如 LLVM),这种速度差异对开发者的生产力有着巨大影响。
- Ninja 速度优势的原因:
- 快速解析: Ninja 的
.ninja
文件语法极其简单,解析速度非常快。相比之下,Make 需要解析功能更丰富但更复杂的 Makefiles。 - 最小化决策: Ninja 在构建时几乎不做决策,所有复杂的逻辑判断都在生成阶段由元构建系统完成。
- 高效依赖处理: Ninja 对编译器生成的依赖文件(
.d
文件)的处理进行了优化,或者使用deps
机制直接解析编译器输出/使用内部数据库,避免了 Make 在处理大量.d
文件时可能出现的性能瓶颈。基准测试表明,Make 在大型项目上的缓慢主要是因为处理.d
文件耗时。 - 命令跟踪: Ninja 会记录用于生成目标的具体命令。如果命令发生变化(例如编译器标志更改),即使源文件时间戳未变,Ninja 也会触发重新构建,确保了构建的一致性,并可能避免了 Make 需要进行的某些状态检查。Make 本身不具备这种内置的命令跟踪能力。
- 内部优化: Ninja 采用了优化的内部数据结构(如文件和命令之间的二分图表示)和技术(如路径字符串的规范化和哈希比较)来加速依赖检查和图的遍历。
- 快速解析: Ninja 的
- 并行性 (Parallelism): Ninja 默认利用所有可用的 CPU 核心并行执行构建任务。开发者无需像使用 Make 那样显式传递
-j
参数来启用并行构建。此外,有观察指出 Ninja 的任务调度策略(尽早执行叶子节点,如链接)可能比 Make(有时会将链接等任务堆积到最后)更能有效地利用并行资源。
下表总结了 Ninja 和 Make 在关键特性和性能方面的对比:
特性 | Ninja | Make | 注释/参考 |
主要目标 | 执行速度 (尤其增量构建) | 通用构建自动化 | |
语法风格 | 低级、极简、“汇编程序” | 高级、功能丰富、类脚本语言 | |
手写适用性 | 不推荐,设计为生成 | 支持,常见实践 (尤其小项目) | |
高级特性 (条件/函数等) | 缺乏 | 丰富 | |
增量/空构建速度 | 非常快 (通常 < 1s) | 较慢 (可能数秒至数分钟,随项目规模增大而变慢) | |
完整构建速度 | 与 Make 类似 (由编译器/链接器主导) | 与 Ninja 类似 | |
并行性 (默认) | 默认开启 (基于 CPU 核心数) | 默认关闭 (需 -j 参数) | |
依赖处理 (头文件) | 内置高效机制 (depfile , deps ) | 需要手动配置或依赖 .d 文件 (大型项目可能慢) | |
命令行变更跟踪 | 内置支持 | 无内置支持 | |
典型用例 | 大型项目、CMake/Meson 后端、需要快速迭代 (如 Chrome, LLVM, Android) | 小型项目、手写 Makefiles、通用自动化任务 |
2.3 适用场景异同
基于上述的设计理念和性能差异,Ninja 和 Make 的适用场景有所不同:
- Ninja: 最适合作为元构建系统(如 CMake、Meson、gn)的后端,用于构建大型、复杂的 C++ 项目,例如 Google Chrome、Android AOSP 的部分组件、LLVM 和 Swift。在这些场景下,其极快的增量构建速度能够显著提升开发者的工作效率。对于已经在使用 CMake 的项目,切换到 Ninja 通常只需要在 CMake 配置时添加
-G Ninja
参数,是一个低成本、高回报的优化措施。然而,Ninja 不适合用于小型项目,因为其速度优势可能不明显,且其简单的语法和缺乏内置功能使得手写.ninja
文件非常不便,甚至不被推荐。 - Make: 对于小型项目,或者需要手动编写构建脚本的场景,Make 仍然是一个可靠且功能强大的选择。它的广泛可用性和悠久历史意味着许多开发者对其比较熟悉。Make 的灵活性也使其能够用于执行编译之外的各种自动化任务。虽然 Make 也可以作为 CMake 的后端,但在大型项目中,其增量构建性能通常不如 Ninja。
一个重要的考量因素是项目是否已经采用了元构建系统。如果答案是肯定的,那么选择 Ninja 作为后端通常能带来显著的性能提升,尤其是在开发者体验方面。Ninja 的设计哲学也间接推动了更优良的构建实践。其默认并行性促使开发者从早期就考虑并行构建的正确性。同时,强制使用生成器将构建逻辑的定义与执行分离,相比于可能变得极其复杂的单一 Makefile 文件,这种分离有助于提升构建配置的清晰度和可维护性。
3. LLVM 构建系统架构概览
LLVM 项目拥有庞大的代码库和复杂的构建需求,其构建系统架构对开发效率至关重要。当前,LLVM 完全依赖 CMake 作为其核心构建配置工具。
3.1 对 CMake 的依赖
LLVM 项目已将其官方构建系统完全迁移到 CMake。这意味着所有旧的构建方式(如 Autotools)已不再受支持。CMake 作为一个跨平台的元构建系统,负责处理 LLVM 构建过程中的配置阶段。它读取项目根目录及子目录下的 CMakeLists.txt
文件,解析其中的指令,探测开发环境(包括编译器、链接器、系统库等),处理用户通过命令行或缓存设置的各种构建选项,并最终生成适用于开发者所选的底层构建工具(即 CMake 生成器)的原生构建文件。LLVM 对 CMake 有最低版本要求,例如官方文档提到需要 CMake 3.20.0 或更高版本(尽管一些较旧的文档或第三方指南可能提及 3.13.x 或 3.18.x)。
3.2 当前的构建流程
使用 CMake 构建 LLVM 的标准流程如下:
- 获取源码 (Checkout): 从官方仓库(通常是
llvm/llvm-project
monorepo)克隆 LLVM 的源代码。 - 创建构建目录 (Create Build Directory): LLVM 强制要求 在源代码树之外进行构建(out-of-source build)。开发者需要创建一个单独的空目录用于存放构建过程中生成的文件和最终产物。这种做法可以保持源代码树的清洁,并且允许在同一源代码树上同时维护多个不同的构建配置(例如,Debug vs. Release,不同目标平台等)。
- 配置 (Configure via CMake): 在构建目录中运行
cmake
命令,指向 LLVM 源代码的根目录(通常是llvm-project/llvm
)。在此阶段,必须指定 CMake 生成器(例如,强烈推荐使用-G Ninja
),并可以通过-D
参数设置各种 CMake 变量来定制构建,例如:CMAKE_BUILD_TYPE
: 指定构建类型 (Debug, Release, RelWithDebInfo, MinSizeRel)。LLVM_ENABLE_PROJECTS
: 选择要构建的 LLVM 子项目 (如 "clang;lld;lldb")。LLVM_TARGETS_TO_BUILD
: 指定要支持的目标架构 (如 "X86;ARM;AArch64" 或 "Native" 或 "all")。- 其他常用选项如
CMAKE_INSTALL_PREFIX
,LLVM_ENABLE_ASSERTIONS
,LLVM_USE_LINKER
等。 CMake 会执行配置检查,并在构建目录中生成原生构建文件(如build.ninja
)以及一个CMakeCache.txt
文件,后者存储了所有配置选项的值。
- 构建 (Build via Native Tool): 配置成功后,在构建目录中运行底层的构建工具(例如,如果选择了 Ninja 生成器,则运行
ninja
命令)。也可以使用 CMake 提供的可移植命令cmake --build.
,它会自动调用配置阶段确定的构建工具。默认情况下,这将构建所有已启用的项目和目标。也可以指定构建特定的目标,例如ninja clang
或ninja opt
。 - 测试 (Test - Optional): 可以通过构建特定的测试目标来运行 LLVM 的测试套件,例如
ninja check-all
或ninja check-llvm
、ninja check-clang
等。 - 安装 (Install - Optional): 如果需要将构建好的 LLVM 工具和库安装到指定位置(由
CMAKE_INSTALL_PREFIX
定义),可以运行cmake --build. --target install
。
LLVM 的构建系统不仅支持基本的编译流程,还通过 CMake 的强大功能支持更高级的构建配置。例如,它支持多阶段引导构建(bootstrap builds),用于使用刚构建的 Clang 再次构建自身;支持 Profile-Guided Optimization (PGO) 构建,利用运行时的性能分析数据来优化编译器自身的性能;以及支持使用 BOLT (Binary Optimization and Layout Tool) 对最终的二进制文件进行后链接优化。对于这些复杂的构建场景,LLVM 在 clang/cmake/caches
目录下提供了一系列预定义的 CMake 缓存文件(.cmake
文件),用户可以通过 cmake -C <cache_file_path>
的方式加载这些文件,从而简化复杂配置标志的设置。这表明 CMake 在 LLVM 中不仅仅是一个简单的 Makefile 生成器,而是作为一个管理高度复杂构建过程的框架。
4. CMake 元构建系统详解
CMake 被称为“元构建系统”(meta-build system),因为它本身不直接编译代码,而是生成用于实际构建工具(如 Make、Ninja 或 IDE)的配置文件。它提供了一个抽象层,允许开发者使用统一的方式描述项目构建过程,而无需关心底层平台或构建工具的具体细节。
4.1 工作原理
CMake 的核心是处理名为 CMakeLists.txt
的文本文件。这些文件使用 CMake 自定义的、相对简单的脚本语言编写。开发者在 CMakeLists.txt
文件中使用各种命令来定义项目信息、查找依赖项、设置编译选项、声明构建目标(如可执行文件和库)以及指定它们之间的依赖关系。
当运行 cmake
命令时,它会:
- 读取
CMakeLists.txt
文件: 从项目顶层目录开始,递归处理子目录中的CMakeLists.txt
文件。 - 探测环境: 检查系统环境,确定可用的编译器、链接器、库、头文件以及操作系统特性。
- 处理指令: 执行
CMakeLists.txt
中的命令,例如使用project()
定义项目名称和语言,使用add_executable()
或add_library()
定义构建目标,使用target_link_libraries()
指定链接依赖,使用find_package()
查找外部库,使用include_directories()
添加头文件搜索路径,使用set()
定义变量,等等。 - 缓存配置: 将用户指定的选项(通过
-D
参数)和探测到的环境信息存储在构建目录下的CMakeCache.txt
文件中,供后续运行使用。 - 生成构建文件: 根据用户选择的生成器,将解析得到的项目结构和构建规则转换为特定构建工具能够理解的原生构建文件。
4.2 生成器机制
生成器(Generator)是 CMake 实现跨平台和跨工具构建的关键机制。每个生成器负责将 CMake 内部对项目的抽象描述转换为特定构建系统所需的具体文件格式和指令。
用户通过 cmake
命令的 -G
参数来选择生成器。常见的生成器包括:
Ninja
: 生成build.ninja
文件,供 Ninja 构建系统使用。Unix Makefiles
: 生成与make
兼容的 Makefile 文件,适用于 Linux、macOS 等 Unix-like 系统。Visual Studio 17 2022
(或其他版本): 生成 Visual Studio 解决方案 (.sln
) 和项目 (.vcxproj
) 文件,适用于 Windows 平台。Xcode
: 生成 Xcode 项目文件,适用于 macOS 平台。
通过选择不同的生成器,开发者可以使用同一套 CMakeLists.txt
文件在不同的操作系统上,使用不同的本地构建工具或 IDE 来构建项目,极大地提高了项目的可移植性和开发者的灵活性。
4.3 从 CMakeLists.txt
到原生构建文件
CMake 将 CMakeLists.txt
中的高级指令转换为原生构建文件的过程可以概括为:
- 解析与内部表示: CMake 解析所有相关的
CMakeLists.txt
文件,构建一个关于项目目标的内部表示,包括源文件、依赖关系、编译标志、链接库等信息。 - 生成器处理: 选定的生成器接收这个内部表示。
- 文件写入: 生成器根据其目标构建系统的规则,将内部表示转换为具体的文件。
- 对于
Ninja
生成器,会生成一个(或多个,通过subninja
).ninja
文件,其中包含 Ninja 变量、规则和构建语句的定义。 - 对于
Unix Makefiles
生成器,会生成多个Makefile
文件(通常每个目录一个),包含 Make 的变量、目标和规则。 - 对于
Visual Studio
生成器,会生成.sln
和.vcxproj
文件,这些是 XML 格式的文件,描述了项目结构、编译选项、链接设置等,可被 Visual Studio IDE 直接加载。
- 对于
- 包含具体指令: 这些生成的原生文件包含了调用编译器、链接器、库归档器等工具所需的确切命令行指令、所有必需的标志以及文件之间的依赖关系。
值得注意的是,CMake 与构建过程的集成通常不止于一次性的文件生成。cmake --build.
命令提供了一个统一的接口来调用底层的构建工具。更重要的是,当 CMakeLists.txt
文件或其包含的文件发生更改时,底层的构建系统(如 Make 或 Ninja,通过 CMake 生成的规则)通常会自动检测到这些更改,并在下次构建时重新运行 CMake 的配置和生成步骤,以确保原生构建文件始终与 CMakeLists.txt
中的定义保持同步。这使得 CMake 不仅仅是一个生成器,更像是一个构建配置的管理器,确保了构建描述和实际执行之间的一致性。
5. LLVM 中 CMake 到 Ninja 的转换过程
在 LLVM 项目中,利用 CMake 生成 Ninja 构建文件是推荐且常用的做法。这个过程涉及特定的 CMake 配置步骤,并最终将 LLVM 复杂的项目结构和构建选项转化为 Ninja 理解的低级指令。
5.1 CMake 配置步骤 (CMake 配置步骤 for LLVM with Ninja)
为 LLVM 配置 Ninja 构建环境的基本步骤如下:
- 安装依赖: 确保系统中已安装 CMake (满足 LLVM 的最低版本要求,如 3.20.0) 和 Ninja 构建工具。Ninja 可以通过系统包管理器(如
sudo apt install ninja-build
或brew install ninja
)或 Python 的 pip (pip install ninja
) 等方式安装。 - 创建构建目录: 如前所述,必须在 LLVM 源代码树之外创建一个空的构建目录。
- 运行 CMake 配置: 在构建目录中执行
cmake
命令。关键在于:- 指定 LLVM 源代码目录(例如
../llvm
或path/to/llvm-project/llvm
)。 - 使用
-G Ninja
参数明确选择 Ninja 作为生成器。 - 通过
-D<VARIABLE>=<VALUE>
设置必要的 LLVM 和 CMake 构建选项。
- 指定 LLVM 源代码目录(例如
一个典型的配置命令示例如下:
Bash
cd build_directory
cmake -S../llvm -B. -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_ENABLE_PROJECTS="clang;lld" \
-DLLVM_TARGETS_TO_BUILD="Native" \
-DLLVM_USE_LINKER=lld \
-DLLVM_PARALLEL_LINK_JOBS=4
这个命令会配置一个 Release 模式的构建,启用 Clang 和 LLD 子项目,构建支持本地架构的目标,使用 LLD 链接器,并将并行链接任务数限制为 4。
5.2 build.ninja
文件的生成细节
当使用 -G Ninja
时,CMake 会执行以下操作来生成 build.ninja
文件:
- 处理 LLVM CMake 脚本: CMake 解析 LLVM 项目中大量的
CMakeLists.txt
文件,包括位于llvm/cmake/modules
下的复杂配置逻辑,以确定所有的构建目标、源文件、依赖项和编译选项。 - 映射 CMake 目标到 Ninja 构建语句: CMake 中的每个目标(例如通过
add_library(LLVMCore...)
或add_executable(clang...)
定义)会被转换成build.ninja
文件中的一系列构建语句 (build...
)。这包括编译每个源文件生成目标文件 (.o
),将目标文件归档成静态库 (.a
) 或链接成共享库 (.so
/.dylib
/.dll
),以及将目标文件和库链接成可执行文件。 - 定义 Ninja 规则: CMake 会为常见的构建任务(如编译 C++ 文件、链接可执行文件、创建静态库等)生成对应的 Ninja
rule
定义。这些规则会包含基于 CMake 配置确定的具体编译器/链接器命令和标志。例如,会生成类似CXX_COMPILER__<target_name>_<CONFIG>
的规则用于编译,以及CXX_EXECUTABLE_LINKER__<target_name>_<CONFIG>
的规则用于链接。这些规则会使用 Ninja 的内置变量($in
,$out
)以及 CMake 传入的变量(如$FLAGS
,$LINK_FLAGS
)。 - 设置 Ninja 变量: 全局的 CMake 设置,如通用的编译器标志 (
CMAKE_CXX_FLAGS
),通常会被转换为build.ninja
文件顶层的 Ninja 变量。 - 处理依赖关系:
- CMake 会将目标之间的显式依赖(例如,
clang
依赖于LLVMCore
)转换为 Ninja 构建语句中的依赖项。 - 对于 C/C++ 源文件的头文件依赖,CMake 会配置编译器(通过
-MD
或类似标志)生成依赖信息,并在相应的 Ninja 编译规则中添加depfile = $DEP_FILE
属性,告知 Ninja 在编译后读取生成的.d
文件来更新依赖图。
- CMake 会将目标之间的显式依赖(例如,
- 处理自定义命令: LLVM 构建过程中包含许多自定义步骤,例如使用
llvm-tblgen
工具生成代码。CMake 的add_custom_command
和add_custom_target
指令会被转换为相应的 Ninja 构建语句和规则。 - 生成最终文件: 最终生成的
build.ninja
文件是一个非常庞大且详细的文件,它精确地描述了构建整个 LLVM 项目(根据所选配置)所需的所有步骤和它们之间的依赖关系。这个文件是低级的、明确的,专为 Ninja 快速执行而设计。
5.3 关键 CMake 变量的作用
在 LLVM 的 CMake 配置中,许多变量直接影响最终生成的 build.ninja
文件内容:
CMAKE_BUILD_TYPE
: (如 Release, Debug) 决定了 Ninja 编译和链接规则中使用的优化级别标志 (-O3
,-O0
) 和调试信息标志 (-g
, 无)。LLVM_ENABLE_PROJECTS
: 控制哪些 LLVM 子项目(如 clang, lld)的目标和构建规则会被包含在build.ninja
文件中。LLVM_ENABLE_RUNTIMES
: 类似地,控制哪些运行时库(如 libcxx, compiler-rt)会被构建。LLVM_TARGETS_TO_BUILD
: 决定为哪些目标架构生成代码,这会影响哪些后端相关的源文件和库被编译和链接,从而改变build.ninja
的内容。CMAKE_C_COMPILER
,CMAKE_CXX_COMPILER
: 指定 Ninja 规则中command
行使用的编译器可执行文件路径。LLVM_USE_LINKER
: 决定链接规则中使用哪个链接器(如lld
),影响链接命令和可能的链接速度/内存使用。BUILD_SHARED_LIBS
,LLVM_BUILD_LLVM_DYLIB
,LLVM_LINK_LLVM_DYLIB
: 控制是生成静态库还是共享库,这会改变build.ninja
中是生成.a
文件并通过ar
/ranlib
规则处理,还是生成.so
/.dylib
/.dll
文件并通过链接器规则处理。LLVM_ENABLE_ASSERTIONS
: 控制是否在编译规则中添加-DNDEBUG
宏定义,影响断言的启用状态。LLVM_PARALLEL_LINK_JOBS
: (仅 Ninja) 这个 CMake 变量会被用来设置 Ninja 的link_job_pool
,从而限制链接步骤的并发数量。CMake 通过在链接目标上设置CMAKE_JOB_POOL_LINK
属性来实现这一点。LLVM_OPTIMIZED_TABLEGEN
: 如果设置为 ON(尤其在 Debug 构建时),CMake 会为llvm-tblgen
工具本身生成使用 Release 优化级别的构建规则,即使项目的其他部分是 Debug 构建。
这个转换过程清晰地展示了 CMake 如何将高层的项目描述和配置选项映射到 Ninja 的低层构建原语。LLVM 特定的 CMake 变量为开发者提供了强大的控制力,让他们能够精确地定制生成的 build.ninja
文件,以满足不同的构建需求。理解这一点很重要:build.ninja
文件并非静态不变,而是 CMake 根据当前配置动态生成的产物。任何影响构建图或编译链接选项的 CMake 变量的改变,都需要重新运行 CMake 来生成更新后的 build.ninja
文件,才能使更改生效。这正是 CMake 作为“元”构建系统的体现——它定义和管理着生成最终构建指令的过程。
6. 在 LLVM 中使用 Ninja 的优势
将 Ninja 作为 LLVM 项目的构建系统后端带来了多方面的好处,其中最显著的是构建速度的提升和对开发周期的积极影响。
6.1 构建速度提升分析
- 显著的增量构建加速: 这是 Ninja 最核心的优势,也是 LLVM 社区广泛采用它的主要原因。对于 LLVM 这样庞大且模块化的项目,开发者在日常工作中通常只修改少量代码。Ninja 能够近乎瞬时地检测出哪些文件需要重新编译,而不需要像 Make 那样花费大量时间扫描整个依赖图或处理大量的依赖文件。这种快速反馈极大地缩短了“编码-编译-测试”的循环时间,提高了开发效率。
- 高效的并行构建: Ninja 默认启用并行构建,能更好地利用现代多核处理器的计算能力。虽然完整构建的时间仍然主要取决于编译器和链接器的性能,但 Ninja 高效的任务调度(可能比 Make 更早地开始链接等后续步骤)和低自身开销有助于更充分地利用硬件资源,可能带来一定的完整构建时间缩短。
- 低构建系统开销: Ninja 本身是一个轻量级工具,其解析和决策过程非常快,占用的 CPU 和内存资源很少。这意味着更多的系统资源可以用于实际的编译和链接任务,而不是被构建系统本身消耗掉。
6.2 资源利用率优化
- CPU: 通过高效的并行调度,Ninja 有助于在编译阶段最大化 CPU 利用率。
- 内存: Ninja 本身内存占用低。然而,它所驱动的高度并行构建,特别是并行链接阶段,可能会给系统内存带来巨大压力,尤其是在构建 Debug 版本或启用 LTO 时。不过,LLVM 的 CMake 构建系统与 Ninja 结合提供了解诀方案:
- 推荐使用内存效率更高的 LLD 链接器 (
-DLLVM_USE_LINKER=lld
)。 - 提供
LLVM_PARALLEL_LINK_JOBS
选项来限制链接阶段的并发任务数,从而控制峰值内存使用。 通过这些机制,可以在享受 Ninja 并行优势的同时,有效管理内存资源。
- 推荐使用内存效率更高的 LLD 链接器 (
- I/O: Ninja 的输出通常比 Make 更简洁,并且其内部处理依赖关系的方式(例如使用二进制日志或优化的
.d
文件解析)可能比 Make 扫描大量文本格式的.d
文件具有更低的 I/O 开销。
6.3 对开发周期的影响
Ninja 对 LLVM 开发周期的积极影响是多方面的:
- 快速反馈: 最直接的影响是大大缩短了等待编译的时间,使得开发者能够更快地验证代码更改、运行测试和进行调试,从而加速整个开发迭代过程。
- 易于采用: 对于已经使用 CMake 的 LLVM 开发者来说,切换到 Ninja 非常简单,只需在 CMake 配置时添加
-G Ninja
参数即可。这使得采用 Ninja 的门槛非常低。 - 促进并行友好代码: Ninja 默认的并行构建行为,从一开始就鼓励开发者编写能够在并行环境中正确构建的代码,有助于及早发现和解决与并行性相关的问题。
综合来看,Ninja 的速度优势,特别是其出色的增量构建性能,结合其作为 CMake 生成器的无缝集成,使其成为 LLVM 社区绝大多数开发者首选的构建工具。它代表了 LLVM 标准的开发构建体验。虽然 Ninja 本身并不直接优化编译或链接步骤的执行时间,但它通过极大地减少构建系统的编排开销,为开发者提供了更快的迭代能力。这种快速迭代对于应用和评估 LLVM 自身的各种编译优化技术(如调整优化标志、使用 PGO、LTO 或 BOLT 等需要多次构建或复杂流程的技术)也至关重要。Ninja 的高效使得这些高级优化工作流的执行更加顺畅和高效。
7. LLVM 社区 Ninja 使用最佳实践
为了在 LLVM 开发中充分利用 Ninja 的优势并规避潜在问题,社区已经形成了一套推荐的最佳实践和配置方法。
7.1 推荐配置与设置
根据 LLVM 官方文档和社区经验,以下是一些推荐的配置和设置:
- 使用 Ninja 生成器: 在运行 CMake 时,务必使用
-G Ninja
指定 Ninja 作为生成器。这是 LLVM 开发者的标准和推荐做法。 - 选择合适的构建类型 (
CMAKE_BUILD_TYPE
):Release
: 用于最终发布、性能基准测试或日常使用。代码经过高度优化,调试信息最少。构建速度可能稍慢于 Debug,但运行时性能最佳。Debug
: 主要用于调试 LLVM 本身。禁用优化 (-O0
),包含完整的调试信息,默认启用断言。运行时性能较差,且占用大量磁盘空间(可能 15-20GB 或更多)和内存。编译速度相对较快(因为优化步骤少),但链接可能非常耗时耗内存。RelWithDebInfo
: 提供了优化(通常-O2
)和调试信息的平衡,适合需要调试优化后代码的场景。
- 启用断言 (
LLVM_ENABLE_ASSERTIONS
): 在开发和测试阶段,建议开启断言 (-DLLVM_ENABLE_ASSERTIONS=ON
) 以帮助捕捉内部逻辑错误。即使是 Release 构建,也可以显式开启断言 (-DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=ON
),这是许多持续集成(buildbot)环境的常用配置,以在保持较好性能的同时进行检查。 - 使用 LLD 链接器 (
LLVM_USE_LINKER
): 强烈推荐使用 LLD (-DLLVM_USE_LINKER=lld
) 作为链接器。LLD 由 LLVM 项目自身开发,链接速度快,内存占用显著低于传统的 BFD ld 或 gold 链接器,尤其是在链接大型 C++ 项目(如 LLVM 和 Clang)时优势明显。使用 LLD 需要确保它已安装在系统中或作为构建的一部分被启用。 - 限制链接并行度 (
LLVM_PARALLEL_LINK_JOBS
): 为了防止在链接阶段耗尽内存,特别是使用 Ninja 的高并发构建时,应设置此选项。一个常见的推荐值是2
或4
,具体取决于系统的可用内存。例如,-DLLVM_PARALLEL_LINK_JOBS=2
。 - 使用 ccache (
LLVM_CCACHE_BUILD
): 启用 ccache (-DLLVM_CCACHE_BUILD=ON
) 可以缓存编译结果,显著加快重新构建的速度,尤其是在切换分支、修改少量文件或重复构建相同配置时。对于自动化构建环境,ccache 通常比增量构建更受推荐,因为它能提供大部分速度优势,同时降低因构建状态损坏导致错误的可能性。 - 构建特定目标/项目 (
LLVM_TARGETS_TO_BUILD
,LLVM_ENABLE_PROJECTS
): 如果你只关心特定的目标架构(如 X86)或特定的子项目(如 Clang),明确指定它们可以显著减少需要编译和链接的代码量,从而缩短构建时间。例如,-DLLVM_TARGETS_TO_BUILD="X86"
和-DLLVM_ENABLE_PROJECTS="clang"
。 - 优化 TableGen (
LLVM_OPTIMIZED_TABLEGEN
): 在进行 Debug 构建时,llvm-tblgen
工具(用于代码生成)的执行可能成为瓶颈。设置-DLLVM_OPTIMIZED_TABLEGEN=ON
可以让 CMake 为llvm-tblgen
本身生成 Release 模式的构建规则,从而加速其执行,进而加快整个构建过程。
下表总结了 LLVM 中用于 Ninja 构建的关键 CMake 变量及其推荐用法:
变量名 | 描述 | 典型值/示例 | 推荐/注释 | 参考 |
CMAKE_BUILD_TYPE | 控制优化级别和调试信息 | Release , Debug , RelWithDebInfo , MinSizeRel | 开发用 RelWithDebInfo 或 Release +断言;深入调试用 Debug (注意资源消耗) | |
LLVM_ENABLE_PROJECTS | 选择要构建的 LLVM 子项目 | "clang;lld" , "mlir" , all | 按需选择,减少构建范围 | |
LLVM_TARGETS_TO_BUILD | 选择要支持的目标架构后端 | "X86" , "ARM;AArch64" , "Native" , all | 按需选择,Native 适用于本地开发 | |
LLVM_ENABLE_ASSERTIONS | 是否启用断言检查 | ON , OFF | 开发和测试时推荐 ON | |
LLVM_USE_LINKER | 指定链接器 | lld , gold , bfd | 强烈推荐 lld 以加速链接和减少内存 | |
LLVM_PARALLEL_LINK_JOBS | 限制并行链接任务数 (Ninja only) | 2 , 4 , 1 | 必须设置以防 OOM,根据内存调整 (e.g., 2-4) | |
LLVM_CCACHE_BUILD | 是否使用 ccache 缓存编译 | ON , OFF | 推荐 ON 以加速重复构建 | |
BUILD_SHARED_LIBS | 是否构建共享库 | ON , OFF (默认) | ON 可减少链接时间和磁盘空间,但可能影响启动时间;LLVM_BUILD_LLVM_DYLIB 更常用 | |
LLVM_OPTIMIZED_TABLEGEN | 是否优化构建 llvm-tblgen | ON , OFF | 在 Debug 构建时推荐 ON 以加速 |
7.2 常用 CMake 选项解析
(上述表格已涵盖关键选项及其作用的解析)
7.3 社区经验分享
LLVM 社区在使用 Ninja 进行构建方面积累了丰富的经验:
- Ninja 是标准: Ninja 配合 CMake 是 LLVM 开发事实上的标准构建方式。
- 链接是瓶颈: 链接阶段,尤其是并行链接,是常见的内存消耗大户和潜在的构建瓶颈。
- LLD 和 Link Jobs 是关键: 使用 LLD 和通过
LLVM_PARALLEL_LINK_JOBS
控制链接并发是解决链接瓶颈的标准对策。 - 构建类型权衡: 开发者通常需要在
Release
(性能好,难调试) 和Debug
(易调试,资源消耗大) 之间权衡,RelWithDebInfo
或Release
+ Assertions 是常见的折衷方案。 - 高级构建依赖 CMake Cache: 对于 PGO、BOLT、交叉编译等复杂场景,利用 LLVM 提供的 CMake Cache 文件 (
-C
) 是简化配置的常用技巧。 - ccache 加速迭代: ccache 被广泛用于进一步减少重复编译的时间。
这些社区实践反映了在大型 C++ 项目中使用高性能构建系统时需要进行的典型权衡:速度、资源消耗、可调试性之间的平衡。LLVM 的 CMake 构建系统通过提供诸如 LLVM_PARALLEL_LINK_JOBS
、LLVM_USE_LINKER
、LLVM_OPTIMIZED_TABLEGEN
等特定选项,展现了其与 Ninja 的协同进化。构建系统本身已经发展出必要的控制机制,以帮助开发者更好地驾驭 Ninja 的强大能力,同时缓解其在 LLVM 这种资源密集型项目上可能带来的副作用(如内存压力)。这表明 LLVM 维护者积极地在 CMake 层面上添加特性来优化 Ninja 的生成结果和使用体验,而不仅仅是依赖 Ninja 的默认行为。
8. 在 LLVM 中使用 Ninja 的挑战与对策
尽管 Ninja 为 LLVM 构建带来了显著优势,但在实际使用中也可能遇到一些挑战。了解这些潜在问题及其解决方案对于顺利进行 LLVM 开发至关重要。
8.1 可能遇到的问题
- 内存消耗过高 (High Memory Consumption): 这是最常见的问题之一,主要发生在链接阶段。Ninja 的高并行度默认会启动大量链接任务,而链接本身(尤其是链接带有大量调试信息或经过 LTO 处理的 C++ 代码)是内存密集型操作。这可能导致系统内存耗尽,触发 OOM killer,或者使得系统因过度使用交换空间而变得极其缓慢甚至卡死。Debug 构建由于包含大量调试符号,尤其容易遇到此问题。
- 构建失败/子命令失败 ("Subcommand Failed"):
ninja: build stopped: subcommand failed.
是一个常见的笼统错误信息。其背后原因多种多样:- 内存不足: 如上所述,链接器或其他编译任务因内存耗尽而被终止。
- 依赖缺失: 系统缺少必要的库、头文件,或者 CMake/Ninja 本身未正确安装或找不到。
- 配置错误: CMake 配置不正确,例如工具链路径错误、编译器不兼容、环境变量设置不当(尤其是在 Windows 上使用 clang-cl 时,需要先加载 VS 开发环境)。
- 源代码错误: 正在编译的 LLVM 代码本身存在编译错误。
- 构建脚本问题: 极少数情况下,可能是 LLVM 的 CMake 脚本或 Ninja 本身的 bug,例如文件生成规则的竞争条件(虽然 Ninja 的调度可能比 Make 更不易触发此类问题) 或 Ninja 内部状态错误。
- 配置错误 (Configuration Errors): 在运行 CMake 时就可能遇到错误,例如 CMake 无法找到 Ninja 可执行文件 (
CMAKE_MAKE_PROGRAM is not set
),或者无法识别或配置 C/C++/ASM 编译器。这通常指向环境设置问题或工具链安装问题。 - 磁盘空间不足 (Insufficient Disk Space): 特别是 Debug 构建,可能需要数十 GB 的磁盘空间。如果磁盘空间不足,构建过程(尤其是链接或归档步骤)会失败。
8.2 限制与注意事项
- 依赖 CMake 正确性: Ninja 完全依赖于 CMake 生成的
build.ninja
文件的正确性。如果 LLVM 的CMakeLists.txt
文件有误,或者 CMake 配置不当,生成的 Ninja 文件就会有问题,导致构建失败。调试这类问题有时需要理解 CMake 的逻辑。 - 不加速编译/链接本身: Ninja 加速的是构建的调度和依赖检查过程,它不能让编译器或链接器本身运行得更快。构建性能的根本瓶颈仍然在于编译和链接工具。
- 需要额外安装: Ninja 是一个独立的工具,需要与 CMake 分开安装。
- 高并行风险: Ninja 默认的高并行度虽然是优点,但也意味着如果系统资源(特别是内存)有限,很容易导致资源耗尽,需要开发者主动进行管理。
8.3 解决方案与规避方法
针对上述挑战,可以采取以下对策:
- 解决内存问题:
- 限制链接并发: 使用 CMake 选项
-DLLVM_PARALLEL_LINK_JOBS=N
(例如 N=1, 2, 或 4) 是最直接有效的方法。 - 使用 LLD: 切换到 LLD 链接器 (
-DLLVM_USE_LINKER=lld
) 可以显著降低链接时的内存峰值。 - 降低全局并发: 如果内存仍然不足,可以通过
ninja -jN
(使用较小的 N) 降低整体并行度,但这会牺牲编译速度。 - 选择构建类型: 避免使用 Debug 构建,优先选择
Release
或RelWithDebInfo
。 - 减少构建内容: 只构建需要的项目和目标 (
LLVM_ENABLE_PROJECTS
,LLVM_TARGETS_TO_BUILD
)。 - 使用共享库: 尝试使用
-DBUILD_SHARED_LIBS=ON
(或LLVM_BUILD_LLVM_DYLIB=ON
)。 - 增加系统资源: 增加物理内存或交换空间。
- 限制链接并发: 使用 CMake 选项
- 处理构建失败:
- 详细检查日志: 仔细阅读 Ninja 和编译器的输出,找到具体的错误信息。
- 检查环境和依赖: 确认所有必需的工具(CMake, Ninja, Python, 兼容的 C++ 编译器, 系统库)都已正确安装且版本符合要求。
- 清理并重新配置: 如果怀疑配置有问题,删除构建目录下的所有文件(或至少
CMakeCache.txt
),然后重新运行 CMake 配置命令。 - 使用
-k
选项: 运行ninja -k 0
可以让 Ninja 在遇到错误时继续尝试构建其他独立的目标,这有助于一次性发现多个编译错误或缺失的依赖。 - 单独构建目标: 如果某个特定文件编译失败,尝试只构建该目标 (
ninja path/to/target.o
) 以隔离问题。
- 解决配置错误:
- 确保 Ninja 在 PATH 中: 确认
ninja
命令可以在命令行中直接执行,或者通过CMAKE_MAKE_PROGRAM
变量告知 CMake 其路径。 - 设置 Windows 环境: 在 Windows 上使用
clang-cl
和 Ninja 时,必须在 Visual Studio 开发者命令提示符(或其他已加载 VS 环境变量的环境)下运行 CMake。 - 显式指定编译器: 如果 CMake 无法自动检测,使用
-DCMAKE_C_COMPILER=/path/to/compiler
和-DCMAKE_CXX_COMPILER=/path/to/compiler++
明确指定。
- 确保 Ninja 在 PATH 中: 确认
- 管理磁盘空间:
- 选择合适的构建类型:
Release
或MinSizeRel
占用的空间远小于Debug
。 - 减少构建内容: 如上所述,只构建必要的组件。
- 使用共享库: 构建共享库可以减少最终安装大小。
- 定期清理: 删除不再需要的旧构建目录。
- 选择合适的构建类型:
可以看出,许多在 LLVM 中使用 Ninja 时遇到的“问题”,实际上是大型 C++ 项目并行构建所面临的普遍资源管理挑战,或者是 CMake 与特定平台/工具链集成的复杂性问题。Ninja 本身作为一个构建执行器相对健壮,但其高性能特性可能会放大系统资源或配置上的短板。LLVM 社区和构建系统开发者已经意识到了这些挑战,特别是链接阶段的内存问题,并提供了如 LLVM_PARALLEL_LINK_JOBS
和推荐 LLD 等成熟的缓解策略和官方指导,这表明项目已经具备了有效利用 Ninja 并管理其潜在副作用的机制。
9. 总结与建议
本报告深入分析了 Ninja 构建系统的设计理念、核心功能及其与传统构建系统(如 Make)的对比,并详细探讨了其在 LLVM 项目中的具体应用、优势、最佳实践以及可能遇到的挑战。
9.1 关键发现回顾
- Ninja 核心特性: Ninja 是一个以速度和简洁性为核心设计目标的低级构建系统。它通过极简的语法、快速的依赖检查和最小化的构建时决策,实现了极其快速的增量构建。它被设计为由 CMake 等元构建系统生成构建文件,而非手动编写。
- Ninja vs. Make: Ninja 在增量构建速度上远超 Make,尤其是在 LLVM 这样的大型项目中,能显著缩短开发迭代周期。Make 功能更丰富,但语法复杂且增量构建较慢。对于使用 CMake 的项目,切换到 Ninja 通常是简单且有效的性能优化。
- LLVM 构建架构: LLVM 完全依赖 CMake 作为元构建系统,负责配置和生成构建文件。标准流程涉及使用 CMake(推荐
-G Ninja
)在源代码树之外进行配置,然后使用 Ninja(或cmake --build.
)执行实际构建。 - CMake 到 Ninja: CMake 将 LLVM 项目的
CMakeLists.txt
文件中定义的目标、依赖和配置选项,转换为包含精确构建规则和命令的低级build.ninja
文件。关键的 LLVM CMake 变量直接控制生成的 Ninja 文件内容。 - Ninja 在 LLVM 中的优势: 主要优势在于大幅提升增量构建速度,从而加速开发周期。高效的默认并行性也提高了资源利用率。Ninja 已成为 LLVM 社区的标准构建工具。
- 最佳实践: 推荐使用
-G Ninja
、LLD 链接器、根据需求选择CMAKE_BUILD_TYPE
、启用断言(开发时)、使用 ccache,并通过LLVM_PARALLEL_LINK_JOBS
控制链接并发以避免内存耗尽。 - 挑战与对策: 主要挑战是链接阶段可能出现的内存消耗过高问题,以及 CMake 配置的复杂性(尤其在特定平台)。解决方案包括限制链接并发、使用 LLD、选择合适的构建类型和确保正确的环境配置。
9.2 针对 LLVM 开发者的最终建议
基于以上分析,为希望高效构建和开发 LLVM 的开发者提供以下建议:
- 优先使用 Ninja: 始终使用 Ninja 作为 CMake 生成器 (
-G Ninja
)。这是 LLVM 社区的标准实践,其带来的增量构建速度提升对于日常开发至关重要。 - 优化链接过程:
- 使用 LLD: 尽可能配置使用 LLD 链接器 (
-DLLVM_USE_LINKER=lld
),以显著减少链接时间和内存占用。 - 限制链接并发: 必须设置
-DLLVM_PARALLEL_LINK_JOBS
为一个适合你系统内存的值(例如,从 2 或 4 开始尝试),以防止因内存耗尽导致的构建失败。
- 使用 LLD: 尽可能配置使用 LLD 链接器 (
- 明智选择构建类型:
- 对于日常开发和测试,
Release
或RelWithDebInfo
通常是更好的选择,以平衡性能和资源消耗。 - 仅在确实需要深入调试 LLVM 内部时才使用
Debug
类型,并确保有足够的内存和磁盘空间。 - 在开发过程中启用断言 (
-DLLVM_ENABLE_ASSERTIONS=ON
) 以便尽早发现问题。
- 对于日常开发和测试,
- 利用缓存和范围控制:
- 启用 ccache (
-DLLVM_CCACHE_BUILD=ON
) 以加速重复编译任务。 - 通过
-DLLVM_ENABLE_PROJECTS
和-DLLVM_TARGETS_TO_BUILD
仅构建你当前工作所需的最小子集,以减少不必要的构建时间。
- 启用 ccache (
- 确保环境正确: 在运行 CMake 之前,确保所有依赖项(编译器、Python 等)已正确安装,并且开发环境已正确设置(特别注意 Windows 上使用
clang-cl
时需要加载 Visual Studio 环境变量)。 - 参考官方文档: LLVM 的
GettingStarted.html
和CMake.html
页面是获取最新配置选项、解决常见问题的重要资源,应经常查阅。
通过遵循这些建议,LLVM 开发者可以最大限度地发挥 Ninja 构建系统的优势,实现更快速、更流畅的开发体验,同时有效管理构建过程中的资源消耗。