上一篇讲了Makefile一些预定义的宏。这一篇会介绍另外两个比较有用推理规则,能够增强描述模块的功能。
推理规则 Inference rules
根据前两篇的内容,已经可以自己编写一个Makefile用来构建一个小型项目了。但是当项目变得很大的时候会发现要是给每个项目都是写一个命令来编译整个Makefile会变得很大。
于是Makefile中加入了推论规则,可以用来根据target,推断出相应出对应的依赖文件。再配合$<宏,就能够减少依赖文件的书写。
先来看一个例子
# makefile 内容
all: file.obj file1.obj
.cpp.obj:
echo $<
# 输出
file.cpp
file1.cpp
这个例子中并没有给出依赖项file.obj和file1.obj所需要的源文件。但是仍然生成了两行command,分别操作了file.cpp和file1.cpp。这就是推理规则的作用。以此类推可以只定义一次我们想要的依赖项就能对每个依赖项执行对应的操作。其语法如下
.from_ext.to_ext:
commands
# search path
{from_path}.from_ext{to_path}.to_ext:
commands
# batch
{from_path}.from_ext{to_path}.to_ext::
commands
有一点需要注意的是推论规则只适用于真正存在的文件,nmake在进行推理时会真正的去 检查相应目录中是否真正存在符合的文件。以上面的makefile为例,实际上file.cpp和file1.cpp真正存在,否则就会报错。
也可以加上指定的路径。这样就可以给不同路径下面的文件执行不同的命令。如下例子,
all: file.obj folder1\file2.obj
.cpp.obj:
echo $<
{folder1\}.cpp.obj:
echo search path $<
# 输出
file.cpp
search path folder1\file2.cpp
可以看到最终被search path模式匹配到的file2.obj和file.obj执行的是不同的命令。注意的是
上面两种用法所执行的命令都是为每个依赖项执行一遍完整的指令。但是实际使用中编译器能够批量接受多个文件进行编译。只需要将文件名同时列在同一行即可。batch模式正是这种用途,使用时执行一次命令,从而加速了构建。看如下例子
all: file.obj file2.obj
.cpp.obj::
echo $<
# 输出
file.cpp folder1\file2.cpp
因为进行编译的源文件通常就只有c,cpp,rc之类的文件,而且编译命令往往也是固定的,因此预定义了很多的推理规则。先看看这个makefile上并没有告诉nmake如何执行命令来生成obj,但是nmake也能很好的编译文件。
all: file.obj
# 输出
cl /c file.cpp
因为nmake预定义了大部分常用的后缀名文件的生成命令,会自动生成相应的命令。
.asm.exe | $(AS) $(AFLAGS) $< | ml $< |
.asm.obj | $(AS) $(AFLAGS) /c $< | ml /c $< |
.asm.exe | $(AS) $(AFLAGS) $< | ml64 $< |
.asm.obj | $(AS) $(AFLAGS) /c $< | ml64 /c $< |
.c.exe | $(CC) $(CFLAGS) $< | cl $< |
.c.obj | $(CC) $(CFLAGS) /c $< | cl /c $< |
.cc.exe | $(CC) $(CFLAGS) $< | cl $< |
.cc.obj | $(CC) $(CFLAGS) /c $< | cl /c $< |
.cpp.exe | $(CPP) $(CPPFLAGS) $< | cl $< |
.cpp.obj | $(CPP) $(CPPFLAGS) /c $< | cl /c $< |
.cxx.exe | $(CXX) $(CXXFLAGS) $< | cl $< |
.cxx.obj | $(CXX) $(CXXFLAGS) /c $< | cl /c $< |
.rc.res | $(RC) $(RFLAGS) /r $< | rc /r $< |
另外也有预定的点指令.SUFFIXES其中也有预定义一些常见的后缀名,.exe .obj .asm .c .cpp .cxx .bas .cbl .for .pas .res .rc .f .f90 可以用来作为ext来匹配。