cmake详解

1.cmake介绍

你或许听过好几种 Make 工具,例如 GNU Make ,QT 的 qmake ,微软的 MS nmake,BSD Make(pmake),Makepp,等等。这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile ,这将是一件让人抓狂的工作。

CMake 就是针对上面问题所设计的工具:它首先允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。显然,CMake 是一个比上述几种 make 更高级的编译配置工具。一些使用 CMake 作为项目架构系统的知名开源项目有 VTK、ITK、KDE、OpenCV、OSG 等 [1]。

特点

cmake 的特点主要有:
1,开放源代码,使用类 BSD 许可发布。http://cmake.org/HTML/Copyright.html
2,跨平台,并可生成 native 编译配置文件,在 Linux/Unix 平台,生成 makefile,在
苹果平台,可以生成 xcode,在 Windows 平台,可以生成 MSVC 的工程文件。
3,能够管理大型项目,KDE4 就是最好的证明。
4,简化编译构建过程和编译过程。Cmake 的工具链非常简单:cmake+make。
5,高效虑,按照 KDE 官方说法,CMake 构建 KDE4 的 kdelibs 要比使用 autotools 来
构建 KDE3.5.6 的 kdelibs 快 40%,主要是因为 Cmake 在工具链中没有 libtool。
6,可扩展,可以为 cmake 编写特定功能的模块,扩充 cmake 功能。

2.安装

linux下cmake安装:

sudo apt-get install cmake

3.使用

3.1第一个简单的cmake工程(单文件)

在某个文件夹新建 main.cpp 和 CMakeLists.txt。

main.cpp内容如下:

//main.cpp
#include <stdio.h>
int main()
{
printf(“Hello World from t1 Main!\n”);
return 0;
}

CMakeLists.txt内容如下:

# 设置cmake的最低版本,这句语句不能省略
cmake_minimum_required (VERSION 2.8)
# 工程的名称,PROJECT 指令的语法是:
# PROJECT(projectname [CXX] [C] [Java])
# 你可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言。这个指令隐式的定义了两个cmake变量
PROJECT (HELLO)
# set命令表示赋值的意思,下面这句话意思就是将main.cpp赋值给SRC_LIST
SET(SRC_LIST main.cpp)
# message表示打印信息的意思,输出message括号内部的内容,
# ${HELLO_BINARY_DIR}表示此项目生成可执行程序的文件夹
# ${HELLO_SOURCE_DIR}表示此项目源文件所在的文件夹
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir " ${HELLO_SOURCE_DIR})
# ${SRC_LIST} 表示取变量的值的意思,用SRC_LIST生成可执行程序,可执行程序的名称为hello
ADD_EXECUTABLE(hello ${SRC_LIST})

备注:cmake对大小写不敏感,所以上面的用大写书写的语句均可以替换成小写,例如PROJECT (HELLO)可以替换为 project (HELLO)。

关于系统预定义的变量的值 : cmake 系统也帮助我们预定义了 PROJECT_BINARY_DIRPROJECT_SOURCE_DIR
变量,他们的值分别跟 HELLO_BINARY_DIRHELLO_SOURCE_DIR 一致。为了统一起见,建议以后直接使用 PROJECT_BINARY_DIR,PROJECT_SOURCE_DIR,即
使修改了工程名称,也不会影响这两个变量。如果使用了
_SOURCE_DIR,修改工程名称后,需要同时修改这些变量。

SET 指令的语法是:
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
现阶段,你只需要了解 SET 指令可以用来显式的定义变量即可。
比如我们用到的是 SET(SRC_LIST main.c),如果有多个源文件,也可以定义成:SET(SRC_LIST main.c t1.c t2.c)。

MESSAGE 指令的语法是:
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] “message to display”…)
这个指令用于向终端输出用户定义的信息,包含了三种类型:
SEND_ERROR,产生错误,生成过程被跳过。
STATUS,输出前缀为—的信息。
FATAL_ERROR,立即终止所有 cmake 过程.
我们在这里使用的是 STATUS 信息输出,演示了由 PROJECT 指令定义的两个隐式变量HELLO_BINARY_DIR 和 HELLO_SOURCE_DIR。

ADD_EXECUTABLE(hello S R C L I S T ) 定 义 了 这 个 工 程 会 生 成 一 个 文 件 名 为 h e l l o 的 可 执 行 文 件 , 相 关 的 源 文 件 是 S R C L I S T 中 定 义 的 源 文 件 列 表 , 本 例 中 你 也 可 以 直 接 写 成 A D D E X E C U T A B L E ( h e l l o m a i n . c p p ) 。 在 本 例 我 们 使 用 了 {SRC_LIST}) 定义了这个工程会生成一个文件名为 hello 的可执行文件,相关的源文件是 SRC_LIST 中定义的源文件列表, 本例中你也可以直接写成 ADD_EXECUTABLE(hello main.cpp)。 在本例我们使用了 SRCLIST)helloSRCLISTADDEXECUTABLE(hellomain.cpp)使{}来引用变量,这是 cmake 的变量应用方式,但是,有一些例外,比如在 IF 控制语句,变量是直接使用变量名引用,而不需要 。 如 果 使 用 了 {}。如果使用了 使{}去应用变量,其实 IF 会去判断名为${}所代表的值的变量,那当然是不存在的了。

编译

 mkdir build 
 cd build
 cmake ..
 make  # 想查看详细的信息: make VERBOSE=1
 # 可以使用make clean来清理make生成的可执行程序

运行:

./hello

输出:

Hello World from t1 Main!

3.2 一个稍微复杂点的cmake项目

任务:
1,为工程添加一个子目录 src,用来放置工程源代码;
2,添加一个子目录 doc,用来放置这个工程的文档 hello.txt
3,在工程目录添加文本文件 COPYRIGHT, README;
4,在工程目录添加一个 runhello.sh 脚本,用来调用 hello 二进制
4,将构建后的目标文件放入构建目录的 bin 子目录;
5,最终安装这些文件:将 hello 二进制与 runhello.sh 安装至/usr/bin,将 doc 目录
的内容以及 COPYRIGHT/README 安装到/usr/share/doc/cmake/t2

下面我们来实现上面的功能。我的目录结构如下:

demo2/
├── build
├── CMakeLists.txt
├── COPYRIGHT
├── doc
│   └── hello.txt
├── README
├── runhello.sh
└── src
    ├── CMakeLists.txt
    ├── hello.cpp
    └── main.cpp

3 directories, 8 files

每个文件的内容这里不详细说明,具体内容见github链接:https://github.com/honggesmile/Cmake_Practice_Demo

语法解释:
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 目录。

INSTALL命令的用法如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

执行上面的命令以后,/tmp/t2的目录结构如下,tree /tmp/t2/usr/ :


/tmp/t2/usr/
├── bin
│   └── runhello.sh
│   └── hello
└── share
    └── doc
        └── cmake
            └── t2
                ├── COPYRIGHT
                ├── hello.txt
                └── README


3.3 静态库与动态库构建

任务:
1,建立一个静态库和动态库,提供 HelloFunc 函数供其他程序编程使用,HelloFunc
向终端输出 Hello World 字符串。
2,安装头文件与共享库。
目录结构如下:

demo3/
├── CMakeLists.txt
├── include
│   └── hello.h
├── lib
└── src
    └── hello.cpp

3 directories, 3 files

CMakeLists.txt内容如下:

cmake_minimum_required(VERSION 3.4)

PROJECT(HELLOLIB)
# 添加头文件的查找路径
include_directories(${PROJECT_SOURCE_DIR}/include)

#设置动态库的生成路径
SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
SET(LIBHELLO_SRC src/hello.cpp)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})

# 动态库版本号设置, VERSION 指代动态库版本,SOVERSION 指代 API 版本。
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)

# 同一个cmake中不可以同时 用ADD_LIBRARY来生成两个同名的静态库和动态库,所以下面的语句应该被注释掉,可以修改静态库的名字,来同时生成静态库和动态库
# 例如使用下面的语句: ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC}),但是这种不是我们想要的
#ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
#ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})

SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
# 用下面的语句来生成静态库
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")

#安装共享库和头文件
INSTALL(TARGETS hello hello_static
        LIBRARY DESTINATION lib
        ARCHIVE DESTINATION lib)
INSTALL(FILES include/hello.h DESTINATION include/hello)

说明: SET(LIBRARY_OUTPUT_PATH lib)可以设置动态库或者静态库的生成路径
ADD_LIBRARY(hello SHARED L I B H E L L O S R C ) : 将 {LIBHELLO_SRC}): 将 LIBHELLOSRC):{LIBHELLO_SRC}编译成库, 加上SHARED 则编译成动态库,以.so结尾. 如果是STATIC选项,则编译成静态库,以.a结尾. cmake默认编译成静态库(即不加参数默认编译成静态库).

动态库版本号设置:
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
VERSION 指代动态库版本,SOVERSION 指代 API 版本。
编译完成后,我们查看下动态库的版本, ls -l lib/ :

-rw-rw-r-- 1 fuhong fuhong 1662 Dec 14 00:22 libhello.a
lrwxrwxrwx 1 fuhong fuhong   13 Dec 14 00:28 libhello.so -> libhello.so.1
lrwxrwxrwx 1 fuhong fuhong   15 Dec 14 00:28 libhello.so.1 -> libhello.so.1.2
-rwxrwxr-x 1 fuhong fuhong 8128 Dec 14 00:28 libhello.so.1.2

安装到系统:
sudo make install 命令执行以后,可以在终端看到下面的输出:

-- Configuring done
-- Generating done
-- Build files have been written to: /home/fuhong/code/cpp/cmake-demo/Cmake_Practice_Demo/demo3/build
[ 50%] Built target hello
[100%] Built target hello_static
Install the project...
-- Install configuration: ""
-- Up-to-date: /usr/local/lib/libhello.so.1.2
-- Up-to-date: /usr/local/lib/libhello.so.1
-- Up-to-date: /usr/local/lib/libhello.so
-- Up-to-date: /usr/local/lib/libhello.a
-- Installing: /usr/local/include/hello/hello.h

说明头文件和动态库以及静态库都安装到了系统的目录下面去了.

3.4 动静态库的使用

上面我们把生成的动态库和头文件安装到了系统中,下面介绍如何使用.
目录结构如下:

demo4/
├── CMakeLists.txt
└── src
    └── main.cpp

CMakeLists.txt内容如下:

cmake_minimum_required(VERSION 3.4)

project(demo4)

# include_directories添加头文件的搜索路径
include_directories(/usr/local/include/hello)

add_executable(main src/main.cpp)

# 添加库的查找路径
link_directories("/usr/local/lib/")
#链接
target_link_libraries(main /usr/local/lib/libhello.so)
#target_link_libraries(main hello)

src/main.cpp内容如下:

//
// Created by fuhong on 20-12-14.
//

#include <hello.h>

int main() {

    HelloFunc();
    return 0;
}

说明:如果头文件不是安装在系统的默认搜索路径(/usr/local/include或者/usr/include),需要手动添加头文件的搜索路径(或者导入头文件的时候书写绝对路径,一般很少这么做), 对应的命令为:
include_directories(头文件的路径)

如果动态库或者静态库不是安装在系统的默认搜索路径(/usr/local/lib或者/usr/lib),需要手动添加头文件的搜索路径(或者导入头文件的时候书写绝对路径,一般很少这么做), 对应的命令为:
link_directories(库的路径)
然后使用 target_link_libraries(target 动态库或者静态库的名称)来链接库,生成可执行程序.

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

一,cmake 变量引用的方式:
前面我们已经提到了,使用 进 行 变 量 的 引 用 。 在 I F 等 语 句 中 , 是 直 接 使 用 变 量 名 而 不 通 过 {}进行变量的引用。在 IF 等语句中,是直接使用变量名而不通过 IF使{}取值.
二,cmake 自定义变量的方式:
主要有隐式定义和显式定义两种,前面举了一个隐式定义的例子,就是 PROJECT 指令,他会隐式的定义_BINARY_DIR 和_SOURCE_DIR 两个变量。
显式定义的例子我们前面也提到了,使用 SET 指令,就可以构建一个自定义变量了。比如:
SET(HELLO_SRC main.SOURCE_PATHc),就 PROJECT_BINARY_DIR 可以通过${HELLO_SRC}来引用这个自定义变量了.
三,cmake 常用变量:
1,CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
_BINARY_DIR
这三个变量指代的内容是一致的,如果是 in source 编译,指得就是工程顶层目录,如果是 out-of-source 编译,指的是工程编译发生的目录。PROJECT_BINARY_DIR 跟其他指令稍有区别,现在,你可以理解为他们是一致的。
2,CMAKE_SOURCE_DIR
PROJECT_SOURCE_DIR
_SOURCE_DIR
这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。也就是在 in source 编译时,他跟 CMAKE_BINARY_DIR 等变量一致。
PROJECT_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 模块路径设置一下。
比如SET(CMAKE_MODULE_PATH P R O J E C T S O U R C E D I R / c m a k e ) 这 时 候 你 就 可 以 通 过 I N C L U D E 指 令 来 调 用 自 己 的 模 块 了 。 8 , E X E C U T A B L E O U T P U T P A T H 和 L I B R A R Y O U T P U T P A T H 分 别 用 来 重 新 定 义 最 终 结 果 的 存 放 目 录 , 前 面 我 们 已 经 提 到 了 这 两 个 变 量 。 9 , P R O J E C T N A M E 返 回 通 过 P R O J E C T 指 令 定 义 的 项 目 名 称 。 四 , c m a k e 调 用 环 境 变 量 的 方 式 使 用 {PROJECT_SOURCE_DIR}/cmake) 这时候你就可以通过 INCLUDE 指令来调用自己的模块了。 8,EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH 分别用来重新定义最终结果的存放目录,前面我们已经提到了这两个变量。 9,PROJECT_NAME 返回通过 PROJECT 指令定义的项目名称。 四,cmake 调用环境变量的方式 使用 PROJECTSOURCEDIR/cmake)INCLUDEEXECUTABLEOUTPUTPATHLIBRARYOUTPUTPATH9PROJECTNAMEPROJECTcmake使ENV{NAME}指令就可以调用系统的环境变量了。比如
MESSAGE(STATUS “HOME dir: E N V H O M E ” ) 设 置 环 境 变 量 的 方 式 是 : S E T ( E N V 变 量 名 值 ) 1 , C M A K E I N C L U D E C U R R E N T D I R 自 动 添 加 C M A K E C U R R E N T B I N A R Y D I R 和 C M A K E C U R R E N T S O U R C E D I R 到 当 前 处 理 的 C M a k e L i s t s . t x t 。 相 当 于 在 每 个 C M a k e L i s t s . t x t 加 入 : I N C L U D E D I R E C T O R I E S ( ENV{HOME}”) 设置环境变量的方式是:SET(ENV{变量名} 值) 1,CMAKE_INCLUDE_CURRENT_DIR 自动添加 CMAKE_CURRENT_BINARY_DIR 和 CMAKE_CURRENT_SOURCE_DIR 到当前处理 的 CMakeLists.txt。相当于在每个 CMakeLists.txt 加入: INCLUDE_DIRECTORIES( ENVHOME)SET(ENV)1,CMAKEINCLUDECURRENTDIRCMAKECURRENTBINARYDIRCMAKECURRENTSOURCEDIRCMakeLists.txtCMakeLists.txtINCLUDEDIRECTORIES({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()添加

3.6 cmake 常用指令

一,基本指令
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 ]
[OUTPUT_VARIABLE ]
[RETURN_VALUE ])
用于在指定的目录运行某个程序,通过 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 variable [RELATIVE path] [globbing
expressions]…)
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( name1 path1 path2 …)
VAR 变量代表找到的文件全路径,包含文件名
FIND_LIBRARY( name1 path1 path2 …)
VAR 变量表示找到的库全路径,包含库文件名
FIND_PATH( name1 path1 path2 …)
VAR 变量代表包含这个文件的路径。
FIND_PROGRAM( name1 path1 path2 …)
VAR 变量代表包含这个程序的全路径。
FIND_PACKAGE( [major.minor] [QUIET] [NO_MODULE]
[[REQUIRED|COMPONENTS] [componets…]])
用来调用预定义在 CMAKE_MODULE_PATH 下的 Find.cmake 模块,你也可以自己
定义 Find模块,通过 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 或
_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 S R C L I S T ) M E S S A G E ( {SRC_LIST}) MESSAGE( SRCLIST)MESSAGE({F})
ENDFOREACH(F)
2,范围
FOREACH(loop_var RANGE total)
ENDFOREACH(loop_var)
从 0 到 total 以1为步进
举例如下:
FOREACH(VAR RANGE 10)
MESSAGE( V A R ) E N D F O R E A C H ( V A R ) 最 终 得 到 的 输 出 是 : 012345678910 3 , 范 围 和 步 进 F O R E A C H ( l o o p v a r R A N G E s t a r t s t o p [ s t e p ] ) E N D F O R E A C H ( l o o p v a r ) 从 s t a r t 开 始 到 s t o p 结 束 , 以 s t e p 为 步 进 , 举 例 如 下 F O R E A C H ( A R A N G E 5153 ) M E S S A G E ( {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( VAR)ENDFOREACH(VAR)012345678910FOREACH(loopvarRANGEstartstop[step])ENDFOREACH(loopvar)startstopstepFOREACH(ARANGE5153)MESSAGE({A})
ENDFOREACH(A)
最终得到的结果是:
5
8
11
14
这个指令需要注意的是,知道遇到 ENDFOREACH 指令,整个语句块才会得到真正的执行。
小结:
本小节基本涵盖了常用的 cmake 指令,包括基本指令、查找指令、安装指令以及控制语句
等,特别需要注意的是,在控制语句条件中使用变量,不能用${}引用,而是直接应用变量
名。
掌握了以上的各种控制指令,你应该完全可以通过 cmake 管理复杂的程序了,下一节,我
们将介绍一个比较复杂的例子,通过他来演示本章的一些指令,并介绍模块的概念。

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

其实使用纯粹依靠 cmake 本身提供的基本指令来管理工程是一件非常复杂的事情,所以,
cmake 设计成了可扩展的架构,可以通过编写一些通用的模块来扩展 cmake.
在本章,我们准备首先介绍一下 cmake 提供的 FindCURL 模块的使用。然后,基于我们前
面的 libhello 共享库,编写一个 FindHello.cmake 模块。

目录结构:

demo6/
├── CMakeLists.txt
└── src
    └── main.cpp

CMakeLists.txt内容如下:

cmake_minimum_required(VERSION 3.4)

PROJECT(curl)
# find_package()命令会查找Find_<>.cmake文件
FIND_PACKAGE(CURL)
ADD_EXECUTABLE(curltest src/main.cpp)
IF(CURL_FOUND)
 INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR})
 TARGET_LINK_LIBRARIES(curltest ${CURL_LIBRARY})
 MESSAGE(STATUS "CURL library found")
 MESSAGE(STATUS "${CURL_INCLUDE_DIR}: " ${CURL_INCLUDE_DIR})
ELSE(CURL_FOUND)
 MESSAGE(FATAL_ERROR ”CURL library not found”)
ENDIF(CURL_FOUND)

附录:

1.系统预定义的变量及设置一些常见的路径

PROJECT_BINARY_DIR:生成二进制可执行程序的目录,即cmake 指令运行的目录
PROJECT_SOURCE_DIR:工程源文件所在的目录,一般为存放CMakeLists.txt所在的目录。
EXECUTABLE_OUTPUT_PATH
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
我们都可以通过 SET 指令重新定义 EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH 变量来指定最终的目标二进制和动静态库的位置(指最终生成的 hello 或者最终的共享库,不包含编译生成
的中间文件)
LIBRARY_OUTPUT_PATH
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

CMAKE_INSTALL_PREFIX: make install时候的安装路径
CMAKE_INSTALL_PREFIX 的默认定义是/usr/local

特殊的环境变量 CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH:
务必注意,这两个是环境变量而不是 cmake 变量。
使用方法是要在 bash 中用 export 或者在 csh 中使用 set 命令设置或者
CMAKE_INCLUDE_PATH=/home/include cmake …等方式。
也就是,如果头文件没有存放在常规路径(/usr/include, /usr/local/include 等),
则可以通过这些变量就行弥补。

FIND_PATH 用来在指定路径中搜索文件名,比如:
FIND_PATH(myHeader NAMES hello.h PATHS /usr/include
/usr/include/hello)
这里我们没有指定路径,但是,cmake 仍然可以帮我们找到 hello.h 存放的路径,就是因
为我们设置了环境变量 CMAKE_INCLUDE_PATH。

将所有的cpp程序生成可执行程序的代码

aux_source_directory(. files)
foreach (file ${files})
    string(REGEX MATCH "[A-Za-z0-9_]+" exe ${file})
    message(${exe})
    add_executable(${exe} ${file})
    target_link_libraries(${exe} ${Boost_LIBRARIES})
endforeach ()

参考链接:https://www.hahack.com/codes/cmake/
书籍<<Cmake 实践>>

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值