环境:Ubuntu 18.04.6
简介:
gcc命令可以帮助我们编译源文件,但当源文件数量多到一定程度时,使用gcc命令就会变得较为复杂。项目构建工具make应运而生,make是一个命令工具,用于解释makefile中指令的命令工具。
在构建项目时,make工具会自动加载当前目录下一个叫makefile
或Makefile
的文件,它规定了哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,以及一些更加复杂的操作。总的来说,makefile就像是一个shell脚本一样,其中也可以执行操作系统的命令。
1. 规则
makefile的基本语法规则如下:
# 每条规则的语法格式:
target1 target2...:depend1 depend2...
command # 前面是tab制表符
command
...
总的来说,每条规则分为三部分:
- 命令:当前这条规则需要执行的动作,一般来说就是shell命令。
- eg:通过某个编译命令,生成库文件,进入目录等。
- 动作可以是多个。每个动作单独占一行,且前面有一个tab制表符。
- 依赖:规则所必须依赖的条件,在规则的命令中可以使用这些依赖。
- 例如:汇编产生的二进制目标文件
*.o
就可以作为依赖使用。 - 如果规则的命令中不需要使用到依赖,那么规则的依赖可以为空。
- 当前规则中的依赖可以是其他规则中的目标,这样就形成了规则之间的嵌套。
- 依赖可以根据要执行的命令的实际需求,指定很多个。
- 例如:汇编产生的二进制目标文件
- 目标:规则中的目标,这个目标和规则中的命令是对应的。
- 通过执行规则中的命令,会生成一个和目标同名的文件。
- 规则中可以有多个命令,因此可以通过多条命令来生成多个目标,所以目标可以有多个。
- 如果通过执行规则中的命令,并不生成任何文件,那么目标被称为
伪目标
。
EG:
测试代码结构如下:
.
├── add.c # 加法源文件
├── include
│ └── head.h # 头文件
├── main.c # 测试文件
└── sub.c # 减法文件
-
单条规则编写:
创建makefile文件,随后添加如下命令:
app:add.c sub.c main.c gcc add.c sub.c main.c -o app -I ./include/
然后在该目录下运行
make
命令即可。 -
嵌套规则编写:
app:add.o sub.o main.c # 这里使用的材料文件并不存在,因此下面要编写新的规则,用于生成这里的材料文件 gcc add.o sub.o main.c -o app -I ./include/ add.o:add.c gcc -c add.c -o add.o -I include sub.o:sub.c gcc -c sub.c -o sub.o -I include
运行
make
指令后目录结构如下:. ├── add.c ├── add.o ├── app ├── include │ └── head.h ├── main.c ├── makefile ├── sub.c └── sub.o
2. 工作原理
本节主要介绍make执行过程中的一些原理。
2.1 规则的执行
-
扫描解析顺序:
make在执行规则时,首先扫描第一条规则,如果第一条规则使用的材料是下面规则的目标,那么也会扫描下一条规则。
-
执行规则:当前规则是否执行,分为两种情况:
- 目标不存在,那么必须要执行。
- 目标存在,但存在材料的更新时间戳大于目标的更新时间戳的情况,或者材料不存在,那么就要重新执行该规则。
-
自动推导:make支持对一些文件的自动推导,比如以汇编后的
.o
文件为原料,但实际上只存在其源文件,那么make会自动为根据同名源文件汇编出二进制文件。并且此功能也会支持文件的更新,即某个源文件更新时,所有涉及该源文件的规则都会更新。
EG:
-
场景一:
# makefile规则如下: app:add.o sub.o main.c gcc add.o sub.o main.c -o app -I ./include/ add.o:add.c gcc -c add.c -o add.o -I include sub.o:sub.c gcc -c sub.c -o sub.o -I include
当前目录结构如下:
. ├── add.c ├── add.o ├── app ├── include │ └── head.h ├── main.c ├── makefile ├── sub.c └── sub.o
此时目标文件、材料均存在且为最新。
此时执行make命令:
make make: “app”已是最新。
-
场景二:
仍是场景一中的文件,但将
sub.o
删除# 执行 rm sub.o make # 输出 gcc -c sub.c -o sub.o -I include gcc add.o sub.o main.c -o app -I ./include/
可以看到,此时以
sub.o
作为目标和原料的两句命令都被重新执行了。 -
场景三:
仍是场景一中的文件,但修改add.c
# 执行 make # 输出 gcc -c add.c -o add.o -I include gcc add.o sub.o main.c -o app -I ./include/
可以看到,以
add.c
为原料的规则,和以add.o
为原料的规则全都执行。 -
场景四:
仍是场景一中的文件,但修改
add.c
,并修改makefile
如下:# makefile规则如下: app:add.o sub.o main.c gcc add.o sub.o main.c -o app -I ./include/ sub.o:sub.c gcc -c sub.c -o sub.o -I include
也就是删除了以add.o为目标,add.c为依赖的规则,随后执行make命令如下:
# 指令 make # 输出 cc -c -o add.o add.c gcc add.o sub.o main.c -o app -I ./include/
可以看到,即使没有以
add.c
为依赖的规则,且存在现成的add.o
文件,make工具依旧会在add.c
源文件更新时执行自动推导,生成新的add.o
文件,并随之执行以add.o
为依赖的规则。
3. 变量
在使用makefile时,为了使文件变得更加灵活,make提供了变量支持,共有三种变量:自定义变量
、预定义变量
、自动变量
。
3.1 自定义变量
用户自己定义的变量就是自定义变量。需要注意的是,特点如下:
- 没有类型。
- 创建时必须赋值。
- 使用时使用
$(变量名)
进行取值。
EG:
执行:
obj = add.o sub.o main.c
target = app
$(target):$(obj)
gcc $(obj) -o $(target) -I include
输出:
cc -c -o add.o add.c
cc -c -o sub.o sub.c
gcc add.o sub.o main.c -o app -I include
3.2 预定义变量
所谓预定义变量,就是makefile预先定义好的变量,用户直接拿来使用就可以了,这些预定义变量都是全大写形式。
特点:
- 预定义变量使用时也需要通过
$()
进行取值。
一些常用预定义变量:
变量名 | 含义 | 默认值 |
---|---|---|
AR | 生态静态库文件的程序名 | ar |
AS | 汇编编译器名称 | as |
CC | c语言编译器名字 | cc |
CPP | c语言预编译器名字 | $(CC)-E |
CXX | c++语言编译器名字 | g++ |
FC | FORTRAN语言编译器的名称 | f77 |
RM | 删除文件的程序名 | rm -f |
ARFLAGS | 生成静态库库文件程序的名称 | 无 |
ASFLAGS | 汇编语言编译器的选项 | 无 |
CFLAGS | C语言编译器的编译选项 | 无 |
CPPFLAGS | C语言预编译的编译选项 | 无 |
CXXFLAGS | C++语言编译器的编译选项 | 无 |
FLAGS | FORTRAN语言编译器的编译选项 | 无 |
EG:
对上一小节的代码进行替换:
obj = add.o sub.o main.c
target = app
$(target):$(obj)
$(CC) $(obj) -o $(target) $(CFLAGS) -I include
3.3 自动变量
所谓自动变量,其实就是用来代替目标文件和依赖文件的变量,通过它们可以获取到当前规则中目标文件和依赖文件的值。
特点如下:
- 自动变量只能在规则的命令中使用,不能用在依赖项和目标项中。
- 使用时无需使用
$()
取值,直接使用即可。
一些常用自动便量:
变量 | 含义 |
---|---|
$* | 目标文件的名称,但不包含后缀名 |
$+ | 表示所有的依赖文件,以空格分隔。但不会去重 |
$< | 表示第一个依赖文件 |
$? | 依赖项中,所有时间戳比目标项晚的依赖文件。 |
$@ | 目标文件的名称,包含后缀名 |
$^ | 所有依赖文件,以空格分隔。会进行去重。 |
EG:
obj = add.o sub.o main.c
target = app
$(target):$(obj)
$(CC) $^ -o $@ $(CFLAGS) -I include
4. 模式匹配
所谓模式匹配,就是将一些规则高度重合的特点抽象出来,通过模式匹配,将这多个规则抽象为一个规则,以减少操作。
一个模式匹配前的例子:
obj=add.o sub.o main.c
target=app
$(target):$(obj)
gcc $^ -o $@ -I include
add.o:add.c
gcc add.c -c
sub.o:sub.c
gcc sub.c -c
可以看到:后两条规则实际上除了文件名不一致,其余都一致,这里就可以使用模式匹配。通过makefile中提供的%
来对文件名进行匹配。
obj=add.o sub.o main.c
target=app
$(target):$(obj)
gcc $^ -o $@ -I include
# 此处通过%来匹配文件名
%.o:%.c
gcc $< -c # 通过$<匹配依赖项中的第一个
5. 函数
makefile中提供了一些函数用于简化makefile文件的编写,其特点如下:
- 所有函数均有返回值。
- 调用时形式为:
$(函数名 参数一 参数二 ...)
。不同参数之间通过空格进行
区分。
下面是两个常用的函数:
5.1 wildcard函数
作用:
用于获取指定目录下指定类型的文件名,返回值通过空格进行分隔,函数原型如下:
$(wildcard PATRTTEN1 PARTTEN2 ...)
-
参数:
-
PARTTEN指的是某个目录下匹配的文件名,例如
*.c
,~/coding/c_c++/cal/*.o
-
PARTTEN可以有多个,从而指定多个目录下的多个类型的文件,参数之间用空格间隔。
-
-
返回值:返回指定目录下匹配的所有文件的名字。
EG:
# 返回当前目录和~/coding/c_c++/test/下的所有c语言源文件名
dep=$(wildcard *.c ~/coding/c_c++/test/*.c)
5.2 patsubst
作用:按照指定的模式替换指定文件名的后缀。
函数原型:
$(patsubst pattern,replacement,text)
- 参数:共有三个,参数间以
逗号
间隔。- pattern:一个模式字符串,指出被替换的文件的后缀名,因为不关心名字,所以通常使用
%
来替换名字,同时也要指出后缀名,如%.c
。 - replacement:也是模式字符串,指出替换后的文件后缀,同样使用
%
来替换名字,同时也要指出替换后的后缀名,如%.o
。 - text:需要进行操作的文件名。
- pattern:一个模式字符串,指出被替换的文件的后缀名,因为不关心名字,所以通常使用
- 返回值:被替换后的文件名。
EG:
dep=add.c sub.c
dep1=$(patsubst %.c,%.o,$(dep))
6. 伪目标
有时在makefile中,某条规则执行并不产生目标,这时该规则的目标就是伪目标,
EG:
src=$(wildcard *.c)
obj=$(patsubst %.c,%.o,$(src))
target=app
$(target):$(obj)
gcc $^ -o $@ -I include
%.o:%.c
gcc $< -c
# 这一句并不会生成目标文件,因此clean是伪目标
clean:
rm $(obj) $(target)
此时如果想要单独执行clean
规则,那么只需:
# 输入
make clean
# 输出
rm add.o sub.o main.c app
问题:
如果当前目录下存在一个名为clean
的文件,那么由于该规则没有依赖,所以在执行该规则前比较目标和依赖的时间戳时,目标总是最新的,这会导致该条规则永远无法执行。因此需要一个办法,免去规则执行前的时间戳比较。
方法:
在makefile文件中声明clean
是一个伪目标即可,声明方法为:
.PHONY:伪目标名称
修改:
src=$(wildcard *.c)
obj=$(patsubst %.c,%.o,$(src))
target=app
$(target):$(obj)
gcc $^ -o $@ -I include
%.o:%.c
gcc $< -c
.PHONY:clean #声明clean是伪目标
clean:
rm $(obj) $(target)
7. 案例
一个案例,结构如下:
.
├── add.c # 加法文件
├── include # 头文件所在目录
│ └── head.h # 头文件
├── main.c # 测试程序
└── src # 减法文件所在目录
└── sub.c # 减法文件
编写对应的makefile文件如下:
# 指定目录下所有的C语言文件,用*匹配
src=$(wildcard *.c ./src/*.c)
# 将src中所有文件名后追替换为.o
dep=$(patsubst %.c,%.o,$(src))
# 目标文件
target=app
# 头文件所在目录
include=./include
# 第一条规则
$(target):$(dep)
gcc $^ -o $@
# 第二条规则,进行模式匹配
%.o:%.c
gcc $< -c -I $(include) -o $@
# 声明伪目标
.PHONY:clean
# 清楚本makefile生成的所有文件
clean:
rm $(target) $(dep) -f
标签:文件,gcc,sub,make,规则,add,Linux,工具,include
From: https://www.cnblogs.com/beasts777/p/17834300.html