《CMake实践》笔记一:PROJECT/MESSAGE/ADD_EXECUTABLE

《CMake实践》笔记一:PROJECT/MESSAGE/ADD_EXECUTABLEphp

《CMake实践》笔记二:INSTALL/CMAKE_INSTALL_PREFIXhtml

《CMake实践》笔记三:构建静态库与动态库 及 如何使用外部共享库和头文件java

 

前言:

开发了5,6年的时间,若是没有KDE4,也许不会有人或者Linux发行版本重视cmake,由于除了Kitware彷佛没有人使用它。经过KDE4的选型和开发,cmake逐渐进入了人们的视线,在实际的使用过程当中,cmake的优点也逐渐的被你们所认识,至少KDE的开发者们给予了cmake极高的评价,同时庞大的KDE项目使用cmake来做为构建工具也证实了cmake的可用性和大项目管理能力。c++

因此,cmake应该感谢KDE,也正由于如此,cmake的开发者投入了KDE从autotools到cmake的迁移过程当中,并至关快速和顺利的完成了迁移,如今整个KDE4开发版本所有使用cmake构建。编程

这也是促使咱们学习cmake的缘由,首先cmake被接受并成功应用,其次,cmake的优点在实际使用中不断的体现出来。xcode

咱们为何不来认识一下这款优秀的工程构建工具呢?bash

在2006年KDE大会,听cmake开发者当面介绍了cmake以后,我就开始关注cmake,并将cmake归入了Everest发行版,做为系统默认组件。最近QT-4.3也正式进入了Everest系统,为KDE4构建完成了准备工做。svn

可是,在学习cmake的过程当中,发现官方的文档很是的少,并且错误也较多,好比:在介绍Find<Name>模块编写的文档中,模块名称为FOO,可是后面却出现了Foo_FIND_QUIETLY的定义,这显然是错误的,这样的定义永远不可能有效,正确的定义是FOO_FIND_QUIETLY。种种缘由,促使我开始写一份“面向使用和实用”的cmake文档,也就是本教程《Cmake实践》(Cmake Practice)工具

本文档是边学习边编写的成果,更像是一个学习笔记和Tutorial,所以不免有失误或者理解不够透彻的地方,好比,我仍然不能理解为何绝大部分使用变量的状况要经过${}引用,而在IF语句中却必须直接使用变量名。也但愿可以有cmake的高手来指点迷津。学习

补:从cmake的maillist,我找到了一些答案,原文是:

The `IF(var)' or `IF(NOT var)' command expects `var' to be the name of a variable. This is stated in CMake's manual. So, for your situation `IF(${libX})' is the same as `IF(/usr/lib/xorg)' and then CMake will check the value of the variable named `/usr/lib/xorg'.

也就是说IF须要的是变量名而不是变量值

这个文档是开放的,开放的目的是为了让更多的人可以读到而且可以修改,任何人均可以对它做出修改和补充,可是,为了你们都可以得到你关于cmake的经验和积累,若是你现错误或者添加了新内容后,请务必CC给我一份,让咱们共同把cmake掌握的更好。

 

1、初识cmake

再也不使你在构建项目时郁闷地想自杀了.
                                               --一位KDE开发者

一、背景知识:

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必然会成为一个主流的构建体系。

 

二、特色:

cmake的特色主要有:

(1)、开放源代码,使用类BSD许可发布。http://cmake.org/HTML/Copyright.html

(2)、跨平台,并可生成native编译配置文件,在Linux/Unix平台,生成makefile,在苹果平台,能够生成xcode,在Windows平台,能够生成MSVC的工程文件。

(3)、可以管理大型项目,KDE4就是最好的证实。

(4)、简化编译构建过程和编译过程。Cmake的工具链很是简单:cmake+make。

(5)、高效虑,按照KDE官方说法,CMake构建KDE4的kdelibs要比使用autotools来构建KDE3.5.6的kdelibs快40%,主要是由于 Cmake在工具链中没有libtool。

(6)、可扩展,能够为cmake编写特定功能的模块,扩充cmake功能。

 

三、问题,难道就没有问题?

(1)、cmake很简单,但绝对没有听起来或者想象中那么简单。

(2)、cmake编写的过程其实是编程的过程,跟之前使用autotools同样,不过你须要编写的是CMakeLists.txt(每一个目录一个),使用的是”cmake语言和语法”。

(3)、cmake跟已有体系的配合并非特别理想,好比pkgconfig,您在实际使用中会有所体会,虽然有一些扩展可使用,但并不理想。

 

四、我的的建议:

(1)、若是你没有实际的项目需求,那么看到这里就能够停下来了,由于cmake的学习过程就是实践过程,没有实践,读的再多几天后也会忘记。

(2)、若是你的工程只有几个文件,直接编写Makefile是最好的选择。

(3)、若是使用的是C/C++/Java以外的语言,请不要使用cmake(至少目前是这样)

(4)、若是你使用的语言有很是完备的构建体系,好比java的ant,也不须要学习cmake,虽然有成功的例子,好比QT4.3的csharp绑定qyoto。

(5)、若是项目已经采用了很是完备的工程管理工具,而且不存在维护问题,没有必要迁移到cmake

(6)、若是仅仅使用qt编程,没有必要使用cmake,由于qmake管理Qt工程的专业性和自动化程度比cmake要高不少。

 

二,安装cmake

须要安装吗?

cmake目前已经成为各大Linux发行版提供的组件,好比Everest直接在系统中包含,Fedora在extra仓库中提供,因此,须要本身动手安装的可能性很小。若是你使用的操做系统(好比Windows或者某些Linux版本)没有提供cmake或者包含的版本较旧,建议你直接从cmake官方网站下载安装。

https://cmake.org/download/

在这个页面,提供了源代码的下载以及针对各类不一样操做系统的二进制下载,能够选择适合本身操做系统的版本下载安装。由于各个系统的安装方式和包管理格式有所不一样,在此就再也不赘述了,相信必定可以顺利安装cmake。

 

3、初试cmake – cmake的helloworld

Hello world,世界 你好

本节选择了一个最简单的例子Helloworld来演练一下cmake的完整构建过程,本节并不会深刻的探讨cmake,仅仅展现一个简单的例子,并加以粗略的解释。咱们选择了Everest Linux做为基本开发平台,由于这个只有一张CD的发行版本,包含了gcc-4.2/gtk/qt3/qt4等完整的开发环境,同时,系统默认集成了cmake最新版本2.4.6。

 

一、准备工做:

首先,在/backup目录创建一个cmake目录,用来放置咱们学习过程当中的全部练习。

mkdir -p /backup/cmake

之后咱们全部的cmake练习都会放在/backup/cmake的子目录下(你也能够自行安排目录,这个并非限制,仅仅是为了叙述的方便)

而后在cmake创建第一个练习目录t1

cd /backup/cmake
mkdir t1
cd t1

在t1目录创建 main.c 和 CMakeLists.txt (注意文件名大小写):

main.c文件内容:

#include <stdio.h>
int main()
{
   printf("Hello World from t1 Main!\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})

 

 

二、开始构建

全部的文件建立完成后,t1目录中应该存在main.c和CMakeLists.txt两个文件接下来咱们来构建这个工程,在这个目录运行:

cmake .  # 注意命令后面的点号,表明本目录

输出大概是这个样子:

-- Check for working C compiler: /usr/bin/gcc
-- Check for working C compiler: /usr/bin/gcc -- works
-- Check size of void*
-- Check size of void* - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- This is BINARY dir /backup/cmake/t1
-- This is SOURCE dir /backup/cmake/t1
-- Configuring done
-- Generating done
-- Build files have been written to: /backup/cmake/t1

 

再让咱们看一下目录中的内容:你会发现,系统自动生成了:

CMakeFiles,CMakeCache.txt,cmake_install.cmake等文件,而且生成了Makefile。

如今不须要理会这些文件的做用,之后你也能够不去理会。最关键的是,它自动生成了Makefile。而后进行工程的实际构建,在这个目录输入make命令,

make

大概会获得以下的彩色输出:

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

若是你须要看到make构建的详细过程,可使用make VERBOSE=1或者VERBOSE=1 make命令来进行构建。这时候,咱们须要的目标文件hello已经构建完成,位于当前目录,尝试运行一下:

./hello

获得输出:

Hello World from Main!

恭喜您,到这里为止您已经彻底掌握了cmake的使用方法。

 

三、简单的解释:

咱们来从新看一下CMakeLists.txt,这个文件是cmake的构建定义文件,文件名是大小写相关的,若是工程存在多个目录,须要确保每一个要管理的目录都存在一个CMakeLists.txt。(关于多目录构建,后面咱们会提到,这里不做过多解释)。

上面例子中的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})

PROJECT指令的语法是:

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

你能够用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是能够忽略的,这个指令隐式的定义了两个cmake变量:

<projectname>_BINARY_DIR 以及 <projectname>_SOURCE_DIR,这里就是 HELLO_BINARY_DIR 和 HELLO_SOURCE_DIR (因此CMakeLists.txt中两个MESSAGE指令能够直接使用了这两个变量),由于采用的是内部编译,两个变量目前指的都是工程所在路径/backup/cmake/t1,后面咱们会讲到外部编译,二者所指代的内容会有所不一样。

同时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其实仍然要使用“cmake语言和语法”去构建,上面的内容就是所谓的“cmake语言和语法”,最简单的语法规则是:

(1)、变量使用${}方式取值,可是在IF控制语句中是直接使用变量名

(2)、指令(参数1 参数2...)

参数使用括弧括起,参数之间使用空格或分号分开。

以上面的ADD_EXECUTABLE指令为例,若是存在另一个func.c源文件,就要写成:ADD_EXECUTABLE(hello main.c func.c)或者ADD_EXECUTABLE(hello main.c;func.c)

(3)、指令是大小写无关的,参数和变量是大小写相关的。但,推荐你所有使用大写指令。

上面的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可执行文件。

 

五、关于语法的疑惑

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.cpp

同时参数也可使用分号来进行分割。下面的例子也是合法的:

ADD_EXECUTABLE(t1 main.c t1.c)能够写成ADD_EXECUTABLE(t1 main.c;t1.c).

咱们只须要在编写CMakeLists.txt时注意造成统一的风格便可。

 

六、清理工程:

跟经典的autotools系列工具同样,运行:

make clean

便可对构建结果进行清理。

 

七、问题?问题!

“我尝试运行了make distclean,这个指令通常用来清理构建过程当中产生的中间文件的,若是要发布代码,必然要清理掉全部的中间文件,可是为何在cmake工程中这个命令是无效的?”

是的,cmake并不支持make distclean,关于这一点,官方是有明确解释的:

由于CMakeLists.txt能够执行脚本并经过脚本生成一些临时文件,可是却没有办法来跟踪这些临时文件究竟是哪些。所以,没有办法提供一个可靠的make distclean方案。

Some build trees created with GNU autotools have a "makedistclean" 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)。

 

八、内部构建 与 外部构建:

上面的例子展现的是“内部构建”,相信看到生成的临时文件比您的代码文件还要多的时候,估计这辈子你都不但愿再使用内部构建!

举个简单的例子来讲明外部构建,以编译wxGTK动态库和静态库为例,在Everest中打包方式是这样的:

解开wxGTK后。

在其中创建static和shared目录。

进入static目录,运行 ../configure –enable-static;make 会在static目录生成wxGTK的静态库。

进入shared目录,运行 ../configure –enable-shared;make 就会在shared目录生成动态库。

这就是外部编译的一个简单例子。

对于cmake,内部编译上面已经演示过了,它生成了一些没法自动删除的中间文件,因此,引出了咱们对外部编译的探讨,外部编译的过程以下:

(1)、首先,请清除t1目录中除main.c CMakeLists.txt以外的全部中间文件,最关键的是CMakeCache.txt。

(2)、在t1目录中创建build 目录,固然你也能够在任何地方创建build目录,不必定必须在工程目录中。

(3)、进入build目录,运行 cmake .. (注意,..表明父目录,由于父目录存在咱们须要的CMakeLists.txt,若是你在其余地方创建了build目录,须要运行cmake <工程的全路径>),查看一下build目录,就会发现了生成了编译须要的Makefile以及其余的中间文件。

(4)、运行make构建工程,就会在当前目录(build目录)中得到目标文件hello。上述过程就是所谓的out-of-source外部编译,一个最大的好处是,对于原有的工程没有任何影响,全部动做所有发生在编译目录。经过这一点,也足以说服咱们所有采用外部编译方式构建工程。

这里须要特别注意的是:

经过外部编译进行工程构建,HELLO_SOURCE_DIR 仍然指代工程路径,即 /backup/cmake/t1 而HELLO_BINARY_DIR 则指代编译路径,即/backup/cmake/t1/build

 

九、小结:

本小节描述了使用cmake构建Hello World程序的所有过程,并介绍了三个简单的指令:PROJECT/MESSAGE/ADD_EXECUTABLE以及变量调用的方法,同时说起了两个隐式变量<projectname>_SOURCE_DIR<projectname>_BINARY_DIR,演示了变量调用的方法,从这个过程来看,有些开发者可能会想,这实在比我直接写Makefile要复杂多了,甚至我均可以不编写Makefile,直接使用gcc main.c便可生成须要的目标文件。是的,正如第一节提到的,若是工程只有几个文件,仍是直接编写Makefile最简单。可是,kdelibs压缩包达到了50多M,您认为使用什么方案会更容易一点呢?

下一节,咱们的任务是让HelloWorld看起来更像一个工程。

未完,待续。。。。

 

 

《CMake实践》笔记一:PROJECT/MESSAGE/ADD_EXECUTABLE

《CMake实践》笔记二:INSTALL/CMAKE_INSTALL_PREFIX

《CMake实践》笔记三:构建静态库与动态库 及 如何使用外部共享库和头文件

相关文章
相关标签/搜索