1. cmake简介
cmake是一个跨平台、开源的构建系统。它是一个集软件构建、测试、打包与一身的软件。它使用与平台和编译器独立的配置文件对软件编译过程进 行控制。现在许多跨平台的开源软件都转向了用cmake来做构建工具,如KDE,Kdevelop,hypertable等,使用cmake,你可以不用关心如何去创建编译可执行文 件和动态库。它为了支持不同的平台,提供了以下特性:跨库依赖检查,并行构建和简单的头文件结构,这使它大大减少了跨平台软件的开发和维护过程的复杂性。 CMake自动生成的Makefile不仅可以通过make命令构建项目生成目标文件,还支持安装(make install)、测试安装的程序是否能正确执行(make test,或者ctest)、生成当前平台的安装包(make package)、生成源码包(make package_source)、产生Dashboard显示数据并上传等高级功能,只要在CMakeLists.txt中简单配置,就可以完成很多复杂的功能,包括写测试用例。 CMake可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。只是 CMake 的组态档取名为 CmakeLists.txt。Cmake 并不直接建构出最终的软件,而是产生标准的建构档(如 Unix 的 Makefile 或 Windows Visual C++ 的 projects/workspaces),然后再依一般的建构方式使用。这使得熟悉某个集成开发环境(IDE)的开发者可以用标准的方式建构他的软件,这种可以使用各平台的原生建构系统的能力是 CMake 和 SCons 等其他类似系统的区别之处。
对于一些简单的项目,你可能可以立即写出Makfile,如
HelloWorld: helloworld.o
cc -o HelloWorld helloworld.o -lm -lz
helloworld.o: helloworld.c helloworld.h
cc -c helloworld.c
当写好这个Makefile以后,你可以用make命令运行以构建项目,如果有文件被修改,那所有的有依赖关系的文件都会被重新构建。但当你把这个项目移植到另外一台机器,可以就会出这样的错误:cc: Command not found,这是因为这台机器上没有叫cc的编译程序,可能它会叫另外一个名字,如cc-XYZ,所以你必须修改Makfile以便进行重新编译。
通过上面的例子,你会发现一但Makefile被移植到了另一个新的平台,在这个平台上没有叫cc的编译器,那这时make就会出错。
可能有人会说,我们可以有autoconf和automake来对环境进行自动检测,但是它们太复杂了,而且autoconf依赖于shell,这些配置文件不能在没有bash的平台上运行。
而CMake解决了这些问题。它只依赖于系统的本地构建工具,而且它是用标准c++写的,在大多数平台上都能运行。
上面的那个例子用CMake来完成你需要写一个CMakeLists.txt文件,如下:
CMAKE_MINIMUM_REQUIRED (VERSION 2.8) # cmake最小版本
PROJECT (HelloWorld) # 工程名
SET(hello_SRCS helloworld.c) # 自定义变量hello_SRCS
ADD_EXECUTABLE(HelloWorld ${hello_SRCS}) # 说明hello_SRCS变量中的源文件需要编译出名为HelloWorld的可执行文件
TARGET_LINK_LIBRARIES(HelloWorld z m) # 指定需要链接的链接库文件,前面makefile中的-lm -lz
你可以用cmake path 和make来构建项目,它会先从path路径中找到CMakeLists.txt文件,读取进行分析,在当前目录中产生适合于平台的Makefiles文件。
2. cmake特点
2.1 跨平台,因为它使用与平台和编译器无关的配置文件,2.2 是一种非常简单的脚本语言,这有点像IDL
2.3 依赖发现机制很强大,使用FIND_PACKAGE
2.4 有很好的扩展性,效果也很高,据说KDE4用了它以后构建速度提高了40%
3. 举例说明
3.1 编译一个可执行文件的CMakeLists.txt
PROJECT( helloworld )SET( hello_SRCS hello.cpp )
ADD_EXECUTABLE( hello ${hello_SRCS})
3.2 编译一个动态库
PROJECT( mylibrary )SET( mylib_SRCS library.cpp )
ADD_LIBRARY( my SHARED ${mylib_SRCS}) // 这里也可以用STATIC标记来构建一个静态库
NOTE:静态库是在编译的时候去连接库,它会把用到的文件代码加入到你的可执行文件中;而动态库是在运行的时候去连接库,可执行文件中只包括动态库代码的一个相对地址。在linux下编译动态库的时候要加一个-fPIC,意思是Position Independent Code
3.3 分层文件目录
trunk ---> PROJECT(clockapp)|--doc ADD_SUBDIRECTORY(libwakeup) # 添加源文件子目录libwakeup
| ADD_SUBDIRECTORY(clock) # 添加源文件子目录clock
|--img
|
|--libwakeup --> SET(wakeup_SRCS wakeup.cpp)
| |-- wakeup.cpp ADD_LIBRARY(wakeup SHARED ${wakeup_SRCS}) # 生成动态库
| `-- wakeup.h
|
|--clock --> SET(clock_SRCS clock.cpp)
| |-- clock.cpp ADD_EXECUTABLE(clock ${clock_SRCS}) # 生成目标可执行文件
`-- clock.h
这里每一个目录都要写一个CMakeLists.txt文件,而不是在trunk写一个文件,这样可的好处是可以把错误分散到每个模块中,是一种分而治 之的思想。
3.4 一些控制流语法: if, foreach ,while
IF(expression)
..
ELSE(expression)..
ENDIF(expression)
...
ENDFOREACH(loop_var)
...
ENDWHILE(condition)
3.5 处理debug和release构建的方法
* SET(CMAKE_BUILD_TYPE Debug) # 设置构建类型为debug* cmake -DCMAKE_BUILD_TYPE=Release ../trunk
* TARGET_LINK_LIBRARIES(wakeup RELEASE ${wakeup_SRCS})
TARGET_LINK_LIBRARIES(wakeup DEBUG ${wakeup_SRCS})
4. 与其它相似工具的对比
4.1 autotools优点:历史悠久,使用广泛;可以自动生成Makefile文件
缺点:它需要有支持shell和m4;只支持Unix平台,而Visual Studio,Borland等在Win32平台上的都不支持;依赖发现大部分依赖手工操作; 生成的文件很大,一般很难看明白。
4.2 Jam
优点:跨平台;支持平行连接
缺点:最初的实现很粗糙;依赖发现机制大部分依赖手工
4.3 SCons
优点:跨平台;
缺点:你需要学习另一门编程语言才会用它;可扩展性不好;好像DKE的源代码最初是选择用Scons来编译的,但不知为什么后来选择了CMake
5. 参考资料
5.1 http://www.cmake.org/
5.2 http://www.cmake.org/Wiki/CMake#Tutorials
5.3 http://www.elpauer.org/stuff/learning_cmake.pdf
http://blog.csdn.net/amuseme_lu/article/details/5862320
CMake运行时,有两种主要模式:执行脚本模式,和执行编译模式。本文主要介绍CMake执行脚本模式。以及如何编写CMake脚本,并给出简单的示例。至于CMake时如何安装、配置等内容,网上到处都有资料可查,本文在此不做介绍。
CMake更多中文资料:http://zh.wikibooks.org/wiki/Category:CMake_%E5%85%A5%E9%96%80
CMake版“Hello World”
先来看一段示例:1. 新建一个文本文件,"cmake.txt";在文件中输入内容:
- message("Hello world")
- cmake -P cmake.txt
首先,需要传递参数"-P" 才能切换到脚本模式下运行;其次,CMake对于"-P"后面传入的文件名没有限制,可以为.txt,也可以为其他后缀,如".cmake",甚至没有后缀也可以。不过建议是采用以".cmake"作为脚本文件的后缀,便于识别区分和管理。然后,这里使用了CMake脚本中一个最基本、最常用的方法:"message()",向终端输出用户定义的信息或变量的值,关于这个方法的详细使用可以参照CMake官方文档中的使用说明,或者在控制台下输入"cmake --help-command message"查看帮助。
CMake脚本语法特性概览
1. 注释
CMake中使用"#"表示注释该行代码。2. 内置命令
与其他语言编程语言不同的是,CMake脚本的语法中没有赋值操作,无论是赋值,还是比较、判断操作,都是通过内置命令来完成的,例如"set(),math()等"。并且CMake中的内置命令不区分大小写。所有的内置命令调用形式为:
- command(arg1 arg2 arg3 ... argn)
3. 变量
CMake中的变量无需声明,并且没有类型概念,这一点类似于python;变量可以认为都是全局的,哪怕在一个宏中定义的变量,也可以在宏的外面被访问到;所有的变量都是一个列表变量,下文在举例时会详细说明这一点;CMake对于变量是大小写敏感的。4. 变量的引用
在CMake中,有两种引用方式:对于变量值的引用,和直接引用这个变量本身,使用方式分别是:- ${varName} 和 varName
CMake脚本中常见方法的使用及示例
1. 赋值
CMake中所有的赋值操作都是通过这个命令完成的:- set(variable value1 value2 value3 ... valueN)
- set(myVar "This is a test print!")
- message("${myVar}")
- set(myvar "a" "b")
- message("${myvar}")
- message(${myvar})
2. 循环
CMake中的循环有两种:foreach()...endforeach()和while()...endwhile()。while循环没什么特别的,会使用判断就会使用while循环,所以这里只讲一下foreach循环。
首先来看下面的例子:
- set(mylist "a" "b" c "d")
- foreach(_var ${mylist})
- message("当前变量是:${_var}")
- endforeach()
- set(result 0)
- foreach(_var RANGE 0 100)
- math(EXPR result "${result}+${_var}")
- endforeach()
- message("from 0 plus to 100 is:${result}")
这里是演示了计算从0一直加到100的结果。注:math()是数学计算函数,具体使用方法可以参考官方帮助文档。好了,foreach循环的基本用法就是这些。
其实在循环中,有一个方法是会经常用到的,它是:list()。关于这个方法的几种常见用法见下示例:
- # list(LENGTH ) LENGTH方法,求长度
- set(mylist a b c d )
- list(LENGTH mylist _length) #获取mylist长度,保存到变量_length中
- message("列表中元素的个数为:${_length}")
- # 稍微综合一点的示例:
- # 生成一个列表a,再将这个列表反序,得到列表b,然后将列表a、b中相应位置的元素合并
- set(strList "a" "b" "c" "d")
- set(reverList ${strList})
- list(REVERSE reverList)
- message("${reverList}")
- foreach(_var ${reverList})
- list(FIND strList ${_var} temp)
- list(GET reverList ${temp} reverItem)
- list(APPEND result "${_var}${reverItem}")
- message("当前元素序号 :${temp}")
- endforeach()
3.条件判断
- #CMake中,判断的用法如下:
- if(expression)
- # then section.
- COMMAND1(ARGS ...)
- COMMAND2(ARGS ...)
- ...
- elseif(expression2)
- # elseif section.
- COMMAND1(ARGS ...)
- COMMAND2(ARGS ...)
- ...
- else(expression)
- # else section.
- COMMAND1(ARGS ...)
- COMMAND2(ARGS ...)
- ...
- endif(expression)
相比于其他“正常的编程语言”,CMake中的判断可以说是比较变态的。
首先看看官方文档中的说明:对于表达式"if()"
True if the constant is 1, ON, YES, TRUE, Y, or a non-zero number.
False if the constant is 0, OFF, NO, FALSE, N, IGNORE, "", or ends in the suffix '-NOTFOUND'.
就是说,CMake中if语句中对于条件真假的判断,不完全是以变量的真假为标准的,有时还和变量名有关。
总之,使用if 时,要特别注意这一点。
下面来看一下if的具体使用示例:
- if(WIN32)
- message("this operation platform is windows")
- elseif(UNIX)
- message("this operation platform is Linux")
- endif()
4. 宏、函数
同大多数脚本语言一样,CMake中也有宏和函数的概念, 关键字分别为"macro"和"function",具体用法如下:- # 宏
- macro( [arg1 [arg2 [arg3 ...]]])
- COMMAND1(ARGS ...)
- COMMAND2(ARGS ...)
- ...
- endmacro()
-
- # 函数
- function( [arg1 [arg2 [arg3 ...]]])
- COMMAND1(ARGS ...)
- COMMAND2(ARGS ...)
- ...
- endfunction()
- macro(sum outvar)
- set(_args ${ARGN})
- set(result 0)
-
- foreach(_var ${_args})
- math(EXPR result "${result}+${_var}")
- endforeach()
-
- set(${outvar} ${result})
- endmacro()
-
- sum(addResult 1 2 3 4 5)
- message("Result is :${addResult}")
- macro(sum outvar)
- set(_args ${ARGN})
- list(LENGTH _args argLength)
- if(NOT argLength LESS 4) # 限制不能超过4个数字
- message(FATAL_ERROR "to much args!")
- endif()
- set(result 0)
-
- foreach(_var ${ARGN})
- math(EXPR result "${result}+${_var}")
- endforeach()
-
- set(${outvar} ${result})
- endmacro()
-
- sum(addResult 1 2 3 4 5)
- message("Result is :${addResult}")
- macro(macroTest)
- set(test1 "aaa")
- endmacro()
-
- function(funTest)
- set(test2 "bbb")
- endfunction()
-
- macroTest()
- message("${test1}")
-
- funTest()
- message("${test2}")
5. 综合示例
最后我们来通过一个稍微复杂综合一点的宏来结束本文。下面的这个宏是找出指定数值范围内全部素数,并输出。- macro(GetPrime output maxNum)
- set(extArg ${ARGN})
- if(extArg)
- message(SEND_ERROR "To much args!")
- endif()
-
- # 没有判断传入的变量是否为数字类型
- set(result)
- foreach(_var RANGE 2 ${maxNum})
- set(isPrime 1)
- math(EXPR upplimit ${_var}-1)
- foreach(_subVar RANGE 2 ${upplimit})
- math(EXPR _temp "${_var}%${_subVar}")
- if(_temp EQUAL 0)
- set(isPrime 0)
- break()
- endif()
- endforeach()
-
- if(isPrime)
- list(APPEND result ${_var})
- endif()
- endforeach()
- set(output ${result})
- endmacro()
-
- GetPrime(output 100)
- message("${output}")