Makefile

什么是Makefile

Makefile 是一个特殊的文件,通常用于管理和自动化软件的构建过程。它包含了一系列的指令,这些指令定义了如何编译和链接程序。Makefile 与 make 工具一起使用,make 是一个构建自动化工具,它会查找 Makefile 文件,并根据其中的指令来构建程序。

Makefile 的基本结构通常包括以下几个部分:

  1. 目标(Targets):这是构建过程中的一个步骤,比如编译一个源文件或安装程序,即最终要生成的目标文件
  2. 依赖(Dependencies):目标需要的文件,如果这些文件发生变化,目标将被重新构建,即目标文件由哪些文件生成
  3. 命令(Commands):生成目标的命令序列,即通过执行该命令,使依赖文件生成目标文件

基本规则

1
2
Targets:Dependencies
(tab)Commands

tab不能用空格代替

Makefile文件的命名:makefile或者Makefile

下面是一个简单的makefile文件

1
2
main: main.c func1.c func2.c
gcc -o main main.c func1.c func2.c

然后执行make命令,即可生成目标文件

Makefile工作原理

  1. 若想生成目标,检查规则中的所有的依赖文件是否都存在

    • 如果有的依赖文件不存在,则向下搜索规则,看是否有生成该依赖文件的规则

    • 如果有规则用来生成该依赖文件,则执行规则中的命令生成依赖文件

    • 如果没有规则用来生成该依赖文件,则报错

  2. 如果所有依赖都存在,检查规则中的目标是否需要更新,必须先检查它的所有依赖,依赖中有任何一个被更新,则目标必须更新(检查的规则是哪个时间大哪个最新)

    • 若目标的时间>依赖的时间,不更新

    • 若目标的时间<依赖的时间,则更新

总结

  • 分析各个目标和依赖之间的关系
  • 根据依赖关系自底向上执行命令
  • 根据依赖文件的时间和目标文件的时间确定是否需要更新
  • 如果目标不依赖任何条件,则执行对应命令,以示更新(如:伪目标)

刚刚的例子中,我们不难发现,只要修改了其中一个文件,那么所有的文件都要重新编译,非常浪费资源,因此我们可以通过Makefile的工作原理修改一下刚刚的简单案例

1
2
3
4
5
6
7
8
9
10
11
main: main.o func1.o func2.o
gcc -o main main.o func1.o func2.o

main.o: main.c
gcc -c main.c -I./

func1.o: func1.c
gcc -c func1.c

func2.o: func2.c
gcc -c func2.c

这时候,执行make命令,会先检查依赖条件是否都存在

  • 如果不存在,会向下寻找生成依赖文件的规则。
  • 如果存在,就会比较目标时间和依赖的生成时间,来决定是否更新目标。

Makefile中的变量

但是,如果一个项目有多个文件呢?难道我们要一个一个的添加对应的规则吗?那如果是这样编写makefile文件就太麻烦了,因此,在makefile中提供了变量,来使我们简化这些操作。

在 makefile 中使用变量有点类似于C语言中的宏定义,使用该变量相当于内容替换,使用变量可以使makefile易于维护,修改起来变得简单。makefile 有三种类型的变量:

  • 普通变量
  • 自带变量
  • 自动变量

普通变量

  • 变量定义直接用=
  • 使用变量值用 $(变量名)

例:下面是变量的定义和使用

1
2
3
4
5
6
7
8
9
10
11
obj = abc		# 定义变量并赋值
bar = $(obj) # 使用变量,$(变量名)

#定义了两个变量: obj、bar, 其中 bar 的值是 obj 变量值的引用。
#除了使用用户自定义变量,makefile 中也提供了一些变量(变量名大写)供用户直接使用,
#我们可以直接对其进行赋值:

CC = gcc #arm-linux-gcc
CPPFLAGS: #C预处理的选项 -I
CFLAGS: #C编译器的选项 -Wall -g -c
LDFLAGS: #链接器选项 -L -l

自动变量

自动变量只能用在命令中,不能用在条件上

  • $@:表示规则中的目标
  • $<:表示规则中的第一个条件
  • $^:表示规则中的所有条件,组成一个列表,以空格隔开,如果这个列表中有重复的项则消除重复项。

模式规则

如果有多个重复的规则,可以使用模式规则

在规则的目标定义中要包含%,它表示一个或多个
在依赖条件中同样可以使用%,依赖条件中的%的取值取决于其目标

比如:

1
2
3
4
5
6
7
8
9
#多个重复的规则
main.o: main.c

fun1.0: fun1.c

fun2.o: fun2.c

#用模式规则则为
%.o: %.C

Makefile函数

makefile中的函数有很多,这里介绍其中最常用的两个:

  • wildcard:查找指定目录下的指定类型的文件

src=$(wildcard *.c)找到当前目录下所有后缀为.c的文件,赋值给 src

  • patsubst:匹配替换

obj=$(patsubst %.c,%o,$(src))把src变量里所有后缀为.c的文件替换成.o

在makefile 中所有的函数都是有返回值的。

如当前目录下有main.c fun1.c fun2.c

src=$(wildcard*.c)等价于 src=main.c fun1.c fun2.c

obj=S(patsubst %.c,%o,$(src))等价于 obj=main.o fun1.o fun2.o

把刚刚的例子修改为

1
2
3
4
5
6
7
8
9
10
11
12
13
target = main		        #自定义目标变量
src=$(wildcard *.c) #获取目录下的所有.c文件
obj=$(patsubst %.c,%o,$(src)) #自定义依赖文件变量,把.c全部换成.o文件
CC = gcc #定义gcc编译器,CC大写
CPPFLAGS = -I./ #定义C预处理的选项

$(target): $(obj)
#$(CC) -c $(target) $(obj)
$(CC) -c $@ $^

%.o: %.c
$(CC) -c $< $(CPPFLAGS)

Makefile的清理操作

用途:清除编译生成的中间.o文件和最终目标文件

命令 make clean

如果当前目录下有同名 clean 文件,则不执行 clean 对应的命令

clean是自定义的,可以修改为clear等你觉得合适的。

解决方案:

  • 伪目标声明:.PHONY:clean
  • 声明目标为伪目标之后,makefile将不会检查该目标是否存在或者该目标是否需要更新

clean 命令中的特殊符号:

  • -此条命令出错,make 也会继续执行后续的命令。如:-rm main.o

​ rm -f:强制执行,比如若要删除的文件不存在,使用 -f 不会报错

  • @不显示命令本身,只显示结果。如:@echo clean done

makefile最终版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
target = main		        #自定义目标变量
src=$(wildcard *.c) #获取目录下的所有.c文件
obj=$(patsubst %.c,%o,$(src)) #自定义依赖文件变量,把.c全部换成.o文件
CC = gcc #定义gcc编译器,CC大写
CPPFLAGS = -I./ #定义C预处理的选项

$(target): $(obj)
#$(CC) -c $(target) $(obj)
$(CC) -c $@ $^

%.o: %.c
$(CC) -c $< $(CPPFLAGS)

.PHONY:clean
clean:
-rm -f $(obj) $(target)

make 命令默认执行第一个出现的目标,可通过 make dest 指定要执行的目标

make -f:-f 执行一个自定义 makefile 文件名称,

使用 make 执行自定义的 makefile 文件:

  • make -f mymake

  • 执行清理操作:make -f mymake clean