[转] Cmake实践

cmake已经开发了5,6年的时间,若是没有KDE4,也许不会有人或者Linux发行版
本重视cmake,由于除了Kitware彷佛没有人使用它。经过KDE4的选型和开发,cmake
逐渐进入了人们的视线,在实际的使用过程当中,cmake的优点也逐渐的被你们所认识,至
少KDE的开发者们给予了cmake极高的评价,同时庞大的KDE项目使用cmake来做为构
建工具也证实了cmake的可用性和大项目管理能力。
因此,cmake应该感谢KDE,也正由于如此,cmake的开发者投入了KDE从
autotools到cmake的迁移过程当中,并至关快速和顺利的完成了迁移,如今整个KDE4开
发版本所有使用cmake构建。
这也是促使咱们学习cmake的缘由,首先cmake被接受并成功应用,其次,cmake
的优点在实际使用中不断的体现出来。
咱们为何不来认识一下这款优秀的工程构建工具呢?
在2006年KDE大会,听cmake开发者当面介绍了cmake以后,我就开始关注
cmake,并将cmake归入了Everest发行版,做为系统默认组件。最近QT-4.3也正式进
入了Everest系统,为KDE4构建完成了准备工做。
可是,在学习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的经验和积累,若是html

你现错误或者添加了新内容后,请务必CC给我一份,让咱们共同把cmake掌握的更好。java

 

一,初识cmake
Cmake再也不使你在构建项目时郁闷地想自杀了.linux

--一位KDE开发者c++

 

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必然会成为正则表达式

一个主流的构建体系。express

 

2,特色:
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功能。windows

 

3,问题,难道就没有问题?
1,cmake很简单,但绝对没有听起来或者想象中那么简单。
2,cmake编写的过程其实是编程的过程,跟之前使用autotools同样,不过你须要编
写的是CMakeLists.txt(每一个目录一个),使用的是”cmake语言和语法”。
3,cmake跟已有体系的配合并非特别理想,好比pkgconfig,您在实际使用中会有所xcode

体会,虽然有一些扩展可使用,但并不理想。bash

 

4,我的的建议:
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官方网站下载安装。
http://www.cmake.org/HTML/Download.html
在这个页面,提供了源代码的下载以及针对各类不一样操做系统的二进制下载,能够选择适合
本身操做系统的版本下载安装。由于各个系统的安装方式和包管理格式有所不一样,在此就不
再赘述了,相信必定可以顺利安装cmake。

 

三,初试cmake – cmake的helloworld

Hello world,世界 你好
本节选择了一个最简单的例子Helloworld来演练一下cmake的完整构建过程,本节并不
会深刻的探讨cmake,仅仅展现一个简单的例子,并加以粗略的解释。咱们选择了
Everest Linux做为基本开发平台,由于这个只有一张CD的发行版本,包含了gcc-

4.2/gtk/qt3/qt4等完整的开发环境,同时,系统默认集成了cmake最新版本2.4.6。

 

1,准备工做:
首先,在/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文件内容:
//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)

 

2,开始构建
全部的文件建立完成后,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命令,大概会获得以下的彩色输出:
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的使用方法。

 

3,简单的解释:
咱们来从新看一下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)

 

4,基本语法规则
前面提到过,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可执行文件。

 

5,关于语法的疑惑
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时注意造成统一的风格便可。

 

6,清理工程:
跟经典的autotools系列工具同样,运行:
make clean

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

 

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

 

8,内部构建与外部构建:
上面的例子展现的是“内部构建”,相信看到生成的临时文件比您的代码文件还要多的时候,
估计这辈子你都不但愿再使用内部构建:-D
举个简单的例子来讲明外部构建,以编译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

 

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

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

 

四,更好一点的Hello World
没有最好,只有更好
从本小节开始,后面全部的构建咱们都将采用out-of-source外部构建,约定的构建目
录是工程目录下的build自录。
本小节的任务是让前面的Hello World更像一个工程,咱们须要做的是:
1,为工程添加一个子目录src,用来放置工程源代码;
2,添加一个子目录doc,用来放置这个工程的文档hello.txt
3,在工程目录添加文本文件COPYRIGHT, README;
4,在工程目录添加一个runhello.sh脚本,用来调用hello二进制
4,将构建后的目标文件放入构建目录的bin子目录;
5,最终安装这些文件:将hello二进制与runhello.sh安装至/usr/bin,将doc目录
的内容以及COPYRIGHT/README安装到/usr/share/doc/cmake/t2,将

 

1,准备工做:

在/backup/cmake/目录下创建t2目录。

将t1工程的main.c和CMakeLists.txt拷贝到t2目录中。

 

2,添加子目录src:
mkdir src
mv main.c src
如今的工程看起来是这个样子:
一个子目录src,一个CMakeLists.txt。
上一节咱们提到,须要为任何子目录创建一个CMakeLists.txt,
进入子目录src,编写CMakeLists.txt以下:
ADD_EXECUTABLE(hello main.c)
将t2工程的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目录。
这里须要提一下的是SUBDIRS指令,使用方法是:
SUBDIRS(dir1 dir2...),可是这个指令已经不推荐使用。它能够一次添加多个子目录,
而且,即便外部编译,子目录体系仍然会被保存。
若是咱们在上面的例子中将ADD_SUBDIRECTORY (src bin)修改成SUBDIRS(src)。

那么在build目录中将出现一个src目录,生成的目标代码hello将存放在src目录中。

 

3,换个地方保存目标二进制
不管是SUBDIRS仍是ADD_SUBDIRECTORY指令(不管是否指定编译输出目录),咱们均可
以经过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了。

 

4,如何安装。
安装的须要有两种,一种是从代码编译后直接make install安装,一种是打包时的指定
目录安装。
因此,即便最简单的手工编写的Makefile,看起来也是这个样子的:
DESTDIR=
install:
mkdir -p $(DESTDIR)/usr/bin
install -m 755 hello $(DESTDIR)/usr/bin
你能够经过:
make install
将hello直接安装到/usr/bin目录,也能够经过make install
DESTDIR=/tmp/test将他安装在
/tmp/test/usr/bin目录,打包时这个方式常常被使用。
稍微复杂一点的是还须要定义PREFIX,通常autotools工程,会运行这样的指令:
./configure –prefix=/usr或者./configure --prefix=/usr/local来指定
PREFIX
好比上面的Makefile就能够改写成:
DESTDIR=
PREFIX=/usr
install:
mkdir -p $(DESTDIR)/$(PREFIX)/bin
install -m 755 hello $(DESTDIR)/$(PREFIX)/bin
那么咱们的HelloWorld应该怎么进行安装呢?
这里须要引入一个新的cmake 指令 INSTALL和一个很是有用的变量
CMAKE_INSTALL_PREFIX。
CMAKE_INSTALL_PREFIX变量相似于configure脚本的 –prefix,常见的使用方法看
起来是这个样子:
cmake -DCMAKE_INSTALL_PREFIX=/usr .
INSTALL指令用于定义安装规则,安装的内容能够包括目标二进制、动态库、静态库以及
文件、目录、脚本等。
INSTALL指令包含了各类安装类型,咱们须要一个个分开解释:
目标文件的安装:
INSTALL(TARGETS targets...
[[ARCHIVE|LIBRARY|RUNTIME]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS
[Debug|Release|...]]
[COMPONENT <component>]
[OPTIONAL]
] [...])
参数中的TARGETS后面跟的就是咱们经过ADD_EXECUTABLE或者ADD_LIBRARY定义的
目标文件,多是可执行二进制、动态库、静态库。
目标类型也就相对应的有三种,ARCHIVE特指静态库,LIBRARY特指动态库,RUNTIME
特指可执行目标二进制。
DESTINATION定义了安装的路径,若是路径以/开头,那么指的是绝对路径,这时候
CMAKE_INSTALL_PREFIX其实就无效了。若是你但愿使用CMAKE_INSTALL_PREFIX来
定义安装路径,就要写成相对路径,即不要以/开头,那么安装后的路径就是
${CMAKE_INSTALL_PREFIX}/<DESTINATION定义的路径>
举个简单的例子:
INSTALL(TARGETS myrun mylib mystaticlib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic
)
上面的例子会将:
可执行二进制myrun安装到${CMAKE_INSTALL_PREFIX}/bin目录
动态库libmylib安装到${CMAKE_INSTALL_PREFIX}/lib目录
静态库libmystaticlib安装到${CMAKE_INSTALL_PREFIX}/libstatic目录
特别注意的是你不须要关心TARGETS具体生成的路径,只须要写上TARGETS名称就能够
了。
普通文件的安装:
INSTALL(FILES files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL])
可用于安装通常文件,并能够指定访问权限,文件名是此指令所在路径下的相对路径。若是
默认不定义权限PERMISSIONS,安装后的权限为:
OWNER_WRITE, OWNER_READ, GROUP_READ,和WORLD_READ,即644权限。
非目标文件的可执行程序安装(好比脚本之类):
INSTALL(PROGRAMS files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL])
跟上面的FILES指令使用方法同样,惟一的不一样是安装后权限为:
OWNER_EXECUTE, GROUP_EXECUTE, 和WORLD_EXECUTE,即755权限
目录的安装:
INSTALL(DIRECTORY dirs... DESTINATION <dir>
[FILE_PERMISSIONS permissions...]
[DIRECTORY_PERMISSIONS permissions...]
[USE_SOURCE_PERMISSIONS]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[[PATTERN <pattern> | REGEX <regex>]
[EXCLUDE] [PERMISSIONS permissions...]] [...])
这里主要介绍其中的DIRECTORY、PATTERN以及PERMISSIONS参数。
DIRECTORY后面链接的是所在Source目录的相对路径,但务必注意:
abc和abc/有很大的区别。
若是目录名不以/结尾,那么这个目录将被安装为目标路径下的abc,若是目录名以/结尾,
表明将这个目录中的内容安装到目标路径,但不包括这个目录自己。
PATTERN用于使用正则表达式进行过滤,PERMISSIONS用于指定PATTERN过滤后的文件
权限。
咱们来看一个例子:
INSTALL(DIRECTORY icons scripts/ DESTINATION share/myproj
PATTERN "CVS" EXCLUDE
PATTERN "scripts/*"
PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ
GROUP_EXECUTE GROUP_READ)
这条指令的执行结果是:
将icons目录安装到 <prefix>/share/myproj,将scripts/中的内容安装到
<prefix>/share/myproj
不包含目录名为CVS的目录,对于scripts/*文件指定权限为 OWNER_EXECUTE
OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ.
安装时CMAKE脚本的执行:
INSTALL([[SCRIPT <file>] [CODE <code>]] [...])
SCRIPT参数用于在安装时调用cmake脚本文件(也就是<abc>.cmake文件)
CODE参数用于执行CMAKE指令,必须以双引号括起来。好比:
INSTALL(CODE "MESSAGE(\"Sample install message.\")")
安装还有几个被标记为过期的指令,好比INSTALL_FILES等,这些指令已经再也不推荐使
用,因此,这里就再也不赘述了。
下面,咱们就来改写咱们的工程文件,让他来支持各类文件的安装,而且,咱们要使用

CMAKE_INSTALL_PREFIX指令。

 

5,修改Helloworld支持安装
在本节开头咱们定义了本节的任务以下:
1,为工程添加一个子目录src,用来存储源代码;
2,添加一个子目录doc,用来存储这个工程的文档hello.txt
3,在工程目录添加文本文件COPYRIGHT, README;
4,在工程目录添加一个runhello.sh脚本,用来调用hello二进制
4,将构建后的目标文件放入构建目录的bin子目录;
5,最终安装这些文件:将hello二进制与runhello.sh安装至/<prefix>/bin,将
doc目录中的hello.txt以及COPYRIGHT/README安装到
/<prefix>/share/doc/cmake/t2,将
首先咱们先补上为添加的文件。
添加doc目录及文件:
cd /backup/cmake/t2
mkdir doc
vi doc/hello.txt
随便填写一些内容并保存
在工程目录添加runhello.sh脚本,内容为:
hello
添加工程目录中的COPYRIGHT和README
touch COPYRIGHT
touch README
下面改写各目录的CMakeLists.txt文件。
1,安装COPYRIGHT/README,直接修改主工程文件CMakelists.txt,加入如下指令:
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/t2)
2,安装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/cmake/t2)

 

6,尝试咱们修改的结果:
如今进入build目录进行外部编译,注意使用CMAKE_INSTALL_PREFIX参数,这里咱们
将它安装到了/tmp/t2目录:
cmake -DCMAKE_INSTALL_PREFIX=/tmp/t2/usr ..
而后运行
make
make install
让咱们进入/tmp/t2目录看一下安装结果:
./usr
./usr/share
./usr/share/doc
./usr/share/doc/cmake
./usr/share/doc/cmake/t2
./usr/share/doc/cmake/t2/hello.txt
./usr/share/doc/cmake/t2/README
./usr/share/doc/cmake/t2/COPYRIGHT
./usr/bin
./usr/bin/hello
./usr/bin/runhello.sh
若是你要直接安装到系统,可使用以下指令:

cmake -DCMAKE_INSTALL_PREFIX=/usr ..

 

7,一个疑问
若是我没有定义CMAKE_INSTALL_PREFIX会安装到什么地方?
你能够尝试如下,cmake ..;make;make install,你会发现

CMAKE_INSTALL_PREFIX的默认定义是/usr/local

 

8,小结:
本小节主要描述了如何在工程中使用多目录、各类安装指令以及
CMAKE_INSTALL_PREFIX变量(你真够牛的,这么点东西竟然罗唆了这么多文字)
在下一小节,咱们将探讨如何在cmake中构建动态库和静态库,以及如何使用外部头文件
和外部共享库,毕竟,这是程序编写中最长使用的(对了,你知道用怎样的gcc参数能够
直接构建静态库和动态库吗?)
五,静态库与动态库构建
读者云,太能罗唆了,一个Hello World就折腾了两个大节。OK,从本节开始,咱们不
再折腾Hello World了,咱们来折腾Hello World的共享库。

本节的任务:

 

1,创建一个静态库和动态库,提供HelloFunc函数供其余程序编程使用,HelloFunc

向终端输出Hello World字符串。

 

2,安装头文件与共享库。
一,准备工做:

在/backup/cmake目录创建t3目录,用于存放本节涉及到的工程

 

二,创建共享库
cd /backup/cmake/t3
mkdir lib
在t3目录下创建CMakeLists.txt,内容以下:
PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)
在lib目录下创建两个源文件hello.c与hello.h
hello.c内容以下:
#include “hello.h”
void HelloFunc()
{
printf(“Hello World\n”);
}
hello.h内容以下:
#ifndef HELLO_H
#define HELLO_H
#include <stdio.h>
void HelloFunc();
#endif
在lib目录下创建CMakeLists.txt,内容以下:
SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})

 

三,编译共享库:

仍然采用out-of-source编译的方式,按照习惯,咱们创建一个build目录,在build
目录中
cmake ..
make
这时,你就能够在lib目录获得一个libhello.so,这就是咱们指望的共享库。
若是你要指定libhello.so生成的位置,能够经过在主工程文件CMakeLists.txt中修
改ADD_SUBDIRECTORY(lib)指令来指定一个编译输出位置或者
在lib/CMakeLists.txt中添加
SET(LIBRARY_OUTPUT_PATH <路径>)来指定一个新的位置。
这二者的区别咱们上一节已经提到了,因此,这里再也不赘述,下面,咱们解释一下一个新的
指令ADD_LIBRARY
ADD_LIBRARY(libname [SHARED|STATIC|MODULE]
[EXCLUDE_FROM_ALL]
source1 source2 ... sourceN)
你不须要写全libhello.so,只须要填写hello便可,cmake系统会自动为你生成
libhello.X
类型有三种:
SHARED,动态库
STATIC,静态库
MODULE,在使用dyld的系统有效,若是不支持dyld,则被看成SHARED对待。
EXCLUDE_FROM_ALL参数的意思是这个库不会被默认构建,除非有其余的组件依赖或者手

工构建。

 

四,添加静态库:
一样使用上面的指令,咱们在支持动态库的基础上再为工程添加一个静态库,按照通常的习
惯,静态库名字跟动态库名字应该是一致的,只不事后缀是.a罢了。
下面咱们用这个指令再来添加静态库:
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
而后再在build目录进行外部编译,咱们会发现,静态库根本没有被构建,仍然只生成了
一个动态库。由于hello做为一个target是不能重名的,因此,静态库构建指令无效。
若是咱们把上面的hello修改成hello_static:
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
就能够构建一个libhello_static.a的静态库了。
这种结果显示不是咱们想要的,咱们须要的是名字相同的静态库和动态库,由于target名
称是惟一的,因此,咱们确定不能经过ADD_LIBRARY指令来实现了。这时候咱们须要用到
另一个指令:
SET_TARGET_PROPERTIES,其基本语法是:
SET_TARGET_PROPERTIES(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)
这条指令能够用来设置输出的名称,对于动态库,还能够用来指定动态库版本和API版本。
在本例中,咱们须要做的是向lib/CMakeLists.txt中添加一条:
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
这样,咱们就能够同时获得libhello.so/libhello.a两个库了。
与他对应的指令是:
GET_TARGET_PROPERTY(VAR target property)
具体用法以下例,咱们向lib/CMakeListst.txt中添加:
GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)
MESSAGE(STATUS “This is the hello_static
OUTPUT_NAME:”${OUTPUT_VALUE})
若是没有这个属性定义,则返回NOTFOUND.
让咱们来检查一下最终的构建结果,咱们发现,libhello.a已经构建完成,位于
build/lib目录中,可是libhello.so去消失了。这个问题的缘由是:cmake在构建一
个新的target时,会尝试清理掉其余使用这个名字的库,由于,在构建libhello.a时,
就会清理掉libhello.so.
为了回避这个问题,好比再次使用SET_TARGET_PROPERTIES定义
CLEAN_DIRECT_OUTPUT属性。
向lib/CMakeLists.txt中添加:
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT
1)
这时候,咱们再次进行构建,会发现build/lib目录中同时生成了libhello.so和

libhello.a

 

五,动态库版本号
按照规则,动态库是应该包含一个版本号的,咱们能够看一下系统的动态库,通常状况是
libhello.so.1.2
libhello.so ->libhello.so.1
libhello.so.1->libhello.so.1.2
为了实现动态库版本号,咱们仍然须要使用SET_TARGET_PROPERTIES指令。
具体使用方法以下:
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
VERSION指代动态库版本,SOVERSION指代API版本。
将上述指令加入lib/CMakeLists.txt中,从新构建看看结果。
在build/lib目录会生成:
libhello.so.1.2
libhello.so.1->libhello.so.1.2
libhello.so ->libhello.so.1

六,安装共享库和头文件

 

以上面的例子,咱们须要将libhello.a, libhello.so.x以及hello.h安装到系统目
录,才能真正让其余人开发使用,在本例中咱们将hello的共享库安装到<prefix>/lib
目录,将hello.h安装到<prefix>/include/hello目录。
利用上一节了解到的INSTALL指令,咱们向lib/CMakeLists.txt中添加以下指令:
INSTALL(TARGETS hello hello_static
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)
注意,静态库要使用ARCHIVE关键字
经过:
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make
make install

咱们就能够将头文件和共享库安装到系统目录/usr/lib和/usr/include/hello中了。

 

七,小结:
本小节,咱们谈到了:
如何经过ADD_LIBRARY指令构建动态库和静态库。
如何经过SET_TARGET_PROPERTIES同时构建同名的动态库和静态库。
如何经过SET_TARGET_PROPERTIES控制动态库版本
最终使用上一节谈到的INSTALL指令来安装头文件和动态、静态库。
在下一节,咱们须要编写另外一个高级一点的Hello World来演示怎么使用咱们已经构建的

构建的共享库libhello和外部头文件。

 

六,如何使用外部共享库和头文件
抱歉,本节仍然继续折腾Hello World.
上一节咱们已经完成了libhello动态库的构建以及安装,本节咱们的任务很简单:
编写一个程序使用咱们上一节构建的共享库。
1,准备工做:
请在/backup/cmake目录创建t4目录,本节全部资源将存储在t4目录。
2,重复之前的步骤,创建src目录,编写源文件main.c,内容以下:
#include <hello.h>
int main()
{
HelloFunc();
return 0;
}
编写工程主文件CMakeLists.txt
PROJECT(NEWHELLO)
ADD_SUBDIRECTORY(src)
编写src/CMakeLists.txt
ADD_EXECUTABLE(main main.c)
上述工做已经严格按照咱们前面季节提到的内容完成了。
3,外部构建
按照习惯,仍然创建build目录,使用cmake ..方式构建。
过程:
cmake ..
make
构建失败,若是须要查看细节,可使用第一节提到的方法
make VERBOSE=1来构建
错误输出为是:
/backup/cmake/t4/src/main.c:1:19: error: hello.h: 没有那个文件或目录
4,引入头文件搜索路径。
hello.h位于/usr/include/hello目录中,并无位于系统标准的头文件路径,
(有人会说了,白痴啊,你就不会include <hello/hello.h>,同志,要这么干,我这
一节就没什么可写了,只能选择一个glib或者libX11来写了,这些代码写出来不少同志
是看不懂的)
为了让咱们的工程可以找到hello.h头文件,咱们须要引入一个新的指令
INCLUDE_DIRECTORIES,其完整语法为:
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
这条指令能够用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,若是路径
中包含了空格,可使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的
后面,你能够经过两种方式来进行控制搜索路径添加的方式:
1,CMAKE_INCLUDE_DIRECTORIES_BEFORE,经过SET这个cmake变量为on,能够
将添加的头文件搜索路径放在已有路径的前面。
2,经过AFTER或者BEFORE参数,也能够控制是追加仍是置前。
如今咱们在src/CMakeLists.txt中添加一个头文件搜索路径,方式很简单,加入:
INCLUDE_DIRECTORIES(/usr/include/hello)
进入build目录,从新进行构建,这是找不到hello.h的错误已经消失,可是出现了一个
新的错误:
main.c:(.text+0x12): undefined reference to `HelloFunc'
由于咱们并无link到共享库libhello上。
5,为target添加共享库
咱们如今须要完成的任务是将目标文件连接到libhello,这里咱们须要引入两个新的指令
LINK_DIRECTORIES和TARGET_LINK_LIBRARIES
LINK_DIRECTORIES的所有语法是:
LINK_DIRECTORIES(directory1 directory2 ...)
这个指令很是简单,添加非标准的共享库搜索路径,好比,在工程内部同时存在共享库和可
执行二进制,在编译时就须要指定一下这些共享库的路径。这个例子中咱们没有用到这个指
令。
TARGET_LINK_LIBRARIES的所有语法是:
TARGET_LINK_LIBRARIES(target library1
<debug | optimized> library2
...)
这个指令能够用来为target添加须要连接的共享库,本例中是一个可执行文件,可是一样
能够用于为本身编写的共享库添加共享库连接。
为了解决咱们前面遇到的HelloFunc未定义错误,咱们须要做的是向
src/CMakeLists.txt中添加以下指令:
TARGET_LINK_LIBRARIES(main hello)
也能够写成
TARGET_LINK_LIBRARIES(main libhello.so)
这里的hello指的是咱们上一节构建的共享库libhello.
进入build目录从新进行构建。
cmake ..
make
这是咱们就获得了一个链接到libhello的可执行程序main,位于build/src目录,运
行main的结果是输出:
Hello World
让咱们来检查一下main的连接状况:
ldd src/main
linux-gate.so.1 => (0xb7ee7000)
libhello.so.1 => /usr/lib/libhello.so.1 (0xb7ece000)
libc.so.6 => /lib/libc.so.6 (0xb7d77000)
/lib/ld-linux.so.2 (0xb7ee8000)
能够清楚的看到main确实连接了共享库libhello,并且连接的是动态库
libhello.so.1
那如何连接到静态库呢?
方法很简单:
将TARGET_LINK_LIBRRARIES指令修改成:
TARGET_LINK_LIBRARIES(main libhello.a)
从新构建后再来看一下main的连接状况
ldd src/main
linux-gate.so.1 => (0xb7fa8000)
libc.so.6 => /lib/libc.so.6 (0xb7e3a000)
/lib/ld-linux.so.2 (0xb7fa9000)
说明,main确实连接到了静态库libhello.a
6,特殊的环境变量CMAKE_INCLUDE_PATH和CMAKE_LIBRARY_PATH
务必注意,这两个是环境变量而不是cmake变量。
使用方法是要在bash中用export或者在csh中使用set命令设置或者
CMAKE_INCLUDE_PATH=/home/include cmake ..等方式。
这两个变量主要是用来解决之前autotools工程中
--extra-include-dir等参数的支持的。
也就是,若是头文件没有存放在常规路径(/usr/include, /usr/local/include等),
则能够经过这些变量就行弥补。
咱们以本例中的hello.h为例,它存放在/usr/include/hello目录,因此直接查找肯
定是找不到的。
前面咱们直接使用了绝对路径INCLUDE_DIRECTORIES(/usr/include/hello)告诉工
程这个头文件目录。
为了将程序更智能一点,咱们可使用CMAKE_INCLUDE_PATH来进行,使用bash的方法
以下:
export CMAKE_INCLUDE_PATH=/usr/include/hello
而后在头文件中将INCLUDE_DIRECTORIES(/usr/include/hello)替换为:
FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)
上述的一些指令咱们在后面会介绍。
这里简单说明一下,FIND_PATH用来在指定路径中搜索文件名,好比:
FIND_PATH(myHeader NAMES hello.h PATHS /usr/include
/usr/include/hello)
这里咱们没有指定路径,可是,cmake仍然能够帮咱们找到hello.h存放的路径,就是因
为咱们设置了环境变量CMAKE_INCLUDE_PATH。
若是你不使用FIND_PATH,CMAKE_INCLUDE_PATH变量的设置是没有做用的,你不能指
望它会直接为编译器命令添加参数-I<CMAKE_INCLUDE_PATH>。
以此为例,CMAKE_LIBRARY_PATH能够用在FIND_LIBRARY中。
一样,由于这些变量直接为FIND_指令所使用,因此全部使用FIND_指令的cmake模块都
会受益。
7,小节:
本节咱们探讨了:
如何经过INCLUDE_DIRECTORIES指令加入非标准的头文件搜索路径。
如何经过LINK_DIRECTORIES指令加入非标准的库文件搜索路径。
若是经过TARGET_LINK_LIBRARIES为库或可执行二进制加入库连接。
并解释了若是连接到静态库。
到这里为止,您应该基本可使用cmake工做了,可是还有不少高级的话题没有探讨,比
如编译条件检查、编译器定义、平台判断、如何跟pkgconfig配合使用等等。
到这里,或许你能够理解前面讲到的“cmake的使用过程其实就是学习cmake语言并编写
cmake程序的过程”,既然是“cmake语言”,天然涉及到变量、语法等.

下一节,咱们将抛开程序的话题,看看经常使用的CMAKE变量以及一些基本的控制语法规则。

 

七,cmake经常使用变量和经常使用环境变量一,cmake变量引用的方式:前面咱们已经提到了,使用${}进行变量的引用。在IF等语句中,是直接使用变量名而不经过${}取值二,cmake自定义变量的方式:主要有隐式定义和显式定义两种,前面举了一个隐式定义的例子,就是PROJECT指令,他会隐式的定义<projectname>_BINARY_DIR和<projectname>_SOURCE_DIR两个变量。显式定义的例子咱们前面也提到了,使用SET指令,就能够构建一个自定义变量了。好比:SET(HELLO_SRC main.SOURCE_PATHc),就PROJECT_BINARY_DIR能够经过${HELLO_SRC}来引用这个自定义变量了.三,cmake经常使用变量:1,CMAKE_BINARY_DIRPROJECT_BINARY_DIR<projectname>_BINARY_DIR这三个变量指代的内容是一致的,若是是in source编译,指得就是工程顶层目录,若是是out-of-source编译,指的是工程编译发生的目录。PROJECT_BINARY_DIR跟其余指令稍有区别,如今,你能够理解为他们是一致的。2,CMAKE_SOURCE_DIRPROJECT_SOURCE_DIR<projectname>_SOURCE_DIR这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。也就是在in source编译时,他跟CMAKE_BINARY_DIR等变量一致。PROJECT_SOURCE_DIR跟其余指令稍有区别,如今,你能够理解为他们是一致的。3,CMAKE_CURRENT_SOURCE_DIR指的是当前处理的CMakeLists.txt所在的路径,好比上面咱们提到的src子目录。4,CMAKE_CURRRENT_BINARY_DIR若是是in-source编译,它跟CMAKE_CURRENT_SOURCE_DIR一致,若是是out-ofsource编译,他指的是target编译目录。使用咱们上面提到的ADD_SUBDIRECTORY(src bin)能够更改这个变量的值。使用SET(EXECUTABLE_OUTPUT_PATH <新路径>)并不会对这个变量形成影响,它仅仅修改了最终目标文件存放的路径。5,CMAKE_CURRENT_LIST_FILE输出调用这个变量的CMakeLists.txt的完整路径6,CMAKE_CURRENT_LIST_LINE输出这个变量所在的行7,CMAKE_MODULE_PATH这个变量用来定义本身的cmake模块所在的路径。若是你的工程比较复杂,有可能会本身编写一些cmake模块,这些cmake模块是随你的工程发布的,为了让cmake在处理CMakeLists.txt时找到这些模块,你须要经过SET指令,将本身的cmake模块路径设置一下。好比SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)这时候你就能够经过INCLUDE指令来调用本身的模块了。8,EXECUTABLE_OUTPUT_PATH和LIBRARY_OUTPUT_PATH分别用来从新定义最终结果的存放目录,前面咱们已经提到了这两个变量。9,PROJECT_NAME返回经过PROJECT指令定义的项目名称。四,cmake调用环境变量的方式使用$ENV{NAME}指令就能够调用系统的环境变量了。好比MESSAGE(STATUS “HOME dir: $ENV{HOME}”)设置环境变量的方式是:SET(ENV{变量名} 值)1,CMAKE_INCLUDE_CURRENT_DIR自动添加CMAKE_CURRENT_BINARY_DIR和CMAKE_CURRENT_SOURCE_DIR到当前处理的CMakeLists.txt。至关于在每一个CMakeLists.txt加入:INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}${CMAKE_CURRENT_SOURCE_DIR})2,CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE将工程提供的头文件目录始终至于系统头文件目录的前面,当你定义的头文件确实跟系统发生冲突时能够提供一些帮助。3,CMAKE_INCLUDE_PATH和CMAKE_LIBRARY_PATH咱们在上一节已经说起。五,系统信息1,CMAKE_MAJOR_VERSION,CMAKE主版本号,好比2.4.6中的22,CMAKE_MINOR_VERSION,CMAKE次版本号,好比2.4.6中的43,CMAKE_PATCH_VERSION,CMAKE补丁等级,好比2.4.6 中的64,CMAKE_SYSTEM,系统名称,好比Linux-2.6.225,CMAKE_SYSTEM_NAME,不包含版本的系统名,好比Linux6,CMAKE_SYSTEM_VERSION,系统版本,好比2.6.227,CMAKE_SYSTEM_PROCESSOR,处理器名称,好比i686.8,UNIX,在全部的类UNIX平台为TRUE,包括OS X和cygwin9,WIN32,在全部的win32平台为TRUE,包括cygwin六,主要的开关选项:1,CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS,用来控制IF ELSE语句的书写方式,在下一节语法部分会讲到。2,BUILD_SHARED_LIBS这个开关用来控制默认的库编译方式,若是不进行设置,使用ADD_LIBRARY并无指定库类型的状况下,默认编译生成的库都是静态库。若是SET(BUILD_SHARED_LIBS ON)后,默认生成的为动态库。3,CMAKE_C_FLAGS设置C编译选项,也能够经过指令ADD_DEFINITIONS()添加。4,CMAKE_CXX_FLAGS设置C++编译选项,也能够经过指令ADD_DEFINITIONS()添加。小结:本章介绍了一些较经常使用的cmake变量,这些变量仅仅是全部cmake变量的不多一部分,目前cmake的英文文档也是比较缺少的,若是须要了解更多的cmake变量,更好的方式是阅读一些成功项目的cmake工程文件,好比KDE4的代码。八,cmake经常使用指令前面咱们讲到了cmake经常使用的变量,相信“cmake即编程”的感受会愈来愈明显,不管如何,咱们仍然能够看到cmake比autotools要简单不少。接下来咱们就要集中的看一看cmake所提供的经常使用指令。在前面的章节咱们已经讨论了不少指令的用法,如PROJECT,ADD_EXECUTABLE,INSTALL,ADD_SUBDIRECTORY,SUBDIRS,INCLUDE_DIRECTORIES,LINK_DIRECTORIES,TARGET_LINK_LIBRARIES,SET等。本节会引入更多的cmake指令,为了编写的方便,咱们将按照cmake man page的顺序来介绍各类指令,再也不推荐使用的指令将再也不介绍,INSTALL系列指令在安装部分已经作了很是详细的说明,本节也不在说起。(你能够将本章理解成选择性翻译,可是会加入更多的我的理解)一,基本指令1,ADD_DEFINITIONS向C/C++编译器添加-D定义,好比:ADD_DEFINITIONS(-DENABLE_DEBUG -DABC),参数之间用空格分割。若是你的代码中定义了#ifdef ENABLE_DEBUG #endif,这个代码块就会生效。若是要添加其余的编译器开关,能够经过CMAKE_C_FLAGS变量和CMAKE_CXX_FLAGS变量设置。2,ADD_DEPENDENCIES定义target依赖的其余target,确保在编译本target以前,其余的target已经被构建。ADD_DEPENDENCIES(target-name depend-target1depend-target2 ...)3,ADD_EXECUTABLE、ADD_LIBRARY、ADD_SUBDIRECTORY前面已经介绍过了,这里再也不罗唆。4,ADD_TEST与ENABLE_TESTING指令。ENABLE_TESTING指令用来控制Makefile是否构建test目标,涉及工程全部目录。语法很简单,没有任何参数,ENABLE_TESTING(),通常状况这个指令放在工程的主CMakeLists.txt中.ADD_TEST指令的语法是:ADD_TEST(testname Exename arg1 arg2 ...)testname是自定义的test名称,Exename能够是构建的目标文件也能够是外部脚本等等。后面链接传递给可执行文件的参数。若是没有在同一个CMakeLists.txt中打开ENABLE_TESTING()指令,任何ADD_TEST都是无效的。好比咱们前面的Helloworld例子,能够在工程主CMakeLists.txt中添加ADD_TEST(mytest ${PROJECT_BINARY_DIR}/bin/main)ENABLE_TESTING()生成Makefile后,就能够运行make test来执行测试了。5,AUX_SOURCE_DIRECTORY基本语法是:AUX_SOURCE_DIRECTORY(dir VARIABLE)做用是发现一个目录下全部的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表。由于目前cmake还不能自动发现新添加的源文件。好比AUX_SOURCE_DIRECTORY(. SRC_LIST)ADD_EXECUTABLE(main ${SRC_LIST})你也能够经过后面提到的FOREACH指令来处理这个LIST6,CMAKE_MINIMUM_REQUIRED其语法为CMAKE_MINIMUM_REQUIRED(VERSION versionNumber [FATAL_ERROR])好比CMAKE_MINIMUM_REQUIRED(VERSION 2.5 FATAL_ERROR)若是cmake版本小与2.5,则出现严重错误,整个过程当中止。7,EXEC_PROGRAM在CMakeLists.txt处理过程当中执行命令,并不会在生成的Makefile中执行。具体语法为:EXEC_PROGRAM(Executable [directory in which to run][ARGS <arguments to executable>][OUTPUT_VARIABLE <var>][RETURN_VALUE <var>])用于在指定的目录运行某个程序,经过ARGS添加参数,若是要获取输出和返回值,可经过OUTPUT_VARIABLE和RETURN_VALUE分别定义两个变量.这个指令能够帮助你在CMakeLists.txt处理过程当中支持任何命令,好比根据系统状况去修改代码文件等等。举个简单的例子,咱们要在src目录执行ls命令,并把结果和返回值存下来。能够直接在src/CMakeLists.txt中添加:EXEC_PROGRAM(ls ARGS "*.c" OUTPUT_VARIABLE LS_OUTPUT RETURN_VALUELS_RVALUE)IF(not LS_RVALUE)MESSAGE(STATUS "ls result: " ${LS_OUTPUT})ENDIF(not LS_RVALUE)在cmake 生成Makefile的过程当中,就会执行ls命令,若是返回0,则说明成功执行,那么就输出ls *.c的结果。关于IF语句,后面的控制指令会提到。8,FILE指令文件操做指令,基本语法为:FILE(WRITE filename "message to write"... )FILE(APPEND filename "message to write"... )FILE(READ filename variable)FILE(GLOB variable [RELATIVE path] [globbingexpressions]...)FILE(GLOB_RECURSE variable [RELATIVE path][globbing expressions]...)FILE(REMOVE [directory]...)FILE(REMOVE_RECURSE [directory]...)FILE(MAKE_DIRECTORY [directory]...)FILE(RELATIVE_PATH variable directory file)FILE(TO_CMAKE_PATH path result)FILE(TO_NATIVE_PATH path result)这里的语法都比较简单,不在展开介绍了。9,INCLUDE指令,用来载入CMakeLists.txt文件,也用于载入预约义的cmake模块.INCLUDE(file1 [OPTIONAL])INCLUDE(module [OPTIONAL])OPTIONAL参数的做用是文件不存在也不会产生错误。你能够指定载入一个文件,若是定义的是一个模块,那么将在CMAKE_MODULE_PATH中搜索这个模块并载入。载入的内容将在处理到INCLUDE语句是直接执行。二,INSTALL指令INSTALL系列指令已经在前面的章节有很是详细的说明,这里不在赘述,可参考前面的安装部分。三,FIND_指令FIND_系列指令主要包含一下指令:FIND_FILE(<VAR> name1 path1 path2 ...)VAR变量表明找到的文件全路径,包含文件名FIND_LIBRARY(<VAR> name1 path1 path2 ...)VAR变量表示找到的库全路径,包含库文件名FIND_PATH(<VAR> name1 path1 path2 ...)VAR变量表明包含这个文件的路径。FIND_PROGRAM(<VAR> name1 path1 path2 ...)VAR变量表明包含这个程序的全路径。FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE][[REQUIRED|COMPONENTS] [componets...]])用来调用预约义在CMAKE_MODULE_PATH下的Find<name>.cmake模块,你也能够本身定义Find<name>模块,经过SET(CMAKE_MODULE_PATH dir)将其放入工程的某个目录中供工程使用,咱们在后面的章节会详细介绍FIND_PACKAGE的使用方法和Find模块的编写。FIND_LIBRARY示例:FIND_LIBRARY(libX X11 /usr/lib)IF(NOT libX)MESSAGE(FATAL_ERROR “libX not found”)ENDIF(NOT libX)四,控制指令:1,IF指令,基本语法为:IF(expression)# THEN section.COMMAND1(ARGS ...)COMMAND2(ARGS ...)...ELSE(expression)# ELSE section.COMMAND1(ARGS ...)COMMAND2(ARGS ...)...ENDIF(expression)另一个指令是ELSEIF,整体把握一个原则,凡是出现IF的地方必定要有对应的ENDIF.出现ELSEIF的地方,ENDIF是可选的。表达式的使用方法以下:IF(var),若是变量不是:空,0,N, NO, OFF, FALSE, NOTFOUND或<var>_NOTFOUND时,表达式为真。IF(NOT var ),与上述条件相反。IF(var1 AND var2),当两个变量都为真是为真。IF(var1 OR var2),当两个变量其中一个为真时为真。IF(COMMAND cmd),当给定的cmd确实是命令并能够调用是为真。IF(EXISTS dir)或者IF(EXISTS file),当目录名或者文件名存在时为真。IF(file1 IS_NEWER_THAN file2),当file1比file2新,或者file1/file2其中有一个不存在时为真,文件名请使用完整路径。IF(IS_DIRECTORY dirname),当dirname是目录时,为真。IF(variable MATCHES regex)IF(string MATCHES regex)当给定的变量或者字符串可以匹配正则表达式regex时为真。好比:IF("hello" MATCHES "ell")MESSAGE("true")ENDIF("hello" MATCHES "ell")IF(variable LESS number)IF(string LESS number)IF(variable GREATER number)IF(string GREATER number)IF(variable EQUAL number)IF(string EQUAL number)数字比较表达式IF(variable STRLESS string)IF(string STRLESS string)IF(variable STRGREATER string)IF(string STRGREATER string)IF(variable STREQUAL string)IF(string STREQUAL string)按照字母序的排列进行比较.IF(DEFINED variable),若是变量被定义,为真。一个小例子,用来判断平台差别:IF(WIN32)MESSAGE(STATUS “This is windows.”)#做一些Windows相关的操做ELSE(WIN32)MESSAGE(STATUS “This is not windows”)#做一些非Windows相关的操做ENDIF(WIN32)上述代码用来控制在不一样的平台进行不一样的控制,可是,阅读起来却并非那么舒服,ELSE(WIN32)之类的语句很容易引发歧义。这就用到了咱们在“经常使用变量”一节提到的 CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS开关。能够SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON)这时候就能够写成:IF(WIN32)ELSE()ENDIF()若是配合ELSEIF使用,可能的写法是这样:IF(WIN32)#do something related to WIN32ELSEIF(UNIX)#do something related to UNIXELSEIF(APPLE)#do something related to APPLEENDIF(WIN32)2,WHILEWHILE指令的语法是:WHILE(condition)COMMAND1(ARGS ...)COMMAND2(ARGS ...)...ENDWHILE(condition)其真假判断条件能够参考IF指令。3,FOREACHFOREACH指令的使用方法有三种形式:1,列表FOREACH(loop_var arg1 arg2 ...)COMMAND1(ARGS ...)COMMAND2(ARGS ...)...ENDFOREACH(loop_var)像咱们前面使用的AUX_SOURCE_DIRECTORY的例子AUX_SOURCE_DIRECTORY(. SRC_LIST)FOREACH(F ${SRC_LIST})MESSAGE(${F})ENDFOREACH(F)2,范围FOREACH(loop_var RANGE total)ENDFOREACH(loop_var)从0到total以1为步进举例以下:FOREACH(VAR RANGE 10)MESSAGE(${VAR})ENDFOREACH(VAR)最终获得的输出是:0123456789103,范围和步进FOREACH(loop_var RANGE start stop [step])ENDFOREACH(loop_var)从start开始到stop结束,以step为步进,举例以下FOREACH(A RANGE 5 15 3)MESSAGE(${A})ENDFOREACH(A)最终获得的结果是:581114这个指令须要注意的是,知道遇到ENDFOREACH指令,整个语句块才会获得真正的执行。小结:本小节基本涵盖了经常使用的cmake指令,包括基本指令、查找指令、安装指令以及控制语句等,特别须要注意的是,在控制语句条件中使用变量,不能用${}引用,而是直接应用变量名。掌握了以上的各类控制指令,你应该彻底能够经过cmake管理复杂的程序了,下一节,我们将介绍一个比较复杂的例子,经过他来演示本章的一些指令,并介绍模块的概念。九,复杂的例子:模块的使用和自定义模块你如今还会以为cmake简单吗?本章咱们将着重介绍系统预约义的Find模块的使用以及本身编写Find模块,系统中提供了其余各类模块,通常状况须要使用INCLUDE指令显式的调用,FIND_PACKAGE指令是一个特例,能够直接调用预约义的模块。其实使用纯粹依靠cmake自己提供的基本指令来管理工程是一件很是复杂的事情,因此,cmake设计成了可扩展的架构,能够经过编写一些通用的模块来扩展cmake.在本章,咱们准备首先介绍一下cmake提供的FindCURL模块的使用。而后,基于咱们前面的libhello共享库,编写一个FindHello.cmake模块。一,使用FindCURL模块在/backup/cmake目录创建t5目录,用于存放咱们的CURL的例子。创建src目录,并创建src/main.c,内容以下:#include <curl/curl.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>FILE *fp;int write_data(void *ptr, size_t size, size_t nmemb, void *stream){int written = fwrite(ptr, size, nmemb, (FILE *)fp);return written;}int main(){const char * path = “/tmp/curl-test”;const char * mode = “w”;fp = fopen(path,mode);curl_global_init(CURL_GLOBAL_ALL);CURLcode res;CURL *curl = curl_easy_init();curl_easy_setopt(curl, CURLOPT_URL, “http://www.linux-ren.org”);curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);res = curl_easy_perform(curl);curl_easy_cleanup(curl);}这段代码的做用是经过curl取回www.linux-ren.org的首页并写入/tmp/curl-test文件中。创建主工程文件CMakeLists.txtPROJECT(CURLTEST)ADD_SUBDIRECTORY(src)创建src/CMakeLists.txtADD_EXECUTABLE(curltest main.c)如今天然是没办法编译的,咱们须要添加curl的头文件路径和库文件。方法1:直接经过INCLUDE_DIRECTORIES和TARGET_LINK_LIBRARIES指令添加:咱们能够直接在src/CMakeLists.txt中添加:INCLUDE_DIRECTORIES(/usr/include)TARGET_LINK_LIBRARIES(curltest curl)而后创建build目录进行外部构建便可。如今咱们要探讨的是使用cmake提供的FindCURL模块。方法2,使用FindCURL模块。向src/CMakeLists.txt中添加:FIND_PACKAGE(CURL)IF(CURL_FOUND)INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR})TARGET_LINK_LIBRARIES(curltest ${CURL_LIBRARY})ELSE(CURL_FOUND)MESSAGE(FATAL_ERROR ”CURL library not found”)ENDIF(CURL_FOUND)对于系统预约义的Find<name>.cmake模块,使用方法通常如上例所示:每个模块都会定义如下几个变量• <name>_FOUND• <name>_INCLUDE_DIR or <name>_INCLUDES• <name>_LIBRARY or <name>_LIBRARIES你能够经过<name>_FOUND来判断模块是否被找到,若是没有找到,按照工程的须要关闭某些特性、给出提醒或者停止编译,上面的例子就是报出致命错误并终止构建。若是<name>_FOUND为真,则将<name>_INCLUDE_DIR加入INCLUDE_DIRECTORIES,将<name>_LIBRARY加入TARGET_LINK_LIBRARIES中。咱们再来看一个复杂的例子,经过<name>_FOUND来控制工程特性:SET(mySources viewer.c)SET(optionalSources)SET(optionalLibs)FIND_PACKAGE(JPEG)IF(JPEG_FOUND)SET(optionalSources ${optionalSources} jpegview.c)INCLUDE_DIRECTORIES( ${JPEG_INCLUDE_DIR} )SET(optionalLibs ${optionalLibs} ${JPEG_LIBRARIES} )ADD_DEFINITIONS(-DENABLE_JPEG_SUPPORT)ENDIF(JPEG_FOUND)IF(PNG_FOUND)SET(optionalSources ${optionalSources} pngview.c)INCLUDE_DIRECTORIES( ${PNG_INCLUDE_DIR} )SET(optionalLibs ${optionalLibs} ${PNG_LIBRARIES} )ADD_DEFINITIONS(-DENABLE_PNG_SUPPORT)ENDIF(PNG_FOUND)ADD_EXECUTABLE(viewer ${mySources} ${optionalSources} )TARGET_LINK_LIBRARIES(viewer ${optionalLibs}经过判断系统是否提供了JPEG库来决定程序是否支持JPEG功能。二,编写属于本身的FindHello模块。咱们在此前的t3实例中,演示了构建动态库、静态库的过程并进行了安装。接下来,咱们在t6示例中演示如何自定义FindHELLO模块并使用这个模块构建工程:请在创建/backup/cmake/中创建t6目录,并在其中创建cmake目录用于存放咱们本身定义的FindHELLO.cmake模块,同时创建src目录,用于存放咱们的源文件。1,定义cmake/FindHELLO.cmake模块FIND_PATH(HELLO_INCLUDE_DIR hello.h /usr/include/hello/usr/local/include/hello)FIND_LIBRARY(HELLO_LIBRARY NAMES hello PATH /usr/lib/usr/local/lib)IF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)SET(HELLO_FOUND TRUE)ENDIF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)IF (HELLO_FOUND)IF (NOT HELLO_FIND_QUIETLY)MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}")ENDIF (NOT HELLO_FIND_QUIETLY)ELSE (HELLO_FOUND)IF (HELLO_FIND_REQUIRED)MESSAGE(FATAL_ERROR "Could not find hello library")ENDIF (HELLO_FIND_REQUIRED)ENDIF (HELLO_FOUND)针对上面的模块让咱们再来回顾一下FIND_PACKAGE指令:FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE][[REQUIRED|COMPONENTS] [componets...]])前面的CURL例子中咱们使用了最简单的FIND_PACKAGE指令,其实他可使用多种参数,QUIET参数,对应与咱们编写的FindHELLO中的 HELLO_FIND_QUIETLY,若是不指定这个参数,就会执行:MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}")REQUIRED参数,其含义是指这个共享库是不是工程必须的,若是使用了这个参数,说明这个连接库是必备库,若是找不到这个连接库,则工程不能编译。对应于FindHELLO.cmake模块中的 HELLO_FIND_REQUIRED变量。一样,咱们在上面的模块中定义了HELLO_FOUND,HELLO_INCLUDE_DIR,HELLO_LIBRARY变量供开发者在FIND_PACKAGE指令中使用。OK,下面创建src/main.c,内容为:#include <hello.h>int main(){HelloFunc();return 0;}创建src/CMakeLists.txt文件,内容以下:FIND_PACKAGE(HELLO)IF(HELLO_FOUND)ADD_EXECUTABLE(hello main.c)INCLUDE_DIRECTORIES(${HELLO_INCLUDE_DIR})TARGET_LINK_LIBRARIES(hello ${HELLO_LIBRARY})ENDIF(HELLO_FOUND)为了可以让工程找到FindHELLO.cmake模块(存放在工程中的cmake目录)咱们在主工程文件CMakeLists.txt中加入:SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)三,使用自定义的FindHELLO模块构建工程仍然采用外部编译的方式,创建build目录,进入目录运行:cmake ..咱们能够从输出中看到:Found Hello: /usr/lib/libhello.so若是咱们把上面的FIND_PACKAGE(HELLO)修改成FIND_PACKAGE(HELLO QUIET),则不会看到上面的输出。接下来就可使用make命令构建工程,运行:./src/hello能够获得输出Hello World。说明工程成功构建。四,若是没有找到hello library呢?咱们能够尝试将/usr/lib/libhello.x移动到/tmp目录,这样,按照FindHELLO模块的定义,就找不到hello library了,咱们再来看一下构建结果:cmake ..仍然能够成功进行构建,可是这时候是没有办法编译的。修改FIND_PACKAGE(HELLO)为FIND_PACKAGE(HELLO REQUIRED),将hellolibrary定义为工程必须的共享库。这时候再次运行cmake ..咱们获得以下输出:CMake Error: Could not find hello library.由于找不到libhello.x,因此,整个Makefile生成过程被出错停止。小结:在本节中,咱们学习了如何使用系统提供的Find<NAME>模块并学习了本身编写Find<NAME>模块以及如何在工程中使用这些模块。后面的章节,咱们会逐渐学习更多的cmake模块使用方法以及用cmake来管理GTK和QT4工程。

相关文章
相关标签/搜索