Loading

《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 的构建过程分为 configurebuild 两步。

image-20220626231708795

1. configure 步骤

CMake 是一个跨平台的构建系统生成器,它可以为不同的构建系统生成构建指令,指导构建系统如何对源代码进行编译链接。configure 步骤的最终结果就是生成特定于构建系统的构建文件。比如,它可以为 Make 生成 Makefile 文件,为 Ninja 生成 Ninja.build 文件。

这一步通常使用通过cmake命令来完成。

通过cmake --help可以看到 cmake 命令支持的详细参数,常用的参数如下:

参数 含义
-S 指定源文件根目录,必须包含一个CMakeLists.txt文件
-B 指定构建目录,构建生成的中间文件和目标文件的生成路径
-D 指定变量,格式为-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 判断任意的工程是不是根工程。

举个例子,假如有下面这样一个工程:

image-20220708223552145

当解析根目录下的 CMakeLists.txt 时,PROJECT_NAMECMAKE_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 同样可以定义一个列表,下面的两种方法都定义了一个列表,其中包含 abcdefghi 三个变量。

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_PREFIXCMAKE_BUILD_TYPECMAKE_CXX_FLAGS 等。

有两种方式修改 Cache 变量:

  • 通过 set(<variable> <value> CACHE INSTERNAL FORCE)
  • 直接在终端中使用 cmake -Dvar=value

属性

属性和变量是两个截然不同的概念,属性总是附加到特定的实体上。可以拥有实体的对象有以下几种:

  1. 源文件(source file)
  2. 目录(directory)
  3. 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 平台.
  4. 全局属性(global)

设置属性的方法是:

set_property(<Scope> <EntityName>
[APPEND] [APPEND_STRING]
PROPERTY <propertyName> [<values>])

其中 APPENDAPPEND_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")
  • LESSGREATEREQUALLESS_EQUALGREATER_EQUAL,用于比较数值。
  • STRLESSSTREQUALSTRGREATERSTRLESS_EQUALSTRGREATER_EQUAL,用于比较字符串。
  • VERSION_LESSVERSION_EQUALVERSION_GREATERVERSION_LESS_EQUALVERSION_GREATER_EQUAL,用于比较版本字符串。
  • MATCHES,用于比较正则表达式。

布尔操作符

ORANDNOT

循环

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 循环示例

  1. foreach 使用 IN 关键字遍历列表
foreach(ITEM IN LISTS MYLIST)
# do something with ${ITEM}
endforeach()
  1. 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()

宏与函数很像,但是有一些区别:

  1. 传递给宏的参数不是真实的值,而是字符串替换。看下面的例子,使用 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)
  1. 执行函数时,控制权会交给函数,函数会开辟一个新的作用域,而宏只是代码替换,其作用域不变。因此宏中不建议使用 return() 语句,这会退出整个 CMake 进程。

TAREGET

CMake 是基于 target 的构建系统,有三种方法创建一个 target

  • add_executable,创建一个可执行文件。
  • add_library,创建一个静态库或者动态库。
  • add_custom_target,创建一个自定义的命令。

指定编译器

CMake 会自动探测可用的工具链,但是也可以手动指定。两种方法可以指定 CMake 构建时使用的编译器。

  1. 使用命令行。在 CMakeConfigure 阶段指定。
cmake .. -DCMAKE_CXX_COMPILER=/usr/bin/g++ -DCMAKE_C_COMPILER=/usr/bin/gcc
  1. 使用 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

有用的链接

posted @ 2022-07-09 23:09  cclemontree  阅读(547)  评论(0)    收藏  举报