CMake

Cmake

CMake是一个 跨平台 的安装(编译)工具,可以用简单的语句来描述所有平台的安装 (编译过程)。

cmake 的学习过程就是实践过程,没有实践,读的再多几天后也会忘记。如果你没有实际的项目需求,那么暂时不必开始学习。

# 进入linux服务器上存放文件的位置
cd /usr/local/src
# 上传
scp cmake-3.21.4.tar.gz root@192.168.159.8:/usr/local/src
# 解压
tar xzvf cmake-3.21.4.tar.gz
# 进入目录
cd cmake-3.21.4
# 配置
./bootstrap --prefix=/usr/local/cmake
# 编译
make
# 安装
make install

# 添加环境变量
vi /etc/profile   
# 在文件末尾追加一下代码:
PATH=/usr/local/cmake/bin:$PATH
export PATH

# 使修改生效 
source /etc/profile   
# 查看PATH值
echo $PATH  

# 查看版本(检验)
cmake --version

# 删除文件 
rm -rf cmake-3.21.4-linux-x86_64.tar.gz

例一:cmake的Hello World!

学习每一样新技术总是从最著名的helloworld开始,cmake也不例外

准备工作

# 在用户目录下新建练习目录
cd /home/ubuntu/cmake
mkdir t1
cd t1
#注意文件名大小写
touch main.c CMakeLists.txt  
// main.c
#include <stdio.h>
int main()
{
printf(“Hello World!\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)

# 最简写法
PROJECT(HELLO)
ADD_EXECUTABLE(hello main.c)

开始构建

在t1目录下运行 cmake .

​ 目录下自动生成了CMakeFiles, CMakeCache.txt, cmake_install.cmake 等文件,并且生成了 Makefile。 不需要理会这些文件的作用,最关键的是,它自动生成了 Makefile.

当前目录下,尝试运行一下: ./hello 得到输出: Hello World!

语法解释

PROJECT 指令的语法是:PROJECT(projectname [CXX] [C] [Java])

可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的, 默认情况表示支持所有语言。

这个指令隐式的定义了两个 cmake 变量:

  • <projectname>_BINARY_DIR
  • <projectname>_SOURCE_DIR

​ 例子中就是HELLO_BINARY_DIR 和 HELLO_SOURCE_DIR(所以 CMakeLists.txt 中两个 MESSAGE指令可以直接使用了这两个变量),因为采用的是内部编译,两个变量目前指的都是工程所在路径/home/ubuntu/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 指令可以用来显式的定义变量即可。 比如我们用到的是 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,产生错误,生成过程被跳过
  • SATUS,输出前缀唯一的信息
  • FATAL_ERROR,立即终止所有 cmake 过程

ADD_EXECUTABLE(hello ${SRC_LIST})

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

基本语法

cmake 其实仍然要使用”cmake 语言和语法”去构建,最简单的语法规则是:

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

注意:

  1. cmake 的语法还是比较灵活而且考虑到各种情况,比如 SET(SRC_LIST main.c)也可以写成 SET(SRC_LIST “main.c”) 是没有区别的,但是假设一个源文件的文件名是 fu nc.c(文件名中间包含了空格)。 这时候就必须使用双引号,如果写成了 SET(SRC_LIST fu nc.c),就会出现错误,提示 你找不到 fu 文件和 nc.c 文件。这种情况,就必须写成: SET(SRC_LIST “fu nc.c”)
  2. 此外,你可以可以忽略掉 source 列表中的源文件后缀,比如可以写成 ADD_EXECUTABLE(t1 main),cmake 会自动的在本目录查找 main.c 或者 main.cpp 等,当然,最好不要偷这个懒,以免这个目录确实存在一个 main.c 一个 main.

清理工程

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

因为 CMakeLists.txt 可以执行脚本并通过脚本生成一些临时文件,但是却没有办法来跟 踪这些临时文件到底是哪些。因此,cmake 并不支持 make distclean

例二:升级版的Hello World!

从本例开始,后面所有的构建我们都将采用 out-of-source 外部构建,约定的构建目录是工程目录下的 build 自录。

外部构建

​ 对于 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 外部编译,一个最大的好处是,对于原有的工程没有任何影响,所有动作全部发生在编译目录。通过这一点,足以说服我们全部采用外部编译方式构建工程。

准备与构建

# 在上例cmake目录下
mkdir t2
cp t1/main.c t1/CMakeLists.txt t2
cd t2
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 目录进行外部编译。
cmake .. 
make 
#构建完成后,你会发现生成的目标文件 hello 位于 build/bin 目录中。

语法解释

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 指令,使用方法是: SUBDIRS(dir1 dir2…),但是这个指令已经不推荐使用。它可以一次添加多个子目录, 并且,即使外部编译,子目录体系仍然会被保存。 如果在上面的例子中将 ADD_SUBDIRECTORY (src bin)修改为 SUBDIRS(src)。 那么在 build 目录中将出现一个 src 目录,生成的目标代码 hello 将存放在 src 目录中。

换个地方保存目标二进制

​ 不论是 SUBDIRS 还是 ADD_SUBDIRECTORY 指令(不论是否指定编译输出目录),我们都可以通过 SET 指令重新定义 EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH 变量来指定最终的目标二进制的位置(指最终生成的 hello 或者最终的共享库,不包含编译生成 的中间文件)

SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) 
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) 

​ 在例一提到了_BINARY_DIR 和 PROJECT_BINARY_DIR 变量,他们指的编译发生的当前目录,如果是内部编译,就相当于 PROJECT_SOURCE_DIR 也就是工程代码所在目录,如果是外部编译,指的是外部编译所在目录,也就是本例中的 build 目录。 所以,上面两个指令分别定义了:可执行二进制的输出路径为 build/bin 和库的输出路径为 build/lib. 本节我们没有提到共享库和静态库的构建,所以,你可以不考虑第二条指令。

​ 问题是,应该把这两条指令写在工程的 CMakeLists.txt 还是 src 目录下的 CMakeLists.txt,把握一个简单的原则,在哪里 ADD_EXECUTABLE 或 ADD_LIBRARY, 如果需要改变目标存放路径,就在哪里加入上述的定义。 在这个例子里,当然就是指 src下的 CMakeLists.txt了。

修改 Helloworld 支持安装

cmake提供了添加make install目标的功能,以允许用户安装二进制文件、库和其他文件

  • 基本安装位置由变量CMAKE_INSTALL_PREFIX控制
  • 该变量通过使用cmake .. -DCMAKE_INSTALL_PREFIX=/install/location调用cmake来设置。

在cmake的时候,最常见的几个步骤就是:

mkdir build && cd build
cmake ..
make
make install

​ 作为一个经常需要被运行的指令,官方提供了一个命令install,只需要经过该命令的安装内容,不需要显示地定义install目标。此时,make install就是运行该命令的内容。

​ install用于指定在安装时运行的规则。它可以用来安装很多内容,可以包括目标二进制、动态库、静态库以及文件、目录、脚本等:

# 首先我们先补上为添加的文件。
# 添加 doc 目录及文件:
cd /backup/cmake/t2
mkdir doc
vi doc/hello.txt
# 随便填写一些内容并保存
# 在工程目录添加 runhello.sh 脚本,内容为:
hello
# 添加工程目录中的 COPYRIGHT 和 README
touch COPYRIGHT
touch README
# 下面改写各目录的 CMakeLists.txt 文件。
# 1,安装 COPYRIGHT/README,直接修改主工程文件 CMakelists.txt,加入以下指令:
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/t2)
# 2,安装 runhello.sh,直接修改主工程文件 CMakeLists.txt,加入如下指令:
INSTALL(PROGRAMS runhello.sh DESTINATION bin)
# 3,安装 doc 中的 hello.txt,这里有两种方式:一是通过在 doc 目录建立
# CMakeLists.txt 并将 doc 目录通过 ADD_SUBDIRECTORY 加入工程来完成。另一种方法
# 是直接在工程目录通过
# INSTALL(DIRECTORY 来完成),前者比较简单,各位可以根据兴趣自己完成,我们来尝试
# 后者,顺便演示以下 DIRECTORY 的安装。
# 因为 hello.txt 要安装到/<prefix>/share/doc/cmake/t2,所以我们不能直接安装
# 整个 doc 目录,这里采用的方式是安装 doc 目录中的内容,也就是使用”doc/”
# 在工程文件中添加
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/t2)

​ 现在进入 build 目录进行外部编译,注意使用 CMAKE_INSTALL_PREFIX 参数,这里将它安装到了/tmp/t2 目录: cmake -DCMAKE_INSTALL_PREFIX=/tmp/t2/usr … 然后运行 make make install

# 进入/tmp/t2 目录看一下安装结果:
./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 ..

例三:Hello World!的共享库

准备工作

在/backup/cmake 目录建立 t3 目录,用于存放本例涉及到的工程

cd /usr/ubuntu/cmake/t3
mkdir lib
# 在 lib 目录下建立两个源文件 hello.c 与 hello.h
// hello.c 内容如下:
#include “hello.h”
void HelloFunc()
{
printf(“Hello World\n”);
}
// hello.h 内容如下:
#ifndef HELLO_H
#define HELLO_H
#include <stdio.h>
void HelloFunc();
#endif
#在 t3 目录下建立 CMakeLists.txt,内容如下:
PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)
# 在 lib 目录下建立 CMakeLists.txt,内容如下:
SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})

编译共享库

仍然采用 out-of-source 编译的方式,按照习惯,建立一个 build 目录,在 build 目录中

cmake ..
make

​ 这时,就可以在lib目录得到一个 libhello.so,这就是期望的共享库。 如果你要指定 libhello.so 生成的位置,可以通过在主工程文件 CMakeLists.txt 中修 改 ADD_SUBDIRECTORY(lib)指令来指定一个编译输出位置或者 在 lib/CMakeLists.txt 中添加,SET(LIBRARY_OUTPUT_PATH <路径>)来指定一个新的位置。

解释一下一个新的 指令 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 对待。

EXCLUDE_FROM_ALL 参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手 工构建。

添加静态库

​ 使用上面同样的命令,在支持动态库的基础上再为工程添加一个静态库,按照一般的习惯,静态库名字跟动态库名字应该是一致的,只不过后缀是.a 罢了。

# 在 lib 目录下CMakeLists.txt加入:
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})

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

# SET_TARGET_PROPERTIES,其基本语法是:
SET_TARGET_PROPERTIES(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)
# 这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和 API 版本。
# 在本例中,我们需要作的是向 lib/CMakeLists.txt 中添加一条:
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
# 检查一下最终的构建结果,我们发现,libhello.a 已经构建完成,位于build/lib 目录中,但是 libhello.so 去消失了。
# 因此应该使用下面这两句
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
# 或者直接简单粗暴
add_library (hello_shared SHARED ${SRC_LIST})
add_library (hello_static STATIC ${SRC_LIST})
set_target_properties (hello_shared PROPERTIES OUTPUT_NAME "hello")
set_target_properties (hello_static PROPERTIES OUTPUT_NAME "hello")

动态库版本号

# 按照规则,动态库是应该包含一个版本号的,我们可以看一下系统的动态库,一般情况是 
libhello.so.1.2 
libhello.so ->libhello.so.1 
libhello.so.1->libhello.so.1.2
# 为了实现动态库版本号,我们仍然需要使用 SET_TARGET_PROPERTIES 指令。
# 具体使用方法如下:
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
# VERSION 指代动态库版本,SOVERSION 指代 API 版本。
# 将上述指令加入 lib/CMakeLists.txt 中,重新构建看看结果。
# 在 build/lib 目录会生成:
libhello.so.1.2
libhello.so.1->libhello.so.1.2
libhello.so ->libhello.so.1

安装共享库和头文件

​ 以上面的例子,需要将 libhello.a, libhello.so.x 以及 hello.h 安装到系统目录,才能真正让其他人开发使用,在本例中将 hello 的共享库安装到/lib 目录,将 hello.h 安装到/include/hello 目录。

# 利用上一节了解到的 INSTALL 指令,我们向 lib/CMakeLists.txt 中添加如下指令:
INSTALL(TARGETS hello hello_static
LIBRARY 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 World之使用外部共享库和头文件

本例我们的任务很简单: 编写一个程序使用我们上个例子构建的共享库。

准备工作

在/usr/ubuntu/cmake 目录建立 t4 目录,本节所有资源将存储在 t4 目录。

// 重复以前的步骤,建立 src 目录,编写源文件 main.c,内容如下:
#include <hello.h>
int main()
{
HelloFunc();
return 0;
}
# 编写工程主文件 CMakeLists.txt
PROJECT(NEWHELLO)
ADD_SUBDIRECTORY(src)
# 编写 src/CMakeLists.txt
ADD_EXECUTABLE(main main.c)

外部构建

按照习惯,仍然建立 build 目录,使用 cmake …方式构建。

过程:

cmake ..
make 

构建失败,如果需要查看细节,可以使用 make VERBOSE=1 来构建

错误输出为是: /backup/cmake/t4/src/main.c:1:19: error: hello.h: 没有那个文件或目录

引入头文件搜索路径

​ hello.h 位于/usr/include/hello 目录中,并没有位于系统标准的头文件路径(其实直接include <hello/hello.h>就可以,这里只是为了用简单的例子演示)

​ 为了让我们的工程能够找到 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),进入 build 目录,重新进行构建,这是找不到 hello.h 的错误已经消失,但是出现了一个新的错误:

main.c:(.text+0x12): undefined reference to `HelloFunc’

因为并没有 link 到共享库 libhello 上。

添加共享库

​ 现在需要完成的任务是将目标文件链接到 libhello,这里需要引入两个新的指令 LINK_DIRECTORIES 和 TARGET_LINK_LIBRARIES

  • LINK_DIRECTORIES 的全部语法是: LINK_DIRECTORIES(directory1 directory2 …)

​ 这个指令非常简单,添加非标准的共享库搜索路径,比如,在工程内部同时存在共享库和可 执行二进制,在编译时就需要指定一下这些共享库的路径。这个例子中我们没有用到这个指 令。

  • TARGET_LINK_LIBRARIES 的全部语法是: TARGET_LINK_LIBRARIES(target library1 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

CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH

​ 务必注意,这两个是环境变量,而不是 cmake 变量。使用方法是要在 bash 中用 export 或者在 csh 中使用 set 命令设置或者 CMAKE_INCLUDE_PATH=/home/include cmake …等方式。

​ 这两个变量主要是用来解决以前 autotools 工程中 --extra-include-dir 等参数的支持的。 也就是,如果头文件没有存放在常规路径(/usr/include, /usr/local/include 等), 则可以通过这些变量就行弥补。 我们以本例中的 hello.h 为例,它存放在/usr/include/hello 目录,所以直接查找肯定是找不到的。

​ 前面我们直接使用了绝对路径INCLUDE_DIRECTORIES(/usr/include/hello)告诉工程这个头文件目录。 为了将程序更智能一点,我们可以使用 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)

这里简单说明一下,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_LIBRARY_PATH 可以用在 FIND_LIBRARY 中。 同样,因为这些变量直接为 FIND_指令所使用,所以所有使用 FIND_指令的 cmake 模块都会受益。

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

1.cmake 变量引用的方式

前面我们已经提到了,使用 进行变量的引用。在 I F 等语句中,是直接使用变量名而不通过 {}进行变量的引用。在 IF 等语句中,是直接使用变量名而不 通过 进行变量的引用。在IF等语句中,是直接使用变量名而不通过{}取值

2.cmake 自定义变量的方式

​ 主要有隐式定义和显式定义两种,前面举了一个隐式定义的例子,就是 PROJECT 指令,会隐式的定义_BINARY_DIR 和_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

​ 这三个变量指代的内容是一致的,如果是 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-ofsource 编译,他指的是 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 指令定义的项目名称。

4.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_DIR

    ECTORIES_PROJECT_BEFORE 将工程提供的头文件目录始终至于系统头文件目录的前面,当你定义的头文件确实跟系统发 生冲突时可以提供一些帮助。

  3. CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH 在例四已经提及。

5.系统信息

  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

6.主要的开关选项

  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_DEFINITION

向 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

例一讲了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 都是无效的。

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]) 
#eg: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_VALUELS_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 是可选的。
2).WHILE

WHILE 指令的语法是:

WHILE(condition)
 COMMAND1(ARGS ...)
 COMMAND2(ARGS ...)
 ...
 ENDWHILE(condition)
3).FOREACH

FOREACH 指令的使用方法有三种形式:

1,列表
FOREACH(loop_var arg1 arg2 ...)
 COMMAND1(ARGS ...)
 COMMAND2(ARGS ...)
 ...
 ENDFOREACH(loop_var)
2,范围
FOREACH(loop_var RANGE total)
ENDFOREACH(loop_var)
从 0 到 total 以1为步进
3,范围和步进
FOREACH(loop_var RANGE start stop [step])
ENDFOREACH(loop_var)
从 start 开始到 stop 结束,以 step 为步进

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值