VC++NMAKE

时间:2023-03-09 03:56:25
VC++NMAKE

目录

第1章 NMAKE    1

1.1 运行NMAKE    1

1.1.1 NMAKE的实质    2

1.2 描述块    3

1.2.1 定义    3

1.2.2 多个描述块    3

1.2.3 依赖    4

1.2.4 长文件名    4

1.2.5 多目标    4

1.2.6 合并    5

1.3 宏    5

1.3.1 定义、使用    5

1.3.2 作用域    6

1.3.3 宏替换    6

1.3.4 文件名宏    6

1.3.5 递归宏    8

1.3.6 内置宏    8

1.3.7 环境变量宏    8

1.4 环境变量    8

1.4.1 查看    9

1.4.2 使用    9

1.4.3 环境变量宏    9

1.4.4 传递    9

1.5 特殊字符    10

1.5.1 #    10

1.5.2 ^    10

1.5.3 \    10

1.5.4 %    11

1.5.5 $    11

1.6 规则    11

1.6.1 简单规则    11

1.6.2 预定义规则    12

1.6.3 自定义规则    12

1.6.4 批量规则    13

1.6.5 规则中使用路径    13

1.7 命令    13

1.7.1 命令前缀    14

1.7.2 内联文件    14

1.8 预处理指令    15

第1章 NMAKE

1.1 运行NMAKE

nmake其实是nmake.exe,它需要在DOS命令窗口下运行。如下图所示,在DOS命令窗口执行nmake /?,试图获取nmake.exe的帮助信息。

VC++NMAKE

执行命令失败,原因是找不到nmake.exe在哪里,根本无法执行nmake.exe。为此,输入它的全路径名(这里是vc6的nmake.exe全路径名,因为中间有空格所以要加上双引号),如下图所示:

VC++NMAKE

每次运行nmake.exe都要指定它的路径名?这也太麻烦了吧?解决方法有两个:

方法一:把C:\Program Files\Microsoft Visual Studio\VC98\Bin增加到环境变量Path;

方法二:运行nmake之前,首先运行VCVARS32.BAT,如下图所示:

VC++NMAKE

说明:

1、方法一对所有程序都会有影响;

2、方法二运行VCVARS32.BAT也是修改环境变量Path,但是它的影响是局部的,只会影响本进程。当上图所示的DOS窗口关闭后,这种影响也随之消失。因此,推荐使用方法二。

每次打开DOS命令窗口,都要执行C:\Program Files\Microsoft Visual Studio\VC98\Bin\VCVARS32.BAT,是不是挺麻烦的?看看VC++2008开始菜单里的"Visual Studio 2008 命令提示"是怎么做的。它其实是一个快捷方式,其命令如下:

%comspec% /k ""C:\Program Files\Microsoft Visual Studio 9.0\VC\vcvarsall.bat"" x86

comspec是系统环境变量,运行上面的命令时%comspec%会被替换为该环境变量所代表的字符串,一般就是 c:\windows\system32\cmd.exe。上面命令的实质就是:运行cmd.exe程序,打开DOS命令窗口,然后再运行vcvarsall.bat这个批处理文件。x86是传递给vcvarsall.bat的参数。/k表示运行完vcvarsall.bat之后,DOS命令窗口不自动关闭。现在,就可以在这个DOS命令窗口里运行nmake.exe了。

借鉴VC++2008的做法,若要运行VC++6.0的nmake,可以创建一个快捷方式,让其运行如下命令:

%comspec% /k "C:\Program Files\Microsoft Visual Studio\VC98\Bin\VCVARS32.BAT"

运行这个快捷方式,即可在弹出的DOS命令窗口里直接运行VC++6.0的nmake.exe了。

1.1.1 NMAKE的实质

NMAKE的实质是一个解释程序,用来执行makefile文件里的命令。

如:运行nmake,它将在当前目录下查找makefile文件,如果找到了就将其载入内存,然后执行该文件里的命令。

也可以指定其它文件,如:

nmake /ftest.mak 或 nmake /f test.mak

均会把test.mak载入内存,然后执行这个文件里的命令。

使用NMAKE的重点在于编写makefile,执行预期的操作。

1.2 描述块

1.2.1 定义

makefile里,最重要的就是描述块(Description Blocks)。其结构为:

targets(目标) : dependents(依赖项)

commands... (命令)

下面是一个简单的makefile,它由一个描述块构成。

Test :

echo 这是一个描述块

注意:

1、目标后面不要紧跟冒号,中间应该有一个空格。也就是说上面"Test :"中间的空格不能缺失;

2、命令以一个或多个制表符(09H)或空格符(20H)开头;

3、命令可以没有,也可以有多个。

运行nmake,这个描述块里的命令将被执行,即"echo 这是一个描述块"将被执行。

1.2.2 多个描述块

一个makefile可以有多个描述块,如下所示:

Test.exe :

echo 生成exe

Test.lib :

echo 生成lib

上面的makefile有两个描述块:Test.exe、Test.lib。运行nmake时,默认只会执行第一个描述块的命令,即"echo 生成exe"会被执行。

若要执行其它描述块的命令,可以给nmake传递一个参数,如:

nmake Test.lib            将执行描述块Test.lib的命令

若要执行多个描述块的命令,也可以给nmake传递参数,如:

nmake Test.lib Test.exe        将依次执行描述块Test.lib、Test.exe的命令

1.2.3 依赖

描述块里,可以指定依赖项。如:

Test.exe : 1.obj

echo 生成exe

1.obj :

echo 生成obj

上面的描述块Test.exe依赖于描述块1.obj。当运行nmake时,将执行描述块Test.exe的命令,但Test.exe依赖于1.obj,因此1.obj里的命令将先被执行,然后才会执行块Test.exe的命令。结果就是"echo生成obj"、"echo生成exe"依次被执行。

一个描述块可以有零个到多个依赖项,多个依赖项之间以空格分隔。如下面的描述块Test.exe,它有两个依赖项1.obj、2.obj。

Test.exe : 1.obj 2.obj

echo 生成exe

1.2.4 长文件名

描述块的目标经常就是一个文件名。如果文件名属于长文件名,就应该用双引号。如下所示:

"VeryLongFileName.exe" : "VeryLongFileName.obj"

echo生成exe

"VeryLongFileName.obj" :

echo生成obj

1.2.5 多目标

下面有两个描述块,其命令是完全相同的:

A :

echo A

echo B

B :

echo A

echo B

可以将它们合并在一起,如下所示:

A B :

echo A

echo B

上面的描述块就是多目标描述块。多个目标之间用空格分隔。

1.2.6 合并

一个makefile里允许描述块的目标重复,nmake对重复项将做合并处理。此时,要特别注意单冒号与双冒号的区别——单冒号合并依赖项,不合并内容;双冒号合并依赖项,同时合并内容。下面是一个例子:

 

makefile

执行描述块ALL的命令顺序

单冒号

ALL : A

echo allA

ALL : B

echo allB

A :

echo A

B :

echo B

两个ALL被合并为

ALL : A B            合并依赖项

echo allA        不合并内容

执行结果:

echo A

echo B

echo allA

注意:echo allB不会被执行,在这个地方会产生警告。

双冒号

ALL :: A

echo allA

ALL :: B

echo allB

A :

echo A

B :

echo B

两个ALL被合并为

ALL : A B            合并依赖项

echo allA

echo allB        合并内容

执行结果:

echo A

echo allA

echo B

echo allB

1.3

1.3.1 定义、使用

假定某个makefile的内容如下,使用编译程序 cl.exe 编译三个cpp文件。

ALL :

cl /c /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" 1.cpp

cl /c /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" 2.cpp

cl /c /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" 3.cpp

可以发现,这三个cpp的编译参数是相同的。如果cpp很多,修改编译参数将会比较麻烦。这里,就可以使用宏。新的makefile内容如下:

CPP_PROJ=/c /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS"

ALL :

cl $(CPP_PROJ) 1.cpp

cl $(CPP_PROJ) 2.cpp

cl $(CPP_PROJ) 3.cpp

CPP_PROJ=/c /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS"就是用来定义宏的。编译时,通过$(CPP_PROJ)将这个宏代表的字符串提取出来。

注意:

1、宏的名称是区分大小写的;

2、对于不再需要的宏,可以通过!UNDEF命令取消。

1.3.2 作用域

参考下面的makefile:

Test.exe : 1.obj 2.obj

echo 生成exe

SOURCE=1.c

1.obj :

echo 编译$(SOURCE)

SOURCE=2.cpp

2.obj :

echo 编译$(SOURCE)

宏SOURCE有两份定义1.c和2.cpp。对于描述块1.obj而言1.c的定义离其最近,所以在1.obj这个块里$(SOURCE)就是1.c;同理在块2.obj里$(SOURCE)就是2.cpp。

1.3.3 宏替换

宏替换的格式为$(macroname:string1=string2)。具体请参考下面的内容:

CPPFILES=1.cpp 2.cpp

OBJFILES=$(CPPFILES:.cpp=.obj)

首先定义宏CPPFILES,其内容为1.cpp 2.cpp。OBJFILES=$(CPPFILES:.cpp=.obj)表示把CPPFILES里的.cpp替换为.obj(不会改变CPPFILES的值)然后赋给OBJFILES,现在OBJFILES的内容为1.obj 2.obj。

1.3.4 文件名宏

假定某个makefile的内容如下。

C:\Release\Test.exe : 1.obj 2.obj

echo $@

echo $*

echo $**

echo $?

echo $<

在这个描述块里,$@、$?、$<……这些都属于文件名宏。具体如下(来自MSDN)

宏名

说明

$@

描述块目标名——路径、文件名、扩展名

这里就是C:\Release\Test.exe

$$@

同上。Valid only as a dependent in a dependency

具体怎么用?

$*

描述块目标名——路径、文件名,不含扩展名

这里就是C:\Release\Test

$**

所有依赖项

这里就是1.obj 2.obj

$?

比当前目标新的依赖项

这里就是1.obj 2.obj里比Test.exe新的依赖项集合

$<

比当前目标新的依赖项,仅在推断规则下有效

$<的用法如下所示:将*.c编译为*.obj时可能就会用到这个推断规则,此时会执行描述块.c.obj,$<就是具体的.c文件。

.c.obj :

echo $<

文件名宏还可以加后缀R、D、F、B,如下面的例子:

C:\Release\Test.exe :

echo $@

echo $(@R)

echo $(@D)

echo $(@F)

echo $(@B)

$@就是C:\Release\Test.exe,至于$(@R)、$(@D)、$(@F)、$(@B)的含义见下表(来自MSDN)

后缀

说明

R

去掉扩展名,即:C:\Release\Test

D

去掉文件名和扩展名,即:C:\Release

F

去掉前面的目录,即:Test.exe

B

去掉前面的目录和扩展名,即:Test

1.3.5 递归宏

三个递归宏:MAKE、MAKEDIR、MAKEFLAGS。使用它们的例子如下:

ALL :

echo $(MAKE)

echo $(MAKEDIR)

echo /$(MAKEFLAGS)

注意:使用MAKEFLAGS的固定格式为/$(MAKEFLAGS)

1.3.6 内置宏

内置宏由nmake创建,makefile里可以修改、使用。vc6的内置宏如下表所示(来自MSDN):

编译器

命令宏

选项宏

Macro Assembler

AS

AFLAGS

Basic Compiler

BC

BFLAGS

C Compiler

CC

CFLAGS

COBOL Compiler

COBOL

COBFLAGS

C++ Compiler

CPP

CPPFLAGS

C++ Compiler

CXX

CXXFLAGS

FORTRAN Compiler

FOR

FFLAGS

Pascal Compiler

PASCAL

PFLAGS

Resource Compiler

RC

RFLAGS

最常用的就是CFLAGS、CPPFLAGS。

1.3.7 环境变量宏

环境变量宏与环境变量之间有着微小的差别,具体请见下一节的内容。

1.4 环境变量

1.4.1 查看

在makefile里,使用set命令可查看环境变量。如下所示

ALL :

set

1.4.2 使用

makefile里使用环境变量的格式为"%%名称%%"。如下面的makefile将显示环境变量OS的内容:

ALL :

echo %%OS%%

1.4.3 环境变量宏

nmake运行时,会为每一个环境变量生成一个宏。如:为环境变量Path、OS……生成宏Path、OS……所以也可以使用"$(名称)"的格式获得环境变量的值,如下面的makefile

ALL :

echo $(OS)

$(OS)取出的是宏OS的值。运行nmake时宏OS被初始化为环境变量OS的值,因此$(OS)取出的就是环境变量OS的值。

明白了这个道理,看看下面的makefile

ALL :

set VAR=1

echo $(VAR)

echo %%VAR%%

通过set VAR=1创建了一个环境变量VAR,但是并没有创建宏VAR,因此$(VAR)取不出值。也就是说:makefile内部创建的环境变量无法通过"$(名称)"的格式获取其值。

1.4.4 传递

运行nmake时,可以在命令行里增加环境变量。如:

nmake CFG="Test - DEBUG" VERSION=5.3.1

nmake将增加两个环境变量 CFG 和 VERSION,同时根据它们创建宏CFG 和 VERSION。makefile里可以使用$(CFG)和$(VERSION)。

1.5 特殊字符

1.5.1 #

makefile里以#号开头的为注释行,nmake执行时注释行将被忽略。

下面的"#第一个makefile"就是注释行:

#第一个makefile

Test :

echo 这是一个描述块

允许#出现在一行中间,如下所示

STR=123#456

ALL :

echo $(STR)

上面的#456是注释,所以宏STR的值是123,而不是123#456

1.5.2 ^

为了表示如下特殊字符,需要在它们的前面加一个^符号

: ; # ( ) $ ^ \ { } ! @ —

如下面的makefile,宏STR的内容为123#456^789

STR=123^#456^^789

ALL :

echo $(STR)

^和后面的换行符会被解释为换行符,如下面的STR为123^\n。注意:STR=123^的下一行必须为空。

STR=123^

ALL :

echo $(STR)

1.5.3 \

\与后面的换行符会被替换为空格符,如下面的LINK32_OBJS为"StdAfx.obj" "Test.obj" "TestDlg.obj" "Test.res"。

LINK32_OBJS= \

"StdAfx.obj" \

"Test.obj" \

"TestDlg.obj" \

"Test.res"

说明:

1、\结尾的下一行,行首的空格符、制表符被自动删除;

2、\必须紧跟换行符,如果\后面是空格符或制表符,就起不到连接下一行的作用。

1.5.4 %

两个%号被解释为一个,如:%%OS%%被解释为%OS%,即取环境变量OS的值。

1.5.5 $

两个$号被解释为一个,当然也可以用^$表示一个$。

1.6 规则

首先看下面的makefile

CPP=1.cpp 2.cpp

OBJ=$(CPP:.cpp=.obj)

ALL : $(OBJ)

link $(OBJ)

1.obj :

cl /c 1.cpp

2.obj :

cl /c 2.cpp

宏CPP的内容是所有的cpp文件,即1.cpp和2.cpp。宏OBJ=$(CPP:.cpp=.obj)表示宏替换,最终OBJ的内容为1.obj 2.obj。描述块ALL的实际内容如下:

ALL : 1.obj 2.obj

link 1.obj 2.obj

运行nmake时,描述块ALL的命令将被执行。ALL依赖于1.obj 和 2.obj 两个描述块,因此1.obj和2.obj这两个描述块的命令将首先被执行。它们分别编译1.cpp和2.cpp为1.obj和2.obj。最后调用 link 1.obj 2.obj 生成 exe 程序。

如果cpp文件有50个,按照上面的写法岂不是要写50个obj块?有没有更加简单的写法?答案就是使用规则(Rule)。

1.6.1 简单规则

下面是一个使用规则的makefile。.c.obj 就是.c文件编译生成.obj文件的规则;.cpp.obj就是.cpp文件编译生成.obj文件的规则。

CPP=1.c 2.c 3.cpp

OBJ=$(CPP:.cpp=.obj)

OBJ=$(OBJ:.c=.obj)

ALL : $(OBJ)

link $(OBJ)

1.obj :

echo 1.c

.c.obj :

echo $<

.cpp.obj :

echo $**

执行描述块ALL的命令时,需要1.obj、2.obj、3.obj。

1.obj没有问题,它是一个被明确定义的描述块,echo 1.c 被首先执行。

2.obj没有对应的描述块,这时会在规则里找。现在有两个规则:.c.obj和.cpp.obj。到底选择哪个呢?答案是看2.c和2.cpp哪个存在。如果2.c存在且时间比2.obj要晚就使用.c.obj规则,如果2.cpp存在且时间比2.obj要晚就使用.cpp.obj规则。命令echo $<中的$<是一个文件名宏,它要么是2.c要么是2.cpp。

3.obj与2.obj的处理流程相同,最终echo $**被执行。

1.6.2 预定义规则

预定义规则就是nmake已经定义的规则。上一节中的.c.obj和.cpp.obj就属于预定义规则。

对于预定义规则,makefile里无需再次定义,可直接使用。

下表就是部分预定义规则,节选自MSDN

规则

命令

缺省命令

.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

1.6.3 自定义规则

下面的makefile创建了.txt.inf规则。一定要注意.SUFFIXES : .txt,它是.txt.inf规则能用起来的关键。

INFS=1.inf 2.inf

Test.txt : $(INFS)

copy $(INFS: =/B+)/B $(@F)/B

.txt.inf :

copy $< $(<B).inf

.SUFFIXES : .txt

这个makefile的执行逻辑:

1、Test.txt依赖于1.inf、2.inf;

2、根据规则.txt.inf生成1.inf和2.inf。其实就是执行如下两条命令:

copy 1.txt 1.inf

copy 2.txt 2.inf

3、最后执行copy $(INFS: =/B+)/B $(@F)/B,其实就是:

copy 1.inf/B+2.inf/B Test.txt/B

最终Test.txt的内容就是1.txt和2.txt合并后的内容。

1.6.4 批量规则

批量规则与简单规则的语法区别在于:批量规则最后是双冒号,简单规则最后是单冒号。参考下面的makefile文件

CPP=1.c 2.c 3.c

OBJ=$(OBJ:.c=.obj)

ALL : $(OBJ)

link $**

.c.obj:

echo $<

规则.c.obj后面是单冒号,因此echo $<将被执行三次,依次为1.c、2.c、3.c。把单冒号换成双冒号,则echo $<只执行一次,此时的$<内容是1.c 2.c 3.c。

1.6.5 规则中使用路径

可以在规则中加入路径,如:{src\}.c{Temp\}.obj。这样的话,输入文件(*.c)将在src目录里查找,输出文件(*.obj)将在Temp目录里查找、生成。

1.7 命令

1.7.1 命令前缀

命令前缀@表示命令执行时不会显示这条命令。如下面的del 1.txt执行时不会显示这条命令。

ALL :

@del 1.txt

命令前缀-表示命令执行失败时不会退出nmake。如下面的copy 1.txt 2.txt因为1.txt已经被删除,所以copy会失败并返回非零值。nmake发现copy返回值不为零,一般情况下会中止执行的,但是因为copy前面有-号nmake会继续执行下去。

ALL :

del 1.txt

-copy 1.txt 2.txt

echo 复制完毕

命令前缀可以混合使用,如下面的copy命令

ALL :

del 1.txt

-@copy 1.txt 2.txt

echo 复制完毕

1.7.2 内联文件

参考下面的makefile

OBJ=1.obj 2.obj 3.obj 4.obj \

5.obj 6.obj 7.obj 8.obj ……

ALL : $(OBJ)

link $**

假如obj文件非常多,那么连接命令 link 1.obj 2.obj 3.obj……将会非常长。在Windows里,命令行的长度是有限制的。如何摆脱这一限制呢?答案就是使用内联文件。改进后的makefile:

OBJ=1.obj 2.obj 3.obj 4.obj \

5.obj 6.obj 7.obj 8.obj ……

ALL : $(OBJ)

link @<<

$**

<<

link @<<中的<<指定了一个匿名的内联文件,nmake会在Temp目录生成一个临时文件。link @<<中的@表示把这个临时文件当作响应文件,link从响应文件里读取命令行参数。

$**是写入临时文件的内容,具体的就是 1.obj 2.obj 3.obj 4.obj……注意$**前的空格符或制表符将被忽略

最后的<<不会写入文件,它表示完成写入。

nmake执行完毕后,内联文件默认会被删除掉。下面的makefile通知nmake保留内联文件,同时还指定了内联文件的名称为inline

OBJ=1.obj 2.obj 3.obj 4.obj \

5.obj 6.obj 7.obj 8.obj ……

ALL : $(OBJ)

link @<<inline

$**

<<KEEP

还可以指定多个内联文件,如下面的makefile

ALL :

copy << /B + << /B 1.txt /B

123

<<

456

<<

执行nmake之后,将生成1.txt,其内容为"123\r\n456\r\n"。它的执行逻辑如下:

1、copy命令里有两个<<,执行时将创建两个临时文件;

2、123和<<是往第一个临时文件里写入123\r\n;

3、456和<<是往第二个临时文件里写入456\r\n;

4、copy把两个临时文件合并写入1.txt。

1.8 预处理指令

C/C++里有预处理指令#include、#if...#elif...#else...#endif……。makefile里也有预处理指令,它们以!开头。

语法

说明

!ERROR text

显示错误并终止nmake的运行

!MESSAGE text

显示一条消息

!INCLUDE [<]filename[>]

包含一个文件

!IF constantexpression

!IFDEF macroname

!IFNDEF macroname

!ELSE[IF constantexpression |IFDEF macroname | IFNDEF macroname]

!ELSEIF

!ELSEIFDEF

!ELSEIFNDEF

!ENDIF

条件预处理

!UNDEF macroname

删除一个宏