CMake学习笔记

本文深入解析CMake工具,涵盖基本指令、查找与安装指令、控制语句等,通过实例展示复杂工程构建、静态与动态库管理及自定义模块使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

==###### ==cmake:是跨平台的编译工具,采用自动化的项目构建工具cmake 可以将程序员从复杂的makefile 文件中解脱出来。cmake 根据内置的规则和语法来自动生成相关的makefile 文件进行编译,同时还支持静态库和动态库的构建,主要用来编写CMakeLists.txt文件,然后用cmake将CMakeLists.txt文件转化为make需要的makefile,最后用make编译源码生成可执行程序或共享库(so)。先cmake,在make。

cmake…表示CMakeLists.txt在上一级目录,
当前目录执行cmake .
由于cmake会生成很多中间文件,最好先建个目录
mkdir build
cd build
cmake ..
make

文件目录结构:
├── build
├── CMakeLists.txt
├── include
│   └── b.h
└── src
    ├── b.c
    └── main.c

make程序通过确保修改所影响的所有文件在需要时进行重新编译来解决所有这些问题。
makefile文件通常与工程的源文件位于同一个目录中。一个makefile文件由一组依赖与规则组成。一个依赖具有一个目标(将要创建的文件)以及他所依赖的源文件集合。规则(规则由本可以在命令行输入的简单命令组成)描述了由依赖文件如何创建目标文件。通常,目标文件是一个可执行文件。

CMakeLists.txt格式

1.同一目录,多个源文件
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo2)
# 指定生成目标
add_executable(Demo main.cc MathFunctions.cc)

aux_source_directory(<dir> <variable>):该命令会查找指定目录下的所有源文件,然后将结果存进指定变量名。

#查找当前目录下的所有源文件
#并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(Demo ${DIR_SRCS})
这样,CMake 会将当前目录所有源文件的文件名赋值给变量 DIR_SRCS ,再指示变量 DIR_SRCS 中的源文件需要编译成一个名称为 Demo 的可执行文件。
2.多个目录,多个源文件

./Demo3
    |
    +--- main.cc
    |
    +--- math/
          |
          +--- MathFunctions.cc
          |
          +--- MathFunctions.h

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo3)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 添加 math 子目录
add_subdirectory(math)
# 指定生成目标 
add_executable(Demo main.cc)
# 添加链接库
target_link_libraries(Demo MathFunctions)


第3行,使用命令 add_subdirectory 指明本项目包含一个子目录 math,这样 math 目录下的 CMakeLists.txt 文件和源代码也会被处理 。第6行,使用命令 target_link_libraries 指明可执行文件 main 需要连接一个名为 MathFunctions 的链接库 。

子目录中的 CMakeLists.txt:
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
aux_source_directory(. DIR_LIB_SRCS)
# 生成链接库
add_library (MathFunctions ${DIR_LIB_SRCS})
在该文件中使用命令 add_library 将 src 目录中的源文件编译为静态链接库。

1.简单实例

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


PROJECT(projectname [CXX] [C] [Java])
你可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的, 默认情况表示支持所有语言。这个指令隐式的定义了两个 cmake 变量: <projectname>_BINARY_DIR 以及<projectname>_SOURCE_DIR

MESSAGE 指令的语法是:
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display"
...) 这个指令用于向终端输出用户定义的信息,包含了三种类型: SEND_ERROR,产生错误,生成过程被跳过。 SATUS,输出前缀为—的信息。
FATAL_ERROR,立即终止所有 cmake 过程.

ADD_EXECUTABLE(hello ${SRC_LIST})
定义了这个工程会生成一个文件名为 hello 的可执行文件,相关的源文件是 SRC_LIST 中
定义的源文件列表, 本例中你也可以直接写成ADD_EXECUTABLE
(hello main.c)。


1,变量使用${}方式取值,但是在 IF 控制语句中是直接使用变量名
2,指令(参数 1 参数 2...)
参数使用括弧括起,参数之间使用空格或分号分开。
以上面的 ADD_EXECUTABLE 指令为例,如果存在另外一个 func.c 源文件,就要写成:
ADD_EXECUTABLE(hello main.c func.c)或者
ADD_EXECUTABLE(hello main.c;func.c)
3,指令是大小写无关的,参数和变量是大小写相关的。但,推荐你全部使用大写指令

CMake 允许为项目增加编译选项,从而可以根据用户的环境和需求选择最合适的编译方案。

cmake 编写的过程实际上是编程的过程,跟以前使用 autotools 一样,不过你需要编
写的是 CMakeLists.txt(每个目录一个),使用的是”cmake 语言和语法”。

内部编译上面已经演示过了,它生成了一些无法自动删除的中间文件,所以, 引出了我们对外部编译的探讨,外部编译的过程如下:

1,首先,请清除t1目录中除main.c CmakeLists.txt之外的所有中间文件,最关键 的是 CMakeCache.txt。
2,在t1目录中建立build 目录,当然你也可以在任何地方建立build目录,不一定必 须在工程目录中。
3,进入build目录,运行cmake ..(注意,..代表父目录,因为父目录存在我们需要的 CMakeLists.txt,如果你在其他地方建立了build目录,需要运行cmake <工程的全 路径>),查看一下 build 目录,就会发现了生成了编译需要的 Makefile 以及其他的中间 文件.
4,运行 make 构建工程,就会在当前目录(build 目录)
中获得目标文件 hello。

上述过程就是所谓的 out-of-source 外部编译,一个最大的好处是,对于原有的工程没 有任何影响,所有动作全部发生在编译目录。通过这一点,也足以说服我们全部采用外部编 译方式构建工程。
通过外部编译进行工程构建,HELLO_SOURCE_DIR 仍然指代工程路径,即
/backup/cmake/t1
而 HELLO_BINARY_DIR 则指代编译路径,即/backup/cmake/t1/build

2.复杂工程

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


ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
这个指令用于向当前工程添加存放源文件的子目录,
并可以指定中间二进制和目标二进制存 放的位置。
EXCLUDE_FROM_ALL 参数的含义是将这个目录从编译过程中排除,
比如,工程 的 example,可能就需要工程构建完成后,
再进入 example 目录单独进行构建(当然,你 也可以通过定义依赖来解决此类问题)。

指令写在工程的 CMakeLists.txt 还是 src 目录下的 CMakeLists.txt,把握一个简单的原则,
在哪里 ADD_EXECUTABLE 或 ADD_LIBRARY, 如果需要改变目标存放路径,就在哪里加入上述的定义。


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


        INSTALL(TARGETS targets...
            [[ARCHIVE|LIBRARY|RUNTIME]
                        [DESTINATION <dir>]
                        [PERMISSIONS permissions...]
                        [CONFIGURATIONS
          [Debug|Release|...]]
                        [COMPONENT <component>]
                        [OPTIONAL]
                       ] [...])
参数中的 TARGETS 后面跟的就是我们通过 ADD_EXECUTABLE 或者 ADD_LIBRARY 定义的 目标文件,可能是可执行二进制、动态库、静态库。
目标类型也就相对应的有三种,ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME 特指可执行目标二进制。
三.静态库与动态库

我们写的代码怎么和别人写的库集成在一起?
一种方式是在生成可执行文件的时候,把printf函数相关的二进制指令和数据包含在最终的可执行文件中,这就是静态链接;另外一种方式是在程序运行的时候,再去加载printf函数相关的二进制指令和数据,这就是动态链接。每个源文件都会首先被编译成目标文件,每个目标文件都提供一些别的目标文件需要的函数或者数据,同时又从别的目标文件中获得一些函数或者数据。因此,链接的过程就是目标文件间互通有无的过程。静态链接是在生成可执行文件的时候把需要的所有内容都包含在了可执行文件中,这导致的问题是可执行文件大,浪费磁盘和内存空间以及静态库升级的问题。动态链接是在程序运行的时候完成链接的,首先是动态链接器被加载到内存中,然后动态链接器再完成类似于静态链接器的所做的事情。
linux上动态库一般的后缀后为.so
静态库一般的后缀为.a
动态库相对于静态库的优势

因为静态链接会将链接库编译进程序里的原因,所以占用就要大了
出于这种原因,静态库不易于维护与更新,如果链接库中有实现有bug等需要更新则需要更新整个程序,因为静态库被编译进程序中了
但动态库就没有这种情况了,因为动态库是程序运行时动态加载的,所以我们只需要更新动态库而不需要更新所有依赖该库的程序(软件)
另一方面,很多程序的开发都会使用到相同的链接库,也就是很多程序(软件)会有相同的依赖
如果将这些依赖全部静态编译的话将会造成存储资源占用过多而造成资源浪费
而使用动态库的方式这些程序(软件)则可以共享一个链接库,而不需要每个程序都带一个链接库,这样就大大地减少了存储资源占用空间

project
    |-- platforms
        |-- win
            |-- DBRx86.lib
            |-- DynamsoftBarcodeReaderx86.dll
        |-- linux
            |-- libDynamsoftBarcodeReader.so
        |-- macos
            |-- libDynamsoftBarcodeReader.dylib
    |-- include
        |-- DynamsoftBarcodeReader.h
    |-- BarcodeReader.cxx
    |-- BarcodeReaderConfig.h.in
    |-- CMakeLists.txt



指令 ADD_LIBRARY
        ADD_LIBRARY(libname    [SHARED|STATIC|MODULE]
          [EXCLUDE_FROM_ALL]
                source1 source2 ... sourceN)
你不需要写全 libhello.so,只需要填写 hello 即可,cmake 系统会自动为你生成 libhello.X
类型有三种:
SHARED,动态库
STATIC,静态库
MODULE,在使用 dyld 的系统有效,如果不支持 dyld,则被当作 SHARED 对待。
四. 引入头文件搜索路径

通过 INCLUDE_DIRECTORIES 指令加入非标准的头文件搜索路径

通过 LINK_DIRECTORIES 指令加入非标准的库文件搜索路径。

通过 TARGET_LINK_LIBRARIES 为库或可执行二进制加入库链接。 并解释了如果链接到静态库。

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)


5.为 target 添加共享库
我们现在需要完成的任务是将目标文件链接到 libhello,这里我们需要引入两个新的指令 LINK_DIRECTORIES 和 TARGET_LINK_LIBRARIES
LINK_DIRECTORIES 的全部语法是:
LINK_DIRECTORIES(directory1 directory2 ...)
这个指令非常简单,添加非标准的共享库搜索路径,比如,在工程内部同时存在共享库和可
执行二进制,在编译时就需要指定一下这些共享库的路径。这个例子中我们没有用到这个指
令。
TARGET_LINK_LIBRARIES 的全部语法是: TARGET_LINK_LIBRARIES(target library1
                      <debug | optimized> library2
...)
这个指令可以用来为 target 添加需要链接的共享库,本例中是一个可执行文件,但是同样可以用于为自己编写
的共享库添加共享库链接。 为了解决我们前面遇到的 HelloFunc 未定义错误,我们需要作的是向
src/CMakeLists.txt 中添加如下指令: TARGET_LINK_LIBRARIES(main hello)
也可以写成
TARGET_LINK_LIBRARIES(main libhello.so) 这里的 hello 指的是我们上一节构建的共享库 libhello.
进入 build 目录重新进行构建。
cmake ..
make
这是我们就得到了一个连接到 libhello 的可执行程序 main,位于 build/src 目录,运 行 main 的结果是输出:
Hello World
让我们来检查一下 main 的链接情况: ldd src/main
        linux-gate.so.1 =>  (0xb7ee7000)
        libhello.so.1 => /usr/lib/libhello.so.1 (0xb7ece000)
        libc.so.6 => /lib/libc.so.6 (0xb7d77000)
        /lib/ld-linux.so.2 (0xb7ee8000)
可以清楚的看到 main 确实链接了共享库 libhello,而且链接的是动态库 libhello.so.1
那如何链接到静态库呢?
方法很简单:
将 TARGET_LINK_LIBRRARIES 指令修改为: TARGET_LINK_LIBRARIES(main libhello.a)
重新构建后再来看一下 main 的链接情况 ldd src/main
        linux-gate.so.1 =>  (0xb7fa8000)
        libc.so.6 => /lib/libc.so.6 (0xb7e3a000)
        /lib/ld-linux.so.2 (0xb7fa9000)
说明,main 确实链接到了静态库 libhello.a

这里简单说明一下,FIND_PATH 用来在指定路径中搜索文件名,比如:
FIND_PATH(myHeader NAMES hello.h PATHS /usr/include
/usr/include/hello)
这里我们没有指定路径,但是,cmake 仍然可以帮我们找到 hello.h 存放的路径,就是因 为我们设置了环境变量 CMAKE_INCLUDE_PATH。
如果你不使用 FIND_PATH,CMAKE_INCLUDE_PATH 变量的设置是没有作用的,你不能指 望它会直接为编译器命令添加参数-I<CMAKE_INCLUDE_PATH>。
以此为例,CMAKE_LIBRARY_PATH 可以用在 FIND_LIBRARY 中。
同样,因为这些变量直接为 FIND_指令所使用,所以所有使用 FIND_指令的 cmake 模块都 会受益。

三. cmake常用变量和常用环境变量
1. cmake 常用变量: 1,CMAKE_BINARY_DIR
   PROJECT_BINARY_DIR
   <projectname>_BINARY_DIR
这三个变量指代的内容是一致的,如果是in source编译,指得就是工程顶层目录,如果 是 out-of-source 编译,指的是工程编译发生的目录。PROJECT_BINARY_DIR 跟其他 指令稍有区别,现在,你可以理解为他们是一致的。
2,CMAKE_SOURCE_DIR PROJECT_SOURCE_DIR <projectname>_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 ${PROJECT_SOURCE_DIR}/cmake) 这时候你就可以通过 INCLUDE 指令来调用自己的模块了。
8,EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH 分别用来重新定义最终结果的存放目录,前面我们已经提到了这两个变量。
9,PROJECT_NAME
返回通过 PROJECT 指令定义的项目名称。

2. 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 我们在上一节已经提及。

3,系统信息
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

4,主要的开关选项:
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常用指令
1.基本指令
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  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 语句是直接执行。
2.INSTALL 指令
INSTALL 系列指令已经在前面的章节有非常详细的说明,这里不在赘述,可参考前面的安
装部分。 

3. 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)

4. 控制指令:
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 管理复杂的程序了,下一节,我 们将介绍一个比较复杂的例子,通过他来演示本章的一些指令,并介绍模块的概念。

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

你现在还会觉得 cmake 简单吗?
本章我们将着重介绍系统预定义的 Find 模块的使用以及自己编写 Find 模块,系统中提供 了其他各种模块,
一般情况需要使用 INCLUDE 指令显式的调用,FIND_PACKAGE 指令是一 个特例,可以直接调用预定义的模块。
其实使用纯粹依靠 cmake 本身提供的基本指令来管理工程是一件非常复杂的事情,所以, cmake
设计成了可扩展的架构,可以通过编写一些通用的模块来扩展 cmake.
在本章,我们准备首先介绍一下 cmake 提供的 FindCURL 模块的使用。然后,基于我们前 面的 libhello 共享库,编写一个 FindHello.cmake 模块。




一,使用 FindCURL 模块
在/backup/cmake 目录建立 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 中。
我们再来看一个复杂的例子,通过<name>_FOUND 来控制工程特性: SET(mySources viewer.c)
SET(optionalSources)

SET(optionalLibs)
FIND_PACKAGE(JPEG)
IF(JPEG_FOUND)
   SET(optionalSources ${optionalSources} jpegview.c)
   INCLUDE_DIRECTORIES( ${JPEG_INCLUDE_DIR} )
   SET(optionalLibs ${optionalLibs} ${JPEG_LIBRARIES} )
   ADD_DEFINITIONS(-DENABLE_JPEG_SUPPORT)
ENDIF(JPEG_FOUND)
IF(PNG_FOUND)
   SET(optionalSources ${optionalSources} pngview.c)
   INCLUDE_DIRECTORIES( ${PNG_INCLUDE_DIR} )
   SET(optionalLibs ${optionalLibs} ${PNG_LIBRARIES} )
   ADD_DEFINITIONS(-DENABLE_PNG_SUPPORT)
ENDIF(PNG_FOUND)
ADD_EXECUTABLE(viewer ${mySources} ${optionalSources} ) TARGET_LINK_LIBRARIES(viewer ${optionalLibs} 通过判断系统是否提供了 JPEG 库来决定程序是否支持 JPEG 功能。
二,编写属于自己的 FindHello 模块。
我们在此前的 t3 实例中,演示了构建动态库、静态库的过程并进行了安装。
接下来,我们在 t6 示例中演示如何自定义 FindHELLO 模块并使用这个模块构建工程:
请在建立/backup/cmake/中建立 t6 目录,并在其中建立 cmake 目录用于存放我们自己 定义的 FindHELLO.cmake 模块,同时建立 src 目录,用于存放我们的源文件。
1,定义 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)
针对上面的模块让我们再来回顾一下 FIND_PACKAGE 指令: FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE]
[[REQUIRED|COMPONENTS] [componets...]])
前面的 CURL 例子中我们使用了最简单的 FIND_PACKAGE 指令,其实他可以使用多种参数,
QUIET参数,对应与我们编写的FindHELLO中的 HELLO_FIND_QUIETLY,如果不指定 这个参数,就会执行:
MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}")
REQUIRED 参数,其含义是指这个共享库是否是工程必须的,如果使用了这个参数,说明这 个链接库是必备库,如果找不到这个链接库,则工程不能编译。对应于 FindHELLO.cmake模块中的 HELLO_FIND_REQUIRED变量。
同样,我们在上面的模块中定义了 HELLO_FOUND, HELLO_INCLUDE_DIR,HELLO_LIBRARY 变量供开发者在 FIND_PACKAGE 指令中使用。
OK,下面建立 src/main.c,内容为: #include <hello.h>
int main()
{
     HelloFunc();
return 0; }
建立 src/CMakeLists.txt 文件,内容如下: FIND_PACKAGE(HELLO)
IF(HELLO_FOUND)
    ADD_EXECUTABLE(hello main.c)
    INCLUDE_DIRECTORIES(${HELLO_INCLUDE_DIR})
    TARGET_LINK_LIBRARIES(hello ${HELLO_LIBRARY})
ENDIF(HELLO_FOUND)
为了能够让工程找到 FindHELLO.cmake 模块(存放在工程中的 cmake 目录) 我们在主工程文件 CMakeLists.txt 中加入:
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

三,使用自定义的 FindHELLO 模块构建工程
仍然采用外部编译的方式,建立 build 目录,进入目录运行:
cmake ..
我们可以从输出中看到:
Found Hello: /usr/lib/libhello.so
如果我们把上面的FIND_PACKAGE(HELLO)修改为FIND_PACKAGE(HELLO QUIET),则 不会看到上面的输出。
接下来就可以使用 make 命令构建工程,运行: ./src/hello 可以得到输出
Hello World。
说明工程成功构建。
四,如果没有找到 hello library 呢?
我们可以尝试将/usr/lib/libhello.x 移动到/tmp 目录,这样,按照 FindHELLO 模块
的定义,就找不到 hello library 了,我们再来看一下构建结果:
cmake ..
仍然可以成功进行构建,但是这时候是没有办法编译的。
修改FIND_PACKAGE(HELLO)为FIND_PACKAGE(HELLO REQUIRED),将hello library 定义为工程必须的共享库。
这时候再次运行cmake ..
我们得到如下输出:
CMake Error: Could not find hello library.
因为找不到 libhello.x,所以,整个 Makefile 生成过程被出错中止。
小结:
在本节中,我们学习了如何使用系统提供的 Find<NAME>模块并学习了自己编写
Find<NAME>模块以及如何在工程中使用这些模块。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值