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的帮助信息。

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

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

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

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

说明:

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.0nmake,可以创建一个快捷方式,让其运行如下命令:

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

运行这个快捷方式,即可在弹出的DOS命令窗口里直接运行VC++6.0nmake.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.exeTest.lib。运行nmake时,默认只会执行第一个描述块的命令,即"echo 生成exe"会被执行。

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

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

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

nmake Test.lib Test.exe        将依次执行描述块Test.libTest.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.obj2.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.c2.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.cppOBJFILES=$(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 $<

文件名宏还可以加后缀RDFB,如下面的例子:

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 递归宏

三个递归宏:MAKEMAKEDIRMAKEFLAGS。使用它们的例子如下:

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

最常用的就是CFLAGSCPPFLAGS

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运行时,会为每一个环境变量生成一个宏。如:为环境变量PathOS……生成宏PathOS……所以也可以使用"$(名称)"的格式获得环境变量的值,如下面的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 VERSIONmakefile里可以使用$(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)

^和后面的换行符会被解释为换行符,如下面的STR123^\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.cpp2.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.obj2.obj这两个描述块的命令将首先被执行。它们分别编译1.cpp2.cpp1.obj2.obj。最后调用 link 1.obj 2.obj 生成 exe 程序。

如果cpp文件有50个,按照上面的写法岂不是要写50obj块?有没有更加简单的写法?答案就是使用规则(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.obj2.obj3.obj

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

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

3.obj2.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的执行逻辑:

1Test.txt依赖于1.inf2.inf

2、根据规则.txt.inf生成1.inf2.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.txt2.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.c2.c3.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"。它的执行逻辑如下:

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

2123<<是往第一个临时文件里写入123\r\n

3456<<是往第二个临时文件里写入456\r\n

4copy把两个临时文件合并写入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

删除一个宏

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值