Makefile_简单入门

简单学一下 Makefile 的编写规则~🧐

前言

首先应该知道 Makefile 是用来给 Linux(Unit)平台下 Make 工具描述源程序之间的相互关系并自动维护编译工作的文件。
Makefile 文件需要按照某种特定语法编写,文件中需要说明如何编译各个源文件并链接生成可执行文件,并要求定义源文件之间的依赖关系。
总而言之,Make 是工具,用户通过编写 Makefile 来告诉 Make 工具如何执行编译工作。
最后,需要指出的是,Windows 下也可以通过 MingW 来安装 Make 工具。

下面,以一个例子来说明如何编写 Makefile,此例子来源于李慧芹 - Makefile 工程文件的编写规则

工程结构

这个例子的工程结构比较简单,直接看源码就可以知道对应的依赖关系:

main.c
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>
// 在 main.c 中引入两个头文件,编译 main.c 时,需要提前编译好这两个文件
#include "tool1.h"
#include "tool2.h"

int main() {
mytool1();
mytool2();

exit(0);
}

.c文件中写好函数的定义。

tool1.c
1
2
3
4
5
6
#include <stdio.h>
#include "tool1.h"

void mytool1() {
printf("tool1 print\n\n");
}

.h文件中写好函数的声明和条件编译规则,以免重复编译。

tool1.h
1
2
3
4
5
6
#ifndef TOOL1_H__
#define TOOL1_H__

void mytool1();

#endif

tool2如法炮制。

tool2.c
1
2
3
4
5
6
#include <stdio.h>
#include "tool2.h"

void mytool2() {
printf("tool2 print\n");
}

tool2.h
1
2
3
4
5
6
#ifndef TOOL2_H__
#define TOOL2_H__

void mytool2();

#endif

编写

现在,开始编写 Makefile,需要说明的是,工程的目录下可以存在多个名称为Makefile的文件,但 Make 工具认为小写的优先级更高。
Makefile 的编写规则比较简单,跟脚本有点类似,语法如下:

1
2
target: prerequisites
command

其中,target是要生成的文件,prerequisites就是编译target所需要的依赖文件,而command就是在终端输入的编译命令了。有一点需要注意的是,command前需要有一个tab符号,且不能用空格代替,不然 Make 识别不出来
有了上面的知识,就可以写出下面的 Makefile 了:

m1
1
2
3
4
5
6
7
8
9
mytool: main.o tool1.o tool2.o
gcc main.o tool1.o tool2.o -o mytool

main.o: main.c
gcc main.c -c -Wall -g -o main.o
tool1.o: tool1.c
gcc tool1.c -c -Wall -g -o tool1.o
tool2.o: tool2.c
gcc tool2.c -c -Wall -g -o tool2.o

此时,在终端输入$ make后,Make 工具就会按照 Makefile 进行编译。
编译完成后,可以发现目录下,出现了很多.o的临时文件,需要删除,手动删除太麻烦,同样借助 Makefile,只需要在下面再加上:

m2
1
2
3
4
5
6
7
8
9
10
11
12
mytool: main.o tool1.o tool2.o
gcc main.o tool1.o tool2.o -o mytool

main.o: main.c
gcc main.c -c -Wall -g -o main.o
tool1.o: tool1.c
gcc tool1.c -c -Wall -g -o tool1.o
tool2.o: tool2.c
gcc tool2.c -c -Wall -g -o tool2.o

clean:
rm *.o mytool -rf

此时,使用 Make 命令编译时就会自动删除多余的.o文件,并生成一个mytool的可执行文件。当然,也可以直接在终端输入$ make clean来清除.o文件。

简化

观察 m2,可以发现有很多重复的部分,而 Makefile 允许我们定义变量来取代重复且冗长的部分,而变量的引用需要使用$()来完成。
此时,我们可以下面的 Makefile 了:

m3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
OBJS=main.o tool1.o tool2.o
CC=gcc
CFLAGS+=-c -Wall -g
mytool: $(OBJS)
$(CC) $(OBJS) -o mytool

main.o: main.c
$(CC) main.c $(CFLAGS) -o main.o
tool1.o: tool1.c
$(CC) tool1.c $(CFLAGS) -o tool1.o
tool2.o: tool2.c
$(CC) tool2.c $(CFLAGS) -o tool2.o

clean:
$(RM) *.o mytool -r

注意,最下面使用RM变量代替了rm -f

继续简化

实际上,Makefile 提供了一些自动变量运行我们进一步简化 Makefile。比如,在当前某一行的实现上,我们可以用$^来代替所有的依赖文件,用$@来代替目标文件。此时 Makefile 就可以写成:

m4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
OBJS=main.o tool1.o tool2.o
CC=gcc
CFLAGS+=-c -Wall -g
mytool: $(OBJS)
$(CC) $^ -o $@

main.o: main.c
$(CC) $^ $(CFLAGS) -o $@
tool1.o: tool1.c
$(CC) $^ $(CFLAGS) -o $@
tool2.o: tool2.c
$(CC) $^ $(CFLAGS) -o $@

clean:
$(RM) *.o mytool -r

写到这里,你会发现,简化后的文件中,好像有几行都是一样的,是不是还可以简化呢?答案是肯定的...😂
在 Makefile 中%是一个通配符,可以用来表示当前某行实现上的相同文件名。此时,Makefile 就可以写成:

m5
1
2
3
4
5
6
7
8
9
10
11
OBJS=main.o tool1.o tool2.o
CC=gcc
CFLAGS+=-c -Wall -g
mytool: $(OBJS)
$(CC) $^ -o $@

%.o:%.c
$(CC) $^ $(CFLAGS) -o $@

clean:
$(RM) *.o mytool -r

这样写,就更简单了。

结语

这么一通通简化下来,看起来好像是越写越简单了,可读性是越来越差了...😅
不过 Makefile 作为一个工具而言,学会这些也差不多了,剩下的,等遇到了再查书~


Buy me a coffee ? :)
0%