【Linux】6:自动化构建工具-make/Makefile
2026/7/5 13:30:11 网站建设 项目流程

目录

一、背景

二、基本使用

2.1 make/makefile是什么

2.2 makefile的基本概念和使用

2.3 项目清理

2.4 理解总是被执行

2.5 推导过程

三、扩展

3.1 makefile文件中变量的使用

3.2 扩展一

3.3 扩展二

3.4 扩展三

3.5 扩展四

3.6 扩展五

四、总结


一、背景

  • 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力
  • 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
  • makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
  • make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
  • make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。

二、基本使用

2.1 make/makefile是什么

make是一个命令,makefile是一个文件。

2.2 makefile的基本概念和使用

首先我们创建一个code.c的源文件,如下所示:

如果我们需要编辑的话就需要使用gcc或者g++;gcc需要我们自己来敲,如果当前工作目录下的源文件非常多的话,一个一个敲太浪费时间了,效率太低了。

那么当源文件非常多的时候,我们如何编译呢?

我们可以使用自动化构建工具-make来创造一个makefile(Makefile)文件,如下所示:

我们执行make命令之后,就会在当前工作目录下查找一个makefile的文件,然后根据makefile文件里面的内容生成code来运行。

在我们编写的makefile文件中:

第一行:code:code.c表示依赖关系,我们要产生code文件需要依赖code.c文件

第二行:gcc code.c -o code表示依赖方法,从code.c到code需要用到的方法,必须以tab键开头

在makefile文件中必须要有依赖方法和依赖关系。

2.3 项目清理

我们不仅想让代码进行编译,我们还想让代码进行清理,清理如下所示:

在我们执行make clean之前,我们当前目录下是由code文件的,当我们执行make clean之后,就会执行rm -f code这一条命令来清理code文件。

此时我们就可以将一个源文件使用make和make clean的命令组合,就可以自动化的进行代码的编译和清理了,如下所示:

第一行:.PHONY:clean表示使用.PHONY来修饰clean,叫做伪目标。

第二行:clean:表示依赖关系,冒号后面为空,表示clean不需要依赖任何文件

第三行:rm -f code表示依赖方法,

问题一:为什么我们想要产生code文件只需要make呢?而clean需要执行make clean呢?

我们将makefile文件中的代码顺序换一下,如下所示:

然后再次执行make命令,就会执行rm -f code这条指令了,如下所示:

然后make code就是执行gcc code.c -o code这条命令了,如下所示:

make命令在扫描makefile文件的时候,是从上往下扫描,默认形成第一个目标文件,也就是使用第一个目标文件的依赖关系形成第一个目标文件。一旦把第一个执行了,后面的依赖关系生成的依赖文件就不执行了,除非你给make传入一个目标文件,你要形成谁,例如make code。

所以,我们默认情况下,写makefile文件时喜欢把我们形成的可执行程序放在最前面。如下所示:

问题二:.PHONY:clean是什么意思?

我们在makefile文件中将这个去掉,如下所示:

然后使用make和make clean来编译,执行和清理文件,如下所示:

发现并不影响我们make的具体操作,为什么makefile文件里面需要加上.PHONY:clean来声明clean是伪目标呢?

首先.PHONY类似一种建议的关键字,来建议这个clean总是被执行,表示对应的依赖关系和依赖方法总是被执行。当我们执行make clean的时候,表示这组依赖关系rm -f code总是会被执行。

2.4 理解总是被执行

在理解总是被执行之前,我们先理解一下总是不被执行,如下所示:

在我们的makefile文件里面,有两个目标文件,一个是code,一个是clean,其中code是没有被.PYHOY修饰的。

我们在第一次执行make来执行编译命令之后,后面的三次使用make来执行编译命令就不让我来执行了。

我们可以在使用.PYHOY来修饰code目标文件,然后再次执行多次make命令,如下所示:

我们发现,我们使用.PYHOY来修饰code目标文件之后,每次make都会执行对应的依赖方法。

问题一:为什么我们在第一次执行make来执行编译命令之后,后面的三次使用make来执行编译命令就不让我来执行了呢?

因为我的源代码没有被更新过,没有更新过的话,就没有必要重新编译形成目标文件。也就是说,我的源代码没有被更新过,目标文件也就不需要重新编译形成。因为我的文件没有改过,为什么要重新再次编译呢?只有在改了源文件之后,才需要重新编译。当我们有100个源文件,我们只修改了10个,再次使用make命令来编辑,就只会重新编译那10个修改过的文件,来提高编译效率。所以我们在形成可执行程序的时候默认不使用.PYHOY来修饰,来避免总是被执行,降低效率。

问题二:make怎么知道目标文件code和源文件code.c谁新谁旧呢?

在linux系统中,一切皆文件,而其中每一个文件都有相关联的三个时间。我们可以使用stat命令来查看,如下所示:

三个时间分别是Access,Modify,Change。

我们知道文件=内容+属性

如果只修改文件的内容Modify时间就会被更新。

如果只修改文件的属性Change时间就会被更新。

如果不对文件属性和内容做修改,只查看文件内容,Access时间就会被更新(不会每一次查看就会更新,一般是查找多次才会被更新)。

一般我们修改文件的内容,会更新Modify时间和Change时间,因为修改内容之后,文件大小会发生变化,Modify时间也会发生变化,文件大小是文件的属性,Modify时间也是文件的属性。

我们回到问题,make怎么知道目标文件code和源文件code.c谁新谁旧呢?因为每一个文件都用三个时间来表示文件在哪一个时间被修改了,make命令在执行依赖方法会查看Modify时间

如果code的Modify时间在code.c的Modify时间之前,就会执行依赖方法,

如果code的Modify时间在code.c的Modify时间之后,就会不会执行依赖方法,

总结:能不能编译,主要就是看目标文件和源文件的Modify时间

我们可以使用touch来修改文件的时间,如下所示:

我们在使用.PYHOY来修饰目标文件之后,就会让make命令不关注Modify时间,可以总是被执行。

.PHONY:让make忽略源文件和可执行目标文件的M时间对比

2.5 推导过程

实际上我们的code目标文件不是依赖code.c文件形成的,而是依赖code,o文件形成的,而code.o是依赖code.s文件形成的,code.s是依赖code.i文件形成的,code.i文件是依赖code.c文件形成的。这就形式了一套依赖链。makefile文件如下所示:

make是如何工作的,在默认的方式下,也就是我们只输入make命令。那么:

  1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
  2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到code这个文件,并把这个文件作为最终的目标文件。
  3. 如果code文件不存在,或是code所依赖的后面的code.o文件的文件修改时间要比code这个文件新(可以用touch测试),那么,他就会执行后面所定义的命令来生成code这个文件。
  4. 如果code所依赖的code.o文件不存在,那么make会在当前文件中找目标为code.o文件的依赖性,如果找到则再根据那一个规则生成code.o文件。(这有点像一个堆栈的过程)
  5. 当然,你的C文件和H文件是存在的啦,于是make会生成code.o文件,然后再用code.o文件声明make的终极任务,也就是执行文件hello了。
  6. 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
  7. 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。
  8. make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。

这样写makefile文件我们只是介绍一下依赖链和递归过程,一般我们是不会这样写的。

三、扩展

3.1 makefile文件中变量的使用

makefile允许我们去定义变量,如下所示:

我们定义了一个变量BIN,如果需要使用这个变量,需要使用${BIN},我们执行make命令之后,会显示echo code,并且执行这条命令,如下所示:

如果我们不想让echo code这条命令显示,可以在makefile文件里面的echo ${BIN}前加一个@符号,如下所示:

3.2 扩展一

运行如下所示:

3.3 扩展二

其中在依赖关系中:

1:$@表示目标文件名字,也就是依赖关系里面的${BIN},也是code

2:$^表示源文件名字,也就是依赖关系里面的${SRC},也是code.c

3:如果源文件有很多个,$^统一代表这些源文件。

运行如下所示:

3.4 扩展三

我们不想看到依赖关系,我们可以在依赖关系前面加上@,但是我们又需要看到过程,如下所示:

3.5 扩展四

我们在实际项目中不用源文件直接生成可执行程序,而是先生成目标文件OBJ,在链接形成可执行程序,如下所示:

3.6 扩展五

如果我们有100个源程序呢?我们需要在变量定义的时候SRC写100个吗?如下所示:

首先使用命令创建100个源文件:

count=1; while [ $count -le 100 ]; do touch code${count}.c; let count++; done

四、总结

makefile文件的最终格式如下:

1 BIN=proc.exe
2 CC=gcc
3 #SRC=$(shell ls *.c)
4 SRC=$(wildcard *.c)
5 OBJ=$(SRC:.c=.o)
6 LFLAGS=-o
7 FLAGS=-c
8 RM=rm -f
9
10 ${BIN}:${OBJ}
11 @${CC} ${LFLAGS} $@ $^
12 @echo "linking ... $^ to $@"
13
14 %.o:%.c
15 @${CC} ${FLAGS} $<
16 @echo "compling ... $< to $@"
17
18 .PHONY:clean
19 clean:
20 ${RM} ${BIN} ${OBJ}

1:SRC表示的是当前目录下的全部.c文件

2:OBJ表示的是所有.c文件的同名.o文件

3:@符号指的是形成目标程序,不显示依赖关系

4:$@表示的是该依赖关系的目标文件

5:$^表示的是该依赖关系的源文件

6:%.o表示的是当前目录下的所有.o文件

7:%.c表示的是当前目录下的所有.c文件

8:%<表示的是,将所有的源文件生成对应的.o文件。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询