Inference Rules(推导规则)
Inference rules(下文简称IR)是一个模板,它用于决定如何从一个具有某种扩展名的文件构造出一个具有另一种扩展名的文件。NMAKE通过IR来确定用来更新target的命令以及推导target的dependents。IR的好处在于它满足了像我这样的懒人的需要。只要提供了正确的IR,则描述语句块就可以极大地化简。请看下面的例子:
foo.obj :
上面的语句将会运作得很好。是不是觉得很吃惊呢?事实上,NMAKE在处理该语句的时候,它首先在当前目录下搜索基本名为foo的文件(假设当前目录下有一个foo.c文件)。然后它查找一个后缀列表(suffix list),里面的每一项包含了从一种类型的文件构造另一种类型的文件需要调用的命令和参数的相关信息。在NMAKE预定义的列表中,foo.c到foo.obj的构造命令为CL。最后NMAKE调用CL,编译foo.c。呵呵,这么一长串的操作一条简单的语句就搞定了,是不是很方便呢!
当出现下列情况之一时,NMAKE就会尝试使用IR:
l NMAKE遇到一个没有任何命令的描述语句块。此时NMAKE就会搜索后缀列表,试图找到一个匹配的命令来构造target。
l 无法找到某个dependent,并且该dependent没有作为target出现在其它dependent line中(即它不是一个pseudotarget)。此时NMAKE就会搜索给定的目录以及后缀列表,试图找到一个IR来构造出该dependent。
l 一个target没有dependent,并且描述语句块中没有给出指令。此时NMAKE就会试图找出一个IR来构造出该target。
l 一个target在NMAKE的命令行中给出,但在makefile里没有该target的相关信息(或根本就没有makefile)。此时NMAKE就会试图找出一个IR来构造出该target。
定义一个IR的语法如下:
[{frompath}].fromext[{topath}].toext;
commands
注意,各语法元素之间不能有任何空格。Dependent的后缀名在fromext中给出,target的后缀名在toext中给出。Frompath和topath是可选的,分别给出了搜索的路径。在每个IR的定义中只能分别为每一个后缀名给出一个搜索路径。如果想要指定多个搜索路径,就必须定义多个IR。并且,如果你为一个后缀指定了搜索路径,那么你也必须为另一个后缀指定搜索路径。即是说,fromext和topath只要有一个存在,则另一个也必须存在。你可以使用{.}或{}来表示当前目录。
另外,要注意的是,如果你在IR中指定了搜索路径,则在dependent lien中也必须指定同样的路径,否则IR将不会应用于dependent line上,例如:
{..\proj}.exe{..\proj}.obj:
该IR不会用于下列语句上:
project1.exe : project1.obj
但会用于下列语句上:
{..\proj}project1.exe : {..\proj}project1.obj
NMAKE本身提供了一个预定义的后缀列表,内容如下:
Rule Command Default Action
.asm.exe $(AS)$(AFLAGS) $*.asm ML $*.ASM
.asm.obj $(AS)$(AFLAGS) /c $*.asm ML /c $*.ASM
.c.exe $(CC)$(CFLAGS) $*.c CL $*.C
.c.obj $(CC)$(CFLAGS) /c $*.c CL /c $*.C
.cpp.exe $(CPP)$(CPPFLAGS) $*.cpp CL $*.CPP
.cpp.obj $(CPP)$(CPPFLAGS) /c $*.cpp CL /c $*.CPP
.cxx.exe $(CXX) $(CXXFLAGS) $*.cxx CL $*.CXX
.cxx.obj $(CXX) $(CXXFLAGS) /c $*.cxx CL /c $*.CXX
.bas.obj $(BC) $(BFLAGS) $*.bas; BC $*.BAS;
.cbl.exe $(COBOL) $(COBFLAGS) $*.cbl, $*.exe; COBOL $*.CBL, $*.EXE;
.cbl.obj $(COBOL) $(COBFLAGS) $*.cbl; COBOL $*.CBL;
.for.exe $(FOR) $(FFLAGS) $*.for FL $*.FOR
.for.obj $(FOR) /c $(FFLAGS) $*.for FL /c $*.FOR
.pas.exe $(PASCAL) $(PFLAGS) $*.pas PL $*.PAS
.pas.obj $(PASCAL) /c $(PFLAGS) $*.pas PL /c $*.PAS
.rc.res $(RC) $(RFLAGS) /r $* RC /r $*
在上表中,类似AFLAG和CFLAG这种被包含在括号里面的是未定义的宏,通过在makefile中对这些宏给出定义,可以为这些命令指定编译器和参数。例如:
$(AS)$(AFLAGS) $*.asm
AS宏用于指定编译器,NMAKE中默认为ML;AFLAGS宏用于给出编译器参数,NMAKE将之留给用户定义,默认为空。所以默认的操作为:
ML $*.asm
这里可以看到将宏展开的语法,就是将宏的名字用圆括号括起来,然后在前面加上一个美元符号。另外需要说明的是,”$*”是NMAKE预定义的一个特殊的宏,其等于target的路径加上target的基本名。
宏(MARCRO)
这个相信大家都十分熟悉了。在makefile中通过使用宏将可以获得很大的灵活性。下面就是在makefile中定义宏的语法:
macroname=string
在makefile中,macroname是宏的名字,其可以是任何字母,数字和下划线的组合,最多可以有1024个字符。另外要注意的是,macroname是大小写敏感的。string是宏的定义体,可以有高达65510个字符。任何包含0个字符或只包含空白的字符串都被视为空字串(null string),此时,该宏也被视为NULL,任何其出现的地方,都会被替换为空白。
在使用宏时,还应知道以下几个具有特殊意义的符号:
l # 用于注释,例如:
command=ML # compile asm file
l \ 将宏定义分作多行来写,例如:
LINKCMD = link myapp
another, , NUL, mylib, myapp
“\”后面的回车换行符会被空格替换,上面两行相当于:
LINKCMD = link myapp another, , NUL, mylib, myapp
l $ 将宏展开,用法在后面介绍。
l ^ 如果要在宏中包含以上符号,但又不使用它们的特殊语义,则可以这样:
dir=c:\windows^
此时,dir相当于字符串”c:\windows\”。
以下是一些语法上的细节:
1) 在定义宏时,宏名字的第一个字符必须是该行的第一个字符;
2) 每行只能定义一个宏;
3) 在”=”两边可以有空格,但它们都会被忽略;
4) 在宏定义体中可以有空格,它们都会被视为宏的一部分;
除了可以在makefile中定义宏之外,宏定义也可以出现在NMAKE命令行中。此时,如果在宏定义中有任何空白,则必须用双引号将之括起来,例如:
NMAKE "LINKCMD = LINK /MAP"
NMAKE LINKCMD="LINK /MAP"
而像下面这样则是不允许的(等号两边有空格):
NMAKE LINKCMD = "LINK /MAP"
使用宏的语法如下(注意,整个语句中不能有任何空格):
$(macroname)
NMAKE会将整个语句用宏替换掉。如果宏未定义,NMAKE会用空白替换之,不会产生任何错误。如果宏的名字只有一个字符,则括号可以省略,例如:$L和$(L)是等价的。
NMAKE为宏的使用还提供了一个很有用的特性,那就是substitution(子替换)。即是在展开宏的时候,你还可以指明将展开的宏中的某部分文本用另外的文本替换掉。例如:
SOURCE=one.c two.c
foo.exe : $(SOURCE:.c=.obj)
LINK $**;
展开来就是这样:
SOURCE=one.c two.c
foo.exe : one.obj two.obj
LINK one.obj two.obj;
语句$(SOURCE:.c=.obj)表示将SOURCE中出现的所有”.c”替换为”.obj”。
由以上的例子可以看出,substitution的语法如下(注意,没有空格):
$(macroname:str1=str2)
此外,NMAKE还提供了4组预定义的宏,它们分别是文件名宏,递归宏,命令宏和参数宏。它们都可以被重新定义,但可能会引起一些不必要的麻烦,因为它们被广泛使用。正所谓“动一发而牵全身”,一个小小的改动,甚至有可能会影响到太阳黑子的运动(蝴蝶效应),这就是使用宏的最大的弊端。
文件名宏
在commands block中使用,以表示特定的文件名,包括:
1) $@ 用来表示相关联的dependent line中第一个target的全名(包括路径)。
2) $$@ 同上,但只能用在dependent line中。
3) $* target的路径加基本名。
4) $** 相应的dependent line中的所有dependent。
5) $? 相应的dependent line中的所有time stamp大于target的dependent。
6) $< 同上,但只能用在IR中。
下面是一个例子:
DIR = c:\objects
$(DIR)\a.obj : a.obj
COPY a.obj $@
最后一句展开来就相当于:copy a.obj c:\objects\a.obj
另外,在使用以上这些宏的时候,还可以通过以下的字符来提取文件名中的某一个部分:
D 路径
B 基本名
F 基本名加扩展名
R 路径加基本名
例如:如果$@表示c:\objects\a.object,则
$(@D) c:\objects
$(@B) a
$(@F) a.obj
$(@R) c:\objects\a
递归宏
有3个,它们都是用来在makefile中方便地进行NMAKE的递归调用,它们分别是:
1) MAKE
表示运行当前makefile的NMAKE程序的名字。例如,如果你在控制台用以下语句运行makefile:
NMAKE her.mak
则MAKE就等于NMAKE。
但如果你将NMAKE.EXE改名为FUCK.EXE,那么你运行makefile的命令就应该改为:
FUCK her.mak
此时,MAKE就等于FUCK。
2) MAKEDIR
表示你调用NMAKE时所在的目录。
3) MAKEFLAGS
表示你运行当前makefile时使用的NMAKE参数。
这几个宏在build程序的不同版本时特别有用,例如:
all : vers1 vers2
vers1 :
cd \vers1
$(MAKE)
cd ..
vers2 :
cd \vers2
$(MAKE) /F vers2.mak
cd ..
NMAKE会分别在.\vers1和.\vers2目录下运行vers1.mak和vers2.mak。
命令宏和参数宏
命令宏表示Microsoft的编译程序(真的很会做生意,任何时候都不忘自己的产品),而参数宏则是表示传递给这些编译器的参数,在默认情况下,参数宏都是未定义的。当然,你可以重新定义它们,让它们表示Boland的编译程序和参数。
命令宏 对应的参数宏
1) AS ml,M的汇编编译器。 AFLAGS
2) BC bc,M的BASIC编译器。 BFLAGS
3) CC cl,M的C编译器。 CFLAGS
4) COBOL cobol,M的COBOL编译器。 COBFLAGS
5) CPP cl,M的C++编译器。 CPPFLAGS
6) CXX cl,M的C++编译器。 CXXFLAGS
7) FOR fl,M的FORTRAN编译器。 FFLAGS
8) PASCAL pl,M的PASCAL编译器。 PFLAGS
9) RC rc,M的资源编译器。 RFLAGS