注意:
1、对于每一个项目必定有一个CMakeLists.txt,我们称为主CMakeLists.txt
2、每一个项目主CMakeLists.txt中必须指定版本信息和项目信息
3、cmake脚本中可以指定生成可执行文件、静态库、动态库
4、cmake脚本不区分大小写,cmake自定义变量与linux保持一致,默认大写
5、编写完成CMakeLists.txt后,使用cmake或ccmake(带自定义选项开关的编译)生成makefile,进一步make后才能生成目标文件!
cmake必备的版本信息和项目信息编写如下:
# 指定最低cmake版本
cmake_minimum_required(VERSION 2.8)
# 项目信息
project(项目名)
5、常用的cmake脚本函数:
cmake_minimum_required (VERSION 版本号):指定cmake编译最低版本,如VERSION 2.8
project(项目名) : 指定cmake项目名称
set(变量名 变量值) :声明变量且赋值,变量在文档中全局有效,如set(var_name "/usr/lib"),引用变量时:${var_name}
MESSAGE(STATUS "Project root directory:" ${PROJECT_ROOT_DIRECTORY}) : 打印信息
add_subdirectory(子目录名无引号):添加源码子目录
aux_source_directory(目录 变量名):自动搜索目录源文件并赋值给某变量(变量自动声明)
include_directories(外部头文件目录无引号):添加外部头文件目录,同vs包含外部头文件目录一样
link_directories(外部库文件目录无引号):添加外部库文件目录,同vs使用外部lib目录一样
add_library(生成库目标名 生成库类型(STATIC|SHARED) 库原文件列表):项目添加(或生成)库文件,可以为静态库或动态库;
add_executable(可执行文件目标名 源文件列表):生成可执行文件
target_link_libraries(可执行文件目标名 链接库1 链接库2):可执行文件链接库
set_target_properties(目标 PROPERTIES 属性名 属性值):设置目标属性如OUTPUT_NAME输出名
get_target_properties(变量 目标 属性名):获取目标对应属性的属性值
install(安装目标还是文件 安装目标对象或头文件列表 DESTINATION 目标位置):安装类型TARGETS指定安装目标,FILES指定安装文件;
add_test(测试用例名称 运行目标 参数列表):如add_test(test1 Demon 4 5) ,Demon是加减运算程序,4和5为运行参数
set_tests_properties(用例 PROPERTIES 属性名 属性值) ,常用法:
set_tests_properties (test_10_5 PROPERTIES PASS_REGULAR_EXPRESSION "is 100000") 表达式结果应该为10000,不对用例报错
6、常用的cmake变量
project_source_dir 变量指定cmake工程根目录
project_binary_dir指定build的目录,如果在根目录使用cmake . 那么在根目录生成一堆污染源码的中间编译文件,所以一般在该目录下创建一个build目录,然后进入build目录使用 cmake .. 指定根目录为父目录,此时编译中间文件都生成在build目录,从而避免污染源码;
7、定制安装
即使学会如何生成可执行文件、静态库、动态库,如果在未安装的情况下,特别是动态库是无法找到的,默认是到/usr/lib去找的,所以需要安装对应库才可以被项目使用
一、可执行文件生成脚本编写
(1)同一目录,一个源文件生成可执行程序脚本
cmake_minimum_required (VERSION 2.8) project (Demo1) add_executable(Demo main.cpp)
使用add_executable直接添加指定源文件main.cpp编译成一个名称为 Demo 的可执行文件;
(2)同一目录,多个源文件生成可执行程序脚本
如果某一目录含有多个源文件,我们不可能一一列举,这样脚本工作量太大,也容易遗漏,此时我们可以使用
aux_source_directory(目录 变量)
查找指定目录下所有的源文件并赋值给某一变量,在编译的时候引用该变量即可达到自动搜索指定目录下所有源文件编译成可执行文件;
# CMake 最低版本号要求 cmake_minimum_required (VERSION 2.8) # 项目信息 project (Demo2) # 查找目录下的所有源文件 # 并将名称保存到 DIR_SRCS 变量 aux_source_directory(. DIR_SRCS) # 指定生成目标 add_executable(Demo ${DIR_SRCS})引用变量:${变量名}
(3)本目录含有源文件,且子目录也包含源文件
假如本目录下有一个子目录mymath,mymath下有n个有关数学计算的头文件和源文件,本目录下也含有其他项目需要的头和源文件,这时候我们如何将子目录的源码和本目录源码一块搜索出来的编译呢?答案是:cmake提供一个添加子目录的函数add_subdirectory,指定某子目录后能自动搜索添加子目录文件(子目录有源文件,如果单纯的是头文件我们使用include_directories(目录路径无引号),如果单纯的是库文件,我们使用link_directoies(目录路径无引号)),由于含有源文件,我们可以把子目录当成一个静态库,也即使编译成一个静态库,然后链接到主程序即可。
# CMake 最低版本号要求 cmake_minimum_required (VERSION 2.8) # 项目信息 project (Demo3) # 查找目录下的所有源文件 # 并将名称保存到 DIR_SRCS 变量 aux_source_directory(. DIR_SRCS) # 添加 math 子目录 add_subdirectory(math) # 指定生成目标 add_executable(Demo ${DIR_SRCS}) # 添加链接库 target_link_libraries(Demo MyMath)
注意,demon中的add_subdirectory为添加子目录,子目录有自己的CMakeList.txt(注意该MakeLists不是主而是子目录不需要添加版本和项目信息)且该CMakeLists目的是将子目录的源码文件编译为静态库以供可执行程序链接;target_link_libraries(链接目标 静态库1 静态库2 静态库3 ..), 这里是将子目录生成的Mymath静态库链接到可执行程序中;(必须添加子目录以便主程序能查到相关定义,其次是将编译后的静态库链接到可执行程序中以供使用);
子目录中必须含有自己的CMakeLists.txt以便能静态编译成静态库形式;
# 查找当前目录下的所有源文件 # 并将名称保存到 DIR_LIB_SRCS 变量 aux_source_directory(. DIR_LIB_SRCS) # 指定生成 MathFunctions 链接库 add_library (Mymath ${DIR_LIB_SRCS})add_library作用就是 为该项目添加一个编译静态库,使用如下:
add_library(静态库或动态库链接target名称 [STATIC|SHARED] 源文件或源文件列表或源文件目录),如果不指定动态或静态,默认链接成静态库
被链接的目标名称不能重复只能相同,假如项目想同时生成库名称相同但类型不一样(静态和动态)的库,怎么办?我们可以将链接成不同名称,然后通过修改库的输出名称,将其改为相同名称即可,举例如同时链接生成test.a和test.so的静态和动态库,库名都是test:(libtest.a libtest.so 两个文件名对于linux默认链接的时候名称都是test,如-L/. -ltest ,大L指定搜索目录,小l指定链接该目录下test的静态或动态库);
add_library(test STATIC ${DIR_LIB_SRCS}
add_library(test_so SHARED ${DIR_LIB_SRCS}
首先将静态库编译目标指定为test,动态库指定为test_so,然后通过修改test_so的目标属性的输出名称指定为test即可,操作如下:
set_target_properties(test_so PROPERTIES OUTPUT_NAME "test") 其中test_so为要修改的目标对象, PROPERTIES指定后续为属性列表(属性名 属性值 的键值对形式出现)OUTPUT_NAME为属性名 “test"为修改的值(OUTPUT_VALUE="test"),
我们输出可以使用获取函数:get_target_properties(OUTPUT_VALUE test_so OUTPUT_NAME) 参数一次为(获取的属性值存储变量 目标对象 获取哪个属性)
获取到后打印输出:MESSAGE(STATUS "test_so OUTPUT_NAME is:" ${OUTPUT_VALUE})
二、静态库生成脚本编写
按照第一部分讲解到的知识,我们可以通过add_library 为项目添加静态库,并指定库为STATIC静态的即可,如果是一个项目必须指定cmake版本和项目信息,故而项目cmake文件编写如下:
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo3)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
aux_source_directory(. DIR_LIB_SRCS)
# 指定生成 MathFunctions 链接库
add_library (MyMath STATIC ${DIR_LIB_SRCS})
解析:
其中aux_source_directory为自动搜索当前目录所有文件并赋值给变量DIR_LIB_SRCS,此项目编译成静态库而不是生成可指定文件,所以使用add_library(目标 库类型 源码列表),而不是使用add_executable(目标 源码列表或目录);
三、动态库生成脚本编写
同理,我们可以使用add_library(目标名 SHARED 源文件列表)为项目添加或生成动态库,实例如下:
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo3)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
aux_source_directory(. DIR_LIB_SRCS)
# 指定生成 MathFunctions 链接库
add_library (MyMath SHARED ${DIR_LIB_SRCS})
一下段落更为详细的介绍了cmake使用的变量和函数,可以作为参考依据:
系统信息
开关选项
指令(参数1 参数2…)
参数使用括弧括起,参数之间使用空格或分号分开。
以ADD_EXECUTABLE指令为例: ADD_EXECUTABLE(hello main.c func.c)或者 ADD_EXECUTABLE(hello main.c;func.c)
指令是大小写无关的,参数和变量是大小写相关的。推荐你全部使用大写指令。
EXEC_PROGRAM
EXEC_PROGRAM(Executable [dir where to run] [ARGS <args>][OUTPUT_VARIABLE <var>] [RETURN_VALUE <value>])
用于在指定目录运行某个程序(默认为当前CMakeLists.txt所在目录),通过ARGS添加参数,通过OUTPUT_VARIABLE和RETURN_VALUE获取输出和返回值,如下示例
# 在src中运行ls命令,在src/CMakeLists.txt添加 EXEC_PROGRAM(ls ARGS "*.c" OUTPUT_VARIABLE LS_OUTPUT RETURN_VALUE LS_RVALUE) IF (not LS_RVALUE) MESSAGE(STATUS "ls result: " ${LS_OUTPUT}) # 缩进仅为美观,语法无要求 ENDIF(not LS_RVALUE)
INCLUDE
INCLUDE(file [OPTIONAL]) 用来载入CMakeLists.txt文件
INCLUDE(module [OPTIONAL])用来载入预定义的cmake模块
OPTIONAL参数的左右是文件不存在也不会产生错误
可以载入一个文件,也可以载入预定义模块(模块会在CMAKE_MODULE_PATH指定的路径进行搜索)
载入的内容将在处理到INCLUDE语句时直接执行
FIND_
FIND_LIBRARY(<VAR> name path1 path2 …)
VAR变量代表找到的库全路径,包含库文件名
FIND_LIBRARY(libX X11 /usr/lib) IF (NOT libx) MESSAGE(FATAL_ERROR "libX not found") ENDIF(NOT libX)
FIND_PATH(<VAR> name path1 path2 …)
VAR变量代表包含这个文件的路径
IF
语法:
IF (expression) COMMAND1(ARGS ...) COMMAND2(ARGS ...) ... ELSE (expression) COMMAND1(ARGS ...) COMMAND2(ARGS ...) ... ENDIF (expression) # 一定要有ENDIF与IF对应
IF (expression), expression不为:空,0,N,NO,OFF,FALSE,NOTFOUND或<var>_NOTFOUND,为真
IF (not exp), 与上面相反
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 dir) 当dir是目录时,为真
IF (DEFINED var) 如果变量被定义,为真
IF (var MATCHES regex) 此处var可以用var名,也可以用${var}
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 (WIN32) MESSAGE(STATUS “This is windows.”) ELSE (WIN32) MESSAGE(STATUS “This is not windows”) ENDIF (WIN32) 上述代码用来控制在不同的平台进行不同的控制,但是,阅读起来却并不是那么舒服,ELSE(WIN32)之类的语句很容易引起歧义。 可以SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON) 这时候就可以写成: IF (WIN32) ELSE () ENDIF () 配合ELSEIF使用,可能的写法是这样: IF (WIN32) #do something related to WIN32 ELSEIF (UNIX) #do something related to UNIX ELSEIF(APPLE) #do something related to APPLE ENDIF (WIN32)
WHILE
语法:
WHILE(condition) COMMAND1(ARGS ...) COMMAND2(ARGS ...) ... ENDWHILE(condition)
其真假判断条件可以参考IF指令
FOREACH(loop_var arg1 arg2 ...) COMMAND1(ARGS ...) COMMAND2(ARGS ...) ... ENDFOREACH(loop_var)
示例: AUX_SOURCE_DIRECTORY(. SRC_LIST) FOREACH(F ${SRC_LIST}) MESSAGE(${F}) ENDFOREACH(F)
FOREACH(loop_var RANGE total) COMMAND1(ARGS ...) COMMAND2(ARGS ...) ... ENDFOREACH(loop_var)
示例: 从0到total以1为步进 FOREACH(VAR RANGE 10) MESSAGE(${VAR}) ENDFOREACH(VAR) 输出: 012345678910
FOREACH(loop_var RANGE start stop [step]) COMMAND1(ARGS ...) COMMAND2(ARGS ...) ... ENDFOREACH(loop_var)
从start开始到stop结束,以step为步进,FOREACH(A RANGE 5 15 3) MESSAGE(${A}) ENDFOREACH(A) 输出: 581114
参考ADD_LIBRARY和SET_TARGET_PROPERTIES用法
t3示例
参考INCLUDE_DIRECTORIES, LINK_DIRECTORIES, TARGET_LINK_LIBRARIES用法
t4示例使用动态库或静态库
t5示例如何使用cmake预定义的cmake模块(以FindCURL.cmake为例演示)
t6示例如何使用自定义的cmake模块(编写了自定义的FindHELLO.cmake)
注意读t5和t6的CMakeLists.txt和FindHELLO.cmake中的注释部分
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
上面的两条命令通常紧跟ADD_EXECUTABLE和ADD_LIBRARY,与其写在同一个CMakeLists.txt即可 使用变量CMAKE_C_FLAGS添加C编译选项
使用变量CMAKE_CXX_FLAGS添加C++编译选项
使用ADD_DEFINITION添加
参考INCLUDE_DIRECTORIES命令用法
参考MESSAGE用法
参考SET和AUX_SOURCE_DIRECTORY用法
建议:在Project根目录先建立build,然后在build文件夹内运行cmake ..,这样就不会污染源代码, 如果不想要这些自动生成的文件了,只要简单的删除build文件夹就可以