CMake 编程简介

1. cmake简介

        cmake是一个跨平台、开源的构建系统。它是一个集软件构建、测试、打包与一身的软件。它使用与平台和编译器独立的配置文件对软件编译过程进 行控制。现在许多跨平台的开源软件都转向了用cmake来做构建工具,如KDE,Kdevelop,hypertable等,使用cmake,你可以不用关心如何去创建编译可执行文 件和动态库。它为了支持不同的平台,提供了以下特性:跨库依赖检查,并行构建和简单的头文件结构,这使它大大减少了跨平台软件的开发和维护过程的复杂性。 CMake自动生成的Makefile不仅可以通过make命令构建项目生成目标文件,还支持安装(make install)、测试安装的程序是否能正确执行(make test,或者ctest)、生成当前平台的安装包(make package)、生成源码包(make package_source)、产生Dashboard显示数据并上传等高级功能,只要在CMakeLists.txt中简单配置,就可以完成很多复杂的功能,包括写测试用例。

如果有嵌套目录,子目录下可以有自己的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)



                FOREACH(loop_var)
                ...
                ENDFOREACH(loop_var)



                WHILE(condition)
                ...
                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";在文件中输入内容:

  1. message("Hello world")
2. 保存文件,打开控制台,切换到cmake.txt所在的目录,输入以下命令:
  1. cmake -P cmake.txt
3. 回车后,可以看到控制台上打印出了"Hello World"字符。

    首先,需要传递参数"-P" 才能切换到脚本模式下运行;其次,CMake对于"-P"后面传入的文件名没有限制,可以为.txt,也可以为其他后缀,如".cmake",甚至没有后缀也可以。不过建议是采用以".cmake"作为脚本文件的后缀,便于识别区分和管理。然后,这里使用了CMake脚本中一个最基本、最常用的方法:"message()",向终端输出用户定义的信息或变量的值,关于这个方法的详细使用可以参照CMake官方文档中的使用说明,或者在控制台下输入"cmake --help-command message"查看帮助。

CMake脚本语法特性概览

1. 注释

    CMake中使用"#"表示注释该行代码。

2. 内置命令

    与其他语言编程语言不同的是,CMake脚本的语法中没有赋值操作,无论是赋值,还是比较、判断操作,都是通过内置命令来完成的,例如"set(),math()等"。并且CMake中的内置命令不区分大小写。
所有的内置命令调用形式为:
  1. command(arg1 arg2 arg3 ... argn)
每个参数均以空格,或者分号分割。注:不建议使用分号分割参数.

3. 变量

    CMake中的变量无需声明,并且没有类型概念,这一点类似于python;变量可以认为都是全局的,哪怕在一个宏中定义的变量,也可以在宏的外面被访问到;所有的变量都是一个列表变量,下文在举例时会详细说明这一点;CMake对于变量是大小写敏感的。

4. 变量的引用

    在CMake中,有两种引用方式:对于变量值的引用,和直接引用这个变量本身,使用方式分别是:
  1. ${varName} 和 varName

CMake脚本中常见方法的使用及示例

1. 赋值

    CMake中所有的赋值操作都是通过这个命令完成的:
  1. set(variable value1 value2 value3 ... valueN)
    调用这个命令后,variable变量将是一个列表,其中包含值"value1,value2,...valueN"。具体使用范例见下:
  1. set(myVar "This is a test print!")
  2. message("${myVar}")
此时,控制台中将打印出"This is a test print!"。这里需要注意的是,message函数中的变量参数,可以写在引号中,并且带引号和不带引号时,有一点差异,下面再看一例:
  1. set(myvar "a" "b")
  2. message("${myvar}")
  3. message(${myvar})
此时,控制台中将分别打印出"a;b"和"ab"。这是因为,不带引号时,${myvar}是一个列表,包含了两个值,而message中相当于接收到了两个参数"a"、"b",因此输出"ab"。而带有引号时,引号中的内容整体将作为一个参数存在。另一个比较特别的地方是,给变量赋值时,可以不用使用引号,例如:set(myvar a "b" c d)。

2. 循环

    CMake中的循环有两种:foreach()...endforeach()和while()...endwhile()。
while循环没什么特别的,会使用判断就会使用while循环,所以这里只讲一下foreach循环。
首先来看下面的例子:
  1. set(mylist "a" "b" c "d")
  2. foreach(_var ${mylist})
  3.      message("当前变量是:${_var}")
  4. endforeach()
以上是foreach最基本、最常见的用法,还有一种比较实用的方法是:foreach(loop_var RANGE start stop [step]) 。使用示例见下:
  1. set(result 0)
  2. foreach(_var RANGE 0 100)
  3.      math(EXPR result "${result}+${_var}")
  4. endforeach()
  5. message("from 0 plus to 100 is:${result}")

    这里是演示了计算从0一直加到100的结果。注:math()是数学计算函数,具体使用方法可以参考官方帮助文档。好了,foreach循环的基本用法就是这些。

其实在循环中,有一个方法是会经常用到的,它是:list()。关于这个方法的几种常见用法见下示例:

  1. # list(LENGTH ) LENGTH方法,求长度
  2. set(mylist a b c d )
  3. list(LENGTH mylist _length)   #获取mylist长度,保存到变量_length中
  4. message("列表中元素的个数为:${_length}")
  1. # 稍微综合一点的示例:
  2. # 生成一个列表a,再将这个列表反序,得到列表b,然后将列表a、b中相应位置的元素合并
  3. set(strList "a" "b" "c" "d")
  4. set(reverList ${strList})
  5. list(REVERSE reverList)
  6. message("${reverList}")
  1. foreach(_var ${reverList})
  2.      list(FIND strList ${_var} temp)
  3.      list(GET reverList ${temp} reverItem)
  4.      list(APPEND result "${_var}${reverItem}")
  5.      message("当前元素序号 :${temp}")
  6. endforeach()

3.条件判断

  1. #CMake中,判断的用法如下:
  2. if(expression)
  3.      # then section.
  4.      COMMAND1(ARGS ...)
  5.      COMMAND2(ARGS ...)
  6.      ...
  7. elseif(expression2)
  8.      # elseif section.
  9.      COMMAND1(ARGS ...)
  10.      COMMAND2(ARGS ...)
  11.      ...
  12. else(expression)
  13.      # else section.
  14.      COMMAND1(ARGS ...)
  15.      COMMAND2(ARGS ...)
  16.      ...
  17. 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的具体使用示例:

  1. if(WIN32)
  2.      message("this operation platform is windows")
  3. elseif(UNIX)
  4.      message("this operation platform is Linux")
  5. endif()
其中,"WIN32"和"UNIX"均为CMake中的常量,具体用法可以参考官方文档。

4. 宏、函数

    同大多数脚本语言一样,CMake中也有宏和函数的概念, 关键字分别为"macro"和"function",具体用法如下:
  1. # 宏
  2. macro( [arg1 [arg2 [arg3 ...]]])
  3.      COMMAND1(ARGS ...)
  4.      COMMAND2(ARGS ...)
  5.      ...
  6. endmacro()
      

  7. # 函数
  8. function( [arg1 [arg2 [arg3 ...]]])
  9.      COMMAND1(ARGS ...)
  10.      COMMAND2(ARGS ...)
  11.      ...
  12. endfunction()
以简单的求和函数为例,我们来看宏的一个示例:
  1. macro(sum outvar)
  2.      set(_args ${ARGN})
  3.      set(result 0)

  4.      foreach(_var ${_args})
  5.          math(EXPR result "${result}+${_var}")
  6.      endforeach()

  7.      set(${outvar} ${result})
  8. endmacro()

  9. sum(addResult 1 2 3 4 5)
  10. message("Result is :${addResult}")
    上面是一段求和宏定义,我们来解读一下代码: "${ARGN}"是CMake中的一个变量,指代宏中传入的多余参数。因为我们这个宏sum中只定义了一个参数"outvar",其余需要求和的数字都是不定形式传入的,所以需要先将多余的参数传入一个单独的变量中。当然,在这个示例中,第一行代码显得多余,因为似乎没必要将额外参数单独放在一个变量中,但是建议这么做。对上面这个宏再进一步加强: 如果我们想限制这个宏中传入的参数数目(尽管在这个宏中实际上是不必要的),那么可以将宏改写一下:
  1. macro(sum outvar)
  2.      set(_args ${ARGN})
  3.      list(LENGTH _args argLength)
  4.      if(NOT argLength LESS 4) # 限制不能超过4个数字
  5.          message(FATAL_ERROR "to much args!")
  6.      endif()
  7.      set(result 0)

  8.      foreach(_var ${ARGN})
  9.          math(EXPR result "${result}+${_var}")
  10.      endforeach()

  11.      set(${outvar} ${result})
  12. endmacro()

  13. sum(addResult 1 2 3 4 5)
  14. message("Result is :${addResult}")
    而CMake中的函数("function")与宏唯一的区别就在于,函数不能像宏那样将计算结果传出来(也不是完全不能,只是复杂一些),并且函数中的变量是局部的,而宏中的变量在外面也可以被访问到,请看下例:

  1. macro(macroTest)
  2.      set(test1 "aaa")
  3. endmacro()

  4. function(funTest)
  5.      set(test2 "bbb")
  6. endfunction()

  7. macroTest()
  8. message("${test1}")

  9. funTest()
  10. message("${test2}")
运行这段代码后,只会打印出一条信息"aaa",由此可以看到宏与函数的区别。

5. 综合示例

    最后我们来通过一个稍微复杂综合一点的宏来结束本文。下面的这个宏是找出指定数值范围内全部素数,并输出。

  1. macro(GetPrime output maxNum)
  2.      set(extArg ${ARGN})
  3.      if(extArg)
  4.          message(SEND_ERROR "To much args!")
  5.      endif()

  6.      # 没有判断传入的变量是否为数字类型
  7.      set(result)
  8.      foreach(_var RANGE 2 ${maxNum})
  9.          set(isPrime 1)
  10.          math(EXPR upplimit ${_var}-1)
  11.          foreach(_subVar RANGE 2 ${upplimit})
  12.              math(EXPR _temp "${_var}%${_subVar}")
  13.              if(_temp EQUAL 0)
  14.                  set(isPrime 0)
  15.                  break()
  16.              endif()
  17.          endforeach()

  18.          if(isPrime)
  19.              list(APPEND result ${_var})
  20.          endif()
  21.      endforeach()
  22.      set(output ${result})
  23. endmacro()

  24. GetPrime(output 100)
  25. message("${output}")

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值