进阶与展望:CMake 3.x新特性、Ninja构建系统、Bazel与CMake对比、未来趋势与学习资源

走到这里,咱们已经一起啃下了CMake与NDK集成的硬骨头。说实话,能坚持到这一章,你已经不是初学者了。现在该抬起头,看看这个领域正在发生什么变化,以及下一步该往哪走。

我经常被问到:“老师,CMake是不是过时了?要不要直接学Bazel?” 嗯,这个问题背后其实藏着对技术选型的焦虑。今天我就把这几年的观察和踩坑经验,一次性说清楚。

一、CMake 3.x 新特性:老工具的新把戏

CMake 3.x 系列其实一直在进化。很多人对CMake的印象还停留在2.8时代——那个写起来像受刑的年代。但3.x之后,体验好了不止一个档次。

1. 预设(Presets)机制

这是3.19引入的,我个人觉得是CMake近年来最实用的功能。以前你要给团队写构建说明,得搞个README,写上“先执行这个,再执行那个”。现在直接一个CMakePresets.json搞定。

核心价值:把构建配置标准化,新人来了不用问“我该用哪个Generator”。
{
  "version": 3,
  "configurePresets": [
    {
      "name": "default",
      "displayName": "默认配置",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Release",
        "ANDROID_ABI": "arm64-v8a"
      }
    }
  ]
}

我在项目里用这个之后,CI脚本直接从50行缩到了10行。你想想看,维护成本降了多少。

2. 文件集(File Sets)

3.23版本开始,target_sources()支持了FILE_SET。以前你要手动区分头文件和源文件,现在可以这样写:

target_sources(my_lib
  PUBLIC
    FILE_SET HEADERS
      BASE_DIRS include
      FILES include/my_lib/api.h
  PRIVATE
    src/impl.cpp
)

这样做的好处是,IDE和构建系统能自动识别头文件路径。我遇到过好几次因为头文件路径写错导致的编译失败,用了这个之后基本没再犯过。

3. 依赖提供者(Dependency Providers)

3.24引入的CMAKE_DEPENDS_USE_COMPILER,说白了就是让CMake直接问编译器“这个文件依赖了谁”,而不是自己傻傻地解析。对于大型项目,这一步能省下不少预处理时间。

我的建议:如果你还在用CMake 3.16以下的版本,赶紧升级。3.20之后的体验完全是两个世界。

二、Ninja构建系统:为什么它比Make快?

聊到构建速度,就绕不开Ninja。我最早接触Ninja是在2016年,当时被它的速度震惊了——同样的项目,Make要跑45秒,Ninja只要12秒。

为什么会这样?

Ninja的设计哲学很简单:不做任何决策。它不像Make那样会去推导依赖关系,而是完全依赖CMake生成的build.ninja文件。说白了,Ninja就是个执行器,所有脑力活都交给了CMake。

特性 Make Ninja
依赖解析 运行时推导 预先生成
增量构建 基于时间戳 基于内容哈希(可选)
并行度 -j参数 默认全核
跨平台 Linux为主 全平台

在Android NDK开发中,我强烈建议你用Ninja。你只需要在CMake配置时加上:

cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake ..

然后直接ninja就行。第一次构建可能感觉不明显,但当你改了一行代码重新编译时,那种秒级反馈的快感,用过就回不去了。

注意:Ninja本身不提供清理功能。你需要用ninja -t clean或者直接删掉build目录。我曾经因为这个在CI上吃了亏——改了构建脚本但没清缓存,结果一直用旧配置在跑。

三、Bazel与CMake对比:谁更适合你?

Bazel是Google家的构建工具,在Android圈子里越来越火。但说实话,它和CMake不是同一个维度的东西。

我拿一个实际项目来对比。假设我们要构建一个包含三个模块的Android NDK项目:

  • CMake方式:写一个顶层的CMakeLists.txt,用add_subdirectory()引入子模块。构建时CMake先生成Makefile或Ninja文件,再调用底层构建器。
  • Bazel方式:每个模块写一个BUILD文件,用cc_librarycc_binary规则。Bazel自己管理所有依赖和缓存。

Bazel的优势在于:

  • 真正的沙盒构建,每个动作都在隔离环境中执行
  • 远程缓存和分布式构建开箱即用
  • 精确的增量分析,改一个文件只重编受影响的部分

但代价也很明显:

  • 学习曲线陡峭,BUILD文件的写法跟CMake完全不同
  • 生态不如CMake成熟,很多第三方库没有现成的Bazel规则
  • 对Android NDK的支持,说实话,不如CMake顺手
我的结论:如果你在做一个全新的、从零开始的超大项目(比如百万行级别),Bazel值得考虑。但如果你在维护现有项目,或者团队里都是CMake熟手,别折腾了,CMake 3.x + Ninja的组合足够你用到2028年。

四、未来趋势:构建工具在往哪走?

我观察到的几个明显趋势:

  1. 模块化构建:不管是CMake的FetchContent还是Bazel的外部依赖,大家都在往“自动拉取、本地缓存”的方向走。以后手动下载第三方库的日子会越来越少。
  2. 缓存为王:CCache、sccache这些工具越来越普及。我现在的项目里,二次构建速度比第一次快了10倍以上,全靠缓存。
  3. 语言层面的构建支持:Rust的Cargo、Go的原生构建工具,都在证明一件事——构建系统最好跟语言深度绑定。C/C++因为历史原因用了CMake这个“中间人”,但未来会不会出现更原生的方案?不好说。
  4. WebAssembly的冲击:Emscripten让C++代码可以编译到WebAssembly,而CMake已经原生支持了。这个方向值得关注。

嗯,这里要注意一点:不要盲目追新。我见过有人为了用Bazel而用Bazel,结果项目延期了两个月。工具是拿来解决问题的,不是拿来炫耀的。

五、学习资源推荐

最后,分享一些我这些年筛选出来的优质资源。说实话,网上关于CMake和NDK的资料很多,但真正能用的不多。

类型 资源名称 推荐理由
官方文档 CMake官方文档(cmake.org/cmake/help) 最权威,没有之一。3.x版本的文档写得比以前好多了
书籍 《Professional CMake》 Craig Scott写的,CMake核心维护者之一。深度足够
实战 Android NDK官方示例(native-activity、hello-jni等) Google维护的,跟最新NDK版本同步
视频 YouTube上的“C++Now”大会CMake相关演讲 每年都有新内容,讲得很深入
社区 CMake Discourse论坛 提问基本24小时内有人回,维护者也会亲自回答
一个小技巧:遇到CMake问题,先搜stackoverflow,再搜CMake Discourse。如果都找不到,去GitHub上找CMake的issue区,很多问题其实已经被讨论过了。

好了,这一章的内容就到这里。CMake的世界很大,但你已经有了足够的地图和工具。剩下的,就是在实战中慢慢打磨了。


公众号:蓝海资料掘金营,微信deep3321