makefile学习-2
···文章内若有错误之处,请大佬指正
变量
为什么要使用变量呢?看下面的makefile:
objs : main.o
main.o : main.c
cc -c main.c
我们可以看到[.c]文件的字符串被重复了两次,如果我们的工程需要加入一个新的[.c]文件,那么我们需要在两个地方加(应该是三个地方,还有一个地方在clean中)。当然,我们的makefile并不复杂,所以在两个地方加也不累,但如果makefile变得复杂,那么我们就有可能会忘掉一个需要加入的地方,而导致编译失败。所以,为了makefile的易维护,在makefile中我们可以使用变量。makefile的变量也就是一个字符串,理解成C语言中的宏可能会更好。
定义变量
object:=Naruto.o Sakura.o Sasuke.o
CC = gcc
FLAG = -o
如果还想要给object追加
object+=Kakashi.o
使用变量
helloworld:$(object)
$(CC) $(object) $(FLAG) helloworld
什么括号都可以,但是要统一
伪目标
- 如果使用 make clean命令,那么会直接运行 rm,无论当前目录下是否存在clean文件,输入命令"make clean"之后,"rm"命令都会被执行,而且,当一个目标被声明为伪目标后,make在执行此规则时不会试图去查找隐含规则来创建这个目标。这样也提高了make的执行效率,同时我们也不用担心由于目标和文件名重名。在书写伪目标规则时,首先需要声明目标是一个伪目标,之后才是伪目标的规则定义。
.PHONY:clean
clean:
rm -rf $(object) *.i *.s
- 没有声明clean为伪目标,如果当前文件夹下没有clean文件,那么输入命令"make clean", rm -rf总会被执行,但如果当前工作目录存在文件"clean"时情况就不一样了,输入命令"make clean"时,编译器发现当前文件夹有一个clean文件,然后就错把它当成了目标文件,那么在编译器眼里它就是up-to-date,所以rm -rf也不执行了
clean:
rm -rf $(object) *.i *.s
- 目标和伪目标的区别就是一个能生成文件,一个不能生成文件
- 就是那个.PHONY的声明,它其实就一个作用,把一个东西明明白白的告诉编译器,这是个伪目标,没有实体文件
- 这里要说明一点的是,clean不是一个文件,它只不过是一个动作名字,有点像C语言中的lable一样,其冒号后什么也没有,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。
- 通过上述分析,我们知道,像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令——“make clean”,以此来清除所有的目标文件,以便重编译。
- .PHONY:之后的都是伪目标
通配符
% 任意一个
%.c 任意一个.c文件
* 所有
*.c 所有.c文件
? 匹配
hello:k?y.c
gcc -o hello k?y.c
执行make时,编译器会根据当前工作目录进行匹配,比如当前文件夹有koy.c和key.c这两个文件,如果进行make,那么就会报错,编译器不知道选择哪个作为依赖文件,但如果当前文件夹只有key.c的话,那么编译器就会自动匹配了
自动变量
$@ 目标文件
$^ 依赖文件
$< 第一个依赖文件
hello:hello.c
gcc -o $@ $^
kakaxi:kakaxi.c
gcc -o $@ $^
$@和$^会自动匹配目标文件和依赖文件
函数
- SRC = $(wildcard *.c)
wildcard函数:获得当前文件夹内所有的.c文件
- OBJS = $(patsubst %.c,%.o,$(SRC))
patsubst函数:将SRC变量里的所有.c文件转化为.o文件
make的工作方式
- 读入所有的Makefile
- 读入被include的其它Makefile
- 初始化文件中的变量
- 推导隐晦规则,并分析所有规则
- 为所有的目标文件创建依赖关系链
- 根据依赖关系,决定哪些目标要重新生成
- 执行生成命令
Makefile书写规则
规则包含两个部分:
- 依赖关系
foo.o: foo.c defs.h
cc -c -g foo.c
前面也已说过,foo.o是我们的目标,foo.c和defs.h是目标所依赖的源文件,而只有一个命令“cc -c -g foo.c”(以Tab键开头)
- 文件的依赖关系,foo.o依赖于foo.c和defs.h的文件,如果foo.c和defs.h的文件日期要比foo.o文件日期要新,或是foo.o不存在,那么依赖关系发生。
- 如果生成(或更新)foo.o文件。也就是那个cc命令,其说明了,如何生成foo.o这个文件。(当然foo.c文件include了defs.h文件)
- 生成目标的方法
- 在Makefile中,规则的顺序是很重要的,因为,Makefile中只应该有一个最终目标,其它的目标都是被这个目标所连带出来的,所以一定要让make知道你的最终目标是什么。一般来说,定义在Makefile中的目标可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个,那么,第一个目标会成为最终的目标。make所完成的也就是这个目标。
TIPS
- 显示规则
显式规则说明了如何生成一个或多个目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令,通过依赖关系可以看出来。
- 隐式规则
object:=helloone.o hellotwo.o hellothree.o #object代表这三个文件
CC = gcc
FLAG = -g #参数
LIB = #库文件
INCLUDE = #包含文件
target = hello #目标文件
$(target):$(object)
$(CC) $(FLAG) $^ -o $@ $(LIB) #$^:依赖文件 $@:目标文件
#伪目标
.PHONY:
clean:
rm -rf *.o $(target)
主要靠的是make的自动推导,比如上述makefile,当前目录只有helloone.c hellotwo.c hellothree.c,并没有.o 文件,运行make命令时,由于本地文件夹内没有.o文件,编译器会自动将.c文件转为.o文件
由于make有自动推导的功能,所以隐式规则可以让我们比较粗糙地简略地书写Makefile,这是由make支持的。
- 注释 : #
如果要在makefile里面使用#,使用"\#"
- 如果要指定特定的Makefile,你可以使用make的“-f”和“--file”参数,如:make -f Make.Linux或make --file Make.AIX。
- 在Makefile使用include关键字可以把别的Makefile包含进来,这很像C语言的#include,被包含的文件会原 模原样的放在当前文件的包含位置。include的语法是:
include<filename>filename可以是当前操作系统Shell的文件模式(可以保含路径和通配符)
在include前面可以有一些空字符,但是绝不能是[Tab]键开始。include和可以用一个或多个空格隔开。举个例子,你有这样几个Makefile:a.mk、b.mk、c.mk,还有一个文件叫foo.make,以及一个变量$(bar),其包含了e.mk和f.mk,那么,下面的语句:
include foo.make *.mk $(bar)
等价于:
include foo.make a.mk b.mk c.mk e.mk f.mk
- make命令开始时,会把找寻include所指出的其它Makefile,并把其内容安置在当前的位置。就好像C/C++的#include指令一样。如果文件都没有指定绝对路径或是相对路径的话,make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:
1.如果make执行时,有“-I”或“--include-dir”参数,那么make就会在这个参数所指定的目录下去寻找。
2.如果目录/include(一般是:/usr/local/bin或/usr/include)存在的话,make也会去找。
- 如果你的当前环境中定义了环境变量MAKEFILES,那么,make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和include不同的是,从这个环境变中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现错误,make也会不理。
- 如果命令太长,你可以使用反斜框‘\’作为换行符