Makefile的基本规则

Posted yll1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Makefile的基本规则相关的知识,希望对你有一定的参考价值。

一、首先了解make是如何工作的,在默认的方式下,也就是我们只输入make命令。会依次执行:
    1、make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
    2、如果找到,它会找文件中的第一个目标文件(target)。
    3、如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
    4、如果edit所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
    5、当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件edit了。


二、Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
1、显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
2、隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。
3、变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
4、文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
5、注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“\\#”。
最后,还值得一提的是,在Makefile中的命令,必须要以[Tab]键开始。


三、自动化变量
所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。
下面是所有的自动化变量及其说明:
$@
    表示规则中的目标文件集。
$%
    仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。
$<
    依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
$?
    所有比目标新的依赖目标的集合。以空格分隔。
$^
    所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
$+
    这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。
$* 
   这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",并且目标的模式是"a.%.b",那么,"$*"的值就是"dir/a.foo"。这

在上述所列出来的自动量变量中。四个变量($@、$<、$%、$*)在扩展时只会有一个文件,而另三个的值是一个文件列表。我们使用函数"dir"或"notdir"就可以做到了。"D"的含义就是Directory,就是目录,"F"的含义就是File,就是文件。
最后想提醒一下的是,对于"$<",为了避免产生不必要的麻烦,我们最好给$后面的那个特定字符都加上圆括号,比如,"$(<)"就要比"$<"要好一些。
还得要注意的是,这些变量只使用在规则的命令中,而且一般都是"显式规则"和"静态模式规则"。其在隐含规则中并没有意义。

四、注意的其他规则:

1、include:可以引用到其他makefile文件,和C语言里面的include有相似之处;

2、Vpath:清除所有已被设置好了的文件搜索目录;

3、make支持四个通配符:“*”,“?”,“~”和“[...]”。这是和linux中的Shell是差不多的;

4、export:使变量传递到下级makefile中;

注意的是,有两个变量,一个是SHELL,一个是MAKEFLAGS,这两个变量不管你是否export,其总是要传递到下层Makefile中,特别是MAKEFILES变量,其中包含了make的参数信息,如果我们执行总控Makefile”时有make参数或是在上层Makefile中定义了这个变量,那么MAKEFILES变量将会是这些参数,并会传递到下层Makefile中,这是一个系统级的环境变量。

5、=、:=、+=和?=的区别;

是最基本的赋值
:= 是覆盖之前的值

?= 是如果没有被赋值过就赋予等号后面的值

+= 是追加等号后面的

特别注意:

 “=”
      make会将整个makefile展开后,再决定变量的值。也就是说,变量的值将会是整个makefile中最后被指定的值。看例子:
            x = foo
            y = $(x) bar
            x = xyz
      在上例中,y的值将会是 xyz bar ,而不是 foo bar 。
 “:=”
      “:=”表示变量的值决定于它在makefile中的位置,而不是整个makefile展开后的最终值。
            x :=foo
            y := $(x)bar
            x := xyz
      在上例中,y的值将会是 foo bar ,而不是 xyz bar 了。



Makefile中常使用函数来处理变量,函数的返回值可以当做变量来使用。

一、字符串处理函数

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

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

    示例: $(subst ee,EE,feet on the street)
   
“feet on the street”中的“ee”替换成“EE”,返回结果是“fEEt on the strEEt”

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

    名称:模式字符串替换函数——patsubst
   
功能:查找<text>中的单词(单词以空格“Tab”回车”“换行分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。这里,<pattern>可以包括通配符“%”,表示任意长度的字串。如果<replacement>中也包含“%”,那么,<replacement>中的这个“%”将是<pattern>中的那个“%”所代表的字串。(可以用“\\”来转义,以“\\%”来表示真实含义的“%”字符)
   
返回:函数返回被替换过后的字符串。

    示例:$(patsubst %.c,%.o,x.c.c bar.c)

     把字串“x.c.c bar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.c.o bar.o”

$(strip <string>)

    名称:去空格函数——strip
   
功能:去掉<string>字串中开头和结尾的空字符。
   
返回:返回被去掉空格的字符串值。
   
示例: $(strip a b c )

     把字串“a b c ”去到开头和结尾的空格,结果是“a b c”

$(findstring<find>,<in>)

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

    第一个函数返回“a”字符串,第二个返回“”字符串(空字符串)

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

    名称:过滤函数——filter
   
功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可以有多个模式。
   
返回:返回符合模式<pattern>的字串。
   
示例:sources := foo.c bar.c baz.s ugh.h

        foo: $(sources)
               cc $(filter %.c %.s,$(sources)) -o foo

       $(filter %.c %.s,$(sources))返回的值是“foo.cbar.c baz.s”

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

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

        mains=main1.o main2.o
    
        $(filter-out $(mains),$(objects))
返回值是“foo.o bar.o”
       
$(sort <list>)

    名称:排序函数——sort
   
功能:给字符串<list>中的单词排序(升序)。
   
返回:返回排序后的字符串。
   
示例:$(sortfoo bar lose)返回“barfoo lose”
   
备注:sort函数会去掉<list>中相同的单词。

$(word <n>,<text>)

    名称:取单词函数——word
   
功能:取字符串<text>中第<n>个单词。(从一开始)
   
返回:返回字符串<text>中第<n>个单词。如果<n><text>中的单词数要大,那么返回空字符串。
   
示例:$(word2, foo bar baz)返回值是“bar”

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

    名称:取单词串函数——wordlist
   
功能:从字符串<text>中取从<s>开始到<e>的单词串。<s><e>是一个数字。
   
返回:返回字符串<text>中从<s><e>的单词字串。如果<s><text>中的单词数要大,那么返回空字符串。如果<e>大于<text>的单词数,那么返回从<s>开始,到<text>结束的单词串。
   
示例:$(wordlist 2, 3, foo bar baz)返回值是“barbaz”

$(words <text>)

    名称:单词个数统计函数——words
   
功能:统计<text>中字符串中的单词个数。
   
返回:返回<text>中的单词数。
   
示例:$(words,foo bar baz)返回值是“3”
   
备注:如果我们要取<text>中最后的一个单词,我们可以这样:$(word $(words<text>),<text>)

$(firstword <text>)

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

以上,是所有的字符串操作函数,如果搭配混合使用,可以完成比较复杂的功能。这里,举一个现实中应用的例子。我们知道,make使用“VPATH”变量来指定依赖文件的搜索路径。于是,我们可以利用这个搜索路径来指定编译器对头文件的搜索路径参数CFLAGS,如:

    override CFLAGS +=$(patsubst %,-I%,$(subst :, ,$(VPATH)))

    如果我们的“$(VPATH)”值是“src:../headers”,那么“$(patsubst %,-I%,$(subst :,,$(VPATH)))”将返回“-Isrc-I../headers”,这正是ccgcc搜索头文件路径的参数。


二、文件名操作函数

下面我们要介绍的函数主要是处理文件名的。每个函数的参数字符串都会被当做一个或是一系列的文件名来对待。

$(dir <names...>)

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

$(notdir <names...>)

    名称:取文件函数——notdir
   
功能:从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后的部分。
   
返回:返回文件名序列<names>的非目录部分。
   
示例:$(notdir src/foo.c hacks)返回值是“foo.chacks”
 
$(suffix <names...>)
   
   
名称:取后缀函数——suffix
   
功能:从文件名序列<names>中取出各个文件名的后缀。
   
返回:返回文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字串。
   
示例:$(suffixsrc/foo.c src-1.0/bar.c hacks)返回值是“.c.c”

$(basename <names...>)

    名称:取前缀函数——basename
   
功能:从文件名序列<names>中取出各个文件名的前缀部分。
   
返回:返回文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字串。
   
示例:$(basenamesrc/foo.c src-1.0/bar.c hacks)返回值是“src/foosrc-1.0/bar hacks”

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

    名称:加后缀函数——addsuffix
   
功能:把后缀<suffix>加到<names>中的每个单词后面。
   
返回:返回加过后缀的文件名序列。
   
示例:$(addsuffix.c,foo bar)返回值是“foo.cbar.c”

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

    名称:加前缀函数——addprefix
   
功能:把前缀<prefix>加到<names>中的每个单词后面。
   
返回:返回加过前缀的文件名序列。
   
示例:$(addprefixsrc/,foo bar)返回值是“src/foosrc/bar”

$(join<list1>,<list2>)

    名称:连接函数——join
   
功能:把<list2>中的单词对应地加到<list1>的单词后面。如果<list1>的单词个数要比<list2>的多,那么,<list1>中的多出来的单词将保持原样。如果<list2>的单词个数要比<list1>多,那么,<list2>多出来的单词将被复制到<list2>中。
   
返回:返回连接过后的字符串。
   
示例:$(joinaaa bbb , 111 222 333)返回值是“aaa111bbb222 333”

三、其他函数

1)call函数是唯一一个可以用来创建新的参数化的函数:$(call<expression>,<parm1>,<parm2>,<parm3>...)

make执行这个函数时,<expression>参数中的变量,如$(1)$(2)$(3)等,会被参数<parm1><parm2><parm3>依次取代。而<expression>的返回值就是call函数的返回值。例如:  

reverse =  $(1) $(2)
foo = $(call reverse,a,b)

那么,foo的值就是ab”。当然,参数的次序是可以自定义的,不一定是顺序的,如:

reverse = $(2) $(1)
foo = $(call reverse,a,b)

此时的foo的值就是ba”。

2)origin函数不像其它的函数,他并不操作变量的值,他只是告诉你你的这个变量是哪里来的?其语法是:$(origin <variable>)

注意,<variable>是变量的名字,不应该是引用。所以你最好不要在<variable>中使用$”字符。Origin函数会以其返回值来告诉你这个变量的“出生情况”,下面,是origin函数的返回值:

undefined

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

default

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

environment

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

file

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

commandline

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

override

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

automatic

      如果<variable>是一个命令运行中的自动化变量。关于自动化变量将在后面讲述。


这些信息对于我们编写Makefile是非常有用的,例如,假设我们有一个Makefile其包了一个定义文件Make.def,在Make.def中定义了一个变量bletch”,而我们的环境中也有一个环境变量bletch”,此时,我们想判断一下,如果变量来源于环境,那么我们就把之重定义了,如果来源于Make.def或是命令行等非环境的,那么我们就不重新定义它。于是,在我们的Makefile中,我们可以这样写:

    ifdef bletch
    ifeq "$(origin bletch)""environment"
    bletch = barf, gag, etc.
    endif
    endif

当然,你也许会说,使用override关键字不就可以重新定义环境中的变量了吗?为什么需要使用这样的步骤?是的,我们用override是可以达到这样的效果,可是override过于粗暴,它同时会把从命令行定义的变量也覆盖了,而我们只想重新定义环境传来的,而不想重新定义命令行传来的。

3)shell函数也不像其它的函数。顾名思义,它的参数应该就是操作系统Shell的命令。它和反引号`”是相同的功能。这就是说,shell函数把执行操作系统命令后的输出作为函数返回。于是,我们可以用操作系统命令以及字符串处理命令awksed等等命令来生成一个变量,如:contents := $(shell cat foo)

注意:这个函数会新生成一个Shell程序来执行命令,所以你要注意其运行性能,如果你的Makefile中有一些比较复杂的规则,并大量使用了这个函数,那么对于你的系统性能是有害的。特别是Makefile的隐晦的规则可能会让你的shell函数执行的次数比你想像的多得多。

以上是关于Makefile的基本规则的主要内容,如果未能解决你的问题,请参考以下文章

Makefile第五课:Makefile介绍

Makefile的基本规则

Makefile的基本规则

Makefile的基本规则

make的使用和Makefile规则和编程及其基本命令(简单)

makefile学习之路——makefile简介