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]