makefile 学习笔记

参考资料:陈浩,《跟我一起写makefile》 :http://blog.csdn.net/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