初试cmake

一、cmake简介

1.背景知识

  cmake 是 kitware 公司以及一些开源开发者在开发几个工具套件(VTK)的过程中衍生品,最终形成体系,成为一个独立的开放源代码项目。项目的诞生时间是 2001 年。其官方网站是 www.cmake.org,可以通过访问官方网站获得更多关于 cmake 的信息。cmake的流行其实要归功于 KDE4 的开发(似乎跟当年的 svn 一样,KDE 将代码仓库从 CVS 迁移到SVN,同时证明了 SVN 管理大型项目的可用性),在 KDE 开发者使用了近 10 年 autotools之后,他们终于决定为 KDE4 选择一个新的工程构建工具,其根本原因用 KDE 开发者的话来说就是:只有少数几个“编译专家”能够掌握 KDE 现在的构建体系(admin/Makefile.common),在经历了 unsermake, scons 以及 cmake 的选型和尝试之后,KDE4 决定使用 cmake 作为自己的构建系统。在迁移过程中,进展异常的顺利,并获得了 cmake 开发者的支持。所以,目前的 KDE4 开发版本已经完全使用 cmake 来进行构建。像 kdesvn,rosegarden 等项目也开始使用 cmake,这也注定了 cmake 必然会成为一个主流的构建体系。

2.特点

  a.开放源代码,使用类 BSD 许可发布。http://cmake.org/HTML/Copyright.html
  b.跨平台,并可生成 native 编译配置文件,在 Linux/Unix 平台,生成 makefile,在苹果平台,可以生成 xcode,在 Windows 平台,可以生成 MSVC 的工程文件。
  c.能够管理大型项目,KDE4 就是最好的证明。
  d.简化编译构建过程和编译过程。Cmake 的工具链非常简单:cmake+make。
  e.高效率,按照 KDE 官方说法,CMake 构建 KDE4 的 kdelibs 要比使用 autotools 来构建 KDE3.5.6 的 kdelibs 快 40%,主要是因为 Cmake 在工具链中没有 libtool。

  d.可扩展,可以为 cmake 编写特定功能的模块,扩充 cmake 功能。

二、安装cmake

可以直接在命令行下载cmake:

$ sudo apt-get install cmakek

也可以从官网下载最新版本:

http://www.cmake.org/HTML/Download.html

 

三、初试cmake

1.首先创建工作文件夹

$ mkdir  -p ./cmake/cmTest
$ cd cmake/cmTest

2.编写测试代码和CMakeLists.txt

复制代码
//main.c
#include<stdio.h>
int main()
{
    printf("Hello world!\n");
    return 0;
}
复制代码
复制代码
//CMakeLists.txt
PROJECT (HELLO)
SET(SRC_LIST main.c)
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir " ${HELLO_SOURCE_DIR})
ADD_EXECUTABLE(hello ${SRC_LIST})
复制代码

3.构建工程

$ cmake .

. 表示在当前目录

这里需要注意的地方:

a.CMakeLists.txt文件名的大小写

$ cmake .
CMake Error: The source directory "/home/hwade/hwade/CMake/cmTest2" does not appear to contain CMakeLists.txt.

文件名必须严格按照标题的书写,正确的文件名在书写CMakeLists.txt时关键字和变量会高亮.

b.CMakeLists.txt最后一行的 ${SRC_LIST},在官网中是直接写着SRC_LIST,这样会报错找不到SRC_LIST

复制代码
$ cmake .
-- This is BINARY dir /home/hwade/hwade/CMake/cmTest -- This is SOURCE dir /home/hwade/hwade/CMake/cmTest -- Configuring done CMake Error at CMakeLists.txt:5 (ADD_EXECUTABLE): Cannot find source file: SRC_LIST Tried extensions .c .C .c++ .cc .cpp .cxx .m .M .mm .h .hh .h++ .hm .hpp .hxx .in .txx -- Build files have been written to: /home/hwade/hwade/CMake/cmTest
复制代码

尽管错误,同样会生成如下文件:

CMakeCache.txt  CMakeFiles  

但并未生成makefile文件

正确的执行结果:

复制代码
$ cmake .
-- This is BINARY dir /home/hwade/hwade/CMake/cmTest -- This is SOURCE dir /home/hwade/hwade/CMake/cmTest -- Configuring done -- Generating done -- Build files have been written to: /home/hwade/hwade/CMake/cmTest
复制代码

目录中的文件

CMakeCache.txt  cmake_install.cmake  main.c
CMakeFiles      CMakeLists.txt       Makefile

输入make命令

$ make
Scanning dependencies of target hello
[100%] Building C object CMakeFiles/hello.dir/main.c.o
Linking C executable hello
[100%] Built target hello

执行完make命令后就会生成可执行文件

$ ls
CMakeCache.txt  cmake_install.cmake  hello   Makefile
CMakeFiles      CMakeLists.txt       main.c
$ ./hello
Hello World!

4.CMakeLists.txt解释

  我们来重新看一下 CMakeLists.txt,这个文件是 cmake 的构建定义文件,文件名是大小写相关的,如果工程存在多个目录,需要确保每个要管理的目录都存在一个
CMakeLists.txt。
a. PROJECT 指令的语法是:

PROJECT(projectname [CXX] [C] [Java])

  你可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言。这个指令隐式的定义了两个 cmake 变量:
<projectname>_BINARY_DIR 以及<projectname>_SOURCE_DIR,这里就是HELLO_BINARY_DIR 和 HELLO_SOURCE_DIR(所以 CMakeLists.txt 中两个 MESSAGE指令可以直接使用了这两个变量),因为采用的是内部编译,两个变量目前指的都是工程所在路径/home/hwade/CMake/cmTest,后面我们会讲到外部编译,两者所指代的内容会有所不同。同时 cmake 系统也帮助我们预定义了 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR变量,他们的值分别跟  HELLO_BINARY_DIR 与 HELLO_SOURCE_DIR 一致。为了统一起见,建议以后直接使用 PROJECT_BINARY_DIR,PROJECT_SOURCE_DIR,即使修改了工程名称,也不会影响这两个变量。如果使用了<projectname>_SOURCE_DIR,修改工程名称后,需要同时修改这些变量。
b. SET 指令的语法是:

SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])

现阶段,只需要了解 SET 指令可以用来显式地定义变量即可。比如我们用到的是 SET(SRC_LIST main.c),如果有多个源文件,也可以定义成: SET(SRC_LIST main.c t1.c t2.c)。
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。
d. ADD_EXECUTABLE指令的语法是:

ADD_EXECUTABLE(hello ${SRC_LIST})

定义了这个工程会生成一个文件名为 hello 的可执行文件,相关的源文件是 SRC_LIST 中定义的源文件列表, 本例中也可以直接写成 ADD_EXECUTABLE(hello main.c)。
  本例使用了${}来引用变量,这是 cmake 的变量应用方式,但是,有一些例外,比如在 IF 控制语句,变量是直接使用变量名引用,而不需要${}。如果使用了${}去应用变
量,其实 IF 会去判断名为${}所代表的值的变量,那当然是不存在的了。
5. 基本语法规则
前面提到过,cmake 其实仍然要使用”cmake 语言和语法”去构建,上面的内容就是所谓的”cmake 语言和语法”,最简单的语法规则是:
a. 变量使用${}方式取值,但是在 IF 控制语句中是直接使用变量名
b. 指令(参数 1 参数 2...)
参数使用括弧括起,参数之间使用空格或分号分开。以上面的 ADD_EXECUTABLE 指令为例,如果存在另外一个 func.c 源文件,就要写成:

ADD_EXECUTABLE(hello main.c func.c)
或者 ADD_EXECUTABLE(hello main.c;func.c)

c. 指令是大小写无关的,参数和变量是大小写相关的。但推荐全部使用大写指令。上面的 MESSAGE 指令我们已经用到了这条规则:

MESSAGE(STATUS “This is BINARY dir” ${HELLO_BINARY_DIR})
也可以写成:
MESSAGE(STATUS “This is BINARY dir ${HELLO_BINARY_DIR}”)

这里需要特别解释的是作为工程名的 HELLO 和生成的可执行文件 hello 是没有任何关系的。hello 定义了可执行文件的文件名,你完全可以写成:

ADD_EXECUTABLE(t1 main.c)

编译后会生成一个 t1 可执行文件。
6. 关于语法的疑惑
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。
同时参数也可以使用分号来进行分割。下面的例子也是合法的:

ADD_EXECUTABLE(t1 main.c t1.c) 

可以写成

ADD_EXECUTABLE(t1 main.c;t1.c)

只需要在编写 CMakeLists.txt 时注意形成统一的风格即可。
7. 清理工程:
跟经典的 autotools 系列工具一样,运行:

$ make clean

即可对构建结果进行清理。
8. Q&A
Q: “我尝试运行了 make distclean,这个指令一般用来清理构建过程中产生的中间文件的,如果要发布代码,必然要清理掉所有的中间文件,但是为什么在 cmake 工程中这个命令是无效的?”
A: 是的,cmake 并不支持 make distclean,关于这一点,官方是有明确解释的:
  因为 CMakeLists.txt 可以执行脚本并通过脚本生成一些临时文件,但是却没有办法来跟踪这些临时文件到底是哪些。因此,没有办法提供一个可靠的 make distclean 方案。
  Some build trees created with GNU autotools have a "make distclean" target that cleans the build and also removes Makefiles and other parts of the generated build system. CMake does not generate a "make distclean" target because CMakeLists.txt files can run scripts and arbitrary commands; CMake has no way of tracking exactly which files are generated as part of running CMake. Providing a distclean target would give users the false
impression that it would work as expected. (CMake does generate a "make clean" target to remove files generated by the compiler and linker.) A "make distclean" target is only necessary if the user performs an in-source build. CMake supports in-source builds, but we strongly encourage users to adopt the notion of an out-of-source build. Using a build tree that is separate from the source tree will prevent CMake from generating any files in the source tree. Because CMake does not change the source tree, there is no need for a distclean target. One can start a fresh build by deleting
the build tree or creating a separate build tree.

  同时,还有另外一个非常重要的提示,就是:我们刚才进行的是内部构建(in-source build),而 cmake 强烈推荐的是外部构建(out-of-source build)。
9. 内部构建与外部构建:
上面的例子展示的是“内部构建”,相信看到生成的临时文件比代码文件还要多的时候,估计这辈子都不希望再使用内部构建,举个简单的例子来说明外部构建,以编译 wxGTK 动态库和静态库为例,在 Everest 中打包方式是这样的:
解开 wxGTK 后,在其中建立 static 和 shared 目录。进入 static 目录,运行

$ ../configure –enable-static
$ make

会在 static 目录生成 wxGTK 的静态库。进入 shared 目录,运行

$ ../configure –enable-shared
$ make

就会在 shared 目录生成动态库。这就是外部编译的一个简单例子。
对于 cmake,内部编译上面已经演示过了,它生成了一些无法自动删除的中间文件,所以引出了对外部编译的探讨,外部编译的过程如下:
a. 首先,请清除 t1 目录中除 main.c CmakeLists.txt 之外的所有中间文件,最关键的是 CMakeCache.txt。
b. 在 t1 目录中建立 build 目录,当然你也可以在任何地方建立 build 目录,不一定必须在工程目录中。
c. 进入 build 目录,运行

$ cmake ..

(注意,..代表父目录,因为父目录存在我们需要的CMakeLists.txt,如果你在其他地方建立了 build 目录,需要运行 cmake <工程的全路径>)

查看一下 build 目录,就会发现了生成了编译需要的 Makefile 以及其他的中间文件。

d. 运行

$ make 

构建工程,就会在当前目录(build 目录)中获得目标文件 hello。上述过程就是所谓的 out-of-source 外部编译,一个最大的好处是,对于原有的工程没有任何影响,所有动作全部发生在编译目录。通过这一点,也足以说服我们全部采用外部编译方式构建工程。
这里需要特别注意的是:
通过外部编译进行工程构建,HELLO_SOURCE_DIR 仍然指代工程路径,即/backup/cmake/t1,而 HELLO_BINARY_DIR 则指代编译路径,即/backup/cmake/t1/build
10. 小结:
本小节描述了使用 cmake 构建 Hello World 程序的全部过程,并介绍了三个简单的指令PROJECT/MESSAGE/ADD_EXECUTABLE 以及变量调用的方法,同时提及了两个隐式变量<projectname>_SOURCE_DIR 及<projectname>_BINARY_DIR,演示了变量调用的方法,从这个过程来看,有些开发者可能会想,这实在比我直接写 Makefile 要复杂多了,甚至我都可以不编写 Makefile,直接使用 gcc main.c 即可生成需要的目标文件。是的,如果工程只有几个文件,还是直接编写 Makefile 最简单。但是,kdelibs 压缩包达到了 50 多 M,您认为使用什么方案会更容易一点呢?

 

posted @ 2017-04-27 19:25  fire909090  阅读(243)  评论(0编辑  收藏  举报