【CMake 入门与进阶(3)】 CMakeLists.txt 语法规则基础及部分常用指令(附使用代码)

        在上两篇中,笔者通过几个简单地示例向大家演示了 cmake 的使用方法,由此可知,cmake 的使用方法其实还是非常简单的,重点在于编写 CMakeLists.txt,CMakeLists.txt 的语法规则也简单,并没有 Makefile 的语法规则那么复杂难以理解!本文我们来学习CMakeLists.txt 的语法规则。

简单语法介绍

  • 注释

        在CMakeLists.txt 文件中,使用“#”号进行单行注释,譬如:

#
# 这是注释信息
#
cmake_minimum_required(VERSION 3.5)
project(HELLO)

        大多数脚本语言都是使用“#”号进行注释。

  • 命令(command)

        通常在CMakeLists.txt文件中,使用最多的是命令,譬如上例中的 cmake_minimum_required   project 都 是命令;命令的使用方式有点类似于 C 语言中的函数,因为命令后面需要提供一对括号,并且通常需要我们提供参数,多个参数使用空格分隔而不是逗号“,”,这是与函数不同的地方。命令的语法格式如下所示:

command(参数 1 参数 2 参数 3 ...)

        不同的命令所需的参数不同,需要注意的是,参数可以分为必要参数和可选参数(通常称为选项),很多命令都提供了这两类参数,必要参数使用表示,而可选参数使用[参数]表示,譬如 set 命令:

set(<variable> <value>... [PARENT_SCOPE])

        set 命令用于设置变量,第一个参数和第二个参数是必要参数,在参数列表(…表示参 数个数没有限制)的最后可以添加一个可选参数 PARENT_SCOPE(PARENT_SCOPE 选项),既然是可选的,那就不是必须的,根据实际使用情况确定是否需要添加。

        在CMakeLists.txt中,命令名不区分大小写,可以使用大小写字母书写命令名,譬如:

project(HELLO) #小写
PROJECT(HELLO) #大写

        这俩的效果是相同的,指定的是同一个命令,并没区别;这个主要看个人喜好,笔者使用小写字母,主要是为了和变量区分开来,因为 cmake 的内置变量其名称都是使用大写字母组成的。

  • 变量(variable)

        在 CMakeLists.txt 文件中可以使用变量,使用 set 命令可以对变量进行设置,譬如:

# 设置变量 MY_VAL
set(MY_VAL "Hello World!")

        上例中,通过 set 命令对变量 MY_VAL 进行设置,将其内容设置为"Hello World!";那如何引用这个变量呢?这与 Makefile 是相同的,通过${MY_VAL}方式来引用变量,如下所示:

#设置变量 MY_VAL
set(MY_VAL "Hello World!")

#引用变量 MY_VAL
message(${MY_VAL})

        变量可以分为 cmake 内置变量以及自定义变量,譬如上例中所定义的 MY_VAL 就是一个自定义变量; 譬如在【CMake 入门与进阶(2)】CMake编译设置——多个源文件编译及生成库文件(附代码)_GPIOB_PIN7的博客-CSDN博客所使用的 LIBRARY_OUTPUT_PATH 和 EXECUTABLE_OUTPUT_PATH 变量则是 cmake 的内置变量,每一个内置变量都有自己的含义,像这样的内置变量还有很多,稍后向大家介绍。

部分常用命令

         cmake 提供了很多命令 ,每一个命令都有它自己的功能、作用,通过这个链接地址 cmake-commands(7) — CMake 3.5.2 Documentation 可以查询到所有的命令及其相应的介绍、使 用方法等等,如下所示:

         大家可以把这个链接地址保存起来,可以把它当成字典的形式在有需要的时候进行查询,由于命令非常多,笔者不可能将所有命令都给大家介绍一遍,这里给大家介绍一些基本的命令,如下表所示:

command 说明
add_executable 可执行程序目标
add_library 库文件目标
add_subdirectory 去指定目录中寻找新的 CMakeLists.txt 文件
aux_source_directory 收集目录中的文件名并赋值给变量
cmake_minimum_required 设置 cmake 的最低版本号要求
get_target_property 获取目标的属性
include_directories 设置所有目标头文件的搜索路径,相当于 gcc 的-L 选项
link_directories 设置所有目标库文件的搜索路径,相当于 gcc 的-L 选项
link_libraries 设置所有目标需要链接的库
list 列表相关的操作
message 用于打印、输出信息
project 设置工程名字
set 设置变量
set_target_properties 设置目标属性
target_include_directories 设置指定目标头文件的搜索路径
target_link_libraries 设置指定目标库文件的搜索路径
target_sources 设置指定目标所需的源文件

        接下来详细地给大家介绍每一个命令。

  • add_executable

        add_executable 命令用于添加一个可执行程序目标,并设置目标所需的源文件,该命令定义如下所示:

add_executable(<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] source1 [source2 ...])

        该命令提供了一些可选参数,这些可选参数的含义笔者就不多说了,通常不需要加入,具体的含义大家 可以自己查看 cmake 官方文档(add_executable — CMake 3.5.2 Documentation);只需传入目标名和对应的源文件即可,譬如:

#生成可执行文件 hello
add_executable(hello 1.c 2.c 3.c)

        定义了一个可执行程序目标 hello,生成该目标文件所需的源文件为 1.c、2.c 和 3.c。需要注意的是,源文件路径既可以使用相对路径、也可以使用绝对路径,相对路径被解释为相对于当前源码路径(注意,这里源码指的是 CMakeLists.txt 文件,因为 CMakeLists.txt 被称为 cmake 的源码,若无特别说明,后续将沿用这个概念!)。

  • add_library

        add_library命令添加一个库文件目标,并设置目标所需的源文件,该命令定义如下所示:

add_library(<name> [STATIC | SHARED | MODULE]
 [EXCLUDE_FROM_ALL]
 source1 [source2 ...])

        第一个参数 name 指定目标的名字,参数 source1…source2 对应源文件列表;add_library 命令默认生成的库文件是静态库文件,通过 SHARED 选项可使其生成动态库文件,具体的使用方法如下:

#生成静态库文件 libmylib.a
add_library(mylib STATIC 1.c 2.c 3.c)

#生成动态库文件 libmylib.so
add_library(mylib SHARED 1.c 2.c 3.c)

        与 add_executable 命令相同,add_library 命令中源文件既可以使用相对路径指定、也可以使用绝对路径指定,相对路径被解释为相对于当前源码路径。

        不管是 add_executable、还是 add_library,它们所定义的目标名在整个工程中必须是唯一的,不可出现两个目标名相同的目标。

  • add_subdirectory

        add_subdirectory 命令告诉 cmake 去指定的目录中寻找源码并执行它,有点像 Makefile 的 include,其定义如下所示:

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

        参数 source_dir 指定一个目录,告诉cmake 去该目录下寻找 CMakeLists.txt文件并执行它;参数binary_dir 指定了一个路径,该路径作为子源码(调用 add_subdirectory 命令的源码称为当前源码或父源码,被执行的源码称为子源码)的输出文件(cmake 命令所产生的中间文件)目录,binary_dir 参数是一个可选参数,如果没有显式指定,则会使用一个默认的输出文件目录;为了后续便于表述,我们将输出文件目录称为 BINARY_DIR。

        譬如工程目录结构如下所示:

├── build
├── CMakeLists.txt
└── src
  ├── CMakeLists.txt
  └── main.c

        顶层 CMakeLists.txt 文件内容如下所示:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.5")
project("HELLO")

# 告诉 cmake 去 src 目录下寻找 CMakeLists.txt
add_subdirectory(src)

        src 目录下的 CMakeLists.txt 文件:

# src 下的 CMakeLists.txt
add_executable(hello main.c)

        进入到 build 目录下,执行 cmake、make 进行构建编译;顶层源码对应的输出文件会存放在 build 目录,也就是执行 cmake 命令所在目录;子源码(src 目录下的 CMakeLists.txt)对应的输出文件会存放在 build/src 目录,包括生成的可执行文件默认会与这些中间文件放置在同一个目录,如下所示:

├── build
│  ├── CMakeCache.txt
│  ├── CMakeFiles
│  ├── cmake_install.cmake
│  ├── Makefile
│  └── src
│    ├── CMakeFiles
│    ├── cmake_install.cmake
│    ├── hello
│    └── Makefile
├── CMakeLists.txt
└── src
  ├── CMakeLists.txt
  └── main.c

        所以由此可知,当前源码调用add_subdirectory命令执行子源码时,若没有为子源码指定BINARY_DIR, 默认情况下,会在当前源码的 BINARY_DIR 中创建与子目录(子源码所在目录)同名的文件夹,将其作为子源码的 BINARY_DIR。 接下来我们修改顶层 CMakeCache.txt 文件:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.5")
project("HELLO")

# 告诉 cmake 去 src 目录下寻找 CMakeLists.txt
add_subdirectory(src output)

        指定子源码的 BINARY_DIR 为 output,这里使用的是相对路径方式,add_subdirectory 命令对于相对路径的解释为:相对于当前源码的 BINARY_DIR;修改完成之后,再次进入到 build 目录下执行 cmake、make 命令进行构建、编译,此时会在 build 目录下生成一个 output 目录,这就是子源码的 BINARY_DIR。

        设置 BINARY_DIR 可以使用相对路径、也可以是绝对路径,相对路径则是相对于当前源码的 BINARY_DIR,并不是当前源码路径,这个要理解。

        通过 add_subdirectory 命令加载、执行一个外部文件夹中的源码,既可以是当前源码路径的子目录、也可以是与当前源码路径平级的目录亦或者是当前源码路径上级目录等等;对于当前源码路径的子目录,不强制调用者显式指定子源码的 BINARY_DIR;如果不是当前源码路径的子目录,则需要调用者显式指定 BINARY_DIR,否则执行源码时会报错。接下来进行测试,譬如工程目录结构如下所示:

├── build
├── CMakeLists.txt
├── lib
│  └── CMakeLists.txt
└── src
  ├── CMakeLists.txt
  └── main.c

        这里一共有 3 个 CMakeLists.txt 文件,lib 目录和 src 目录是平级关系,顶层 CMakeLists.txt 内容如下:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.5")
project("HELLO")

# 加载 src 目录下的源码
add_subdirectory(src)

        src 目录下的 CMakeLists.txt:

# src 目录 CMakeLists.txt
add_executable(hello main.c)

# 加载平级目录 lib 中的源码
add_subdirectory(../lib)

        此时调用 add_subdirectory 加载 lib 目录的源码时并为显式指定 BINARY_DIR,进入到 build 目录下,执行 cmake 命令,发生了报错,而且提示我们 add_subdirectory 命令必须要指定 BINARY_DIR,那我们将 src 目录下的 CMakeLists.txt 进行修改,显式指定 BINARY_DIR,如下所示:

# src 目录 CMakeLists.txt
add_executable(hello main.c)

# 加载平级目录 lib 中的源码
add_subdirectory(../lib output)

        接着再次执行 cmake(每次执行 cmake 前进行清理,将 build 目录下生成的所有文件全部删除)即可。


未完待续...

猜你喜欢

转载自blog.csdn.net/cj_lsk/article/details/131042474
今日推荐