在了解cMake之前,可以从本站博客《操作系统发展简史》先了解一下GNU,GCC,g++,gcc的区别

如何使用cMake

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 作为项目架构系统的知名开源项目有 VTK、ITK、KDE、OpenCV、OSG 等。在 linux 平台下使用 CMake 生成 Makefile 并编译的流程如下:

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

mian.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdlib.h>
#include <stdio.h>

double power(double base, int exponent)
{
int result = base;
int i;
if (exponent == 0) {
return 1;
}
for(i = 1; i < exponent; ++i){
result = result * base;
}
return result;
}

int main(int argc, char *argv[])
{
if (argc < 3){
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
double result = power(base, exponent);
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}

编写 CMakeLists.txt
  首先编写 CMakeLists.txt 文件,并保存在与 main.c 源文件同个目录下:

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)

# 项目信息
project (Demo1)

# 指定生成目标
add_executable(Demo main.cc)

  CMakeLists.txt 的语法比较简单,由命令、注释和空格组成,其中命令是不区分大小写的。符号 # 后面的内容被认为是注释。命令由命令名称、小括号和参数组成,参数之间使用空格进行间隔。对于上面的 CMakeLists.txt 文件,依次出现了几个命令:

  1. cmake_minimum_required:指定运行此配置文件所需的 CMake 的最低版本;
  2. project:参数值是 Demo1,该命令表示项目的名称是 Demo1 。
  3. add_executable:将名为 main.cc 的源文件编译成一个名称为 Demo 的可执行文件。

指定C++标准
C++ 语言有不同版本的标准,如 C++ 98、C++ 11、C++ 14 等。不同标准提供的特性有所区别,故有必要在 CMakeLists.txt 中为项目指定使用何种标准,进而让 CMake 使用正确的编译器标志(比如 -std=c++11)。对于 CMake v3.1 以上版本,可以通过 CMAKE_CXX_STANDARD 变量指定标准,或通过 target_compile_features 函数根据使用的特性自动推断适用于目标的编译器标志。

cmake_minimum_required(VERSION 3.15)

# set the project name and version
project(${PROJECT_NAME} VERSION 1.0)

# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

强制指定C++标准
  出于对编译器的兼容性之考虑,上面这种设置标准的方法并不是强制执行。假设某个项目设置使用 C++ 11 标准,但用户使用的编译器并不支持 -std=gnu++11 (或等价的)标志,将不会导致错误或警告,而是在允许的情况下添加 -std=gnu++98 标志。换言之,CMake 将自动“衰减”至最接近的标准。如果确实需要强制指定标准,禁用这种自动衰减调整,那么可以通过设置 CXX_STANDARD_REQUIRED 实现。

  CXX_STANDARD_REQUIRED 是一个布尔类型的变量,用于描述是否需要(强制)指定 CXX_STANDARD。当打开此选项,且当前使用的编译器不支持指定的标准时,会在 configuring 阶段报错失败,不会进行编译。

对上一节中的小例子稍加修改,使其强制使用 C++ 11 标准:

cmake_minimum_required(VERSION 3.1)

project (hello_cpp11)

set(CMAKE_CXX_STANDARD 11)  # 将 C++ 标准设置为 C++ 11
set(CMAKE_CXX_STANDARD_REQUIRED ON)  # C++ 11 是强制要求,不会衰退至低版本

add_executable(hello_cpp11 main.cpp)

编译项目
  之后,在当前目录执行 cmake . ,得到 Makefile 后再使用 make 命令编译得到 Demo1 可执行文件。

[ehome@xman Demo1]$ cmake .
-- The C compiler identification is GNU 4.8.2
-- The CXX compiler identification is GNU 4.8.2
-- Check for working C compiler: /usr/sbin/cc
-- Check for working C compiler: /usr/sbin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/sbin/c++
-- Check for working CXX compiler: /usr/sbin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/ehome/Documents/programming/C/power/Demo1
[ehome@xman Demo1]$ make
Scanning dependencies of target Demo
[100%] Building C object CMakeFiles/Demo.dir/main.cc.o
Linking C executable Demo
[100%] Built target Demo
[ehome@xman Demo1]$ ./Demo 5 4
5 ^ 4 is 625
[ehome@xman Demo1]$ ./Demo 7 3
7 ^ 3 is 343
[ehome@xman Demo1]$ ./Demo 2 10
2 ^ 10 is 1024

同一目录,多个源文件
  上面的例子只有单个源文件。现在假如把 power 函数单独写进一个名为 MathFunctions.c 的源文件里,使得这个工程变成如下的形式:

./Demo2
    |
    +--- main.c
    |
    +--- MathFunctions.c
    |
    +--- MathFunctions.h

  这个时候,CMakeLists.txt 可以改成如下的形式

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)

# 项目信息
project (Demo2)

# 指定生成目标
add_executable(Demo main.cc MathFunctions.c)

  唯一的改动只是在 add_executable 命令中增加了一个 MathFunctions.c 源文件。这样写当然没什么问题,但是如果源文件很多,把所有源文件的名字都加进去将是一件烦人的工作。更省事的方法是使用 aux_source_directory 命令,该命令会查找指定目录下的所有源文件,然后将结果存进指定变量名。其语法如下:

aux_source_directory(<dir> <variable>)

  因此,可以修改 CMakeLists.txt 如下:

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)

# 项目信息
project (Demo2)

# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)

# 指定生成目标
add_executable(Demo ${DIR_SRCS})

  这样,CMake 会将当前目录所有源文件的文件名赋值给变量 DIR_SRCS ,再指示变量 DIR_SRCS 中的源文件需要编译成一个名称为 Demo 的可执行文件。

多个目录,多个源文件
  现在进一步将 MathFunctions.h 和 MathFunctions.c 文件移动到 math 目录下。

./Demo3
    |
    +--- main.cc
    |
    +--- math/
        |
        +--- MathFunctions.cc
        |
        +--- MathFunctions.h

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

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)

# 项目信息
project (Demo3)

# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)

# 添加 math 子目录
add_subdirectory(math)

# 指定生成目标 
add_executable(Demo main.cc)

# 添加链接库
target_link_libraries(Demo MathFunctions)

  该文件添加了下面的内容: 第3行,使用命令 add_subdirectory 指明本项目包含一个子目录 math,这样 math 目录下的 CMakeLists.txt 文件和源代码也会被处理 。第6行,使用命令 target_link_libraries 指明可执行文件 main 需要连接一个名为 MathFunctions 的链接库 。子目录中的 CMakeLists.txt:

# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
aux_source_directory(. DIR_LIB_SRCS)

# 生成链接库
add_library (MathFunctions ${DIR_LIB_SRCS})

  在该文件中使用命令 add_library 将 src 目录中的源文件编译为静态链接库。

QMake

  1. 接着是qmake,qmake是什么,先说一下Qt这个东西。Qt是跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。简单的说就是C++的第三方库,使用这个库你可以很容易生成windows,Linux,MAC os等等平台的图形界面。现在的Qt还包含了开发各种软件一般需要用到的功能模块(网络,数据库,XML,多线程啊等等),比你直接用C++(只带标准内裤那种)要方便和简单。
  2. 你可以用Qt简简单单就实现非常复杂的功能,是因为Qt对C++进行了扩展,你写一行代码,Qt在背后帮你写了几百上千行,而这些多出来的代码就是靠Qt专有的moc编译器(The Meta-Object Compiler)和uic编译器(User Interface Complier)来重新翻译你那一行代码。问题来了,你在进行程序编译前就必须先调用moc和uic对Qt源文件进行预处理,然后再调用编译器进行编译。上面说的那种普通makefile文件是不适用的,它没办法对qt源文件进行预处理。所以qmake就产生了。
  3. qmake工具就是Qt公司制造出来,用来生成Qt 专用makefile文件,这种makefile文件就能自动智能调用moc和uic对源程序进行预处理和编译。qmake当然必须也是跨平台的,跟cmake一样能对应各种平台生成对应makefile文件。
  4. qmake是根据Qt 工程文件(.pro)来生成对应的makefile的。工程文件(.pro)相对来说比较简单,一般工程你都可以自己手写,但是一般都是由Qt的开发环境 Qt Creator自动生成的,你还是只需要按下那个邪恶三角形就完事了。
  5. 还没有完,由于qmake很简单很好用又支持跨平台,而且是可以独立于它的IDE,所以你也可以用在非Qt工程上面,照样可以生成普通的makefile,只要在pro文件中加入CONFIG -= qt 就可以了。
  6. 那这样qmake和cmake有什么区别?不好意思,cmake也是同样支持Qt程序的,cmake也能生成针对qt 程序的那种特殊makefile,只是cmake 的CMakeLists.txt 写起来相对与qmake的pro文件复杂点。qmake 是为 Qt 量身打造的,使用起来非常方便,但是cmake功能比qmake强大。一般的Qt工程你就直接使用qmake就可以了,cmake的强大功能一般人是用不到的。当你的工程非常大的时候,又有qt部分的子工程,又有其他语言的部分子工程,据说用cmake会方便。