cmake实践操作记录(cmake practice)

仅供自翻方便,如果是学习,建议参cmake practice原文件

初试 cmake – cmake 的 helloworld

mkdir cmake
cd cmake
mkdir t1
cd t1
touch main.c
touch CMakeLists.txt

  • 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})
  1. cmake . (注意命令后面的点号,代表本目录)生成了 Makefile
  2. make
    如果你需要看到 make 构建的详细过程,可以使用 make VERBOSE=1 或者 VERBOSE=1
    make 命令来进行构建。
  3. ./hello

得到输出:

Hello World from Main

指令解析

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指令可以直接使用了这两个变量)
    因为采用的是内部编译,两个变量目前指的都是工程所在路径~/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 指令可以用来显式的定义变量即可

MESSAGE 指令

MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display"...)

这个指令用于向终端输出用户定义的信息,包含了三种类型

  • SEND_ERROR,产生错误,生成过程被跳过。
  • SATUS ,输出前缀为 — 的信息。
  • FATAL_ERROR,立即终止所有 cmake 过程.

ADD_EXECUTABLE指令

ADD_EXECUTABLE(hello ${SRC_LIST})
  • 生成一个文件名为 hello 的可执行文件,相关的源文件是 SRC_LIST 中定义的源文件列表
  • 本例我们使用了${}来引用变量,这是 cmake 的变量引用方式
  • 但是,有一些例外,比如在 IF 控制语句,变量是直接使用变量名引用,而不需要${}

将本例改写成一个最简化的 CMakeLists.txt:

PROJECT(HELLO)
ADD_EXECUTABLE(hello main.c)

基本语法规则

  1. 变量使用${}方式取值,但是在 IF 控制语句中是直接使用变量名
  2. 指令(参数 1 参数 2…)
    参数使用括弧括起,参数之间使用空格或分号分开。
  3. 指令是大小写无关的,参数和变量是大小写相关的。但,推荐你全部使用大写指令
    补充:假设一个源文件的文件名是 fu nc.c(文件名中间包含了空格)。这时候就必须使用双引号。平时可不用。例子:SET(SRC_LIST “fu nc.c”)

清理工程

跟经典的 autotools 系列工具一样,对构建结果进行清理运行:
make clean

外部构建

  1. 创建build
  2. cmake <工程的全路径>
  • warning: HELLO_SOURCE_DIR 仍然指代工程路径,即 /backup/cmake/t1,而 HELLO_BINARY_DIR 则指代编译路径,即/backup/cmake/t1/build

更好一点的 Hello World

  • 在/backup/cmake/目录下建立 t2 目录。
    将 t1 工程的 main.c 和 CMakeLists.txt 拷贝到 t2 目录中。
mkdir t2
cd t2
  • 添加子目录 src
mkdir src
mv main.c src
  • 进入子目录 src,编写 CMakeLists.txt 如下:
ADD_EXECUTABLE(hello main.c)
  • 将 t2 工程的 CMakeLists.txt 修改为:
PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)

然后建立 build 目录,进入 build 目录进行外部编译。

cmak ..
make

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(dir1 dir2…),但是这个指令已经不推荐使用。它可以一次添加多个子目录,并且,即使外部编译,子目录体系仍然会被保存。

换个地方保存目标二进制

  • 通过 SET 指令重新定义 EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH 变量
    来指定最终的目标二进制的位置(指最终生成的 hello 或者最终的共享库,不包含编译生成
    的中间文件)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

所以,上面两个指令分别定义了:
可执行二进制的输出路径为 build/bin 和库的输出路径为 build/lib.本节我们没有提到共享库和静态库的构建,所以,你可以不考虑第二条指令。

  • 在哪里 ADD_EXECUTABLE 或 ADD_LIBRARY,如果需要改变目标存放路径,就在哪里加入上述的定义
例子:
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR})
ADD_EXECUTABLE(hello main.c)

如何安装

两种

  • 一种是从代码编译后直接 make install 安装
  • 一种是打包时的指定目录安装

CMAKE_INSTALL_PREFIX 变量类似于 configure 脚本的 – prefix,常见的使用方法看
起来是这个样子:

cmake -DCMAKE_INSTALL_PREFIX=/usr

INSTALL 指令用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及
文件、目录、脚本等。

目标文件的安装

参数中的 TARGETS 后面跟的就是我们通过 ADD_EXECUTABLE 或者 ADD_LIBRARY 定义的
目标文件,可能是可执行二进制、动态库、静态库。
目标类型也就相对应的有三种,ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME
特指可执行目标二进制。
DESTINATION 定义了安装的路径,如果路径以/开头,那么指的是绝对路径,这时候
CMAKE_INSTALL_PREFIX 其实就无效了。如果你希望使用 CMAKE_INSTALL_PREFIX 来
定义安装路径,就要写成相对路径,即不要以/开头,那么安装后的路径就是
${CMAKE_INSTALL_PREFIX}/<DESTINATION 定义的路径>

在这里插入图片描述

普通文件的安装

可用于安装一般文件,并可以指定访问权限,文件名是此指令所在路径下的相对路径。如果
默认不定义权限 PERMISSIONS,安装后的权限为:
OWNER_WRITE, OWNER_READ, GROUP_READ,和 WORLD_READ,即 644 权限。

非目标文件的可执行程序安装(比如脚本之类)

跟上面的 FILES 指令使用方法一样,唯一的不同是安装后权限为:
OWNER_EXECUTE, GROUP_EXECUTE, 和 WORLD_EXECUTE,即 755 权限

进行安装

CmakeLists.txt 主目录

PROJECT (HELLO)
ADD_SUBDIRECTORY(src bin)
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/t2)
INSTALL(PROGRAMS runhello.sh DESTINATION bin)
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/t2)

CmakeLists.txt src


ADD_EXECUTABLE(hello main.c)
INSTALL(TARGETS hello RUNTIME  DESTINATION bin)

安装

cmake -DCMAKE_INSTALL_PREFIX=/tmp/t2/usr ..
make
make install

安装结果

./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 ..

静态库与动态库构建

建立共享库

在 t3 目录下建立 CMakeLists.txt,内容如下:

```yaml
PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)
```

在 lib 目录下建立两个源文件 hello.c 与 hello.h
在 lib 目录下建立 CMakeLists.txt

```yaml
SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
```

编译共享库

cmake ..
make

这时,你就可以在 lib 目录得到一个 libhello.so
可以在 lib/CMakeLists.txt 中添加
SET(LIBRARY_OUTPUT_PATH <路径>)来指定一个新的位置。

类型有三种:
SHARED,动态库
STATIC,静态库
MODULE,在使用 dyld 的系统有效,如果不支持 dyld,则被当作 SHARED 对待。
EXCLUDE_FROM_ALL 参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手工构建。

添加静态库

下面我们用这个指令再来添加静态库:

ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})

然后再在 build 目录进行外部编译,我们会发现,静态库根本没有被构建,仍然只生成了
一个动态库。因为 hello 作为一个 target 是不能重名的,所以,静态库构建指令无效。

如果我们把上面的 hello 修改为 hello_static:

ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})

就可以构建一个 libhello_static.a 的静态库了。这种结果显示不是我们想要的,我们需要的是名字相同的静态库和动态库。

在本例中,我们需要作的是向 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.

让我们来检查一下最终的构建结果,我们发现,ibhello.a 已经构建完成,位于
build/lib 目录中,但是 libhello.so 去消失了
在实验的时候发现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

动态库版本号

为了实现动态库版本号,我们仍然需要使用 SET_TARGET_PROPERTIES 指令。
具体使用方法如下:

SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)

安装共享库和头文件

利用上一节了解到的 INSTALL 指令,我们向 lib/CMakeLists.txt 中添加如下指令:

INSTALL(TARGETS hello hello_staticLIBRARY 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 中了。

如何使用外部共享库和头文件

为了让我们的工程能够找到 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)

为了解决我们前面遇到的 HelloFunc 未定义错误,我们需要作的是向
src/CMakeLists.txt 中添加如下指令:

TARGET_LINK_LIBRARIES(main hello)
也可以写成
TARGET_LINK_LIBRARIES(main libhello.so)

进入 build 目录重新进行构建。
cmake …
make
这是我们就得到了一个连接到 libhello 的可执行程序 main,位于 build/src 目录,运
行 main 的结果是输出:
Hello World

那如何链接到静态库呢?
方法很简单:
将 TARGET_LINK_LIBRRARIES 指令修改为:

TARGET_LINK_LIBRARIES(main libhello.a)

特殊的环境变量 CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH

为了将程序更智能一点,我们可以使用 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)

cmake 常用变量和常用环境变量

  1. cmake 变量引用的方式:
    前面我们已经提到了,使用${}进行变量的引用。在 IF 等语句中,是直接使用变量名而不通过${}取值
  2. cmake 自定义变量的方式:
    主要有隐式定义和显式定义两种,前面举了一个隐式定义的例子,就是 PROJECT 指令,他会隐式的定义<projectname>_BINARY_DIR<projectname>_SOURCE_DIR 两个变量。
    显式定义的例子我们前面也提到了,使用 SET 指令,就可以构建一个自定义变量了。比如:
    SET(HELLO_SRC main.SOURCE_PATHc),就 PROJECT_BINARY_DIR 可以通过${HELLO_SRC}来引用这个自定义变量了.
  3. cmake 常用变量:
    1. CMAKE_BINARY_DIR
      PROJECT_BINARY_DIR
      <projectname>_BINARY_DIR.
    2. CMAKE_SOURCE_DIR
      PROJECT_SOURCE_DIR
      <projectname>_SOURCE_DIR
      工程顶层目录
    3. CMAKE_CURRENT_SOURCE_DIR
      指的是当前处理的 CMakeLists.txt 所在的路径,比如上面我们提到的 src 子目录。
    4. CMAKE_CURRRENT_BINARY_DIR
      如果是 in-source 编译,它跟 CMAKE_CURRENT_SOURCE_DIR 一致,如果是 out-of-source 编译,他指的是 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 模块路径设置一下。
    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 中的 2
2,CMAKE_MINOR_VERSION,CMAKE 次版本号,比如 2.4.6 中的 4
3,CMAKE_PATCH_VERSION,CMAKE 补丁等级,比如 2.4.6 中的 6
4,CMAKE_SYSTEM,系统名称,比如 Linux-2.6.22
5,CMAKE_SYSTEM_NAME,不包含版本的系统名,比如 Linux
6,CMAKE_SYSTEM_VERSION,系统版本,比如 2.6.22
7,CMAKE_SYSTEM_PROCESSOR,处理器名称,比如 i686.
8,UNIX,在所有的类 UNIX 平台为 TRUE,包括 OS X 和 cygwin
9,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 的代码。

基本指令

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-target1 depend-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 指令来处理这个 LIST

6,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_VALUE
LS_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
expressions]...)
variable [RELATIVE path] [globbing
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 WIN32
ELSEIF(UNIX)
#do something related to UNIX
ELSEIF(APPLE)
#do something related to APPLE
ENDIF(WIN32)

2, WHILE

WHILE 指令的语法是:
	WHILE(condition)
	COMMAND1(ARGS ...)
	COMMAND2(ARGS ...)
	...
ENDWHILE(condition)
其真假判断条件可以参考 IF 指令。

3,FOREACH

FOREACH 指令的使用方法有三种形式:
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)
最终得到的输出是:
0
1
2
3
4
5
6
7
8
9
10

3,范围和步进
FOREACH(loop_var RANGE start stop [step])
ENDFOREACH(loop_var)
从 start 开始到 stop 结束,以 step 为步进,
举例如下
FOREACH(A RANGE 5 15 3)
MESSAGE(${A})
ENDFOREACH(A)
最终得到的结果是:
5
8
11
14

这个指令需要注意的是,知道遇到 ENDFOREACH 指令,整个语句块才会得到真正的执行。

小结:
本小节基本涵盖了常用的 cmake 指令,包括基本指令、查找指令、安装指令以及控制语句等,特别需要注意的是,在控制语句条件中使用变量,不能用${}引用,而是直接应用变量名。
掌握了以上的各种控制指令,你应该完全可以通过 cmake 管理复杂的程序了,下一节,我们将介绍一个比较复杂的例子,通过他来演示本章的一些指令,并介绍模块的概念。

复杂的例子:模块的使用和自定义模块

使用 FindCURL 模块

建立 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.txt

PROJECT(CURLTEST)
ADD_SUBDIRECTORY(src)

建立 src/CMakeLists.txt

ADD_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 中。

定义 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)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值