DDZZ669 发表于 2024-9-15 16:30

CMake构建实战读书笔记02-简单构建与CMake基础语法

本篇来学习《CMake构建实践 项目开发卷》的第1~3章中的一些知识点。

# 1 简单构建

## 1.1 构建静态库

按照书中内容,编写测试代码:

a.c

```c
#include <stdio.h>

void a()
{
    printf("a\n");
}
```

b.c

```c
#include <stdio.h>

void b()
{
    printf("b\n");
}
```

libab.h

```c
void a();
void b();
```

main.c

```c
#include "libab.h"
#include <stdio.h>

int main()
{
    a();
    b();
   
    return 0;
}
```

然后编写Makefile来进行编译

```makefile
main: main.o libab.a
        gcc main.o -o main -L. -lab #链接静态库并生成主程序
   
main.o: main.c
        gcc -c main.c -o main.o #c文件生成目标文件
   
libab.a: a.o b.o
        ar rcs libab.a a.o b.o #归档,将a.o和b.o打包为静态库
   
a.o: a.c
        gcc -c a.c -o a.o #c文件生成目标文件
   
b.o: b.c
        gcc -c b.c -o b.o #c文件生成目标文件
       
clean:
        rm *.o *.a main || true
```

编译运行的结果如下:



## 1.2 构建动态库

动态库的测试代码可以直接使用刚才静态库的测试代码

只需要修改Makefile

```makefile
main: main.o libab.so
        gcc main.o -o main -L. '-Wl,-R$$ORIGIN' -lab #链接静态库并生成主程序
   
main.o: main.c
        gcc -c main.c -o main.o #c文件生成目标文件
   
libab.so: a.o b.o
        gcc -shared a.o b.o -o libab.so #生成动态库
   
a.o: a.c
        gcc -fPIC -c a.c -o a.o #c文件生成目标文件
   
b.o: b.c
        gcc -fPIC -c b.c -o b.o #c文件生成目标文件
       
clean:
        rm *.o *.so main || true
```

编译运行的结果如下:



# 2 安装CMake

本篇的测试环境的Ubuntu20.04,默认是没有CMake的



可以使用如下指令来安装CMake

```sh
sudo apt-get install cmake
```

安装完成之后,可以写一个cmake文件进行简单测试:



# 3 CMake基础语法

## 3.1 CMake程序种类

CMake程序根据文件名,分为两类:

- 名为CMakeLists.txt的文件:用于组织构建项目源程序的目录结构
- 扩展名为.cmake的程序:又可分为脚本程序和模块程序两种

## 3.2 CMake注释

CMake注释可分为单行注释嗯和多行注释两种

```cmake
# 单行注释

#[[ 多行注释 可以换行]]
```

## 3.3 命令调用

CMake的命令调用类似于C语言中的函数调用。

先书写命令名称,后面括号里的命令参数

```cmake
message(a b c) # 输出 “abc”
```

## 3.4 命令参数

### 3.4.1 引号参数

用引号包裹在内的参数

```cmake
message("hello
cmake!")
```



### 3.4.2 非引号参数

未被引号包裹的参数,这种参数不能包含任何空白字符,也不能包含圆括号,#符号,双引号或反斜杠

```cmake
message("x;y;z") # 引号参数
message(x y z) # 多个非引号参数
message(x;y;z) # 非引号参数
```



### 3.4.3 括号参数

```cmake
message([===[
abc
def
]===])
```



## 3.5 变量

### 3.5.1 普通变量

通过set方式进行变量的定义。

示例代码:

```cmake
function(f)
        set(a "我是修改后的a")
        set(b "我是b")
        set(c "我是c" PARENT_SCOPE)
endfunction()

set (a "我是a")
f()

message("a: ${a}")
message("b: ${b}")
message("c: ${c}")
```

运行结果



分析运行结果:

- 第一行是输出变量a的值,即全局中set的a的值
- 第二行是输出变量b的值,b在全局中没有定义,输出为空
- 第三行是输出变量c的值,c虽然在全局中没有定义,但在函数中,c通过PARENT_SCOPE将其定义到父级的作用域中

### 3.5.2 缓存变量

缓存变量比普通变量多了CACHE和FORCE参数,缓存变量具有全局的作用域,因此不需要PARENT_SCOPE参数。

示例代码:

```cmake
cmake_minimum_required(VERSION 3.16)
project(MatchOrder)

set(a 缓存变量 CACHE STRING "")
set(a 普通变量)

message("\${a}: ${a}")
message("\$CACHE{a}: $CACHE{a}")
```

运行结果:



### 3.5.3 环境变量

环境变量具有全局的作用域,不支持使用参数列表来定义值。

main.cmake

```makefile
message("main \$ENV{PATH}: $ENV{PATH}")
set(ENV{PATH} "path")
message("main \$ENV{PATH}: $ENV{PATH}")

execute_process(
        COMMAND ${CMAKE_COMMAND} -P setenv.cmake
        OUTPUT_VARIABLE out
)
message("${out}")

message("main \$ENV{PATH}: $ENV{PATH}")
```

这里将PATH环境变量设置为“path”,查看PATH的值,然后再调用setenv.cmake程序后,查看PATH的值

setenv.cmake的内容如下,就是清空了PATH的值

```makefile
message("before setenv \$ENV{PATH}: $ENV{PATH}")
set(ENV{PATH}) #清空
message("after setenv \$ENV{PATH}: $ENV{PATH}")
```

运行结果如下:



分析运行结果:

- PATH默认我Linux系统的环境变量
- 在main.cmake中修改之后,变为了path
- 在setenv.cmake子进程中的PATH,使用的是父进程的PATH,因此也是path
- 在setenv.cmake中将PATH清空
- 再回到主进程查看PATH,仍为主进程修改的“path”

这是因为,CMake的set命令仅对当前CMake进程有些,因此setenv.cmake中将PATH清空,不会影响setenv.cmake中PATH的值。

## 3.6 列表

列表,即用分号隔开的字符串,

## 3.7 控制结构

类似于C语言中的if、while、for语句

### 3.7.1 if条件分支

if条件分支的语法

```cmake
if(<条件>)
        <命令>...
elseif(<条件>)
        <命令>...
else(<条件>)
        <命令>...
endif()
```

### 3.7.2 while判断分支

while判断分支的语法

```cmake
while(<条件>)
        <命令>...
        ...break()
        ...
        ...continue()
        ...
endwhile()
```

### 3.7.3 foreach遍历循环

foreach遍历循环的语法

```cmake
foreach(<循环变量> <循环项的列表>)
        <命令>...
endforeach()
```

例子

```cmake
foreach(x A;B;C D E F)
        message("x: ${x}")
endforeach()

message("---")

set(list X;Y;Z)
foreach(x ${list})
        message("x: ${x}")
endforeach()
```

运行结果如下:



## 3.8 条件语法

条件语法,即判断某个条件为真或假

### 3.8.1 常量、变量和字符串条件

常量条件,CMake中支持的真和假的常量定义如下

| 常量类型 | 常量值                                           | 条件结果 |
| -------- | ------------------------------------------------ | -------- |
| 真值常量 | 1、ON、YES、TRUE、Y,或非零数值                  | 真       |
| 假值常量 | 0、OFF、NO、FALSE、Y、IGNORE、空字符串、NOTFOUND | 假       |

### 3.8.2逻辑运算

三种逻辑运算:

- 与:AND
- 或:OR
- 非:NOT

### 3.8.3 单条件参数

根据单个参数进行判断的条件,例如

```cmake
set(a 1)

if(DEFINED a)
        meaasge("DEFINED a为真")
endif()
```

这里if中的a是一个参数

### 3.8.4 双条件参数

通过两个参数的取值来判断的条件,例如

```cmake
set(a 1)

if(2 GREATER a)
        meaasge("2 GREATER a为真")
endif()
```

这里if中的2和a是两个参数

### 3.8.5 括号和优先级

CMake中条件语法求值的优先级从高到低:

- 当前最内层括号中的条件
- 单参数条件
- 双参数条件
- 逻辑运算条件NOT
- 逻辑运算条件AND
- 逻辑运算条件OR

## 3.9 命令定义

可以自定义一些宏,以及函数

### 3.9.1 宏定义

宏定义的语法,可以传参数

```cmake
macro(<宏名> [<参数1>...])
        <命令>...
endmacro()
```

### 3.9.2 函数定义

函数定义的语法,可以传参数

```cmake
function(<函数名> [<参数1>...])
        <命令>...
endfunction()
```

### 3.9.3 参数的访问

- ${ARGC}:表示参数的个数
- ${ARGV}:表示完整的实参列表
- ${ARGN}:表示无对应形式参数的实际参数列表
- ${ARGV0}:表示第1个参数,依此类推

实例代码:

```cmake
macro(my_macro p)
        message("ARGC: ${ARGC}")
        message("ARGV: ${ARGV}")
        message("ARGN: ${ARGN}")
        message("ARGV0: ${ARGV0}, ARGV1: ${ARGV1}")
endmacro()

function(my_func p)
        message("ARGC: ${ARGC}")
        message("ARGV: ${ARGV}")
        message("ARGN: ${ARGN}")
        message("ARGV0: ${ARGV0}, ARGV1: ${ARGV1}")
endfunction()

my_macro(x y z)
my_func(x y z)
```

运行结果如下:



宏与函数的运行结果类似,这里开分析宏的运行结果:

- ARGC表示接收到的参数个,有3个参数
- ARGV表示完整的实参,为x;y;z
- ARGN表示无对应形式参数的实参,这里的宏在定义的时候,仅定义了一个参数p,实际传入的是x;y;z,因此x对应p,无对应的实参为y;z
- ARGV0和ARG1为传入的前两个参数

# 4 总结

本篇介绍了前3章的内容,先是使用gcc与makefile学习简单构建,并在Linux中安装CMake工具,然后开始学CMake的一些基础语法,并进行实际测试。
页: [1]
查看完整版本: CMake构建实战读书笔记02-简单构建与CMake基础语法