makefile 学习笔记

 

参考资料:陈浩,《跟我一起写makefile》 :http://blog.youkuaiyun.com/haoel/article/details/2886/

 

Makefile的格式和规则

    target ... : prerequisites ...
            command
            ...
            ...

target是目标文件,Object File或者可执行文件,或者标签

prerequisites是生成target依赖的所有文件。

command是make需要执行的命令。(任意的Shell命令)

需要注意的是command前必须是一个[Tab]。

这是个文件的依赖关系:生成target依赖于prerequisites 里面的文件,生成规则由command给出。实际上,Makefile中最核心的内容是,当prerequisites 中有文件比target新,就执行command中的指令。

 

make的自动推导

make识别到一个XXX.o时,可以自动将XXX.c加入到依赖文件中,并且cc –o XXX.c 也能推导出来。因此在书写makefile的时候可以省去那些可以由makefile推导出来的东西。比如以上的makefile简化之后:

objects = main.o kbd.o command.o display.o /
              insert.o search.o files.o utils.o

    edit : $(objects)
            cc -o edit $(objects)

    main.o : defs.h
    kbd.o : defs.h command.h
    command.o : defs.h command.h
    display.o : defs.h buffer.h
    insert.o : defs.h buffer.h
    search.o : defs.h buffer.h
    files.o : defs.h buffer.h command.h
    utils.o : defs.h

    .PHONY : clean
    clean :
            rm edit $(objects)

可以看出,每一个XXX.o的依赖文件只列出了头文件,下面的command也省略了,因为这两个东西都可以被make自动推导出来。

 

clean规则

每一个makefile都应该有一个清理规则,clean总是应该放在makefile的最后部分。一般为:

        clean:
            rm edit $(objects)

更好的做法是:

.PHONY : clean
        clean :
                -rm edit $(objects)

.PHONY表明clean是一个伪目标,-rm前面的减号意思是当对某些文件操作失败时,继续后面的操作。

 

make的工作流程

读入所有的Makefile。
读入被include的其它Makefile。
初始化文件中的变量。
推导隐晦规则,并分析所有规则。
为所有的目标文件创建依赖关系链。
根据依赖关系,决定哪些目标要重新生成。
执行生成命令。

makefile的最开始部分应该是make的最终目标,一般情况下一个makefile都只有一个最终目标。

makefile的文件搜寻

当工程的文件放在不同的目录时,写makefile时需要指定搜寻的目录和规则。

VPATH = ….

makefile中有一个特殊变量VPATH,通过指定VPATH告诉make在VPATH所指的目录寻找依赖的文件(当然是在当前目录没有找到的前提下),多个目录用冒号隔开。

vpath <pattern> <directories>

<pattern>需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”结尾的文件。<pattern>指定了要搜索的文件集,而<directories>则指定了<pattern>的文件集的搜索的目录,多个目录用冒号隔开。

vpath和上面的VPATH并不相同,vpath是makefile中的关键字,它可以指定不同的文件在不同的搜索目录中,比如:

vpath %.h ../headers

表示在../headers目录搜索.h头文件。

 

伪目标

前面提到过伪目标,伪目标其实和真正的目标区别不大,最大的区别就是伪目标只是一个便签,不生成真正的目标文件。我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。如:

.PHONY clean

伪目标可以非常有用。之前我们说过,makefile一般只有一个最终目标,就是其第一个目标,如果想一次性生成多个目标怎么办呢?这就可以借助于伪目标来实现。比如:

all : prog1 prog2 prog3
    .PHONY : all

    prog1 : prog1.o utils.o
            cc -o prog1 prog1.o utils.o

    prog2 : prog2.o
            cc -o prog2 prog2.o

    prog3 : prog3.o sort.o utils.o
            cc -o prog3 prog3.o sort.o utils.o

这里的最终目标是一个伪目标all,在执行make 时候伪目标总是会被执行,因此依赖的三个文件会被生成。

再比如,我们在清理的时候可能需要执行不同级别的清理,那么也可以用伪目标来区分。

 

多目标

上文提到要生成多个目标时候可以使用伪目标来实现,然而实际上makefile的target可以不止一个。有些情况下,这一组目标有某个或者某些相同的依赖文件而且生成的规则相似,那么就可以使用makefile的多目标来生成。如:

  bigoutput littleoutput : text.g
            generate text.g -$(subst output,,$@) > $@

bigoutput : text.g
            generate text.g -big > bigoutput
    littleoutput : text.g
            generate text.g -little > littleoutput

左边的规则与右边的等价。简单解释一下,由于bigoutput和littleoutput都依赖文件text.g,且生成规则相似,则写成下面一行的命令形式。其中-$(subst output,,$@)中的”$”表示要执行一个makefile函数,函数名是subst,后面都是参数。“$@”表示目标的集合。这个函数的作用是截取字符串。

在定义多目标规则时,利用静态模式将更加有用。静态模式的基本语法是:

<targets ...>: <target-pattern>: <prereq-patterns ...>
            <commands>
            ...

targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。

target-parrtern是指明了targets的模式,也就是的目标集模式。

prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。

换一种更加通俗的说法,target定义的是一个目标的集合,里面包含了很多的目标,而target-pattern定义的是本次命令执行的目标模式,也就是说从target目标集合里选出匹配target-pattern模式的目标来作为本次命令的真正目标。而prereq-patterns定义的是依赖文件的匹配模式。举个简单的例子,如果target-pattern为“%.o”,那么目标集合就是target中所有以[.o]为后缀的目标文件。而如果prereq-patterns定义为“%.c”,于是依赖的文件就是target-pattern匹配的所有的[.o]目标文件对应的[.c]文件。

一个例子:

    objects = foo.o bar.o

    all: $(objects)

    $(objects): %.o: %.c
            $(CC) -c $(CFLAGS) $< -o $@

由之前的分析可以看出,target-pattern为[%.o]匹配了objects中所有的[.o]目标文件,即foo.o bar.o,而[%.c]表明命令的依赖文件是foo.c bar.c。命令中的[$<]表示所有的依赖目标集(也就是foo.c bar.c),[$@]表示目标集(也就是foo.o bar.o)。

自动生成依赖性

 

 

 

makefile的变量

makefile的变量在定义时需要赋给一个初值,使用时前面要有一个$符合,一般的使用惯例是“${}”或“$()”,若要使用真正的“$”符号则表示为“$$”。一个简单的例子:

edit : main.o kbd.o command.o display.o /
           insert.o search.o files.o utils.o
            cc -o edit main.o kbd.o command.o display.o /
                       insert.o search.o files.o utils.o

    main.o : main.c defs.h
            cc -c main.c
    kbd.o : kbd.c defs.h command.h
            cc -c kbd.c
    command.o : command.c defs.h command.h
            cc -c command.c
    display.o : display.c defs.h buffer.h
            cc -c display.c
    insert.o : insert.c defs.h buffer.h
            cc -c insert.c
    search.o : search.c defs.h buffer.h
            cc -c search.c
    files.o : files.c defs.h buffer.h command.h
            cc -c files.c
    utils.o : utils.c defs.h
            cc -c utils.c
    clean :
            rm edit main.o kbd.o command.o display.o /
               insert.o search.o files.o utils.o

 

这个makefile的edit依赖文件很多,可以用一个变量来代替它。

objects = main.o kbd.o command.o display.o /
              insert.o search.o files.o utils.o

于是,第一行就可以变为:

  edit : $(objects)
            cc -o edit $(objects)

这样为书写makefile,以及在大的工程中修改makefile带了很大的便利,也让makefile更加简洁易懂。makefile中的目标、依赖、命令都可以用变量来表示。makefile定义变量的方法有三种,一种是像通常的编程语言一样使用类似如下的方式。这种方式定义变量的好处在于可以将变量的定义放在后面,换句话说,前面的命令可以使用后面定义的变量。

objects = main.o kbd.o command.o display.o

另外一种变量定义方式是使用“:=”操作符,这种定义方式不能使用后面定义的变量。例如:

    x := foo
    y := $(x) bar
    x := later

等价于

    y := foo bar
    x := later

而,

    y := $(x) bar
    x := foo

却等价于

    y := bar
    x := foo

还有一种用于定义变量的操作符“?=”,

x ?= foo

表示如果x没有定义过,那么x = foo,否则什么也不做。

 

变量值的替换的例子:

    foo := a.o b.o c.o
    bar := $(foo:.o=.c)

   foo := a.o b.o c.o
    bar := $(foo:%.o=%.c)

上面两种表示的效果是一样的——变量替换,让$(bar)变量的值为“a.c b.c c.c”,但是有一点点不同。左边$(foo:.o=.c)表示的是,变量foo中所有.o结尾的字符串,替换成.c结尾的字符串。右边$(foo:%.o=%.c)这种形式是静态模式的表示形式,变量foo中匹配模式%.o的替换成了%.c,%是必需的。

我们可以使用“+=”操作符给变量追加值,即增加一个值。

    objects = main.o foo.o bar.o utils.o
    objects += another.o

 

define关键字可以用来定义多行变量,如:

    define two-lines
    echo foo
    echo $(bar)
    endef

以define开头,以endef结尾。命令前面必需有[Tab],相当于
two-lines = foo $(bar)

 

有一种非常有用的变量叫目标变量,定义语法和举例如下:

<target ...> : <variable-assignment>

<target ...> : overide <variable-assignment>

    prog : CFLAGS = -g
    prog : prog.o foo.o bar.o
            $(CC) $(CFLAGS) prog.o foo.o bar.o

    prog.o : prog.c
            $(CC) $(CFLAGS) prog.c

    foo.o : foo.c
            $(CC) $(CFLAGS) foo.c

    bar.o : bar.c
            $(CC) $(CFLAGS) bar.c

当设置了目标变量时,这个变量会作用到由这个目标所引发的所有的规则中去。如上例,prog : CFLAGS = -g中定义了目标变量CFLAGS,因此在所有由prog引起的命令中,这个CFLAGS都会起作用。target还可以用模式变量。如:

<pattern ...> : <variable-assignment>

<pattern ...> : override <variable-assignment>

    %.o : CFLAGS = -O

 

条件判断

  libs_for_gcc = -lgnu
    normal_libs =

    foo: $(objects)
    ifeq ($(CC),gcc)
            $(CC) -o foo $(objects) $(libs_for_gcc)
    else
            $(CC) -o foo $(objects) $(normal_libs)
    endif

条件判断由ifeq开始,后面括号里是进行比较的两个变量或者常量,ifeq ($(CC),gcc)表示如果变量CC是gcc的话,执行下一条命令,否则(else)执行else下的命令,判断由endif结束。实际上,ifeq可以由下面几种方式来使用,而上例使用的是第一种方式。

    ifeq (<arg1>, <arg2>)
    ifeq '<arg1>' '<arg2>'
    ifeq "<arg1>" "<arg2>"
    ifeq "<arg1>" '<arg2>'
    ifeq '<arg1>' "<arg2>"

另外一个条件判断关键字是ifneq,意思很明白,和ifeq的恰好相反,但是用法相同。

第三个条件关键字是“ifdef”,

    ifdef <variable-name>

如果变量<variable-name>的值非空,那到表达式为真。否则,表达式为假。实际上,ifdef就是测试变量是否有值。另外还有一个与之对应的ifndef,意思非常明确就不多说了。

在条件判断中最好不要出现自动化变量(如”$@”),因为自动化变量在运行时才会有值。

 

makefile的函数

实际上在之前的例子中已经多次提到过makefile的函数了。当然这个函数和我们程序语言的函数有相同点也有很大的不同,makefile函数调用后有返回值,而且我们可以使用这个返回值,但是makefile的函数只有那么多,不能由用户自己来定义,不过也够用了。函数的调用语法如下:

    $(<function> <arguments1,arguments2,arguments3>)  或者使用{}括号

<function>是函数名,后面是参数列表,函数名和参数之间用空格隔开,参数之间用逗号隔开。

字符串处理函数:

$(subst <from>,<to>,<text>)

    名称:字符串替换函数——subst。
    功能:把字串<text>中的<from>字符串替换成<to>。
    返回:函数返回被替换过后的字符串。

 

$(patsubst <pattern>,<replacement>,<text>)

    名称:模式字符串替换函数——patsubst。
    功能:查找<text>中的单词(单词以“空格”、“Tab”或  “回车”“换行”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。

    返回:函数返回被替换过后的字符串。

 

$(strip <string>)

    名称:去空格函数——strip。
    功能:去掉<string>字串中开头和结尾的空字符。
    返回:返回被去掉空格的字符串值。

 

$(findstring <find>,<in>)

    名称:查找字符串函数——findstring。
    功能:在字串<in>中查找<find>字串。
    返回:如果找到,那么返回<find>,否则返回空字符串。

 

$(filter <pattern...>,<text>)

    名称:过滤函数——filter。
    功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可以有多个模式。
    返回:返回符合模式<pattern>的字串。

 

$(filter-out <pattern...>,<text>)

    名称:反过滤函数——filter-out。
    功能:以<pattern>模式过滤<text>字符串中的单词,去除符合模式<pattern>的单词。可以有多个模式。
    返回:返回不符合模式<pattern>的字串。

 

$(sort <list>)

    名称:排序函数——sort。
    功能:给字符串<list>中的单词排序(升序)。
    返回:返回排序后的字符串。

    备注:sort函数会去掉<list>中相同的单词。

 

$(word <n>,<text>)

    名称:取单词函数——word。
    功能:取字符串<text>中第<n>个单词。(从一开始)
    返回:返回字符串<text>中第<n>个单词。如果<n>比<text>中的单词数要大,那么返回空字符串。

 

$(wordlist <s>,<e>,<text>)

    名称:取单词串函数——wordlist。
    功能:从字符串<text>中取从<s>开始到<e>的单词串。<s>和<e>是一个数字。
    返回:返回字符串<text>中从<s>到<e>的单词字串。如果<s>比<text>中的单词数要大,那么返回空字符串。如果<e>大于<text>的单词数,那么返回从<s>开始,到<text>结束的单词串。

 

$(words <text>)

    名称:单词个数统计函数——words。
    功能:统计<text>中字符串中的单词个数。
    返回:返回<text>中的单词数。

    备注:如果我们要取<text>中最后的一个单词,我们可以这样:$(word $(words <text>),<text>)。

 

$(firstword <text>)

    名称:首单词函数——firstword。
    功能:取字符串<text>中的第一个单词。
    返回:返回字符串<text>的第一个单词。
    备注:这个函数可以用word函数来实现:$(word 1,<text>)。

 

文件名操作函数:

$(dir <names...>)

    名称:取目录函数——dir。
    功能:从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部分。如果没有反斜杠,那么返回“./”。
    返回:返回文件名序列<names>的目录部分。

$(notdir <names...>)

    名称:取文件函数——notdir。
    功能:从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后的部分。
    返回:返回文件名序列<names>的非目录部分。

$(suffix <names...>)

    名称:取后缀函数——suffix。
    功能:从文件名序列<names>中取出各个文件名的后缀。
    返回:返回文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字串。

$(basename <names...>)

    名称:取前缀函数——basename。
    功能:从文件名序列<names>中取出各个文件名的前缀部分。
    返回:返回文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字串

$(addsuffix <suffix>,<names...>)

    名称:加后缀函数——addsuffix。
    功能:把后缀<suffix>加到<names>中的每个单词后面。
    返回:返回加过后缀的文件名序列。

 

$(addprefix <prefix>,<names...>)

    名称:加前缀函数——addprefix。
    功能:把前缀<prefix>加到<names>中的每个单词后面。
    返回:返回加过前缀的文件名序列。

 

$(join <list1>,<list2>)

    名称:连接函数——join。
    功能:把<list2>中的单词对应地加到<list1>的单词后面。如果<list1>的单词个数要比<list2>的多,那么,<list1>中的多出来的单词将保持原样。如果<list2>的单词个数要比<list1>多,那么,<list2>多出来的单词将被复制到<list2>中。
    返回:返回连接过后的字符串。

 

特殊函数:

 

$(foreach <var>,<list>,<text>)

foreach函数和shell中的for语句类似,用于循环控制。这个命令的解释是:将<list>中的字符串逐一取出放入临时变量<var>中(循环完成,<var>便不复存在),然后再执行<text>中包含的命令,最后将执行之后的结果返回。每次返回的结果由一个空格隔开,循环完成之后的返回值就是由空格隔开的一系列结果。

例如:

names := a b c d

files := $(foreach n,$(names),$(n).o)

$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以,$(files)的值是“a.o b.o c.o d.o”。

 

$(if <condition>,<then-part>,<else-part>)

if函数是一个条件控制语句,首先计算<condition>,如果<condition>返回非空字符,则计算<then-part>,否则计算<else-part>,当然和编程语言一样<else-part>是可选的。最后函数返回计算部分的结果。

 

$(call <expression>,<parm1>,<parm2>,<parm3>...)

call函数计算<expression>的结果,<expression>中的变量,如$(1),$(2),$(3),会被parm1,parm2,parm3替换。最后函数返回<expression>的结果。

例如:

reverse = $(1) $(2)

foo = $(call reverse,a,b)

此时的foo的结果就是“a b”。

 

$(origin <variable>)

origin函数返回变量<variable>是在哪儿定义的。一般不在<variable>中使用$,因为此处指的是变量名。

函数的返回值有几种情况:

 

  • “undefined”

如果<variable>从来没有定义过,origin函数返回这个值“undefined”。

  • “default”

如果<variable>是一个默认的定义,比如“CC”这个变量,这种变量我们将在后面讲述。

  • “environment”

如果<variable>是一个环境变量,并且当Makefile被执行时,“-e”参数没有被打开。

  • “file”

如果<variable>这个变量被定义在Makefile中。

  • “command line”

如果<variable>这个变量是被命令行定义的。

  • “override”

如果<variable>是被override指示符重新定义的。

  • “automatic”

如果<variable>是一个命令运行中的自动化变量。

 

$(shell <command> <var>)

shell函数的作用是在makefile中调用shell命令来处理变量,返回值就是shell命令执行的结果。比如:

files := $(shell echo *.c)

files最后的值就是所有.c文件。

 

make的控制函数:

$(error <text ...>)

产生一个致命的错误,<text ...>是错误信息,并结束make。

 

$(warning <text ...>)

产生一个warning信息,并继续执行make。

 

GNU makefile中的一些常见目标

     “all”
        这个伪目标是所有目标的目标,其功能一般是编译所有的目标。
     “clean”
        这个伪目标功能是删除所有被make创建的文件。
     “install”
        这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。
     “print”
        这个伪目标的功能是例出改变过的源文件。
     “tar”
        这个伪目标功能是把源程序打包备份。也就是一个tar文件。
     “dist”
        这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件。
     “TAGS”
        这个伪目标功能是更新所有的目标,以备完整地重编译使用。
     “check”和“test”
        这两个伪目标一般用来测试makefile的流程。

 

makefile的检查规则

     “-n”
    “--just-print”
    “--dry-run”
    “--recon”
    不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行

 

    “-t”
    “--touch”
    这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状态。

 

    “-q”
    “--question”
    这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印出一条出错信息。

 

    “-W <file>”
    “--what-if=<file>”
    “--assume-new=<file>”
    “--new-file=<file>”
    这个参数需要指定一个文件。一般是是源文件(或依赖文件),Make会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所发生的规则命令。

 

make的参数

“-b”
“-m”
这两个参数的作用是忽略和其它版本make的兼容性。

 

“-B”
“--always-make”
认为所有的目标都需要更新(重编译)。

 

“-C <dir>”
“--directory=<dir>”
指定读取makefile的目录。如果有多个“-C”参数,make的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。

 

“—debug[=<options>]”
输出make的调试信息。它有几种不同的级别可供选择,如果没有参数,那就是输出最简单的调试信息。下面是<options>的取值:
    a —— 也就是all,输出所有的调试信息。(会非常的多)
    b —— 也就是basic,只输出简单的调试信息。即输出不需要重编译的目标。
    v —— 也就是verbose,在b选项的级别之上。输出的信息包括哪个makefile被解析,不需要被重编译的依赖文件(或是依赖目标)等。
    i —— 也就是implicit,输出所以的隐含规则。
    j —— 也就是jobs,输出执行规则中命令的详细信息,如命令的PID、返回码等。
    m —— 也就是makefile,输出make读取makefile,更新makefile,执行makefile的信息。

 

“-d”
相当于“--debug=a”。

 

“-e”
“--environment-overrides”
指明环境变量的值覆盖makefile中定义的变量的值。

 

“-f=<file>”
“--file=<file>”
“--makefile=<file>”
指定需要执行的makefile。

 

“-h”
“--help”
显示帮助信息。

 

“-i”
“--ignore-errors”
在执行时忽略所有的错误。

 

“-I <dir>”
“--include-dir=<dir>”
指定一个被包含makefile的搜索目标。可以使用多个“-I”参数来指定多个目录。

 

“-j [<jobsnum>]”
“--jobs[=<jobsnum>]”
指同时运行命令的个数。

 

“-k”
“--keep-going”
出错也不停止运行。

 

“-l <load>”
“--load-average[=<load]”
“—max-load[=<load>]”
指定make运行命令的负载。

“-n”
“--just-print”
“--dry-run”
“--recon”
仅输出执行过程中的命令序列,但并不执行。

“-o <file>”
“--old-file=<file>”
“--assume-old=<file>”
不重新生成的指定的<file>,即使这个目标的依赖文件新于它。

“-p”
“--print-data-base”
输出makefile中的所有数据,包括所有的规则和变量。

 

“-r”
“--no-builtin-rules”
禁止make使用任何隐含规则。

“-R”
“--no-builtin-variabes”
禁止make使用任何作用于变量上的隐含规则。

“-s”
“--silent”
“--quiet”
在命令运行时不输出命令的输出。

“-S”
“--no-keep-going”
“--stop”
取消“-k”选项的作用

 

隐含规则

1、编译C程序的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.c”,并且其生成命令是“$(CC) –c $(CPPFLAGS) $(CFLAGS)”

2、编译C++程序的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.cc”或是“<n>.C”,并且其生成命令是“$(CXX) –c $(CPPFLAGS) $(CFLAGS)”。(建议使用“.cc”作为C++源文件的后缀,而不是“.C”)

7、汇编和汇编预处理的隐含规则。
“<n>.o” 的目标的依赖目标会自动推导为“<n>.s”,默认使用编译品“as”,并且其生成命令是:“$(AS) $(ASFLAGS)”。“<n>.s” 的目标的依赖目标会自动推导为“<n>.S”,默认使用C预编译器“cpp”,并且其生成命令是:“$(AS) $(ASFLAGS)”。

8、链接Object文件的隐含规则。
“<n>”目标依赖于“<n>.o”,通过运行C的编译器来运行链接程序生成(一般是“ld”),其生成命令是:“$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)”。这个规则对于只有一个源文件的工程有效,同时也对多个Object文件(由不同的源文件生成)的也有效。

 

隐含规则使用的变量:

1、关于命令的变量。

AR
    函数库打包程序。默认命令是“ar”。
AS
    汇编语言编译程序。默认命令是“as”。
CC
    C语言编译程序。默认命令是“cc”。
CXX
    C++语言编译程序。默认命令是“g++”。
CO
    从 RCS文件中扩展文件程序。默认命令是“co”。
CPP
    C程序的预处理器(输出是标准输出设备)。默认命令是“$(CC) –E”。

RM
    删除文件命令。默认命令是“rm –f”。

ARFLAGS
    函数库打包程序AR命令的参数。默认值是“rv”。
ASFLAGS
    汇编语言编译器参数。(当明显地调用“.s”或“.S”文件时)。
CFLAGS
    C语言编译器参数。
CXXFLAGS
    C++语言编译器参数。

CPPFLAGS
    C预处理器参数。( C 和 Fortran 编译器也会用到)。

LDFLAGS
    链接器参数。(如:“ld”)

 

自动化变量

所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。

 

$@
    表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。

$%
    仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。

$<
    依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。

$?
    所有比目标新的依赖目标的集合。以空格分隔。

$^
    所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。

$+
    这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。

$*
   这个变量表示目标模式中"%"及其之前的部分。

$(@D)
    表示"$@"的目录部分(不以斜杠作为结尾),如果"$@"值是"dir/foo.o",那么"$(@D)"就是"dir",而如果"$@"中没有包含斜杠的话,其值就是"."(当前目录)。

$(@F)
    表示"$@"的文件部分,如果"$@"值是"dir/foo.o",那么"$(@F)"就是"foo.o","$(@F)"相当于函数"$(notdir $@)"。

"$(*D)"
"$(*F)"

    和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子,"$(*D)"返回"dir",而"$(*F)"返回"foo"

"$(%D)"
"$(%F)"

    分别表示了函数包文件成员的目录部分和文件部分。这对于形同"archive(member)"形式的目标中的"member"中包含了不同的目录很有用。

"$(<D)"
"$(<F)"

    分别表示依赖文件的目录部分和文件部分。

"$(^D)"
"$(^F)"

    分别表示所有依赖文件的目录部分和文件部分。(无相同的)

"$(+D)"
"$(+F)"

    分别表示所有依赖文件的目录部分和文件部分。(可以有相同的)

"$(?D)"
"$(?F)"

    分别表示被更新的依赖文件的目录部分和文件部分。

 

隐含规则搜索算法

比如我们有一个目标叫 T。下面是搜索目标T的规则的算法。请注意,在下面,我们没有提到后缀规则,原因是,所有的后缀规则在Makefile被载入内存时,会被转换成模式规则。如果目标是"archive(member)"的函数库文件模式,那么这个算法会被运行两次,第一次是找目标T,如果没有找到的话,那么进入第二次,第二次会把"member"当作T来搜索。

1、把T的目录部分分离出来。叫D,而剩余部分叫N。(如:如果T是"src/foo.o",那么,D就是"src/",N就是"foo.o")

2、创建所有匹配于T或是N的模式规则列表。

3、如果在模式规则列表中有匹配所有文件的模式,如"%",那么从列表中移除其它的模式。

4、移除列表中没有命令的规则。

5、对于第一个在列表中的模式规则:
    1)推导其"茎"S,S应该是T或是N匹配于模式中"%"非空的部分。
    2)计算依赖文件。把依赖文件中的"%"都替换成"茎"S。如果目标模式中没有包含斜框字符,而把D加在第一个依赖文件的开头。
3)测试是否所有的依赖文件都存在或是理当存在。(如果有一个文件被定义成另外一个规则的目标文件,或者是一个显式规则的依赖文件,那么这个文件就叫"理当存在")
    4)如果所有的依赖文件存在或是理当存在,或是就没有依赖文件。那么这条规则将被采用,退出该算法。

6、如果经过第5步,没有模式规则被找到,那么就做更进一步的搜索。对于存在于列表中的第一个模式规则:
    1)如果规则是终止规则,那就忽略它,继续下一条模式规则。
2)计算依赖文件。(同第5步)
3)测试所有的依赖文件是否存在或是理当存在。
4)对于不存在的依赖文件,递归调用这个算法查找他是否可以被隐含规则找到。
5)如果所有的依赖文件存在或是理当存在,或是就根本没有依赖文件。那么这条规则被采用,退出该算法。

7、如果没有隐含规则可以使用,查看".DEFAULT"规则,如果有,采用,把".DEFAULT"的命令给T使用。

一旦规则被找到,就会执行其相当的命令,而此时,我们的自动化变量的值才会生成。

转载于:https://www.cnblogs.com/jaletech/p/3371190.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值