主要有隐式定义和显式定义两种。
隐式定义的一个例子是PROJECT
指令,它会隐式的定义< projectname >_BINARY_DIR
和< projectname >_SOURCE_DIR
两个变量;显式定义使用SET
指令构建自定义变量,比如:SET(HELLO_SRCmain.c)
就可以通过${HELLO_SRC}
来引用这个自定义变量了。
使用${}
进行变量的引用;在IF
等语句中,是直接使用变量名而不通过${}
取值。
CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
< projectname >_BINARY_DIR
这三个变量指代的内容是一致的,如果是in-source编译,指得就是工程顶层目录;如果是out-of-source编译,指的是工程编译发生的目录。PROJECT_BINARY_DIR
跟其它指令稍有区别,目前可以认为它们是一致的。
CMAKE_SOURCE_DIR
PROJECT_SOURCE_DIR
< projectname >_SOURCE_DIR
这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。也就是在in-source编译时,他跟CMAKE_BINARY_DIR
等变量一致。PROJECT_SOURCE_DIR
跟其它指令稍有区别,目前可以认为它们是一致的。
(out-of-source build与in-source build相对,指是否在CMakeLists.txt所在目录进行编译。)
CMAKE_CURRENT_SOURCE_DIR
当前处理的CMakeLists.txt所在的路径,比如上面我们提到的src子目录。
CMAKE_CURRRENT_BINARY_DIR
如果是in-source编译,它跟CMAKE_CURRENT_SOURCE_DIR
一致;如果是out-of-source编译,指的是target编译目录。使用ADD_SUBDIRECTORY(src bin)
可以更改这个变量的值。使用SET(EXECUTABLE_OUTPUT_PATH <新路径>)
并不会对这个变量造成影响,它仅仅修改了最终目标文件存放的路径。
CMAKE_CURRENT_LIST_FILE
输出调用这个变量的CMakeLists.txt的完整路径
CMAKE_CURRENT_LIST_LINE
输出这个变量所在的行
CMAKE_MODULE_PATH
这个变量用来定义自己的cmake模块所在的路径。如果工程比较复杂,有可能会自己编写一些cmake模块,这些cmake模块是随工程发布的,为了让cmake在处理CMakeLists.txt时找到这些模块,你需要通过SET指令将cmake模块路径设置一下。比如SET(CMAKE_MODULE_PATH,${PROJECT_SOURCE_DIR}/cmake)
这时候就可以通过INCLUDE指令来调用自己的模块了。
EXECUTABLE_OUTPUT_PATH
新定义最终结果的存放目录
LIBRARY_OUTPUT_PATH
新定义最终结果的存放目录
PROJECT_NAME
返回通过PROJECT
指令定义的项目名称。
使用$ENV{NAME}指令就可以调用系统的环境变量了。比如MESSAGE(STATUS "HOME dir: $ENV{HOME}")
设置环境变量的方式是SET(ENV{变量名} 值)。
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})
CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE
将工程提供的头文件目录始终置于系统头文件目录的前面,当定义的头文件确实跟系统发生冲突时可以提供一些帮助。
CMAKE_INCLUDE_PATH和CMAKE_LIBRARY_PATH
CMAKE_MAJOR_VERSION
,CMAKE主版本号,比如2.4.6中的2CMAKE_MINOR_VERSION
,CMAKE次版本号,比如2.4.6中的4CMAKE_PATCH_VERSION
,CMAKE补丁等级,比如2.4.6中的6CMAKE_SYSTEM
,系统名称,比如Linux-2.6.22CMAKE_SYSTEM_NAME
,不包含版本的系统名,比如LinuxCMAKE_SYSTEM_VERSION
,系统版本,比如2.6.22CMAKE_SYSTEM_PROCESSOR
,处理器名称,比如i686UNIX
,在所有的类Unix平台为TRUE
,包括OSX和cygwinWIN32
,在所有的Win32平台为TRUE
,包括cygwinCMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS
用来控制IF ELSE
语句的书写方式。
BUILD_SHARED_LIBS
这个开关用来控制默认的库编译方式。如果不进行设置,使用ADD_LIBRARY
并没有指定库类型的情况下,默认编译生成的库都是静态库;如果SET(BUILD_SHARED_LIBSON)
后,默认生成的为动态库。
CMAKE_C_FLAGS
设置C编译选项,也可以通过指令ADD_DEFINITIONS()
添加。
MAKE_CXX_FLAGS
设置C++编译选项,也可以通过指令ADD_DEFINITIONS()
添加。
这里引入更多的cmake指令,为了编写的方便,将按照cmakeman page 的顺序介绍各种指令,不再推荐使用的指令将不再介绍。
PROJECT(HELLO)
指定项目名称,生成的VC项目的名称,使用${HELLO_SOURCE_DIR}
表示项目根目录。
INCLUDE_DIRECTORIES
指定头文件的搜索路径,相当于指定gcc的-I参数
INCLUDE_DIRECTORIES(${HELLO_SOURCE_DIR}/Hello) #增加Hello为include目录
TARGET_LINK_LIBRARIES
添加链接库,相同于指定-l参数
TARGET_LINK_LIBRARIES(demoHello) #将可执行文件与Hello连接成最终文件demo
LINK_DIRECTORIES
动态链接库或静态链接库的搜索路径,相当于gcc的-L参数
LINK_DIRECTORIES(${HELLO_BINARY_DIR}/Hello)#增加Hello为link目录
ADD_DEFINITIONS
向C/C++编译器添加-D定义,比如:
ADD_DEFINITIONS(-DENABLE_DEBUG-DABC)
参数之间用空格分割。如果代码中定义了:
这个代码块就会生效。如果要添加其他的编译器开关,可以通过CMAKE_C_FLAGS
变量和CMAKE_CXX_FLAGS
变量设置。
ADD_DEPENDENCIES*
定义target依赖的其它target,确保在编译本target之前,其它的target已经被构建。ADD_DEPENDENCIES(target-name depend-target1 depend-target2 ...)
ADD_EXECUTABLE
ADD_EXECUTABLE(helloDemo demo.cxx demo_b.cxx)
指定编译,好像也可以添加.o文件,将cxx编译成可执行文件
ADD_LIBRARY
ADD_LIBRARY(Hellohello.cxx) #将hello.cxx编译成静态库如libHello.a
ADD_SUBDIRECTORY
ADD_SUBDIRECTORY(Hello) #包含子目录
ADD_TEST
ENABLE_TESTING
ENABLE_TESTING
指令用来控制Makefile是否构建test目标,涉及工程所有目录。语法很简单,没有任何参数,ENABLE_TESTING()
一般放在工程的主CMakeLists.txt中。
ADD_TEST
指令的语法是:ADD_TEST(testnameExename arg1 arg2 …)
testname是自定义的test名称,Exename可以是构建的目标文件也可以是外部脚本等等,后面连接传递给可执行文件的参数。
如果没有在同一个CMakeLists.txt中打开ENABLE_TESTING()
指令,任何ADD_TEST
都是无效的。比如前面的Helloworld例子,可以在工程主CMakeLists.txt中添加
生成Makefile后,就可以运行make test
来执行测试了。
AUX_SOURCE_DIRECTORY
基本语法是:AUX_SOURCE_DIRECTORY(dir VARIABLE)
,作用是发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表,因为目前cmake还不能自动发现新添加的源文件。比如:
可以通过后面提到的FOR EACH
指令来处理这个LIST。
CMAKE_MINIMUM_REQUIRED
语法为CMAKE_MINIMUM_REQUIRED(VERSION versionNumber [FATAL_ERROR])
,
比如:CMAKE_MINIMUM_REQUIRED(VERSION 2.5 FATAL_ERROR)
如果cmake版本小与2.5,则出现严重错误,整个过程中止。
EXEC_PROGRAM
在CMakeLists.txt处理过程中执行命令,并不会在生成的Makefile中执行。具体语法为:
用于在指定的目录运行某个程序,通过ARGS添加参数,如果要获取输出和返回值,可通过OUTPUT_VARIABLE
和RETURN_VALUE
分别定义两个变量。
这个指令可以帮助在CMakeLists.txt处理过程中支持任何命令,比如根据系统情况去修改代码文件等等。举个简单的例子,我们要在src目录执行ls命令,并把结果和返回值存下来,可以直接在src/CMakeLists.txt中添加:
在cmake生成Makefile过程中,就会执行ls命令,如果返回0,则说明成功执行,那么就输出ls *.c
的结果。关于IF
语句,后面的控制指令会提到。
FILE指令
文件操作指令,基本语法为:
INCLUDE指令
用来载入CMakeLists.txt文件,也用于载入预定义的cmake模块。
OPTIONAL参数的作用是文件不存在也不会产生错误,可以指定载入一个文件,如果定义的是一个模块,那么将在CMAKE_MODULE_PATH中搜索这个模块并载入,载入的内容将在处理到INCLUDE语句是直接执行。
INSTALL指令
FIND_指令
FIND_系列指令主要包含一下指令:
FIND_LIBRARY示例:
另外一个指令是ELSEIF
,总体把握一个原则,凡是出现IF的地方一定要有对应的ENDIF
,出现ELSEIF
的地方,ENDIF
是可选的。表达式的使用方法如下:
IF(string MATCHES regex)
当给定的变量或者字符串能够匹配正则表达式regex
时为真。比如:
数字比较表达式
按照字母序的排列进行比较。
IF(DEFINED variable)
,如果变量被定义,为真。
一个小例子,用来判断平台差异:
上述代码用来控制在不同的平台进行不同的控制,但是阅读起来却并不是那么舒服, ELSE(WIN32)
之类的语句很容易引起歧义。
这就用到了我们在 常用变量 一节提到的CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS
开关。可以SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTSON)
,这时候就可以写成:
如果配合ELSEIF使用,可能的写法是这样:
WHILE指令的语法是:
其真假判断条件可以参考IF指令。
FOREACH指令的使用方法有三种形式:
(1)列表
像我们前面使用的AUX_SOURCE_DIRECTORY
的例子
(2)范围
从0到total以1为步进,举例如下:
最终得到的输出是:
(3)范围和步进
从start开始到stop结束,以step为步进。举例如下:
最终得到的结果是:
这个指令需要注意的是,直到遇到ENDFOREACH
指令,整个语句块才会得到真正的执行。
这里将着重介绍系统预定义的Find模块的使用以及自己编写Find模块,系统中提供了其他各种模块,一般情况需要使用INCLUDE
指令显式的调用,FIND_PACKAGE
指令是一个特例,可以直接调用预定义的模块。
其实纯粹依靠cmake本身提供的基本指令来管理工程是一件非常复杂的事情,所以cmake设计成了可扩展的架构,可以通过编写一些通用的模块来扩展cmake。
首先介绍一下cmake提供的FindCURL
模块的使用,然后基于前面的libhello
共享库,编写一个FindHello.cmake
模块。
(一)使用FindCURL模块
在/backup/cmake目录建立t5目录,用于存放CURL的例子。
建立src目录,并建立src/main.c,内容如下:
这段代码的作用是通过curl取回www.Linux-ren.org
的首页并写入/tmp/curl-test
文件中
建立主工程文件CmakeLists.txt
,如下:
建立src/CmakeLists.txt
现在自然是没办法编译的,我们需要添加curl的头文件路径和库文件。
方法1:
直接通过INCLUDE_DIRECTORIES
和TARGET_LINK_LIBRARIES
指令添加:
我们可以直接在src/CMakeLists.txt
中添加:
然后建立build目录进行外部构建即可。
现在要探讨的是使用cmake提供的FindCURL模块。
方法2:
使用FindCURL
模块。向src/CMakeLists.txt
中添加:
对于系统预定义的Find
模块,使用方法一般如上例所示,每一个模块都会定义以下几个变量:
可以通过
来判断模块是否被找到,如果没有找到,按照工程的需要关闭某些特性、给出提醒或者中止编译,上面的例子就是报出致命错误并终止构建。
如果
为真,则将
加入INCLUDE_DIRECTORIES
,将
加入TARGET_LINK_LIBRARIES
中。
我们再来看一个复杂的例子,通过
来控制工程特性:
通过判断系统是否提供了JPEG库来决定程序是否支持JPEG功能。
(二)编写属于自己的FindHello模块
接下来在t6示例中演示如何自定义FindHELLO
模块并使用这个模块构建工程。在/backup/cmake/
中建立t6目录,并在其中建立cmake目录用于存放我们自己定义的FindHELLO.cmake
模块,同时建立src目录,用于存放我们的源文件。
1.定义cmake/FindHELLO.cmake
模块
针对上面的模块让我们再来回顾一下FIND_PACKAGE
指令:
前面的CURL例子中我们使用了最简单的FIND_PACKAGE
指令,其实它可以使用多种参数:
QUIET
参数,对应与我们编写的FindHELLO
中的HELLO_FIND_QUIETLY
,如果不指定这个参数,就会执行:
REQUIRED
参数,其含义是指这个共享库是否是工程必须的,如果使用了这个参数,说明这个链接库是必备库,如果找不到这个链接库,则工程不能编译。对应于FindHELLO.cmake
模块中的HELLO_FIND_REQUIRED
变量。
同样,我们在上面的模块中定义了HELLO_FOUND
,HELLO_INCLUDE_DIR
, HELLO_LIBRARY
变量供开发者在FIND_PACKAGE
指令中使用。
下面建立src/main.c
,内容为:
建立src/CMakeLists.txt
文件,内容如下:
为了能够让工程找到 FindHELLO.cmake 模块(存放在工程中的cmake目录)我们在主工程文件 CMakeLists.txt 中加入:
(三)使用自定义的FindHELLO模块构建工程
仍然采用外部编译的方式,建立build目录,进入目录运行:
我们可以从输出中看到:
如果我们把上面的FIND_PACKAGE(HELLO)
修改为FIND_PACKAGE(HELLO QUIET)
,
不会看到上面的输出。接下来就可以使用make命令构建工程,运行:
可以得到输出
说明工程成功构建。
(四)如果没有找到hellolibrary呢?
我们可以尝试将/usr/lib/libhello.x
移动到/tmp目录,这样按照FindHELLO
模块的定义,找不到hellolibrary
了,我们再来看一下构建结果:
仍然可以成功进行构建,但是这时候是没有办法编译的。
修改FIND_PACKAGE(HELLO)
为FIND_PACKAGE(HELLO REQUIRED)
,将hellolibrary
定义为工程必须的共享库。
这时候再次运行
我们得到如下输出:
因为找不到libhello.x,所以,整个Makefile生成过程被出错中止。
1.怎样区分debug、release版本
建立debug/release两目录,分别在其中执行cmake -D CMAKE_BUILD_TYPE=Debug(或Release)
,需要编译不同版本时进入不同目录执行make
即可:
另一种设置方法——例如DEBUG
版设置编译参数DDEBUG
在执行cmake
时增加参数即可,例如cmake -D DEBUG_mode=ON
2.怎样设置条件编译
例如debug
版设置编译选项DEBUG
,并且更改不应改变CMakelist.txt
使用option command
,eg:
使其生效的方法:首先cmake
生成makefile
,然后make edit_cache
编辑编译选项;linux下会打开一个文本框,可以更改,改完后再make生成目标文件——emacs不支持make edit_cache
;
局限:这种方法不能直接设置生成的makefile,而是必须使用命令在make前设置参数;对于debug、release版本,相当于需要两个目录,分别先cmake一次,然后分别makeedit_cache一次;
期望的效果:在执行cmake时直接通过参数指定一个开关项,生成相应的makefile
。