什么是CMake?
你或许听过好几种 Make 工具,例如 GNU Make ,QT 的 qmake ,微软的 MS nmake,BSD Make(pmake),Makepp,等等。这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile ,这将是一件让人抓狂的工作。CMake 就是针对上面问题所设计的工具:它首先允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。显然,CMake 是一个比上述几种 make 更高级的编译配置工具。一些使用 CMake 作为项目架构系统的知名开源项目有 VTKITKKDEOpenCVOSG[1]

在 linux 平台下使用 CMake 生成 Makefile 并编译的流程如下:

  1. 编写 CMake 配置文件 CMakeLists.txt 。
  2. 执行命令 cmake PATH 或者 ccmake PATH 生成 Makefile(ccmakecmake 的区别在于前者提供了一个交互式的界面)。其中, PATH 是 CMakeLists.txt 所在的目录。
  3. 使用 make 命令进行编译。

本文将从实例入手,一步步讲解 CMake 的常见用法,文中所有的实例代码可以在这里找到。如果你读完仍觉得意犹未尽,可以继续学习我在文章末尾提供的其他资源。

简单实例-helloworld

  • 工程文件结构:
  • src源码
1
2
3
4
5
6
7
8
#include <stdio.h>

int main(void)
{
printf("hello world\n");

return 0;
}
  • CMakeLists
1
2
3
4
5
6
7
8
# CMake 最低版本号要求
cmake_minimum_required(VERSION 3.0)

# 项目信息
project(demo)

# 指定生成目标
add_executable(main main.c)
  • 编译
    创建build目录,以便将cmake生成文件与源代码分开;
    在build目录下执行cmake ../

CMake交叉编译

对于交叉编译,CMake并不知道目标系统是什么,所以需要设置一些CMake变量来告知CMake

  • CMAKE_SYSTEM_NAME:即目标系统名,这里是Linux
  • CMAKE_SYSTEM_PROCESSOR :目标系统的处理器名

对于工具链,则是通过下面2个变量来定位,

  • CMAKE_C_COMPILER:C编译器的可执行文件名称
  • CMAKE_CXX_COMPILER:C++编译器的可执行文件名称

这些变量可以在调用CMake时通过命令行传递,但是这种做法容易出错,而且用起来不方便,所以CMake提供了工具链文件的方式来传递这些变量信息

这里编译为openwrt mips架构:

1
2
3
4
5
6
7
#openwrt_linux_setup.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR mips)

set(tools /opt/openwrt_mt7688_gcc-4.8/gcc-4.8)
set(CMAKE_C_COMPILER ${tools}/bin/mipsel-openwrt-linux-gcc)
set(CMAKE_CXX_COMPILER ${tools}/bin/mipsel-openwrt-linux-g++)

交叉编译配置

CMake给交叉编译预留了一个很好的变量即CMAKE_TOOLCHAIN_FILE,它定义了一个文件的路径,这个文件即toolChain,里面set了一系列你需要改变的变量和属性,包括C_COMPILER,CXX_COMPILER,如果用Qt的话需要更改QT_QMAKE_EXECUTABLE以及如果用BOOST的话需要更改的BOOST_ROOT(具体查看相关Findxxx.cmake里面指定的路径)。CMake为了不让用户每次交叉编译都要重新输入这些命令,因此它带来toolChain机制,简而言之就是一个cmake脚本,内嵌了你需要改变以及需要set的所有交叉环境的设置。

这里面也牵扯了一些相关的变量设置,在这里简单介绍下几个比较重要的

1. CMAKE_SYSTEM_NAME: 即你目标机target所在的操作系统名称,比如ARM或者Linux你就需要写”Linux”, 如果Windows平台你就写”Windows”,如果你的嵌入式平台没有相关OS你即需要写成”Generic”, 只有当CMAKE_SYSTEM_NAME这个变量被设置了,CMake才认为此时正在交叉编译,它会额外设置一个变量CMAKE_CROSSCOMPILING为TRUE.

2. CMAKE_C_COMPILER: 顾名思义,即C语言编译器,这里可以将变量设置成完整路径或者文件名,设置成完整路径有一个好处就是CMake会去这个路径下去寻找编译相关的其他工具比如linker,binutils等,如果你写的文件名带有arm-elf等等前缀,CMake会识别到并且去寻找相关的交叉编译器。

3. CMAKE_CXX_COMPILER: 同上,此时代表的是C++编译器。

4. CMAKE_FIND_ROOT_PATH: 代表了一系列的相关文件夹路径的根路径的变更,比如你设置了/opt/arm/,所有的Find_xxx.cmake都会优先根据这个路径下的/usr/lib,/lib等进行查找,然后才会去你自己的/usr/lib和/lib进行查找,如果你有一些库是不被包含在/opt/arm里面的,你也可以显示指定多个值给CMAKE_FIND_ROOT_PATH,比如set(CMAKE_FIND_ROOT_PATH /opt/arm /opt/inst)

5. CMAKE_FIND_ROOT_PATH_MODE_PROGRAM: 对FIND_PROGRAM()起作用,有三种取值,NEVER,ONLY,BOTH,第一个表示不在你CMAKE_FIND_ROOT_PATH下进行查找,第二个表示只在这个路径下查找,第三个表示先查找这个路径,再查找全局路径,对于这个变量来说,一般都是调用宿主机的程序,所以一般都设置成NEVER

6. CMAKE_FIND_ROOT_PATH_MODE_LIBRARY: 对FIND_LIBRARY()起作用,表示在链接的时候的库的相关选项,因此这里需要设置成ONLY来保证我们的库是在交叉环境中找的.

7. CMAKE_FIND_ROOT_PATH_MODE_INCLUDE: 对FIND_PATH()和FIND_FILE()起作用,一般来说也是ONLY,如果你想改变,一般也是在相关的FIND命令中增加option来改变局部设置,有NO_CMAKE_FIND_ROOT_PATH,ONLY_CMAKE_FIND_ROOT_PATH,BOTH_CMAKE_FIND_ROOT_PATH

8. BOOST_ROOT: 对于需要boost库的用户来说,相关的boost库路径配置也需要设置,因此这里的路径即ARM下的boost路径,里面有include和lib。

9. QT_QMAKE_EXECUTABLE: 对于Qt用户来说,需要更改相关的qmake命令切换成嵌入式版本,因此这里需要指定成相应的qmake路径(指定到qmake本身)

同一目录,多个源文件

aux_source_directory 命令,该命令会查找指定目录下的所有源文件,然后将结果存进指定变量名。其语法如下:

1
aux_source_directory(<dir> <variable>)

示例:

多个目录,多个源文件

对于这种情况,需要分别在项目根目录和子目录目录里各编写一个 CMakeLists.txt 文件。为了方便,我们可以先将子目录里的文件编译成静态库再由 main 函数调用;

示例:

参考引用

CMake 入门实战
CMake交叉编译配置
使用CMake交叉编译Arm Linux程序