7.2.2.2 编写make
1、Makefile 宏定义
makefile 里的宏是大小写敏感的,一般都使用大写字母。它们几乎可以从任何地方被引用,可以代表很多类型,例如可以存储文件名列表,存储可执行文件名和编译器标志等。要定义一个宏,在 makefile 中,任意一行的开始写下该宏名,后面跟一个等号,等号后面是要设定的这个宏的值。如果以后要引用到该宏时,使用 $ ( 宏名 ) ,或者是 ${ 宏名 } ,注意宏名一定要写在圆或花括号之内。把上一小节所举的例子,用引入宏名的方法,可以写成下面的形式:
OBJS = main.o io.o
CC = gcc
CFLAGS = -Wall -O -g
executable: $(OBJS)
$(CC) $(OBJS) -o executable
main.o : main.c
$(CC) $(CFLAGS) -c main.c -o main.o
io.o : io.c
$(CC) $(CFLAGS) -c io.c -o io.o
在这个 makefile 中引入了三个宏定义,所以如果当这些宏中的某些值发生变化时,开发者只需在要修改的宏处,将其宏值修改为要求的值即可, makefile 中用到这些宏的地方会自动变化。在 make中还有一些已经定义好的内部变量,有几个较常用的变量是 $@ , $< , $? , $*, $^ ( 注意:这些变量不需要括号括住 ) 。
$@ 扩展为当前规则的目标文件名;
$< 扩展为当前规则依赖文件列表中的第一个依赖文件;
$? 扩展为所有的修改日期比当前规则的目标文件的创建日期更晚的依赖文件,该值只有在使用显式规则时才会被使用;
$* 扩展成当前规则中目标文件和依赖文件共享的文件名,不含扩展名;
$^ 扩展为整个依赖文件的列表 ( 除掉了所有重复的文件名 ) 。
利用这些变量,可以把上面的 makefile 写成:
OBJS = main.o io.o
CC = gcc
CFLAGS = -Wall -O -g
executable: $(OBJS)
$(CC) $^ -o $@
main.o : main.c
$(CC) $(CFLAGS) – c $< -o $@
io.o : io.c
$(CC) $(CFLAGS) -c $< -o $@
可以将宏变量应用到其他许多地方,尤其是当把它们和函数混合使用的时候,正确使用宏,会给开发者带来极大的便利。
2 、隐含规则
请注意,在上面的例子里,几个产生 .o 文件的命令都是以 .c 文件作为依赖文件产生 .o 目标 (obj)文件,这是一个标准的生成目标文件的步骤。如果把生成 main.o 和 io.o 的规则从 makefile 中删除,make 会查找它的隐含规则,然后会找到一个适当的命令去执行。实际上 make 已经知道该如何生成这些目标文件,它使用变量 CC 做为编译器,并且传递宏 CFLAGS 给 C 编译器 (CXXFLAGS 用于 C++ 编译器 ) , CPPFLAGS(C 预处理选项 ) , TARGET_ARCH ( 就目前例子而言,还不用考虑这个宏 ) ,然后它加入开关选项 -c ,后面跟预定义宏 $<( 第一个依赖文件名 ) ,最后是开关项 -o ,后跟预定义宏 $@ ( 目标文件名 ) 。一个C编译的具体命令将 会是:
$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
在 make 工具中所包含的这些内置的或隐含的规则,定义了如何从不同的依赖文件建立特定类型的目标。 Unix 系统通常支持一种基于文件扩展名即文件名后缀的隐含规则。这种后缀规则定义了如何将一个具有特定文件名后缀的文件 ( 例如 .c 文件 ) ,转换成为具有另一种文件名后缀的文件 ( 例如 .o 文件 ) :
系统中默认的常用文件扩展名及其含义为:
.o 目标文件
.c C 源文件
.f FORTRAN 源文件
.s 汇编源文件
.y Yacc-C 源语法
.l Lex 源语法
而 GNU make 除了支持后缀规则外还支持另一种类型的隐含规则即模式规则。这种规则更加通用,因为可以利用模式规则定义更加复杂的依赖性规则。同时可用来定义目标和依赖文件之间的关系,例如下面的模式规则定义了如何将任意一个 .c 文件转换为文件名相同的 .o 文件:
%.o : %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
3 、伪目标
如果需要最终产生两个和更多的可执行文件,但这些文件是相互独立的,也就是说任何一个目标文件的重建,不会影响其他目标文件。此时,可以通过使用所谓的伪目标来达到这一目的。一个伪目标和一个真正的目标文件的唯一区别在于,这个目标文件本身并不存在。因此, make 总是会假设它需要被生成,当 make 把该伪目标文件的所有依赖文件都更新后,就会执行它的规则里的命令行。
举一个简单的例子,如果 makefile 开始处输入
all : executable1 executable2
这里 executable1 和 executable2 是最终希望生成的两个可执行文件。 make 把这个 'all' 做为它的主要目标,每次执行时都会尝试把 'all' 更新。但是,由于这行规则里并没有命令来作用在一个叫'all' 的实际文件上 ( 事实上, all 也不会实际生成 ) ,所以这个规则并不真的改变 'all' 的状态。可既然这个文件并不存在,所以 make 会尝试更新 all 规则,因此就检查它的依赖文件 executable1,exectable2 是否需要更新,如果需要,就把它们更新,从而达到生成两个目标文件的目的。 伪目标在 makefile 中广泛使用。
4 、函数
makefile 里的函数跟它的宏很相似,在使用的时候,用一个 $ 符号开始后跟圆括号,在圆括号内包含函数名,空格后跟一系列由逗号分隔的参数。例如,在 GNU Make 里有一个名为 'wildcard'的函数,它只有一个参数,功能是展开成一列所有符合由其参数描述的文件名,文件间以空格间隔。可以像下面所示使用这个命令:
SOURCES = $(wildcard *.c)
这样会产生一个所有以 '.c' 结尾的文件的列表,然后存入变量 SOURCES 里。当然你不需要一定要把结果存入一个变量。
另一个有用的函数是 patsubst (patten substitude, 匹配替换的缩写 ) 函数。它需要3个参数:第一个是一个需要匹配的模式,第二个表示用什么来替换它,第三个是一个需要被处理的由空格分隔的字列。例如,处理那个经过上面定义后的变量,
OBJS = $(patsubst %.c,%.o,$(SOURCES))
这个语句将处理所有在 SOURCES 宏中的文件名后缀是 '.c' 的文件 ,用 '.o' 把 '.c' 取代。注意这里的 % 符号是通配符,匹配一个或多个字符,它每次所匹配的字符串叫做一个 ‘ 柄 '(stem) 。在第二个参数里, % 被解释成用第一参数所匹配的那个柄。
感兴趣的读者如果需要更进一步的了解,请参考 GNU Make 手册。
