Makefile

在没整理的文件里翻出来一篇很久以前写的关于makefile的知识, 整理一下放出来

前置知识: c++的编译过程

makefile文件是什么

Makefile最大的作用是实现对工程的自动编译

Makefile 可以简单的认为是一个工程文件的编译规则,描述了整个工程的编译和链接等规则

一个大型工程往往是由许源文件组成的,这些源文件按照其功能,类型,模块放在了若干个文件,这些文件有编译的先后顺序,这些规则都必须通过Makefile来实现

makefile文件怎么用

写完Makefile后,只需要用命令行工具(windows用cmd)进入到Makefile的同级目录,输入make即可开始编译

一般来说, 会有makefile和Makefile两个文件, 如果不指定, 默认执行 makefile, 一般Makefile由工程师编写, makefile由客户编写

makefile文件工作原理

运行规则

如果这个工程没有编译过,那么我们的所有 C 文件都要编译并被链接。

如果这个工程的某几个 C 文件被修改,那么我们只编译被修改的 C 文件,并链接目标程序

如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的 C 文件,并链接目标程序

工作流程

创建程序(make程序)首先读取makefile文件,然后再激活编译器,汇编器,资源编译器和连接器以便产生最后的输出,最后输出并生成的通常是可执行文件

make 会在当前目录下找名字叫“Makefile”或“makefile”的文件。

如果找到,它会找文件中的第一个目标文件 ( target ),并把这个文件作为最终的目标文件。

如果 target 文件不存在,或是 target 所依赖的后面的 .o 文件的文件修改时间要比 target 这个文件新,那么,他就会执行后面所定义的命令来生成 target 这个文件。

如果 target 所依赖的.o 文件也存在,那么 make 会在当前文件中找目标为.o 文件的依赖性,如果找到则再根据那一个规则生成.o 文件

当然,你的 C 文件和 H 文件是存在的啦,于是 make 会生成 .o 文件,然后再用 .o 文 件声明 make 的终极任务,也就是执行文件 target 了

这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。

makefile文件编写

makefile文件里有什么

Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。

1、显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。

2、隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。

3、变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。

4、文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根 据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲 述。

5、注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“#”。

最后,还值得一提的是,在Makefile中的命令,必须要以[Tab]键开始。

1
2
3
4
5
6
7
8
9
10
11
Targets...: Prerequisites...
Command
...

或者

targets : prerequisites ; command
command
...

如果mommand命令与prerequisites不在一行, 那command必须要以[Tab]键开始, 如果和 prerequisites 在一行, 那么可以用分号做为分隔

Target: 目标

Prerequisites: 目标依赖

Command: 规则的命令行

eg:

有以下文件

main.c tool1.c tool1.h tool2.c tool2.h

makefile文件写这样

1
2
3
4
5
6
7
8
9
10
mytool:main.o tool1.o tool.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

关于变量

image-20230113101144008
1
2
3
4
5
6
7
8
9
10
11
12
13
14
OBJS=main.o tool1.o tool.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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
OBJS=main.o tool1.o tool.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

$^代指上一句中的target

$@代指目标文件

1
2
3
4
5
6
7
8
9
10
OBJS=main.o tool1.o tool.o
CC=gcc
CFLAGS+=-c -Wall -g

mytool:$(OBJS)
$(CC) $^ -o $@
%.o:%.c
$(CC) $^ $(CFLAGS) -o $@
clean:
$(RM) *.o mytool -r
1
2
%.o:%.c
$(CC) $^ $(CFLAGS) -o $@

代表一个函数, %表示通配

补充:

有一些函数

image-20230113112911385
1
2
3
4
5
6
7
8
9
10
11
12
src= $(wildcard ./*.c)
OBJS=$(patsubst %.c, %.o, &(src))
target=mytool
CC=gcc
CFLAGS+=-c -Wall -g

$(target):$(OBJS)
$(CC) $^ -o $@
%.o:%.c
$(CC) $^ $(CFLAGS) -o $@
clean:
rm *.o mytool -rf

关于make clean命令:

实际上是指定执行了clean命令, 他没有依赖项, 执行的是shell命令

假设在当前路径下有一个文件叫clean, 那么运行make clean时总是会显示已是最新

解决方案: 把clean设为伪目标

1
2
3
.PHONY:clean
clean:
rm *.o mytool -rf