0%

CMake使用Ninja加速C++代码构建过程

Ninja的小巧、快速、稳定确实惊艳到我,在使用Ninja之前,我们一直使用Incredi Build加速我们的产品构建,IB确实是一款非常优秀的商业软件,直到最近在使用IB编译时,经常性导致服务器死机,我不得不寻求其他加速构建过程的工具,这时我找到了Ninja,我们的代码是采用CMake构建的,而CMake刚好支持ninja的generator,所以引入Ninja没有花费太多时间,让人敬佩的是,此后就再也没有死机过了,真的是再有没有👍。

CMake如何使用Ninja

如下以Windows为例:

  1. 下载ninja.exe,放到某个目录,假设D:\tool,并将D:\tool加入PATH环境变量;
  2. 假设源代码目录为D:\code,其中包含CMakeLists.txt文件,那么构建过程如下:
1
2
3
4
5
cd /d D:\code
mkdir build
cd build
cmake .. -G "Ninja" -DCMAKE_MAKE_PROGRAM=D:\tool\ninja.exe
ninja

Ninja和MSVC的关系

在Windows上一般使用的编译器是MSVC,Ninja和MSVC的关系就像make和gcc的关系,当然Ninjia也可以用在Linux下和gcc配合。所以Ninja只是负责调用编译器干活,本生并不负责编译,需要弄清楚。

如何使用预编译头文件

CMake并没有内置预编译头文件的支持,需要通过target_compile_options、set_source_files_properties这些已有的命令设置预编译参数,从github上可以找到一些封装,可以开箱即用,例如:cmake-precompiled-headerCMakePCHCompiler,其中CMakePCHCompiler的目标就是成为CMake的标准模块。

但是这些封装中都没有考虑到使用ninjia在预编译时存在一个问题,预编译的过程是首先需要根据预编译头文件生成pch文件(类似obj文件),然后再编译其他cpp文件,也就是其他cpp文件需要依赖这个pch文件,他们有编译的先后顺序,如果使用MSVS编译,它会保证这个顺序,但是换成ninja后,这个依赖关系并不会自动生成到build.ninja等文件中,这样编译的过程就会出现pch还没生成就开始编译其他cpp文件,自然就编译不过。

这个问题我研究了很长时间,才发现在CMakeLists.txt中增加一些编译参数设置才行:

  • 在预编译头文件对应的cpp文件上,主要关注OBJECT_OUTPUTS属性
    1
    set_source_files_properties(${header_src} PROPERTIES COMPILE_FLAGS "/Yc${header_path} /FI${header_path} /Fp${win_pch}" OBJECT_OUTPUTS ${win_pch})
  • 在其他每个cpp文件上,主要关注OBJECT_DEPENDS属性
    1
    set_source_files_properties(${src} PROPERTIES OBJECT_DEPENDS ${win_pch})

这样生成的ninjia构建文件才会生成正确的依赖关系。

Ninja和MSVC共享中间文件

Ninja加速编译过程是非常好的选择,但是开发调试还是需要在IDE下,Windows下就是MSVS,所以还是希望CMake也生成MSVS工程,然后打开MSVS开发调试。但是Ninja和MSVS两个generator只能二选一,有没有办法能够两者同时使用呢,我做了一些尝试,解决了中间一些问题,还有待继续:

  1. 首先要解决的是生成2个generator,CMake是没有办法在同一个build目录生成两次不同的generator的,所以需要用不同的目录,还是上面的例子:

    1
    2
    3
    4
    5
    cd /d D:\code
    mkdir build
    cd build
    cmake .. -G "Ninja" -Bninja -DCMAKE_MAKE_PROGRAM=D:\tool\ninja.exe
    cmake .. -G "Visual Studio 15 2017 Win64"

    这样ninja的工程生成在build\ninja目录下,MSVS工程生成在build目录下。

  2. 其次要解决ninja和MSVC生成的中间文件的位置不一样问题,如obj文件。ninja是一个单配配置构建系统,而MSVS是一个多配置构建系统,他们的中间文件路径是不一样的。

    针对这个问题,我对CMake源代码进行了一些修改,将ninjia输出的中间文件路径改为和MSVS一样,参见我的github仓库:https://github.com/zhongpan/CMake

  3. 最后要解决MSVC和ninjia要能够认彼此生成的编译中间文件,切换后不会触发重编,可能中间有一些彼此特有的文件需要生成,这一步还有待继续💪。

-------------本文结束感谢您的阅读-------------