• 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 FilesMicrosoft Visual StudioVC98Bin增加到环境变量Path

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

    说明:

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

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

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

    %comspec% /k ""C:Program FilesMicrosoft Visual Studio 9.0VCvcvarsall.bat"" x86

    comspec是系统环境变量,运行上面的命令时%comspec%会被替换为该环境变量所代表的字符串,一般就是 c:windowssystem32cmd.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 FilesMicrosoft Visual StudioVC98BinVCVARS32.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:ReleaseTest.exe : 1.obj 2.obj

        echo $@

        echo $*

        echo $**

        echo $?

        echo $<

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

    $@ 

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

    这里就是C:ReleaseTest.exe

    $$@ 

    同上。Valid only as a dependent in a dependency

    具体怎么用?

    $* 

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

    这里就是C:ReleaseTest

    $** 

    所有依赖项

    这里就是1.obj 2.obj

    $? 

    比当前目标新的依赖项

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

    $< 

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

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

    .c.obj :

        echo $<

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

    C:ReleaseTest.exe :

        echo $@

        echo $(@R)

        echo $(@D)

        echo $(@F)

        echo $(@B)    

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

    后缀

    R

    去掉扩展名,即:C:ReleaseTest

    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^ 。注意: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 456 "。它的执行逻辑如下:

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

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

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

    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

    删除一个宏

  • 相关阅读:
    个人关于浮动的理解
    css课堂笔记(盒子模型,标准文档流,浮动,美化)
    css和html课堂笔记
    html中的行内元素和块级元素
    css简介及常用语法
    html简介及常用功能
    权重比较
    盒模型
    css常见内联和块级元素
    Vigenère密码
  • 原文地址:https://www.cnblogs.com/hanford/p/6027930.html
Copyright © 2020-2023  润新知