综述
Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
- 显示规则即由书写者明显指出了目标文件、依赖文件以及要执行的命令的规则。
- 隐晦规则是指makefile有着自动推导的功能,能够让我们书写时省略掉部分的语句。
- 变量定义是指makefile可以自定义变量来替换字符让更改变得简单。
- 文件指示是指makefile可以像C语言中的 #include 引用其他的makefile文件,在合适的位置展开;也可以像C语言预编译的 #if 视情况执行makefile中有效的部分。
- 注释,makefile的注释和shell脚本类似,只有行注释,以 # 开头。
其他一些注意事项:
- makefile的文件名可以是任何自定义,但若只使用 make 不加任何参数的话默认只搜索当前目录下的“Makefile”、“makefile”、“GNUmakefile”(GNU make)。若使用其他名字需要使用 make -f 或 make --file 参数来指定文件名。
- makefile 中引用其他 makefile 使用 include 关键字,如果make执行时,有“-I”或“–include-dir”参数,那么make就会在这个参数所指定的目录下去寻找。如果目录< prefix >/include(一般是:/usr/local/bin或/usr/include)存在的话,make也会去找。如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,可以在include前加一个减号“-”。和其它版本make兼容的相关命令是sinclude。
- 环境变量 MAKEFILES ,如果你的当前环境中定义了环境变量MAKEFILES,那么,make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和include不同的是,从这个环境变中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现错误,make也会不理。不建议使用,一旦被定义将影响当前环境所有的makefile。
- 如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。
规则
语法
targets : prerequisites
command
…
或:
targets : prerequisites ; command
command
…
注意:command行若为单独一行则必须以【Tab】开头。命令过长可以用换行符 “\” 。
一般来说,make会以UNIX的标准Shell,也就是/bin/sh来执行命令。
通配符
首先,在command行中,所有当前操作系统shell适用的通配符都可以使用,在目标和依赖文件中,也可以使用如下通配符: *、?、[…] 。另外有几个特殊的自动变量也可以当作通配符使用:
- $@:所有目标文件
- $^:目标依赖的所有文件
- $<:第一个依赖文件
- $?:所有更新过的依赖文件
注意:赋值号的右边不能使用通配符的含义。例如“objects := *.c” ,那么 objects 的值并不是我们期望的所有c文件的集合。想使用通配符可以使用“ wildcard ”关键字,改为“ objects := $(wildcard *.c) ”
文件搜寻
有时,源文件并不是放在同一个目录下,此时就需要文件搜寻已让makefile找到相关的依赖文件。主要有两种方式:
(首先不论如何,当前目录永远是makefile最优先搜寻的目录)
- 特殊变量 VPATH :设置该变量可以让makefile在当前目录找寻无果后去往相应的目录搜寻,可以设置多个目录,用冒号 “ : ” 隔开,makefile会依次按设置的目录顺序搜寻。
- make 的 vpath 关键字,其主要有如下用法:
1、vpath < pattern > < directories >
为符合模式< pattern >的文件指定搜索目录< directories >。
2、vpath < pattern >
清除符合模式< pattern >的文件的搜索目录。
3、vpath
清除所有已被设置好了的文件搜索目录。
注意:
- < pattern >需要包含“%”字符。“%”的意思是匹配零或若干字符。
- < pattern >指定了要搜索的文件集,而< directories >则指定了< pattern >的文件集的搜索的目录,也可以使用冒号隔开来设置多个目录。例如:vpath %.h …/headers 表示,要求make在“…/headers”目录下搜索所有以“.h”结尾的文件。(如果某文件在当前目录没有找到的话)
- 另外当多条vpath设置相同的文件集时,make会按设置的目录的顺序依次搜寻。
伪目标
伪目标即不需要生成具体目标文件的目标,比如每个 makefile 约定俗成的 clean 目标就算是一个伪目标。“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个“目标”才能让其生效,比如 make clean。为了避免伪目标和真实的文件重名,我们可以使用" .PHONY "来显示地标记一个伪目标,比如 .PHONY : clean 。
伪目标一般没有依赖文件,但它是可以有依赖文件的,也可以被当作 make 的默认目标(即放在第一个目标,当然它实际也不会生成一个真实的文件,但如果它的依赖文件不存在,make 还是会继续寻找依赖文件的依赖关系来生成对应的依赖文件)。因此如果你想在一个makefile里一次性生成多个可执行文件,那么可以这样利用伪目标:
final : a b c
.PHONY final
a : a.o
cc -o a a.o
a.o : a.c
cc -o a.o a.c
# ... 剩下的省略。。。
这样的话,只需要键入一个make指令就可以生成 a b c 三个可执行文件。
多目标
多目标,指的是目标(target)也可以有多个文件。当某些操作比较冗余时,即可用多目标来简化我们的代码。此技巧通常和函数、自动变量等一起使用,后续再详细介绍。
静态模式
静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。
语法如下:
<targets …>: < target-pattern >: <prereq-patterns …>
< commands >
…
- targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。
- target-parrtern是指明了targets的模式,也就是的目标集模式。
- prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。
当使用这一条语句后,自动变量 $ < 和 $@ 会根据这条语句设置的进行更改,例如:
files = a.o b.o c.elf
# filter为一个函数,这里的作用为只提取files中的 .o 文件
$(filter %.o, $(files)) : %.o : %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elf, $(files)) : %.elf : %.bin
# 这里的 $< 和 $@ 变化为新一条静态模式设置的值
$(OBJCOPY) -O binary -S $@ $<
当 files 中的文件很多,种类很杂时,这种方式就大大减少了代码量,非常方便。
自动生成依赖性
编译器的自动生成依赖性
之前讲到依赖文件中需要把源文件中包含的头文件都写出来,比较麻烦也不好维护。但实际上绝大多数的C/C++编译器都可以自动生成依赖性,比如gcc使用 -M 参数可以自动地把需要的头文件加入依赖关系并打印出来,这样我们就不需要过多地关注哪个源文件包含了哪些头文件(当然重复包含等代码上的问题还是要注意)。
例如:
cc -M main.c
其输出是:
main.o : main.c defs.h
一般来说标准库头文件(stdio.h等)不会打印出来。
另外GNU的C/C++编译器的 -M 会把标准库头文件也打印出来,不想打印可以使用 -MM 参数。
makefile自动生成依赖性
GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一个[.c]的文件都生成一个[.d]的 Makefile 文件,[.d]文件存放对应[.c]文件的依赖关系。于是,我们可以写出[.c]文件和[.d]文件的依赖关系,并让make自动更新或自成[.d]文件,并把其包含在我们的主Makefile中,就可以自动化地生成每个文件的依赖关系了。
这里给出一个模式规则来产生[.d]文件:
%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
set -e 是bash的命令,作用是遇到错误即终止报错不再继续执行。.$$$$ 是随机字符,即生成的中间文件,比如[.d.1234]。
sed 命令这里是对一些字符串做了替换,就是在把[.o : .c]加上[.d]变成[.o .d : .c],即把.d文件加在目标target中,从而达到自动更新的效果,并把匹配的结果放到[.d]中。最后是删掉中间文件。
至此,你可能也发现了,我们也可以在[.d]文件的依赖后面加上我们需要设置的命令,让该文件存放一个完整的规则,于是在主 Makefile 中就只需要按照我们想要的次序 include 相应的[.d]文件就可以了。
例如:
sources = main.c a.c b.c
include $(sources : .c = .d)
第二行的效果是把sources中的 .c 换为 .d 。