makefile中的规则,目标依赖

1 定义

这是makfile的核心规则。

目标:依赖
为了完成目标要做的事

目标就是目标,生成目标需要的东西叫依赖,为了完成目标所做的事叫生成方法。
这就是makefile的核心内容。注意:生成方法前面是一个缩进,不是四个空格!

2.示例

我们看一个简单的例子

gcc -c demo.c
gcc -o demo demo.o

这是使用gcc编译程序的指令,如果我们想通过make来完成这个过程我们需要明确三个问题,我们的目标是什么?这个目标的依赖是什么?目标的生成方法是什么?
首先,我们需要生成demo.o这个文件,然后再用demo.o生成可执行程序demo。

生成demo.o

  • 我们的目标是生成demo.o文件
  • demo.o依赖demo.c文件
  • 使用gcc -c demo.c来生成demo.o
    那么根据makefile的规则,我们可以写出下面的makefile
    demo.o:demo.c
    gcc -c demo.c

生成可执行程序demo

  1. 我们的目标是生成一个叫demo的可执行程序。
  2. 依赖是demo.o这个源程序。
  3. 使用gcc -o demo demo.c这个指令来生成。
    demo:demo.o
    gcc -o demo demo.o

有了这两个规则,我们就可以把它们写入makefile里,那写入的时候有先后顺序吗?
有的

我们先把生成可执行程序的规则放上面。

demo:demo.o
gcc -o demo demo.o

demo.o:demo.c
gcc -c demo.c
ubuntu@R7000:~/make_study/chapter1$ make
gcc -c demo.c
gcc -o demo demo.o
ubuntu@R7000:~/make_study/chapter1$ ls
Makefile demo demo.c demo.o

这样我们就得到了想要的可执行程序和想保留的目标文件。

我们把demo和demo.o删除了,再把两条规则的顺序颠倒一下看看结果。

demo.o:demo.c
gcc -c demo.c
demo:demo.o
gcc -o demo demo.o
ubuntu@R7000:~/make_study/chapter1$ make
gcc -c demo.c
ubuntu@R7000:~/make_study/chapter1$ ls
Makefile demo.c demo.o
ubuntu@R7000:~/make_study/chapter1$

可以看到,只生成了demo.o文件,并没有生成可执行程序demo,没有达到我们想要的结果,这是为什么?

make只会执行makefile里的第一条规则,如果这条规则的依赖不存在,他就会去下面找能生成这个依赖的规则,如果找不到规则,无法生成所需要的依赖,就报错。

这就解释了为什么把生成可执行程序的规则放上面会得到我们想要的答案,而把生成目标文件的放第一条却得不到我们想要的结果。

那我们有没有别的方法来实现我们想要的结果呢? !指定我们想要的目标就可以了。

ubuntu@R7000:~/make_study/chapter1$ make demo
gcc -c demo.c
gcc -o demo demo.o

明白了make只会执行第一条规则后,似乎还有一个问题,那就是我们中间为什么要删除demo和demo.o

make的生成方法又可以称为更新方法,我们希望的是如果有文件被修改,我们重新编译它,没有修改的我们就不要动他了,所以make在执行的时候会查看依赖的时间是否比目标新,我们10:00生成了目标,10:10修改了依赖文件,那依赖就是比目标新,我们就需要执行更新方法来生成新的目标。

order-only依赖
依赖文件不存在时,会执行对应的方法生成,但依赖文件更新并不会导致目标文件的更新
如果目标文件已存在,order-only依赖中的文件即使修改时间比目标文件晚,目标文件也不会更新。
定义方法如下:

targets : normal-prerequisites | order-only-prerequisites

3.伪目标

我们有些时候并不需要生成什么东西,只是想做一些其他的事情,比如上面所做的删除demo和demo.o 对于这类并不生成文件的目标,makefile里叫做伪目标,使用**.PHONY**来定义伪目标。

demo.o:demo.c
gcc -c demo.c
demo:demo.o
gcc -o demo demo.o
.PHONY:clean
clean:
-rm -f demo.o demo

此时我们执行make clean就可以删除demo和demo.o,rm前面-的意思是,如果执行失败,不管,接着往下执行。

除了clean,还有install,uninstall等伪目标

4.规则中的通配符

make 支持三个通配符: * ,? 和 ~
但我们通常只使用*,例如想删除所有.o文件我们可以这么写

demo.o:demo.c
gcc -c demo.c
demo:demo.o
gcc -o demo demo.o
.PHONY:clean
clean:
-rm -f *.o demo

5.文件搜索

对于大型项目,通常会分为bin,src,include,lib等目录,把相关资源放到对应的目录里,make默认只在当前目录里寻找文件,如何让make去其他文件找呢?

5.1 VPATH

使用全大写的VPATH来指定寻找文件的路径。make会依次在当前目录,src,obj,bin查找

VPATH = src:obj:bin

5.2 vpath

小写的vpath支持模式匹配,根据文件类型去对应的文件夹查找,% 的意思是匹配零或若干字符

vapth %.c src
vapth %.o obj
vpath %.h include

6.多目标

  • $@:自动变量,后面会讲,代表当前规则的目标
  • $(subst .c, .o, demo1.c):函数,后面会讲,表示把.c替换为.o
    demo1.c:
    gcc -c $@ -o $(subst .c,.o,$@)
    等价于
    gcc -c demo1.c -o demo.o

我们的有demo1.o 到demo10.c 10个文件,他们的编译都需要一个叫add.h的文件,那么我们需要写10条规则,他们的依赖是相同的,生成方法大致也是相同的。我们就可以这么写。

demo1.c demo2.c .... demo10.c :add.h
gcc -c $@ add.h -o $(subst .c,.o,$@)

这条规则就相当于把10个目标拆开执行,使用相同的依赖和更新方法。

demo1.c :add.h
gcc -c $@ add.h -o $(subst .c,.o,$@)

demo2.c :add.h
gcc -c $@ add.h -o $(subst .c,.o,$@)

...

demo10.c :add.h
gcc -c $@ add.h -o $(subst .c,.o,$@)

多目标帮助我们简化了书写过程,但也存在一些问题

  1. 所依赖的必须相同,如果不同,就需要在依赖的地方写成所有依赖的集合。
  2. 只要其中有一个目标需要更新,所有目标都会执行,也就是说,对于上面的例子,要么0次,要么10次。

7.静态模式

  • $<:自动变量,代表第一个依赖的名称
    对多目标的一个扩展,让多目标变得更加灵活。
    语法
    <targets ...> : <target-pattern> : <prereq-patterns ...>
    commands

targets:多个目标
target-pattern:目标集模式
prereq-patterns:依赖集模式

举例:需要把demo1.c到demo10.c10个源文件编译成对应的目标文件demo1.o到demo10.o

demo1.o demo2.o ... demo10.o: %.o:%.c
gcc -c $<

多目标好理解,重点在与 %.o:%.c

目标:依赖
更新方法

冒号前是目标,冒号后是依赖,所以%.o:%.c这一坨就是依赖,但这到底依赖的是什么啊?
依赖的是%.c ,%是什么,需要通过模式匹配告诉我们,demo1.o符合%.o的匹配模式,那%就是demo1
它的意思就是说demo1.o符合%.o,那%就代表demo1,%.c就表示demo1.c。所以我们真正的依赖就是demo1.c。

所以上面例子其实是

demo1.o:demo1.c
....
demo2.o:demo2.c
...
demo3.o:demo3.c
...
...

demo10.o:demo10.c
...

8.同一目标多条规则

同一目标可以对应多条规则。同一目标的所有规则中的依赖会被合并。但如果同一目标对应的多条规则都写了更新方法,则会使用最新的一条更新方法,并且会输出警告信息。
同一目标多规则通常用来给多个目标添加依赖而不用改动已写好的部分。

input.o: input.cpp utility.inl
g++ -c input.cpp
main.o: main.cpp scene.h input.h test.h
g++ -c main.cpp
scene.o: scene.cpp scene.h utility.inl
g++ -c scene.cpp

input.o main.o scene.o : common.h

同时给三个目标添加了一个依赖common.h,但是不用修改上面已写好的部分。