一、编译工具及构建工具介绍
二、Makefile的简单讲解
在之前的课程,都是直接使用gcc对代码进行编译,这对简单的工程是可以的,但当我们遇到复杂的工程
时,每次用gcc等编译工具去操作就会显得很低效。因此make工具就出现了, make的出现是为了解决
手动编译和链接大型工程的问题,它可以避免重复的工作,提高效率,保证正确性。make工具就根据
makefile中的命令进行编译和链接的。但是当工程非常大的时候,手写makefile也是非常麻烦的,如果
换了个平台makefile又要重新修改,因此更高级的一些构建系统或者工具工具像cmake、qmake、ninja
和auto make就出现了,它们可以根据一些配置文件来自动化编译和链接软件项目。
cmake是一个跨平台的构建系统,它可以根据CMakeLists.txt中的指令来生成不同平台和工具的工程文
件,例如Makefile、Visual Studio解决方案、Ninja文件等。cmake可以支持多种语言和多种架构,它还
提供了一些高级功能,如测试、打包、安装等。
qmake是一个用于Qt项目的构建系统,它可以根据.pro或.pri中的指令来生成Makefile或其他形式的工程
文件。
ninja是一个小巧而快速的构建工具,它可以根据ninja.build中的规则来执行编译和链接命令。ninja主要
关注于性能和效率,它可以利用多核处理器和并行处理来加速构建过程。ninja通常不需要用户直接编写
配置文件,而是由其他构建系统(如cmake)来生成
auto make是一个用于生成Makefile.in文件的工具,Makefile.in是一种用于auto conf的配置文件格式,
auto conf是一个用于生成configure脚本的工具。configure脚本是一个用于检测系统环境并生成最终的
Makefile文件的脚本Makefile.am是一种用于auto make的配置文件格式,它包含了一些指令和变量,用
于定义程序或库的源文件、目标文件、依赖关系和编译选项等。
make是一个经典而通用的构建工具,它可以根据Makefile中的规则来执行编译和链接命令。make可以
支持多种平台和工具,它还提供了一些高级功能,如条件判断、函数调用、模式匹配。
回顾下编译的四个过程:预处理(Pre-Processing)、编译(Compiling)、汇编 (Assembliang)、链接
(Linking)
target ... : prerequisites ...
command
...
...
target 也就是一个目标文件,可以是 Object File,也可以是执行文件。还可以是一个标签( Label),
对于标签这种特性,在后续的讲“伪目标”中会有叙述。prerequisites 就是,要生成那个 target 所需要的
文件或是目标。command 也就是 make 需要执行的任意shell命令。
Makefile一个示例:
debug:
@echo "hello world"
#include
int main(int argc, char *argv[])
{
printf("hello world!\n");
return 0;
}
Makefile修改如下:
debug:
@echo "hello world"
test:
gcc -o test test.c
执行命令make test 可以生成 test文件, 执行make debug可以输出“hello world”:
test@test:~/makefiletest$ rm test
test@test:~/makefiletest$ ls
Makefile test.c
test@test:~/makefiletest$ make debug
hello world
test@test:~/makefiletest$ ls
Makefile test.c
test@test:~/makefiletest$ make test
gcc -o test test.c
test@test:~/makefiletest$ ls
Makefile test test.c
如果一个目标和一个实际文件同名,那么make会认为该目标已经是最新的,不需要重新生成,也不会执
行其命令。通过将目标声明为伪目标.PHONY: debug
,可以避免这种情况,强制执行其命令。
debug:
@echo "hello world"
test:
gcc -o test test.c
.PHONY: debug
Makefile中的变量赋值运算符有四种,分别是=
、:=
、?=
和+=
, $
符号表示取变量的值,当变量名多于一个字符时,使用"( )
":
=
表示延迟展开赋值,即变量的值是在使用时才确定,可能会受到后面的赋值影响。例如,VAR_A = A,VAR_B = $(VAR_A) B,VAR_A = AA
,那么最后VAR_B的值是AA B,而不是A B。
:=
表示直接赋值,即变量的值是在定义时就确定,不会受到后面的赋值影响。例如,VAR_A := A, VAR_B := $(VAR_A) B,VAR_A := AA
,那么最后VAR_B的值是A B,而不是AA B。
?=
表示条件赋值,即只有当变量没有被赋值时,才使用等号后面的值作为变量的值。例如,VAR ?= new_value
,如果VAR在之前没有被赋值,那么VAR的值就为new_value,否则保持原来的值不变。
+=
表示追加赋值,即将等号后面的值追加到变量原来的值之后,形成一个新的值。例如,VAR += new_value
,如果VAR在之前没有被赋值,那么VAR的值就为new_value,如果VAR在之前被赋值为
old_value,那么VAR的值就为old_value new_value
$
符的其他用法:
$^
表示所有的依赖文件
$@
表示生成的目标文件
$<
代表第一个依赖文件
采用#
进行一行注释
语法格式是:
$(var:a=b)或${var:a=b}
表示把变量var的值中的a后缀替换成b后缀。例如:
src := a.c b.c c.c
obj := $(src:c=o)
把变量src的值中的.c后缀替换成.o后缀,赋值给变量obj,结果是:
obj := a.o b.o c.o
举例:
# 这是一个Makefile的注释
TARGET = hello #TARGET延迟赋值hello
CC := gcc #CC立即赋值gcc
CC += -g #CC追加赋值-g, gcc -g表示添加调试信息,可用于gdb的调试
SRC = hello.c
OBJ = $(SRC:.c=.o) #变量的替换引用,把hello.c的.c替换成.o
debug :
@echo "hello world"
echo $(SRC)
echo $(OBJ)
$(TARGET): $(SRC)
$(CC) -o $@ $<
# $(CC) -o ${TARGET} hello.c
compile: $(TARGET)
clean:
@rm hello hello.o -r
.PHONY: clean compile
Makefile函数的基本格式是:$(
或者是${
,其中,是函数名,是函数的参数,参数之间要用逗号分隔
开,参数和函数名之间使用空格分开。调用函数的时候要使用字符“$
”,后面可以跟小括号或者大括号。
1) wildcard 通配符:
Makefile中的wildcard 是一个函数,用于扩展通配符,返回与通配符匹配的文件列表。通配符是一种特
殊的字符,可以表示多个文件名或目录名,常见的通配符有 *
和 ?
,分别表示任意长度的任意字符和单个
任意字符。格式如下:
$(wildcard arguments)
比如*.c 表示所有以 .c 结尾的文件名,a?.txt 表示所有以 a 开头,中间有一个任意字符,以 .txt 结尾的文
件名。例如:
SRC = $(wildcard src/*.c)
表示查找并返回src目录下所有的.c文件, *表示通配符, 匹配一个或者多个任意字符
2)shell:
$(shell )
cmd: 执行命令名称
args:参数列表
返回值: 返回命令执行结果
例如:
SRC = $(shell find . -name *.c)
表示查找当前目录及子目录下的所有.c文件结尾的代码源文件
3) patsubst替换函数:
$(patsubst pattern,replacement,text)
pattern: 是一个包含通配符 %
的模式,表示匹配任意长度的任意字符
replacement: 是一个替换字符串,也可以包含 %
,表示用 pattern 中匹配的字符替换。
text: 是一个要处理的文本,可以包含多个以空格分隔的单词。
返回值:patsubst 函数会在 text 中找到所有符合 pattern 的单词,并用 replacement 替换它们,然后
返回替换后的文本。
例如,如果有一个变量 src,它的值是:
SRC = a.c b.c c.c
想把它的值中的所有 .c 后缀替换成 .o 后缀,可以这样写:
OBJ = $(patsubst %.c,%.o,$(src))
这样,obj 的值就是:
OBJ = a.o b.o c.o
$(subst from,to,text)
from: 是要被替换的字符或单词
to: 是替换后的字符或单词
text: 是要处理的字符串。
返回值:subst 函数会在 text 中找到所有的 from,并用 to 替换它们,然后返回替换后的字符串。
例如
$(subst ee,EE,feet on the street)
返回:
fEEt on the strEEt
综合举例,测试工程代码目录:
test@test:~/makefiletest$ tree
.
├── Makefile
└── src
└── test.c
Makefile内容:
CC = gcc
CC += -g
SRC := $(shell find . -name *.c)
TARGET := $(patsubst %.c, %,$(subst src,obj, $(SRC)))
debug:
@echo "hello world"
echo $(SRC)
echo $(TARGET)
$(TARGET): $(SRC)
mkdir -p obj
$(CC) -o $@ $<
compile: $(TARGET)
clean:
@rm obj -r
.PHONY: clean compile
test.c内容:
#include
int main()
{
printf("hello world\n");
return 0;
}
执行:
make compile
生成obj/test:
pg@pg-Default-string:~/makefiletest$ tree -a
.
├── Makefile
├── obj
│ └── test
└── src
└── test.c
5)dir函数:
$(dir NAMES...)
dir 函数是一个用于从文件名序列中提取目录部分的函数
优化Makefile内容:
CC = gcc
CC += -g
SRC := $(shell find . -name *.c)
TARGET := $(patsubst %.c, %,$(subst src,obj, $(SRC)))
debug:
@echo "hello world"
echo $(SRC)
echo $(TARGET)
$(TARGET): $(SRC)
mkdir -p $(dir $(TARGET))
$(CC) -o $@ $<
compile: $(TARGET)
clean:
@rm $(dir $(TARGET)) -r
.PHONY: clean compile
6)suffix函数
$(suffix )
功能:从文件名序列中取出各个文件名的后缀。
返回值:返回文件名序列的后缀序列,如果文件没有后缀,则返回空字串。
例如:
$(suffix src/foo.c src-1.0/bar.c hacks)
返回:
.c .c
7)basename函数
格式:
$(basename )
功能:从文件名序列中取出各个文件名的前缀部分。
返回值:返回文件名序列的前缀序列,如果文件没有前缀,则返回空字串。
例如:
$(basename src/foo.c src-1.0/bar.c hacks)
返回:
src/foo src-1.0/bar hacks
8) addsuffix函数
$(addsuffix ,)
功能:把后缀加到中的每个单词后面。
返回:返回加过后缀的文件名序列。
例如:
$(addsuffix .c,foo bar)
返回值:
foo.c bar.c”
9)addprefix函数
功能:把前缀加到中的每个单词后面。
返回值:返回加过前缀的文件名序列。
例如:
$(addprefix src/,foo bar)
返回值:
src/foo src/bar
10)foreach函数
$(foreach ,,)
把list中使用空格分割的单词依次取出并赋值给变量var, 然后执行text表达式
例如:
files := foo bar baz
files-with-c := $(foreach file,$(files),$(file).c)
11)条件判断语言
Makefile条件判断有下面几种:
ifeq/ifneq
语句:
ifeq
语句 : 判断参数 是否相等,相等为 true, 否则是 false.
ifeq (arg1, arg2)
#arg1 arg2 相等执行这里的语句
else
#arg1 arg2 不相等执行这里的语句
endif
ifneq
语句:判断参数 是否不等,不等为 true, 否则为 false.
ifneq (arg1, arg2)
#arg1 arg2 不相等执行这里的语句
else
#arg1 arg2 相等执行这里的语句
endif
ifdef/ifndef
语句
ifdef
语句: 判断参数 是否有值 ,有值为 true, 否则是 false
ifndef
: 判断参数 是否没有值 ,没有值为 true, 否则为 false.
ifdef
:
ifdef var
#如果定义了var,执行这里的内容
else
#如果没定义var,执行这里的内容
endif
ifndef
:
infdef var
#如果没定义var,执行这里的内容
else
#如果定义var,执行这里的内容
endif