什么是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 等 。
入门案例:单个源文件的内部构建和外部构建
CMake外部构建
对于简单的项目,只需要写几行代码就可以了。例如,假设现在我们的项目中只有一个源文件 main.cpp 。
#include <stdio.h>
int main()
{
printf("Hello World from t1 Main!\n");
return 0;
}
1.编写CMakeList.txt
CMakeLists.txt 的语法比较简单,由命令、注释和空格组成,其中命令是不区分大小写的(推荐全部使用大写指令)。符号 # 后面的内容被认为是注释。命令由命令名称、小括号和参数组成,参数之间使用空格进行间隔。(指令(参数1 参数 2…))。变量使用${}方式取值,但是在IF控制语句中是直接使用变量名。
首先编写 CMakeLists.txt 文件,并保存在与 main.cc 源文件同个目录下:
PROJECT (HELLO)
SET(SRC_LIST main.cpp)
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir "${HELLO_SOURCE_DIR})
ADD_EXECUTABLE(hello ${SRC_LIST})
2.生成CMakeFiles
- 执行命令 cmake PATH 或者 ccmake PATH 生成 Makefile(ccmake 和 cmake 的区别在于前者提供了一个交互式的界面)。其中, PATH 是 CMakeLists.txt 所在的目录。
- 使用 make 命令进行编译。
-
指令解释
-
PROJECT指令的语法:
PROJECT(projectname [CXX] [C] [Java])
你可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言。这个指令隐式的定义了两个 cmake 变量:
< projectname>_BINARY_DIR以及< projectname>_SOURCE_DIR,这里就是HELLO_BINARY_DIR和HELLO_SOURCE_DIR(所以CMakeLists.txt中两个MESSAGE指令可以直接使用了这两个变量),因为采用的是内部编译,两个变量目前指的都是工程所在路径,后面我们会讲到外部编译,两者所指代的内容会有所不同。
同时cmake 系统也帮助我们预定义了 PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR变量,他们的值分别跟HELLO_BINARY_DIR与HELLO_SOURCE_DIR一致。
为了统一起见,建议以后直接使用PROJECT_BINARY_DIR,PROJECT_SOURCE_DIR,即使修改了工程名称,也不会影响这两个变量。如果使用了< projectname>_SOURCE_DIR ,修改工程名称后,需要同时修改这些变量。 -
SET指令的语法:
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
SET指令可以用来显式的定义变量。
比如我们用到的是SET(SRC_LIST main.c),如果有多个源文件,也可以定义成:SET(SRC_LIST main.c t1.c t2.c) -
MESSAGE指令的语法:
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] “message to display”…)
这个指令用于向终端输出用户定义的信息,包含了三种类型:
SEND_ERROR,产生错误,生成过程被跳过
SATUS — ,输出前缀为 的信息
FATAL_ERROR,立即终止所有cmake 过程
我们在这里使用的是STATUS 信息输出,演示了由PROJECT 指令定义的两个隐式变量HELLO_BINARY_DIR和HELLO_SOURCE_DIR。 -
ADD_EXECUTABLE(hello ${SRC_LIST})
定义了这个工程会生成一个文件名为hello 的可执行文件,相关的源文件是 SRC_LIST 中定义的源文件列表, 本例中也可以直接写成ADD_EXECUTABLE(hello main.c)
在本例我们使用了$ {}来引用变量,这是cmake 的变量应用方式,但是,有一些例外,比如在IF 控制语句,变量是直接使用变量名引用,而不需要$ {}。如果使用了$ {}去应用变量,其实IF 会去判断名为${}所代表的值的变量,那当然是不存在的了。
将本例改写成一个最简化的CMakeLists.txt:
PROJECT(HELLO)
ADD_EXECUTABLE(hello main.c)
cmake 的语法还是比较灵活而且考虑到各种情况,比如SET(SRC_LIST main.c)也可以写成 SET(SRC_LIST “main.c”)是没有区别的,但是假设一个源文件的文件名是 fu nc.c(文件名中间包含了空格)。这时候就必须使用双引号,如果写成了 SET(SRC_LIST fu nc.c),就会出现错误,提示你找不到fu 文件和nc.c 文件。这种情况,就必须写成:SET(SRC_LIST “fu nc.c”)
此外,你可以可以忽略掉source 列表中的源文件后缀,比如可以写成ADD_EXECUTABLE(t1 main),cmake 会自动的在本目录查找main.c或者main.cpp等,当然,最好不要偷这个懒,以免这个目录确实存在一个 main.c 一个main
更像工程的一些改进
- 为工程添加一个子目录src,用来放置工程源代码;
- 添加一个子目录doc,用来放置这个工程的文档hello.txt
- 在工程目录添加文本文件COPYRIGHT, README;
- 在工程目录添加一个runhello.sh 脚本,用来调用hello二进制
- 将构建后的目标文件放入构建目录的bin 子目录;
- 最终安装这些文件:将hello二进制与runhello.sh安装至/usr/bin,将doc目录的内容以及COPYRIGHT/README安装到/usr/share/doc/cmake/t2
步骤
- 添加子目录src
- 进入子目录src,编写 CMakeLists.txt:ADD_EXECUTABLE(hello main.c)
- 将原工程的 CMakeLists.txt修改为:
PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin) - 然后建立build 目录,进入build目录进行外部编译。
cmake . .
make - 构建完成后,你会发现生成的目标文件 hello 位于 build/bin目录中
语法解释:
-
ADD_SUBDIRECTORY指令
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。EXCLUDE_FROM_ALL参数的含义是将这个目录从编译过程中排除,比如,工程的example,可能就需要工程构建完成后,再进入 example目录单独进行构建(当然,你也可以通过定义依赖来解决此类问题)。上面的例子定义了将src子目录加入工程,并指定编译输出(包含编译中间结果)路径为bin目录。如果不进行 bin目录的指定,那么编译结果(包括中间结果)都将存放在build/src 目录(这个目录跟原有的src目录对应),指定 bin 目录后,相当于在编译时将src重命名为bin,所有的中间结果和目标二进制都将存放在bin目录。
换个地方保存二目标进制
不论是否指定编译输出目录,我们都可以通过SET指令重新定义EXECUTABLE_OUTPUT_PATH 和LIBRARY_OUTPUT_PATH 变量来指定最终的目标二进制的位置(指最终生成的hello或者最终的共享库,不包含编译生成的中间文件)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
在第一节我们提到了< projectname>_BINARY_DIR和PROJECT_BINARY_DIR 变量,他们指的编译发生的当前目录,如果是内部编译,就相当于 PROJECT_SOURCE_DIR 也就是工程代码所在目录,如果是外部编译,指的是外部编译所在目录,也就是本例中的 build目录。
所以,上面两个指令分别定义了:
可执行二进制的输出路径为build/bin
库的输出路径为build/lib.
问题是,我应该把这两条指令写在工程的 CMakeLists.txt 还是 src 目录下的CMakeLists.txt,把握一个简单的原则,在哪里ADD_EXECUTABLE或ADD_LIBRARY,如果需要改变目标存放路径,就在哪里加入上述的定义。
在这个例子里,当然就是指src下的CMakeLists.txt 了
如何安装
首先先补上为添加的文件:
-
添加doc目录及文件:
mkdir doc
vi doc/hello.txt -
在工程目录添加runhello.sh脚本,内容为:
hello -
添加工程目录中的COPYRIGHT 和 README
touch COPYRIGHT
touch README
- 安装 COPYRIGHT/README,直接修改主工程文件CMakelists.txt,加入以下指令:
INSTALL(FILES COPYRIGHT README DESTINATION share/doc) - 安装 runhello.sh,直接修改主工程文件CMakeLists.txt,加入如下指令:
INSTALL(PROGRAMS runhello.sh DESTINATION bin)
3,安装 doc 中的hello.txt,这里有两种方式:一是通过在doc目录建立CMakeLists.txt 并将 doc 目录通过ADD_SUBDIRECTORY 加入工程来完成。另一种方法是直接在工程目录通过INSTALL(DIRECTORY 来完成),前者比较简单,各位可以根据兴趣自己完成,我们来尝试后者,顺便演示以下DIRECTORY 的安装。因为hello.txt 要安装到/< prefix>/share/doc/cmake/t2,所以我们不能直接安装整个doc目录,这里采用的方式是安装 doc ” 目录中的内容,也就是使用 doc/”在工程文件中添加
INSTALL(DIRECTORY doc/ DESTINATION share/doc/)
重要:
这里在make前需要改变默认安装路径:DCMAKE_INSTALL_PREFIX
cmake -DCMAKE_INSTALL_PREFIX=/home/lbf/cn/usr . .
然后运行
make
make install