《CMake Best Practice》第 1 章笔记--Quick Start
《CMake Best Practice》第 1 章笔记--Quick Start
最小CMakeLists.txt文件
cmake_minimum_required(VERSION 3.21)
project(
"chapter1"
VERSION 1.0
DESCRIPTION "A simple project to demonstrate basic CMake usage"
LANGUAGES CXX)
add_executable(Chapter1)
target_sources(Chapter1 PRIVATE src/main.cpp)
CMake构建过程
CMake 的构建过程分为 configure 和 build 两步。

1. configure 步骤
CMake 是一个跨平台的构建系统生成器,它可以为不同的构建系统生成构建指令,指导构建系统如何对源代码进行编译链接。configure 步骤的最终结果就是生成特定于构建系统的构建文件。比如,它可以为 Make 生成 Makefile 文件,为 Ninja 生成 Ninja.build 文件。
这一步通常使用通过cmake命令来完成。
通过cmake --help可以看到 cmake 命令支持的详细参数,常用的参数如下:
| 参数 | 含义 |
|---|---|
| -S | 指定源文件根目录,必须包含一个CMakeLists.txt文件 |
| -B | 指定构建目录,构建生成的中间文件和目标文件的生成路径 |
| -D | 指定变量,格式为-D = |
比如,指明使用当前目录作为源文件目录,其中包含 CMakeLists.txt 文件;使用 build 目录作为构建目录;设定变量 CMAKE_BUILD_TYPE 的值为 Debug :
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
CMake 支持许多不同的构建系统,默认会使用 Make 来作为构建系统。如要使用其它构建系统,需要在 Cmake 命令中显式切换。比如可以使用下面的指令切换构建系统为 Ninja 。
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -G Ninja
这样就会生成 ninja.build 文件。
2. build 步骤
这一步是真正的构建步骤,通过调用 make 或者 Ninja 等构建系统来执行编译链接命令。使用下列命令完成:
cmake --build <dir> [<options>] [-- <build-tool-options>]
源码目录和构建目录
CMake 工程通常会将目录分为两类,一类是源码目录,一类是构建目录。
CMake基本语法
定义一个工程的语法如下:
project(
"chapter1"
VERSION 1.0
DESCRIPTION "A simple C++ project to demonstrate basic CMake
usage" LANGUAGES CXX
)
当前的工程名会被存进 PROJECT_NAME 变量中,如果当前工程是根工程,工程名还会存进 CMAKE_PROJECT_NAME 变量。从 CMake 3.21 版本开始,可以用 PROJECT_IS_TOP_LEVEL 变量判断当前工程是不是根工程。另外,可以使用 <PROJECT-NAME>_IS_TOP_ LEVEL 判断任意的工程是不是根工程。
举个例子,假如有下面这样一个工程:

当解析根目录下的 CMakeLists.txt 时,PROJECT_NAME 和 CMAKE_PROJECT_NAME 的值都是 CMakeBestPractices 。当解析 chapter_1/CMakeLists.txt 时,PROJECT_NAME 的值会变成 Chapter 1 ,但是 CMAKE_PROJECT_NAME 的值依然还是 CMakeBestPractices 。
变量
使用 set 设置一个变量,使用 unset 删除一个变量。变量是大小写敏感的,建议的做法是变量名全部大写。变量都是以字符串的形式保存。
set(MYVAR "1234")
unset(MYVAR)
使用 ${} 访问一个变量,比如:
message(STATUS "The content of MYVAR are ${MYVAR}")
变量也可以嵌套,比如:
${outer_${inner_variable}_variable}
CMake 有许多以 CMake_ 作为前缀的预定义变量,见链接:
https://cmake.org/cmake/help/latest/manual/cmake-variables.7.html
列表
使用 set 同样可以定义一个列表,下面的两种方法都定义了一个列表,其中包含 abc、def、ghi 三个变量。
set(MYLIST abc def ghi)
set(MYLIST "abc;def;ghi")
使用 list 命令可以操作列表。
#在MYLIST中寻找abc这个值,如果找到了,返回其所在的位置给ABC_INDEX,否则返回-1给ABC_INDEX
list(FIND MYLIST abc ABC_INDEX)
#获取MYLIST中位置为1的值给ABC
list(GET MYLIST 1 ABC)
#给MYLIST追加一个值xyz
list(APPEND MYLIST "xyz")
Cache变量
Cache 变量会被缓存在 CMakeCache.txt 文件中,它相当于一个全局变量,我们在同一个 cmake 工程中的任意位置都可以使用。定义 Cache 变量的语法如下:
# set(<variable> <value>... CACHE <type> <docstring> [FORCE])
set(CACHE_VAR "Default cache value" CACHE STRING "A sample for cache variable")
使用 $CACHE{<varialbe>} 引用 Cache 变量,当然,也可以使用 ${} 引用 Cache 变量,但是当有一个与 Cache 变量同名的 Normal 变量出现时,后面使用 ${} 引用这个变量的值都是以 Normal 为准,如果没有同名的 Normal 变量,CMake 才会自动使用 Cache 变量。
CMake 本身会有一些默认的全局 Cache 变量。例如:CMAKE_INSTALL_PREFIX、CMAKE_BUILD_TYPE、CMAKE_CXX_FLAGS 等。
有两种方式修改 Cache 变量:
- 通过
set(<variable> <value> CACHE INSTERNAL FORCE) - 直接在终端中使用
cmake -Dvar=value
属性
属性和变量是两个截然不同的概念,属性总是附加到特定的实体上。可以拥有实体的对象有以下几种:
- 源文件(source file)
- 目录(directory)
- target:cmake可构建三种 target:archive,、library 和 runtime.
- Executables 总是 runtime targets.
- Static libraries 总是 archive targets.
- Module libraries总是 library targets.
- 对 non-DLL 平台,shared libraries 是 library targets.
- 对 DLL 平台, DLL 是 runtime target, 对应的导入库是 archive target. All Windows-based systems including Cygwin 都是 DLL 平台.
- 全局属性(global)
设置属性的方法是:
set_property(<Scope> <EntityName>
[APPEND] [APPEND_STRING]
PROPERTY <propertyName> [<values>])
其中 APPEND 和 APPEND_STRING 是可选的,它们用来给已存在的属性追加值。
<Scope> 可以是下面这些值:
GLOBAL,全局属性,影响整个构建过程DIRECTORY <dir>,绑定到当前目录,或者指定了<dir>的话,会绑定到<dir>指定的目录。可以使用命令set_directory_properties代替。TARGET <targets>,给指定的target绑定属性。可以使用命令set_target_properties代替。SOURCE <files>,给指定的一系列源文件附加属性。可以使用命令set_source_files_properties代替。INSTALL <files>,给安装文件附加属性,用来控制cpack的行为。TEST <tests>,给特定的测试用例附件属性,可以使用set_test_properties代替。CACHE <entry>,给Cache变量附加属性。
有许多预定义的属性,参见:
https://cmake.org/cmake/help/latest/manual/cmake-properties.7.html
循环和条件
条件使用关键字 if()、elseif()、else()、endif()。
一元操作符
简单列举几个支持的一元操作符:
COMMAND,如果给定的名称为可调用的命令、宏、函数名,则为真。EXISTS,判断给定的路径或者文件是否存在。DEFINED,如果给定值是一个已定义的变量,则为真。IS_DIRECTORY,判断给定的路径是不是一个目录。IS_SYMLINK,判断给定的值是不是一个符号链接。IS_ABSOULTE,判断是不是绝对路径。
一元操作符使用示例:
if(DEFINED MY_VAR)
二元操作符
二元操作符使用示例:
if(MYVAR STREQUAL "FOO")
LESS、GREATER、EQUAL、LESS_EQUAL、GREATER_EQUAL,用于比较数值。STRLESS、STREQUAL、STRGREATER、STRLESS_EQUAL、STRGREATER_EQUAL,用于比较字符串。VERSION_LESS、VERSION_EQUAL、VERSION_GREATER、VERSION_LESS_EQUAL、VERSION_GREATER_EQUAL,用于比较版本字符串。MATCHES,用于比较正则表达式。
布尔操作符
OR、AND、NOT
循环
while()、endwhile()。
foreach()、endforeach()。
可以使用break()、continue() 跳出循环。
WHILE 循环示例
set(MYVAR 0)
while(MYVAR LESS "5")
message(STATUS "Chapter1: MYVAR is '${MYVAR}'")
math(EXPR MYVAR "${MYVAR}+1")
endwhile()
FOREACH 循环示例
foreach使用IN关键字遍历列表
foreach(ITEM IN LISTS MYLIST)
# do something with ${ITEM}
endforeach()
foreach使用RANGE关键字范围遍历
foreach(ITEM RANGE 0 10)
# do something with ${ITEM}
endforeach()
函数
# 定义一个函数,函数名为 foo,有一个参数ARG1
function(foo ARG1)
# do something
endfunction()
# 调用函数,使用bar作为参数
foo("bar")
函数是一个新的作用域,函数里面定义的变量只在函数内部有效,除非显式的在 set 定义一个变量时显式的指定 PARENT_SCOPE 使该变量在其父作用域范围内依然有效。这可以用来从函数里面返回值。比如:
function(myfunc)
set(FUNC_VAR "adsfda" PARENT_SCOPE)
endfunction()
myfunc()
message(${FUNC_VAR})
宏
宏定义:
macro(<name> [<arg1> ...])
<commands>
endmacro()
宏与函数很像,但是有一些区别:
- 传递给宏的参数不是真实的值,而是字符串替换。看下面的例子,使用
DEFINED会检测出来参数未定义。
macro(test_macro_argument arg)
message(STATUS "arg: ${arg}")
if (DEFINED arg)
message(STATUS "arg: ${arg}")
else()
message(STATUS "not defined arg")
endif()
endmacro()
test_macro_argument(22)
- 执行函数时,控制权会交给函数,函数会开辟一个新的作用域,而宏只是代码替换,其作用域不变。因此宏中不建议使用
return()语句,这会退出整个CMake进程。
TAREGET
CMake 是基于 target 的构建系统,有三种方法创建一个 target 。
add_executable,创建一个可执行文件。add_library,创建一个静态库或者动态库。add_custom_target,创建一个自定义的命令。
指定编译器
CMake 会自动探测可用的工具链,但是也可以手动指定。两种方法可以指定 CMake 构建时使用的编译器。
- 使用命令行。在
CMake的Configure阶段指定。
cmake .. -DCMAKE_CXX_COMPILER=/usr/bin/g++ -DCMAKE_C_COMPILER=/usr/bin/gcc
- 使用
set修改系统变量。
set(CMAKE_CXX_COMPILER "/usr/bin/g++")
set(CMAKE_C_COMPILER "/usr/bin/gcc")
构建类型
CMake 有 4 种构建类型。
Debug,未优化的版本,包含所有的调试符号,所有的assert语句会被开启。相当于指定GCC的编译选项-O0 -g。Release,不包含任何调试符号,所有的assert语句会被删除。相当于指定GCC的编译选项-O3 -DNDEBUG。RelWithDebInfo,优化代码,包含调试符号,但是所有的assert语句会被删除。相当于指定GCC的编译选项-O2 -g -DNDEBUG。MinSizeRel,与Release相同,但是它会尽量生成最小的文件,而不是尽量生成最快的文件。相当于指定GCC的编译选项-Os -DNDEBUG。

浙公网安备 33010602011771号