简单样例演示
我们一句话来概括make/Makefile是什么:
- make是一个命令;
- Makefile是一个文件。(我们的makefile首字母既可以大写也可以小写:M/m)
我们来快速的见见Makefile,make:
注意:
- 图中的Tab键开头是语法,不可以弄错了;
- Makefile是当前目录下的文件。
Makefile的基本概念
依赖关系与依赖方法
code:code1:这个为依赖关系
gcc -o code code1:这个为依赖方法
我们的依赖对象可以有许多个,这个称为依赖列表:code1 code2 code3........
依赖关系不可以出现问题,依赖方法更不能无法无天🤕
完善Makefile
对于上述代码,我们不仅仅想要编译我们的代码,也想对代码进行清理,我们可以打开Makefile
在Makefile中,.PHONY
是一个特殊的伪目标,用来声明那些不对应实际文件的目标。这样,即使存在同名的文件,make
命令也会执行这些伪目标后面的命令。这对于定义清理(clean)任务特别有用,因为清理任务通常需要删除文件,而这些文件可能与Makefile中的目标同名。
# 声明.PHONY,使得clean成为一个伪目标
.PHONY: clean# 定义clean目标,用于清理工作
clean:rm -f code
运用.PHONY,我们可以理解为.PHONY是Makefile当中的一个修饰符:
在.PHONY后面修饰的东西,我们将其称为伪目标
我们就可以使用该目标:clean进行依赖关系,依赖关系是可以为空的,后面的代码就是clean的依赖方法。
在这个Makefile中,我们定义了一个名为clean
的伪目标,它执行rm -f code
命令来删除名为code
的文件。这里有几个要点:
-
.PHONY: clean
:这行声明了clean
是一个伪目标,这样即使存在名为code
的文件,make clean
命令也会执行。 -
clean:
:这行定义了clean
目标的开始。 -
rm -f code
:这是实际执行删除文件code
的命令。-f
选项表示强制删除,并且不提示。
要使用这个Makefile,只需在命令行中输入make clean
,就会执行定义在clean
目标下的命令,删除code
文件:
lfz@hcss-ecs-ff0f:~/lesson/mk$ make clean
rm -f code
如果想要在清理时删除更多的文件或目录,可以在rm -f code
命令后面添加更多的删除命令
我们的到此,Makefile的内容是:
code:code.cgcc -o code code.c
.PHONY:clear
clear:rm -f code
我们使用make就会执行gcc操作,我们make clean就会执行rm操作,我们对于该Makefile的内容不需要make code就可以进行gcc操作,我们将Makefile的相关代码位置交换,我们发现:
make
命令在处理Makefile
时,会从上到下扫描文件,并默认执行第一个目标(目标规则)。如果你不指定任何目标,make
就会执行Makefile
中的第一个目标。如果你指定了特定的目标,make
就会执行与该目标相关联的命令。
.PHONY:
我们来谈谈.PHONY: clean
,如果我们将该代码在Makefile中删除,我们发现其实再make clean也是可以进行很好的使用的,那我们为什么在Makefile中还要写.PHONY: clean呢?
即使在Makefile中删除了.PHONY: clean
这一行,make clean
命令仍然可以工作,这是因为make
默认会认为所有的目标都是文件名,如果这些文件不存在,make
就会尝试创建它们。但是,对于clean
这样的目标,我们并不希望make
去创建一个名为clean
的文件,就像make code
,而是希望执行一系列清理命令。
使用.PHONY: clean
有几个好处:
-
明确意图:它明确告诉
make
,clean
不是一个实际的文件,而是一个需要执行的命令序列。 -
避免冲突:如果没有
.PHONY
声明,而当前目录下恰好有一个名为clean
的文件,make
就会认为make clean
的目的是更新这个文件,而不是执行清理操作。 -
防止误操作:如果没有
.PHONY
声明,make
在执行make clean
时会检查clean
文件是否存在以及是否比依赖文件新。如果clean
文件存在且较新,make
会认为目标是最新的,不会执行任何操作,这可能会导致清理命令不被执行。 -
依赖关系:在复杂的Makefile中,
clean
目标可能依赖于其他目标。使用.PHONY
可以确保即使这些依赖目标是文件,make clean
也会被执行。 -
跨目录构建:在多目录构建系统中,
.PHONY
目标可以确保在任何子目录中调用make clean
都会执行清理操作,而不受文件系统结构的影响。
总的来说,虽然在某些情况下不声明.PHONY
也能工作,但为了代码的清晰性和可维护性,以及避免潜在的错误,最好还是在Makefile中声明.PHONY: clean
。
make的时间戳:
make
通过比较文件的时间戳来判断文件是否需要重新编译。如果源文件的时间戳没有更新(例如,通过某些工具修改文件内容但没有改变时间戳),make
就可能认为文件没有变化,从而不进行重新编译:
在Linux下,一切皆文件,文件=内容+属性,所以我们修改文件无非就是修改内容或者修改属性,要们就全部修改,我们可以使用:
stat 文件名
stat
命令是一个用于显示文件或文件系统状态信息的命令行工具
查看文件导致的Access比较特殊,我们待会儿再谈。
我们在此基础上,通过vim打开code.c文件进行修改,再次stat发现:
我们通过vim指令达到了对code.c文件内容的修改,Modify时间戳变化是应该的,但是我们好像也没有刻意的去修改文件属性,为什么Change的时间戳也会发生变化?
因为我们修改文件的同实,我是在code.c文件中加入了一行printf代码,这样文件的size(大小)就发生了变化,所以在我们修改文件内容的时候,文件内容和文件属性被同时修改了,导致了Modify和Change的时间戳发生了变化,其实Modify的时间戳也是文件的属性,所以通过文件内容的修改,他们两者是同时变化的。
我们只想修改文件属性,可以使用权限的变化:chmod:
现在我们来说说Access时间戳:atime=Access Time的缩写
访问时间戳(atime)记录了文件最后一次被读取的时间。虽然它在某些情况下有用,但每次文件被访问时,操作系统都需要更新atime,这会导致额外的磁盘I/O开销,降低系统性能,尤其是在高并发的文件访问场景下,会增加磁盘I/O压力,影响文件访问效率。此外,维护atime还会占用一定的存储空间和管理成本。为了提高性能,可以考虑禁用atime更新或使用relatime等优化策略,以减少atime更新的频率和开销。(因为我们其实对文件的访问是占比很大的,相对于文件内容,属性的修改)
(为了提高性能,文件系统可能会对 atime 的更新进行优化,但这种优化通常不会影响 ctime 的更新机制,因为时间戳也是属性)
我们make对新老文件编译的更新本质就是根据Modfiy Time时间戳
注意:是更新,不是再生
之前学习过:touch是用来新建文件的,但是touch的核心作用是更改文件对应的时间,我们可以使用man touch进行查看:
我们可以:
touch 当前目录下存在的文件##默认更新该文件的所有时间戳
touch 当前目录下不存在的文件##新建一个文件
我们上图的时间轴也是PHONT的原理:也就是.PHONY修饰的伪目标为什么总能被执行:
我们用.PHONY修饰code:
.PHONY:code
那么,我们的make(make code)就可以总被执行了:
但是对于形成我们自己的可执行程序,我们一般不要使用.PHONY修饰, 因为我们就是要使用时间对比来区分新旧,使编译过的代码不要再编译了,节省编译时间。
Makefile的推导规则:
Makefile在翻译的时候,可以理解为Makefile在自己的内部会维护一个类似于栈的东西,Makefile文件会被make命令从上到下进行扫描,扫描到第一个依赖关系(要形成code,需要依赖code.o)但是当前的code.o根本就不存在,这时候将code.o所依赖的方法先入栈,然后跳过该依赖方法找到下一个要形成的目标code.o,需要依赖code.s.........(不存在的依赖对象的依赖方法先无脑入栈)
一直到依赖对象是code.c是存在的,那么就开始依次出栈并执行:
所以我们的Makefile的推导方法就是根据依赖链,不存在的先将其依赖方法进行入栈,最后出栈实现,将整体的依赖链进行链接,上面这么麻烦的去写是为了理解其推导逻辑,正常我们是就刚刚开始写的那样就ok了的。
变量版的Makefile:
变量版的 Makefile
是指在 Makefile
中使用变量来简化和优化构建规则。使用变量可以提高 Makefile
的可读性、可维护性和可复用性。以下是关于变量版 Makefile
的详细介绍:
- 定义变量:在
Makefile
中,可以使用=
或:=
来定义变量。例如:
这里定义了两个变量:CC = gcc CFLAGS = -g -Wall
CC
表示编译器,CFLAGS
表示编译选项。 - 使用变量:在规则中,可以通过
$(变量名)
或${变量名}
的方式来引用变量。例如:
在这里,all: hello hello: hello.o$(CC) $(CFLAGS) -o hello hello.o hello.o: hello.c$(CC) $(CFLAGS) -c hello.c
$(CC)
和$(CFLAGS)
分别被替换为gcc
和-g -Wall
,从而简化了命令的书写。
作用:
- 提高可读性:使用变量可以将复杂的命令分解为多个部分,使
Makefile
更加清晰易懂。例如,将编译器和编译选项分别定义为变量,可以避免在命令中重复书写复杂的参数。 - 提高可维护性:当需要修改编译器或编译选项时,只需在变量定义处进行修改,而不需要逐个修改每个命令。这大大减少了维护的工作量,降低了出错的可能性。
- 提高可复用性:通过定义通用的变量,可以在多个规则中复用相同的设置。例如,可以在不同的目标文件中使用相同的编译器和编译选项,避免了重复定义。
以下是一个简单的变量版 Makefile
示例:
BIN:=code
CC:=gcc
SRC:=code.c
FLAGS:=-o
RM=rm -f$(BIN):$(SRC)@$(CC) $(FLAGS) $@ $^@echo "linking $^ to $@".PHONY:clean
clean:@$(RM) $(BIN)@echo "remove... $(BIN)".PHONY:test
test:@echo $(BIN)@echo $(CC)@echo $(SRC)@echo $(FLAGS)@echo $(RM)
%.o: %.c
是 Makefile
中的一种模式规则(pattern rule),用于定义如何从 .c
文件生成对应的 .o
文件。这种规则使用了通配符 %
来表示文件名的模式匹配:
BIN:=code
CC:=gcc
SRC:=code.c
OBJ:=code.o
LFLAGS:=-o
FLAGS:=-c
RM=rm -f$(BIN):$(OBJ)$(CC) $(LFLAGS) $@ $^
%.o:%.c$(CC) $(FLAGS) $<
这种模式规则可以应用于多个文件,而不需要为每个文件单独编写规则。例如,如果在当前目录下有 main.c
和 utils.c
,那么 make
会自动应用这条规则来生成 main.o
和 utils.o
。(如果有1000个.c文件,我们就不需要自己手写了)
当然,我们当前的SRC只是code.c这一个文件,为了传入更多.c文件,我们有以下两种方法:
$(shell ...)
我们可以使用shell来达到对外部命令使用后文件的提取,也可以达到(如果有1000个.c文件,我们就不需要自己手写了):
SRC:=$(shell ls *.c)
SRC:=$(shell ls *.c)
是 Makefile
中的一行代码,用于将当前目录下所有 .c
文件的列表赋值给变量 SRC。
$(shell ...)
:这是一个 make
函数,用于执行 shell 命令并捕获其输出。shell
函数将命令的输出作为字符串返回。
还可以使用:
$(wildcard ...)
在 Makefile
中,wildcard
函数用于展开通配符模式,并返回匹配该模式的所有文件名的列表。它是一个内置的 make
函数,通常用于动态地获取文件列表,而不需要执行外部的 shell 命令。
SRC := $(wildcard *.c)
使用 wildcard
函数来获取当前所在目录的所有 .c
文件的列表
接下来对于OBJ这.o文件的变量,我们该这么获取.o文件呢?准确来说是需要所有源文件的.o,这样的话,就会根据所有的.c生成.o时,就可以自动化的进行编译,就可以将所有的OBJ链接成可执行程序,我们可以使用:
OBJ:=$(SRC:.c=.o)
我们结合以上代码:
BIN:=code
CC:=gcc
SRC:=$(shell ls *.c)
OBJ:=$(SRC:.c=.o)
LFLAGS:=-o
FLAGS:=-c
RM=rm -f$(BIN):$(OBJ)$(CC) $(LFLAGS) $@ $^
%.o:%.c$(CC) $(FLAGS) $<.PHONY:clean
clean:$(RM) $(BIN) $(OBJ).PHONY:test
test:@echo $(SRC)@echo $(OBJ)
测试:
常用的预定义变量:
$@
:表示规则中的目标文件名。例如,在规则hello: hello.o
中,$@
表示hello
。$<
:表示规则中的第一个依赖文件名。例如,在规则hello.o world.o: hello.c world.c
中,$<
表示hello.c
。(喂完后接着下一个world.c
)(%.o:%.c )$^
:表示规则中的所有依赖文件名,以空格分隔。例如,在规则hello: hello.o another.o
中,$^
表示hello.o another.o
。$?
:表示规则中所有比目标文件新的依赖文件名,以空格分隔。例如,在规则hello: hello.o another.o
中,如果another.o
比hello
新,则$?
表示another.o
。
我们可以在依赖方法前添加@符号,可以达到回显隐藏的功能,可以是Makefile的执行输入更加的简洁和美观。(隐藏的是命令)
总之,变量版的 Makefile
是一种更加高效和灵活的构建方式,能够帮助开发者更好地管理项目的构建过程。