跟我一起学CMake

时间:2024-04-07 15:35:25

如今CMake使用的人数越来越多,包括我项目组里,很多大牛们在写Qt程序的时候都不用自带的qmake,貌似会出现很多问题,他们往往都用自己写的CMake来编译系统,今天我也和大家一起来学学这个高大上的工具--CMake。

首先,一个CMake要想运行,必须在同目录下有CMake脚本,说说是脚本,其实并没什么可怕,说白了就是一串CMake作者自己写的token语法分析文件--CMakeLists.txt。

这个文件比如你的项目有很多文件夹,必须在每个源代码文件夹下都有一个CMakeLists.txt.它会根据CMake命令中的add_subdirectory自动的递归分析。

讲完脚本的大致概念,让我们现在开始学写我们自己的脚本,在这里我引用CMake官网的相关Sample我们一起学习。注:在CMake中命令是不区分大小写的,而变量跟C一样是区分大小写的,这一点需要注意,我的默认习惯是命令用小写,比较顺手。

[1] cmake_minium_required(VERSION 2.6),版本号VERSION后面的数字根据你所决定的CMake最低版本号来决定,在这里我们用了官网sample的2.6。

[2] project(HELLO), 这代表你整个工程的工程名,同时CMake会根据这个工程名生成两个变量一个是${HELLO_SOURCE_DIR}一个 是${HELLO_BINARY_DIR},前者是当前source源文件所在路径,后者是生成的二进制文件锁在路径,如果你选用的CMake官方推荐的 外部编译模式(建立build,在build生成中间文件和目标文件,和源文件分离)这个时候两者的变量值是不同的,否则相同。

[3] add_subdirectory(Hello) add_subdirectory(Demo),在这里官方例子给出了本目录下有两个sub目录,所以你在顶层写完CMakeLists之后同时也必须在 你add_sub的目录下写相应的CMake脚本,CMake程序并不会再创建额外的进程来进行遍历,它会一层一层的逐步递归下去,其实这也是UNIX设 计的经典哲学--分而治之。

[4] 在Hello目录中: add_library(Hello hello.cxx),表明在这个sub中是生成一个Hello的库,第一个参数是库名,之后的源文件名(可以有多个)。

[5] 在Demo目录中:include_directories(${HELLO_SOURCE_DIR}/Hello)表明当前工程需要去include头 文件的路径为当前源代码所在路径下的Hello目录下,相当于GCC中的-I.另外要注意你如果依次往下写,比如include_directories 之后又来一句include_directories,这个是个追加过程而不是覆盖过程,同学看到可以不用担心自己之前的目录是否被覆盖了。

[6] link_directories(${HELLO_SOURCE_DIR}/Hello)表明当前工程所需要链接的库的路径所在地址,相当于GCC中的-L.

[7] add_executable(helloDemo demo.cxx demo_b.cxx),表明在当前Demo目录下会生成一个可执行文件,可执行文件的后缀名你不需要担心,CMake会自动判断当前环境合理加上。

[8] target_link_libraries(helloDemo Hello)表明可执行文件的生成必须需要库Hello的支持(包括前者include Hello的头文件)。

这个简单的sample讲完,接下来我们来看一下CMake的语法方面的注意点。

首先讲一下添加给GCC预处理宏的几种用法,有的同学一开始肯定想当然的给cmake .. -DMY_FLAG这样来用,其实这样是不对的,这样其实只是给CMake赋予的MY_FLAG的变量,而最终GCC是根本没有感知到该宏有没有定义的, 所以一般用法是两种,一种是在cmake ..的时候加一把CMAKE_CXX_COMPILE_FLAGS=xxx,另外一种是在CMake脚本内部手动加上一句 add_definitions(-DMY_FLAG)这两种方法都可以传给GCC相关的预处理宏,但是还是有区别的,前者针对的有效目录是所有目录,而 后者针对的目录是当前目录乃至当前目录以下的所有子目录,所以在这里需要特别注意。

command (args ...),arg之间可以用空格分离,如果arg中有空格需要用双引号括起来。

其次,CMake中对于字符串支持list格式,即一个变量可以支持字符串数组,比如set(VAR a b c)和set(VAR
a;b;c)两种形式都可以支持${VAR}为a b
c,这种格式时候foreach()进行循环遍历。但是这个时候要注意command(${VAR})表明同时执行了三次command(command
a; command b; command
c;)而command("${VAR}")注意其中的双引号表明只进行了一次遍历(command("a b c"))这个是需要我们注意的。

CMake也提供了流程控制,比如对于C中的if,CMake的写法也是如此if(var) command endif(var)(其中endif中的var可以写成空),判断语句可以有多重形式,可以验证变量是否为true,也可以是否FOUND,(empty, 0, N, NO, OFF, FALSE, NOTFOUND, or -NOTFOUND)等。

CMake同样提供了迭代结构,类似于Qt中的foreach,比如你设置了一个列表字符串var,这个时候你就可以简单的foreach(temp
${var}) command(${temp})
endforeach(${temp})来对var中的变量进行循环迭代处理,这个时候要注意有没有双引号的问题。

CMake也支持自定义编写宏和函数,函数可以像C语言一样内部定义局部变量,宏:macro(var VALUE)
command({var}) endmacro(var)和function(var VALUE) command(${var})
enfunciton(hello)等。

CMake支持include其他.cmake控制,类似于Qt中的pri可以写一些全局的通用脚本。同时CMake的add_definitions也
要重点理解一下,在CMake中,默认的变量命名是name:type=value,所以在cmake命令进行编译的时候可以动态的增加一些你自己的
CMake变量,比如cmake ..
-DMY_VALUE=ON,这个时候你的${MY_VALUE}就被定义了,在CMake脚本里面你可以用if({MY_VALUE})来进行判断当前
时候这个变量被指定,如果被指定了,你可以通过脚本里的add_definitons给gcc添加命令比如
add_definitons(-DMY_VALUE),这个时候的MY_VALUE才真正到了你的编译环境中,前者的MY_VALUE其实是给
CMake脚本用的,这里需要注意。

另外CMake中的set_target_properties也很强大,可以用他来做很多事情,我目前用到的就是给Qt自动
moc,set_target_properties(${target} PROPERTIES AUTOMOC
ON)这个时候你的target就会被自动moc不需要自己手动去添加了。

到这里,我们差不多简单的学习了一下CMake的相关语法,不得不说CMake是一个管理项目很棒的高级工具,要学会利用好这个工具,我们还需要投入更多
的精力来学习,所以一起努力来迎接CMake的美好吧,enjoy! 同时也希望志同道合的朋友可以互相交流,给我留言或者私信,谢谢!