默认的情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,找到了解释这个文件。在这三个文件名中,最好使用“Makefile”这个文件名,因为,这个文件名第一个字符为大写,这样有一种显目的感觉。最好不要用“GNUmakefile”,这个文件是GNU的make识别的。有另外一些make只对全小写的“makefile”文件名敏感,但是基本上来说,大多数的make都支持“makefile”和“Makefile”这两种默认文件名。当然,你可以使用别的文件名来书写Makefile,比如:“Make.Linux”,“Make.Solaris”,“Make.AIX”等,如果要指定特定的Makefile,你可以使用make的“-f”和“--file”参数,如:make -f Make.Linux或make --file Make.AIX。
1.Makefile概述
target… : prerequisites … command |
target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label)。
prerequisites就是,要生成那个target所需要的文件或是目标。
command也就是make需要执行的命令。(任意的Shell命令)
1.1.工作方式
执行makefile时找到第一个target并和prerequisites比较,如果没有target或者target比prerequisites老的话就会执行下一行的命令,如果prerequisites也不存在就会在当前文件中为prerequisites寻找依赖。
makefile只执行第一个目标和这个目标所依赖的文件。
1.2.Makefile文件常用函数
wildcard : 扩展通配符函数,功能是展开成一列所有符合由其参数描述的文件名,文件间以空格间隔。例如objects= $(wildcard *.c),会产生一个所有以.c结尾的文件列表(本例结果为add.c sub.c div.c mul.c cal.c),然后存入变量objects里。
patsubst : 匹配替换函数, patsubst函数需要3个参数,第一个是需要匹配的文件样式,第二个是匹配替换成什么文件,第三个是需要匹配的源文件。例如objects : $(patsubst%.c,%.o,$(wildcard*.c)会被处理为objects :=add.o sub.o div.o mul.o cal.o。
notdir : 去除路径函数,只留下文件名,如: .lsrc/mytest.c,通过notdir之后得到mytest.c。
subst : 字符串处理函数,$(subst FROM, TO, TEXT),即将字符串TEXT中的子串FROM变为TO。
filter :
1.3.Makefile变量
变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有 : 、 # 、 = 或是空字符(空格、回车等)。变量是大小写敏感的,“foo”、“Foo”和“FOO”是三个不同的变量名。
变量在声明时需要给予初值,而在使用时,需要给在变量名前加上$符号,但最好用小括号 () 或是大括号 {} 把变量给包括起来。如果你要使用真实的 $字符,那么你需要用 $$来表示。变量会在使用它的地方精确地展开,就像C/C++中的宏一样。
变量是可以使用后面的变量来定义的。例如:
foo=$(bar) bar=$(ugh) ugh=Huh? all: echo$(foo) |
这样有时候会造成递归调用,如:A=$(B) B=$(A)。想要避免可以使用“:=”符号进行赋值,这样就可以达到前面的变量不能使用后面的变量,只能使用前面已定义好了的变量的效果。
1.3.1多行变量
使用define关键字设置的变量可以有多行,这有利于设置一系列命令。define后面跟的是变量名字,重启一行后是变量的值,变量定义以endef结尾。如果define定义的变量中没有以Tab键开头就不会识别为命令。
1.3.2环境变量
1.3.3目标变量
1.3.4模式变量
1.3.5Makefilei自动化变量
$@ : 目标文件
$^ : 所有依赖文件
$< : 第一个依赖文件
$? : 比目标还要新的依赖文件列表
1.3.6变量传递
对于多级的目录下的Makefile,定义的变量是可以传递的,如果想Ⅹ定义的变量传递到下一级使用:
export <变量名> |
如果不想让他传递到下一级使用:
unexport <变量名> |
如果想把所有变量都传递到下一级直接使用export即可后面不用键变量名。如果什么前缀都不加,会传递到下一级但不会覆盖下一级定义的变量。
1.4.Makefile赋值操作
= 基本的赋值
:= 覆盖之前的值
?= 如果没有被赋值过就赋等号后面的值
+= 给变量追加等号后面的值
1.5Makefile的自助推导
伪目标
伪目标只是一个标签,而不是一个文件,例如最常用的clean标签,下面这个例子:
clean: rm *.o |
清除作用,是一个伪目标,伪目标不是一个文件而是一个标签,make无法生成它的依赖关系和决定它是否要执行。我们只有通过显式地指明这个“目标”才能让其生效,当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显式地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。如:
.PHONY :clean |
伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文件,但你只想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个特性:
all :prog1 prog2 prog3 prog1 :prog1.outils.o cc -o prog1 prog1.o utils.o prog2 :prog2.o cc -o prog2 prog2.o prog3 :prog3.osort.outils.o cc -o prog3 prog3.o sort.o utils.o |
Makefile中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它三个目标。由于默认目标的特性是,总是被执行的,但由于“all”又是一个伪目标,伪目标只是一个标签不会生成文件,所以不会有“all”文件产生。于是,其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。
文件搜寻
当有多级目录存在时,需要让make自己寻找源文件位置,此时需要变量VPATH
VPATH=src : ../head |
make时首先在当前目录查找,找不到的情况下,到所指定的“../head”和“src”目录中去找寻文件了。
另一种方法是使用关键字vpath,
vpath <pattern> <directories> |
为符合模式<pattern>的文件指定搜索目录<directories>。
vpath <pattern> |
清除符合模式<pattern>的文件的搜索目录。
vpath |
清除所有已被设置好了的文件搜索目录。
vapth使用方法中的<pattern>需要包含 % 字符。 % 的意思是匹配零或若干字符,例如:
vpath %.h ../headers |
该语句表示,要求make在“../headers”目录下搜索所有以 .h 结尾的文件。(如果某文件在当前目录没有找到的话)
我们可以连续地使用vpath语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的<pattern> ,或是被重复了的<pattern>,那么,make会按照vpath语句的先后顺序来执行搜索。如:
vpath %.c foov path % blishv path %.c bar |
其表示 .c 结尾的文件,先在“foo”目录,然后是“blish”,最后是“bar”目录。
vpath %.c foo:bar vpath % blish |
而上面的语句则表示 .c 结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。
多目标
有多个目标都需要执行时,参考如下例子:
bigoutput littleoutput :text.g generate text.g -$(subst output,,$@)> $@ |
另外也可以使用伪目标的方式来实现多目标的执行,例如:
all1:$(OBJ) subsystem creat2 subsystem: $(MAKE) -C file2 $(OBJ): %.o: %.c $(CC) -c $(CFLAGS) $< -o $@ -I ./file2 creat2: $(MKDIR) haha2 |
静态模式
静态模式可以更加容易的定义多目标规则,比如想要一个make命令把所有的.c都生成.o文件,或者想要输出多个可执行文件。
<targets ...> :<target-pattern> : <prereq-patterns...> |
targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。
target-pattern是指明了targets的模式,也就是的目标集模式。
prereq-patterns是目标的依赖模式,它对target-pattern形成的模式再进行一次依赖目标的定义。
例如:如果我们的<target-pattern>定义成 %.o ,意思是我们的<target>;集合中都是以 .o 结尾的,而如果我们的<prereq-patterns>定义成 %.c ,意思是对<target-pattern>所形成的目标集进行二次定义,其计算方法是,取<target-pattern>模式中的 % (也就是去掉了 .o 这个结尾),并为其加上 .c 这个结尾,形成的新集合。
所以,我们的“目标模式”或是“依赖模式”中都应该有 % 这个字符,如果你的文件名中有 % 那么你可以使用反斜杠 \ 进行转义,来标明真实的 % 字符。
嵌套执行MAKE
在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的Makefile。总控Makefile的变量可以传递到下级的Makefile中,但是不会覆盖下层的Makefile中所定义的变量,除非指定了 -e 参数。
$(MAKE) -C file2
条件语句
条件表达式的语法为:
<conditional-directive> <if为真执行的内容> endif |
和
<conditional-directive> <if为真执行的内容> else <if为假执行的内容> endif |
conditional-directive是关键字,资格关键字有四种
ifeq
ifneq
ifdef
ifndef
2.单个目录编写实例
1.单个文件下目录文件如下
main.c sub.c sum.c mul.c headfile.h |
(放在同一目录下)
2.编写最简单的Makefile文件
main:main.o sub.o add.o mul.o gcc -o main main.o sub.o add.o mul.o main.o:main.c headfile.h gcc -c main.c -o main.o add.o:add.c headfile.h gcc -c add.c sub.o:sub.c headfile.h gcc -c sub.c mul.o:mul.c headfile.h gcc -c mul.c |
3.对于文件比较多时候,如果需要添加或删除文件可能会出现遗漏,可以使用变量来代替文件名字。(例如定义为OBJ=…,使用时则$(OBJ))。
在文件开头添加定义
CC = gcc OBJ = main.o sub.o add.o mul.o |
修改使用main.o sub.o add.o mul.o处为$(OBJ)
修改使用gcc处为$(CC)
4.自助推导,只要make看到了*.o文件,它就会自动把与之对应的*.c文件加到依赖文件中,并且gcc -c *.c也会被推导出来,
根据自动推导修改程序
修改后参考代码:(不唯一)
OBJ = main.o sub.o add.o mul.o CC = gcc main: $(OBJ) $(CC) -o main $(OBJ) main.o: headfile.h add.o: headfile.h sub.o: headfile.h mul.o: mul.c headfile.h PHONY: clean clean: rm $(OBJ) |
5. 使用自动变量($^ $< $@)修改Makefile文件
修改后参考程序
OBJ = main.o sub.o add.o mul.o CC = gcc main: $(OBJ) $(CC) -o $@ $^ main.o: main.c headfile.h $(CC) -c $< add.o: main.c headfile.h $(CC) -c $< sub.o: sub.c headfile.h $(CC) -c $< mul.o: mul.c headfile.h $(CC) -c $< PHONY: clean clean: rm $(OBJ) |
6.使用缺省规则..c.o:
..c.o:
gcc -c $<
这个规则表示,所有的 *.o 目标文件都是依赖于相应的 *.c 源文件的, 例如 main.o 依赖于 main.c 。
OBJ = main.o sub.o add.o mul.o CC = gcc main: $(OBJ) $(CC) -o main $@ $^ ..c.o: $(CC) -c $< clean: rm $(OBJ) |
修改后编译运行
多目录下Makefile文件
3.多级目录编写实例
当文件比较多,有多级目录时情况比较复杂,如果一个子目录与其他子目录无关,不需要调用其他子目录的程序可以考虑使用递归make,如果一个子目录中的代码依赖于另一个子目录中的代码,那么在顶层使用单个 makefile 可能会更好。
示例代码目录树如下:
. ├── file1.c ├── file_t1.c ├── inc │ └── headfile.h ├── Makefile ├── treeA │ ├── file2 │ │ ├── file2.c │ │ ├── file2.h │ │ └── Makefile │ ├── file41.c │ ├── file4.c │ ├── haha2 │ └── Makefile └── treeB ├── file3.c └── Makefile |
此示例的Makefile使用递归的方式编写,对于顶级makefile的编写如下,其他目录下Makefile编写类似。
#首先是设置编译器名字 CC=gcc #使用wildcard函数获取当前目录下所有的.c源文件 SOU = $(wildcard *.c) #通过替换字符串函数替换上一步中SOU中的.c字符得到.o结尾的目标文件 OBJ_THIS = $(SOU:.c=.o) #使用shell命令的find命令查找当前文件夹下所有的.o编译的目标文件,用于make clean清除命令 OBJ_ALL = $(shell find ./ -name "*.o") #当前路径 PATH=$(shell pwd) #头文件路径,也可以用相对路径 INC=-I/$(PATH)/inc #要编译的子目录 SUBTREE=treeB treeA #编译生成的可执行文件 EXE = test #用伪目标来实现执行多个目标,因为makefile只会编译第一个目标和这个目标的依赖文件,all是一个伪目标 all:$(SUBTREE) test #声明$(SUBTREE)是一个伪目标 #进入每个子目录分别编译其中的Makefile .PHONY:$(SUBTREE) $(SUBTREE): $(MAKE) -C $@ #编译当前目录的.o到可执行文件 test:$(OBJ_THIS) $(CC) -o test $(OBJ_THIS) $(INC) $(CFLAGS) #静态模式:把当前目录所有.c文件生成.o文件 $(OBJ_THIS): %.o: %.c $(CC) -c $< -o $@ $(INC) #也是伪目标,清除编译操作生成的文件 clean: rm ./test rm $(OBJ_ALL) |
使用make编译,要清除编译结果只需执行make clean,Makefile会寻找clean标签执行
详细例子参考../常用linux程序合计合集/其他代码/Makefile