首页 > 代码库 > Linux笔记之Makefile

Linux笔记之Makefile

规则:

目标 : 依赖

        命令


make是如何工作的:

    (1)make在当前目录下寻找makefile或Makefile。 

   (2)如果找到,他会寻找文件中的第一个目标文件(target),并把这个文件作为第一个目标。

   (3)如果目标文件不存在,或者目标文件所依赖的.o文件修改时间要比目标文件新,那么,就会执行后面所定义的命令来生成目标文件。

   (4)如果目标文件所以依赖的.o文件也存在,那么make会在当前文件中寻找目标为.o文件所依赖性,如果找到则再根据这一规则生成.o文件。(这有些像堆栈的过程。)

   (5)于是make生成.o文件,然后再用.o文件生成make的终极目标。

   这就是make的依赖性,make会一层又一层的去找文件的依赖关系,直到最终编译出第一个目标文件。在寻找的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或者编译不成功,make根本就不理会。make只会管文件的依赖性。

    

   所以,像clean那样,没有被第一个目标文件直接或者间接关联,那么他后面所定义的命令就不会被自动执行,不过,我们可以显式的要make执行---make clean



Makefile的 组成:

    (1)显示规则

        说明如何生成一个或多个目标文件,需要作者明确指出要生成的文件、文件的依赖文件,生成的命令。

    (2)隐晦规则

        因为make有自动推到功能,所以隐晦的规则可以让我们比较粗糙简略的书写Makefile。

    (3)变量的定义

        Makefile中我们需要定义一些变量,变量一般都是字符串。这个有点像C语言中的宏,当Makefile被执行时,变量会自动扩展到相应的引用位置。

    (4)文件指示

        包含三部分:

            a.在一个Makefile中引用另一个Makefile,像C语言中的include。

            b.根据某些情况制定Makefile中的有小部分,像C语言中的预编译#if。

            c.定义一个多行的命令

    (5)注释

        Makefile中只有行注释,用#字符,如果在Makefile要使用#,则使用反斜杠进行转义,例如"\#" 。


GNU的make工作步骤:(其他的make类似)

    (1)读入所有的Makefile

    (2)读入被include的其他Makefile

    (3)初始化文件中的变量

    (4)推导隐晦规则,并分析所有规则

    (5)为所有的目标文件创建依赖关系链

    (6)根据依赖关系,决定哪些目标要重新生成

    (7)执行生成命令

    其中1~5属于第一阶段,6~7属于第二阶段。第一阶段中,如果定义的变量被使用,那么make会把其展开在使用的位置上,但make并不会立刻展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。




在规则中使用通配符:

如果想定义一系列比较类似的文件,则通配符是较好的选择。make支持的通配符有三种。

    (1) * 

        例如*.c表示后缀为c的文件,需要注意的是,如果文件名中有通配符,如*,则可以使用转义字符\*来表示真实的*字符。

    (2) ? 

    (3) [..]




文件搜索:

    大的工程中,有大量的源文件,我们通常的做法是把这些源文件分类,病存放在不同的目录中。所以,当make需要去找文件的依赖关系式,可以再文件前加上路径名,但有一个最好的方法就是把一个路径告诉make,让make自己去找。

    Makefile中的特殊变量VPATH就是完成这任务的,如果没有知名这个变量,make只会在当前的目录中寻找依赖关系和目标文件。如果定义了这个变量,那么make就会在当前目录找不到的情况下,去指定的目录去寻找文件了。

            VPATH = src:../headers

    指明了两个目录,src和../headers,make会按照这个顺序去搜索。目录“冒号”分开(但是,当前目录永远都是最高优先搜索的地方)。

    另外一个设置文件搜索路径的方法就是使用make的关键字vpath(注意是小写),这个不是变量,这是一个make关键字,这和上面的VPATH变量类似,但是它更加灵活,他可以指定不同的文件在不同的搜索目录中。这是一个灵活的功能,使用方法由三种:

    (1)vpath  <pattern>  <directories>

        为符合模式<pattern>的文件指定搜索目录<directories>。

    (2)vpath <pattern>

        清除符合模式<pattern>的文件的搜索目录。

    (3)vpath

        清除所有被设置好了的文件搜素目录。

    vpath是哦那个方法中的pattern需要包含%字符。%字符意思是匹配零或者若干字符,例如,%.h表示所有以.h结尾的文件。<pattern>指定了要搜素的文件集,而<directories>则指定了<pattern>的文件的搜索目录。

    例如:vpath %.h    ../headers表示make在../headers目录下搜索所有的.h结尾的文件。(如果某些文件在当前目录没有找到的话)


伪目标:

一般的Makefile最后的"clean"的目标,就是一个伪目标。

    像前面所说的那样,既然我们生成了许多编译文件,我们也需要一个清楚他们的“目标”以备完整的重新编译而用。

我们并不是生成"clean"这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法为生产它的依赖关系和决定它是否要执行,我们只有通过显式的指明需要生成这个目标,才会使其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义。

    当然,为了更好的避免和文件重名,我们一般使用一个特殊的标记".PHONY"来指明一个目标是“伪目标”,向make说明,不论是否有这个文件,这个目标就是“伪目标”

1
2
3
4
5
.PHONY:clean
 
clean:
 
rm *.o  temp



静态模式:

静态模式可以更加容易定义多目标的规则。语法为:

1
2
3
4
5
<target...>:<target-pattern>:<prereq-patterns...>
 
<command>
 
......

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

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

    prereq-patterns是目标文件的依赖模式,它对target-pattern形成的模式在进行一次依赖目标的定义。例如:<target-pattern>定义成%.c,意思是我们的<target>集合中都是以".o"结尾的,二如果我们的<prereq-patterns>定义成为"%.c",意思是对<target-pattern>所形成的目标集进行二次定义,其计算方法是:取<target-pattern>模式的"%“(也就是去掉[.o]这个结尾),并为其加上[.c]这个结尾,形成新的集合。

    所以,”目标模式“或是”依赖模式“都可以有"%"这个字符,如果文件名中有"%",那么可以使用"\%"表示。

例如:

1
2
3
4
5
6
7
objects = foo.o bar.o
 
all = $(objects)
 
$(objects):%.o:%.c
 
$(cc) -c $(CFLAGS) $< -o $@

    指明了我们的目标从$(objects)中获取,"%.o”表明要所有以".o"结尾的目标,也就是"foo.o bar.o",也就是变量$object集合的模式,而依赖模式"%.c"则驱魔师"%.o"的"%",也就是"foo bar ",并为其加上".c"后缀,于是,我们的依赖目标就是"foo.c bar.c"。而命令中的"$<"和"$@"都是自动化变量。"S<"表示所有的依赖目标集(也就是"foo.c  bar.c"),"$@"则表示目标集(也就是"foo.o  bar.o")。

    于是上面的规则展开后就是:

1
2
3
4
5
6
7
foo.o  :  foo.cc
 
$(cc)  -c  $(CFLAGS)  foo.c  -0  foo.o
 
bar.o : bar.c
 
$(cc) -c   $(CFLAGS)   bar.c  -o  bar.c

如果"%.o"有几百个,那么这种简单的"静态模式规则"可以写完一大堆的规则,效率很高。

命令出错:

    每当命令运行完后,make会检查每一个命令的返回码,如果命令返回成功,那么make会执行下一条命令,如果一个规则中的某一个命令错了(命令退出码非零),那么make就会终止当前的规则,将有可能终止所有的规则的执行。

    有时候,命令的出错并不代表就是错误的,例如mkdir,如果当前目录不存在需要创建的文件,就会成功执行,但是如果,当前目录中有一个这样的同名文件,那么就会出错二终止规则的运行。为了忽略出错,我们可以再Makefile的命令行前面加一个减号“-”(在TAB键之后),标记为不管命令是否出错都认为是成功的。

例如:

1
2
3
clean:
 
-rm -f *.o

下面定义一个变量,其值为空格:

1
2
3
nullstring :=
 
space := $(nullstring) #end of line

nullstring是一个Empty变量,其中什么也没有,而我们的space的值是一个空格,因为在操作符右边很难描述一个空格,这里采用的技术很管用,先用一个Empty变量来标明变量的值开始了,而后采用“#”注释符来表明变量的定义的终止。这样,我们就可以定义出其值为空格的变量。




显式命令:

    通常,make会把要执行的命令显示在屏幕,当使用"@"字符在命令行前,那么这个命令就不会别显式出来。

    例如:

       echo 正在编译这个模块...

    执行效果为,屏幕显示:

        echo 正在编译这个模块...

        正在编译这个模块...

    但,使用"@,屏幕显示:

        正在编译这个模块...


命令执行:

要注意,需要在上一条命令的结果需要作用于下一条命令,应该使用分好隔离这两条命令。例如:

1
2
3
cd  /home/yangrui/study
 
pwd

而使用";",即:

1
cd /home/yangrui/study  pwd

其中,执行make exec时,第一个例子中的cd没有作用,pwd会打印出当前Makefile的目录,而第二个cd 就会起作用,pwd会打印出"/home/yangrui/study"




定义命令包:

    如果Makefile中出现一些相同的命令序列,那么我们可以为这些相同的序列定义一个变量。定义这种命令序列以"define"开始,以"endef"结束。

    例如:

1
2
3
4
5
define run-yacc
 
run-yacc  $(firstword s^)
 
mv y.tab.c  $@

这里的run-yacd就是这个命令报的名字,其不可以和Makfile中的变量重名。(类似于C语言的函数)



变量:

    (1)变量基础

        变量在声明时需要赋初值,而在使用时需要在变量名之前加上"$",但最好用()或者{}包括起来,若需要使用真正的‘$‘字符,则需要使用“$$"。

        例如:

1
2
3
4
5
6
7
objects = program.o   foo.o   utils.objects
 
program : $(objects)
 
cc -o program $(objects)
 
$(objects) : defs.h

        变量会在使用的地方展开,像c/c++中的宏。

        例如: 

1
2
3
4
5
foo = c
 
prog.o : prog.$(foo)
 
$(foo)$(foo)  -$(foo)  prog.$(foo)

        展开后成为:

1
2
3
prog.o  : prog.cc
 
cc -c prog.c

                        (但是一般不会像这样去做)

    (2)变量中的变量

        定义变量时,还可以使用其他变量来够着变量的值。在Makefile中有两种定义变量值得方法:

            <1>使用"="

                "=“左边是变量,右边是其他变量,且右侧的其他变量也试试文件中的任何一处,也就是说,右侧的值不一定是已经定义好的值 ,也可以使用后面定义的值。

                例如: 

1
2
3
4
5
6
7
8
9
foo = $(bar)
 
bar = $(ugh)
 
ugh = Huh?
 
all:
 
echo : $(foo)

                则使用make all时,结果为"Huh?"

                这种方法有好处也有坏处,比如递归定义时:

1
2
3
a = $(b)
 
b = $(a)

                这样,会让make嵌入无限的变量展开过程中去,当然,make是有能力检查这种错误并报错的,但是效率太低。

        <2>使用":="

            这种方法中变量的值不能使用后面的变量,只能使用前面已经定义好的变量。

            例如: 

1
2
3
y :=  $(x)  bar
 
x := foo

            则,y的值为”bar“,而不是”foo  bar“.


            技巧:如何定义一个值为空格的变量

1
2
3
nullstring :=
 
space := $(nullstring) #end of the line

            nullstring是一个Empty变量,其中什么都没有,而我们space值为空格,因为在操作符右边很难去描述一个空格,这里的技术很管用,先使用一个Empty的变量来表明变量的开始,而后面采用"#"注释来表明变量定义的终止,这样,我们就可以定义出一个值为空格的变量。

    (3)变量的高级用法

        <1>变量值的替换

            a.可以替换变量中共有的部分。格式为

                $(var:a=b) 或者 ${var:a=b}

            意思是吧变量"var"中所有以"a"字符结“结尾”的“a"全部替换为“b”字串。这里“结尾”的意思是“空格”或者“结束符”。

            例如:

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

        这时候$(bar)的值就是“a.c  b.c  c.c”

        b.另外一种变量替换的技术以"静态模式"定义。

        例如:

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

        这个例子让$(bar)变量的值为"a.c b.c c.c"。

    <2>把变量的值在当变量

        例如:

1
2
3
4
5
x = y
 
y = z
 
a:=$($(x))

        这里,$(a)的值就是"z"。

        这种方式中,还可以使用多个变量组成一个变量的名字,然后取值:

        例如:

1
2
3
4
5
6
7
first_second = hello
 
a = first
 
b = second
 
all=$($a_$b)

        这里,$(all)就是"hello"。


追加变量值:

    可以使用"+="操作符给变量追加值。


override指示符:

    如果有变量是通过make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果想在Makefile中设置这类参数,那么,可以使用"override"指示符,语法是:

                override <variable> = <value>

                override <variable> := <value>

当然,还可以追加:

                override <variable> +=<more text>

对于多行的命令符,我们使用define指示符,在define指示符前,也同样可以使用override指示符,例如:

1
2
3
4
5
override  define foo
 
bar
 
endef


多行变量:

    使用define关键字设置变量的值可以换行,这有利于一系列的命令(前面的"命令包"就利用这种关键字)。

define指示符后面跟的是变量的名字,重起一行是变量的值,定义以endef关键字结束。工作方式和"="相同。例如:

1
2
3
4
5
6
7
define two-lines
 
echo foo
 
echo $(bar)
 
endef


环境变量:

    make运行时的系统环境变量可以再make开始运行时被载入到Makefile中,但是如果Makefile中已经定义了这个变量,或者这个变量由make命令带入,那么系统的环境变量的值就会被覆盖。(如果make指定了"-e"参数,那么系统环境变量将会覆盖Makefile中定义的变量)。

    因此,如果我们在环境变量中设置了"CFLAGS"环境变量,那么我们就可以在所有的Makefile中使用这个变量了。这对于我们使用统一的编译参数有较大的好处。如果Makefile中定义了CFLAGS,那么则会使用Makfile中的变量,如果没有定义则使用系统环境变量的值,一个共性和个性的统一,像"全局变量"和"局部变量"的特性。

    当make嵌套使用时,上层的Makfile定义的变量会以系统环境变量的方式传递到下层的Makefile中。当然,在默认情况下,只有通过命令行设置的变量才会被传递。而定义在文件中的变量,如果要向下层Makefile传递,则需要使用export关键字。

注意:不建议把环境变量定义在系统环境中。


目标变量:

    前面所说的都是"全局变量",在整个文件中,我们都可以访问这些变量。当然"自动化变量"除外,如"$<"这种自动化变量就属于"规则性变量",这种变量的值依赖于规则的目标和依赖目标的定义。当然,也可以为某个目标设置局部变量,这种变量被称为"Target-specific Variable".他可以和全局变量同名,因为他的作用于就是这个规则以及连带的规则,所以只在作用范围内有效,而不会影响到规则链以外的全局变量的值。语法是:

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

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

    <variable-assignment>可以使前面提到的各种赋值表达式,如"="、":="、"+="或者"?="。第二个语法是针对make命令带入的变量,或者是系统环境变量。这个特性非常有用,当我们设置这样一个变量,这个变量会作用到由这个目标引发的所有的规则中去。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

这个例子中,不管全局的"CFLAGS"值是什么,在prog目标,以及其所引发的所有规则中(prog.o  foo.o  bar.o 的规则中),$(CFLAGS)的值都是"-g"。




模式变量:

    在GNU的make中,还支持模式变量(Pattern-specific  Variable),通过上面的目标变量,我们知道,变量还可以定义在目标上。模式变量的好处是:我们可以定义一种"模式",然后把变量定义在符合这种模式的所有目标上。

我们知道,make的"模式"一般是至少含有一个"%"的。所以,可以如下的方式给所有以[.o]结尾的目标定义目标变量:

                %.o : CFLAGS -o 

同样的,模式变量的语法和"目标变量"一样:

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

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

overirde同样是针对系统环境传入的变量,或者make命令行指定的变量。


使用条件判断:

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
libs_for_gcc = -l gnu
 
normal_libs =
 
 
 
foo : $( objects)
 
ifeq($(cc) , gcc)
 
$(cc) -o  $(objects)  $(libs_for_gnu)
 
else
 
$(cc) -o  $(objects)  $(normal-libs)
 
endif

含义:判断是否使用"gcc",如果是,则使用"GUN"函数编译目标。

上面的例子中有三个关键字:ifeq  else   endif 。

上面的例子还可以写的更加简洁些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
libs_for_gcc = -l gnu
 
normal_libs =
 
 
 
ifeq($(cc) , gcc)
 
libs = $(libs_for_gcc)
 
else
 
libs = $(normal_libs)
 
endif
 
 
 
foo: $(objects)
 
$(cc) -o  foo $(objects)  $(libs)


语法:

    <1>

        <condiitonal-directive>

        <text-if-true>

        endif


    <2>

        <condiitonal-directive>

        <text-if-true>

        else

        <text-if-false>

        endif

    这里的<condiitonal-directive>表示关键字共有四个:

    <1>关键字ifeq

        ifeq   (<arg1> , <arg2>)

        ifeq   ‘<arg1>’ , ‘<arg2>‘

        ifeq   ”<arg1>“ , "<arg2>"

        ifeq   "<arg1>" , ‘<arg2>‘

        ifeq   ‘<arg1>‘ , "<arg2>"

    <2>关键字ifneq

        ifneq   (<arg1> , <arg2>)

        ifneq   ‘<arg1>’ , ‘<arg2>‘

        ifneq   ”<arg1>“ , "<arg2>"

        ifneq   "<arg1>" , ‘<arg2>‘

        ifneq   ‘<arg1>‘ , "<arg2>"

    <3>关键字ifdef

    <4>关键字ifndef

    其中,<condiitonal-directive>这一行,多余的空格是允许的,但是不能以[TAB]作为开始,否则就会被认为是命令。

    注意:make实在读取Makefile时就会计算条件表达式的值,并根据表达式的值来选择语句。所以,你最好不要把自动化变量(如"$@"等)放入条件表达式,因为自动化变量是在运行时才有的。


使用函数:

    (1)函数的调用语法

        $(<function>  <arguments>)或者${<function>  <arguments>}

    这里,<function>就是函数名,make支持的函数不多.<arguments>就是函数的参数,参数间以逗号","分隔,而函数名和参数之间以“空格”分隔。函数调用以"$"开头。

    (2)字符串处理函数

        <1>subst

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

            名称:字符串替换函数----subst

            功能:把字符串<text>中的<from>字符串替换为<to>

            返回:函数返回替换过来的字符串

            例子:

1
$(subst ee,EE,feet  on  the street)

            返回的结果就是"fEEt  on  the strEEt"

        <2>patsubst

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

            名称:模式字符串替换函数----patsubst

            功能:查找<text>中的单词(单词以“空格”、“TAB”或者“回车”、“换行”分隔)是否符合模式<pattern>,如果匹配,则以<replacement>替换。这里的<pattern>可以使通配符"%",表示任意长度的字串,如果<replacement>中冶包含"%",那么,<replacement>中的这个"%"将是<pattern>里的"%"所代表的字串。(也可以使用"‘\"来转义,用"\%"表示)。

            返回:被替代过后的字符串。

            例子:

1
$(patsubst %.c , %.o , x.c.c  bar.c)

            返回结果是:"x.c.o  bar.o"

            这个前面提到的“变量”章节有关知识很相似:

                $(var:<pattern> = <replacement>)

            相当于:

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

        <3>strip

                $(strip <string>)

                名称:去空格函数----strip

                功能:去掉<string>字串中开头和结尾的空字符。

                返回:被去掉空格的字符串值。

                例子:

1
$(strip   a  b   c  )

                返回结果是:"a b c"

        <4>findstring

                $(findstring <find>,<in>)

                名称:在字串<in>中查找<find>字串。

                例子:

1
2
3
$(findstring  a,a b c)
 
$(findstring  a ,b c)

                返回结果,第一个返回"a"字符串,第二个返回“”字符串(空字符)。

        <5>filter

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

                名称:过滤函数----filter

                功能:以<pattern>模式过滤<text>字符串的单词,保留符合模式<pattern>的单词。可以有多个模式。

                返回:符合模式<pattern>的字串。

                例子:

1
2
3
4
5
sources := foo.c  bar.c  baz.s  ugh.h
 
foo : $(sources)
 
cc $(filter %.c %.s , $(sources))  -o  foo

                则,$(filter  %.c  %.s ,$(sources))返回的值是"foo.c   bar.c  baz.s"

        <6>filter-out

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

                名称:反过滤函数----filter-out

                功能:以<pattern>模式过滤<teext>字串中的单词,去除符合模式<pattern>的单词,可以有多个模式。

                返回:不符合模式<pattern>的字串

                例子:

1
2
3
4
5
objects = main1.o  foo.o  main2.o  bar.o
 
mains = main1.o  main2.o
 
$(filter-out-out  $(mains),$(objects))

                则返回值为"foo.o  bar.o"

        <7>sort

                $(sort<list>)

                名称:排序函数----sort

                功能:给字符串<list>中的单词排序(升序)

                返回:排序后的字符串

                例子:

1
$(sort  foo bar  lose)

                返回结果为: "bar  foo  lose"

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

    <8>word

                $(word  <n>,<text>)

                名称:取单词函数----word

                功能:取字符串<text>中第n个单词。(从第一个开始)

                返回:字符串<text>的第<n>个单词。如果<n>比<text>中的单词数要大,那么返回空字符串。

                例子:

1
$(word 2 , foo bar  baz )

                返回:"bar"

    <9>wordlist

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

                名称:取单词函数----worklist

                功能:从字符串<text>中取从<s>开始到<e>的单词,<s>和<e>都是数字。

                返回:返回字符串<text>中从<s>到<e>的单词。如果<s>比<text>的单词数要打,那么返回空字符串,如果<e>大于<text>的单词数,那么返回<s>开始,到<text>结束的单词。

                例子:

1
$(wordlist 2,3  ,foo bar  baz)

                返回:"bar  baz"

    <10>works

                $(words <text>)

                名称:单词个数统计函数----words

                功能:统计<text>中字符串的单词个数

                返回:<text>中的单词数

                例子:

1
$(words ,foo bar  baz)

                返回值是"3".

                备注:如果我们要提取<text>最后一个单词,可以这样:

                                $(word $(words<text>),<text>)

    <11>firstword

                $(firstword <text>)

                名称:首单词函数----firstword

                功能:取字符串<text>的第一个单词。

                返回:字符串<text>的第一个单词。

                例子:

1
$(firstword  foo bar )

                返回值:"foo"。

                备注:这个函数还可以用word实现:

                                $(word  1,<text>)


文件名操作函数:

    <1>dir

                $(dir  <names...>)

                名称:取目录函数----dir 

                功能:从文件名序列<name>中取出目录部分。目录部分是值最后一个反斜杠"/"之前的部分,如果没有反斜杠,就返回"./"。

                返回:返回文件名序列<name>的目录部分。

                例子:

1
$(dir  src/soo.c  hacks)

                返回值:"src/   ./"

    <2>notdir

                $(notdir  <names...>)  

                名称:取文件函数----notdir

                功能:从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠“/”之后的部分。

                返回:文件名序列<names>的非目录部分。

                例子:

1
$(notdir  src/foo.c  hacks )

                返回:"foo.c  hacks"

    <3>suffix

                $(suffix <names>)

                名称:取后缀函数----suffix

                功能:从文件名序列<names>中取出各个文件名的后缀。

                返回:文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字符。

                例子:

1
$(suffix  src/foo.c  src-1.0/bar.c  hacks)

                返回值:".c  .c "

    <4>basename

                $(basename <names...>)

                名称:取前缀函数----basename

                功能:从文件名序列<names>中取出各个文件名的前缀部分。

                返回:文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字符。

                例子:

1
$(basename  src/foo.c  src-1.0/bar.c  hacks)

                返回值:"src/foo   src-1.0/bar  hacks"。

    <5>addsuffix

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

                名称:加前缀函数----addsuffix 

                返回:加过后缀的文件名序列。

                例子:

1
$(addsuffix  .c , foo  bar)

                返回值:“foo.c  bar.c ”

    <6>addprefix

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

                名称:加前缀函数----addprefix

                功能:把前缀<prefix>加在<names>的每个单词后面。

                返回:加过前缀的文件名序列。

                例子:

1
$(addprefix src/,foo  bar)

                返回:"src/foo  src/bar "

    <7>join

                $(join  <list1> , <list2>)

                名称:连接函数

                功能:把<list2>中的单词对应的加入到<list1>的单词的后面。如果<list1>的单词比<list2>要多,那么<list1>中多出来的单词将保持原样。如果<list2>的单词比<list1>多,则<list2>多出来的单词将会被复制到<list2>中。

例子:

1
$(join  aaa  bbb  ,111  222  333)

                返回:"aaa111 bbb222  333"


foreach函数:

    foreach函数和别的函数非常不一样。因为这个函数是用来做循环的,Makefile中的foreach函数几乎是仿照Unix的标准Shell(/bin/sh)中的for语句,或者是C-Shell(/bin/csh)中的foreach语句而构建的。他的语法是:

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

    这个函数的意思是,把参数<list>中的单词逐一放到参数<var>所指定的变量中去,然后在执行<text>所包含的表达式。每一个<text>都会返回一个字符串,循环过程中,<text>的所返回的每个字符会以空格分隔,最后当整个循环结束时,<text>则返回每个字符串所组成的字符串(空格分隔)将会是foreach函数的返回值。

所以,<var>最好是一个变量名,<list>可以是一个表达式,而<text>一般会使用<var>这个参数来枚举<list>中的单词。

例子:

1
2
3
names := a b c d
 
files     := $(foreach  n, $(names),$(n).o)

则files的值为"a.o  b.o  c.o  d.o"

    注意:foreach的<var>参数是一个临时的局部变量,foreach函数执行完后,参数<var>的变量将不再作用,其作用域只在foreach函数中。


if函数:

    if函数非常像GNU的make所支持的条件语句----ifeq,其语法是:

                $(if <condition> , <then-pa>)

或者:

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

返回值:如果<condition>为真,(非空字符串),那么<then-part>会是整个函数的返回值。如果<condition>位假,(空字符串),那么<else-part>会是整个函数的返回值,此时如果<else-part>没有被定义,则整个函数返回空字串。


call函数:

    call函数是唯一一个可以用来创建新的参数化的函数。可以写一个复杂的表达式,在表达式中,可以定义很多参数,然后你可以用call函数向这个表达式传递参数,语法:

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

    当make执行这个函数时,<expression>参数中的变量,如$(1),$(2),$(3)等,会被参数<的返回值就是call函数的返回值。

例子:

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

那么foo的值就是"a b"当然,参数的次序可以自有定义,不一定是顺序的。

例子:

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

那么,foo的值就是"b  a"。

origin函数:

    origin函数不像其他函数,他并不操作变量的值,他只是告诉你你的这个变量是哪里来的。语法:

                $(origin <variable>)

    注意:<variable>是变量的名字,不应该是引用。最好不要在<variable>中使用"$"字符。origin会以返回值的方式告诉你这个变量的"出生情况",有一下情况:

    (1)“undefined”

        就是未定义的变量。

    (2)“default”

        变量<variable>是一个默认的值,比如"CC"这个变量。

    (3)"file"

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

    (4)"command line"

        如果变量<variable>被命令行定义。

    (5)“override”

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

    (6)“automatic”

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

shell函数:

    shell函数不像其他函数。他的参数是操作系统的Shell命令,它和反引号" ‘ "是相同的功能。

例子:

1
2
3
contents := $(shell  cat foo)
 
files := $(shell  eccho *.c)

    注意,这个函数会生成另一个shell程序执行命令,所以必须注意其运行性能,如果你的Makefile中有一个复杂的规则,并大量利用了这个函数,那么对你的系统是有害的。特别是Makefile的隐晦规则可能让你的shell函数执行次数比你想象的要多。


控制Makefile的函数:

    make提供了一下函数控制make的运行,通常,你需要检测一些运行Makefile时的运行信息,并且根据这个信息来决定,你是让make继续执行,还是停止。

    (1)error

                $(error <text.....>)

    产生一个致命错误,<text...>是错误信息。注意,error函数不会在一被使用就产生错误信息,所以如果你把其定义在某个变量中,并在后续脚本中使用了这个变量,那么,可以这样:

范例一:

1
2
3
4
5
$ifdef  ERROR_001
 
$(error error is $(ERROR_001))
 
endif

范例二:

1
2
3
4
5
ERR = $(error found an error !)
 
.PHONY:err
 
err : ; $(ERR)

    (2)warning

                $(warning  <text...>)

这个函数很想error函数,只是它并不会让make退出,只是输出一段警告信息,而make继续执行。



make的运行:

    (1)make的退出码(三个):

                0 - 表示成功执行

                1 - 如果make运行时出现任何错误,其返回1。

                2 - 如果你使用了make的"-q"选项,并且make使得一些目标不需要更新,那么返回2。

    (2)执行Makefile

        前面提到,GNU  make默认寻找的Makefile的规则是在当前目录下依次寻找三个文件——"GUNmakefile"、"makefile"、"Makefile"。按照顺序寻找三个文件,一旦找到,就开始读取这个文件并执行。

当前,我们也可以给make命令制定一个特殊的名字Makefile。要达到这样效果需要使用make的"-f"或者“--file”参数("--makefile"参数也可以)。例如我们的Makefile的名字是"hchen.mk",则可以:

make -f  hchen.mk

来运行。

    (3)指定目标

        一般来说,make的最终目标是makefile中的第一个目标。而其他的目标一般是由这个目标连带出来的。这是make的默认行为。一般来说,你的makefile的第一个目标是由多个目标组成的,你可以指定make,让其完成你指定的目标,需要在make命令后直接跟着目标的名字就可以了。(例如之前说到的"make clean"形式)。

    任何在makefile中的目标都可以成为最终目标,但是除了"-"打头,或者包含了"="的目标。因为有这些字符的目标,会被解析为命令行参数或者变了。甚至没有被我们明确写出来的目标也可以成为make的终极目标。也就是说,只要make可以找到其隐含规则推导规则,那么这个隐含目标同样可以成为终极目标。

    有一个make的环境变量"MAKECMDGOALS",这个变量会存在你指定的终极目标的列表中,如果在命令行上,你没有指定目标,那么,这个变量是空值。这个变量可以让你使用在一些特殊的情况下,例如:

sources = foo.c  bar.c 
ifneq ($(MAKECMDGOALS) ,clean)
includes $(sources :.c=.d)
endif

基于上面的例子,只要我们输入的命令不是"make clean",那么makefile会自动包含"foo.d"和"bar.d"这两个makefile。

使用指定终极目标的方法可以方便的编译我们的程序,例如:

.PHONY : all
all : prog1  prog2  prog3  prog4 

可以看出,这个make中有四个需要编译的程序,我们可以使用"make all"编译所有的目标(如果把all作为第一个目标,那么只需要执行"make"),也可以使用"make prog2"来单独编译目标"prog2"。

既然make可以指定所有的makef中的目标,那么也包括"为目标",于是我们根据这种性质来让我们的makefile根据不同的目标完成不同的事情。在Unix中,软件发布时,特别是GNU这种开源的软件发布时,其makefile都包含了编译、安装、打包等功能,我们可以根据这种规则来书写我们的makefile的目标。

    <1>"all"

        这个伪目标是所有目标的目标,其功能一般是编译所有的目标。

    <2>“clean”

        这个伪目标是为了删除所有被make创建的文件。

    <3>"install"

        这个文目标是为了安装已经编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。

    <4>"print"

        这个伪目标功能室列出改变过的源文件。

    <5>"tar"

        这个伪目标是为了把源程序打包,也就是一个tar文件。

    <6>“dist”

        这个伪目标功能就是创建一个压缩文件,一般是把tar文件压缩成为Z文件,或者gz文件。

    <6>"TAGS"

        这个伪目标功能室更新所有的目标,以备完整的重新编译。

    <7>"check"和"test"

        这两个伪目标一般用来测试makefile的流程。

    当一个项目的makefile不一定要书写这些目标,这些东西都是GNU的东西,但是如果这样写,一是充实,而是专业。并且,如果要书写这些功能,最好使用这种名字来命令你的目标。




检查规则:

有时候,我们不希望makefile中的规则执行起来,我们只是检查一下我们的命令,或者执行的序列。可以使用:

        "-n"

        "--just-printf"

        "--dry-run"

        "--recon"

只打印,不执行

        "-t"

        "--touch"

把目标文件的时间更新,但不更改目标文件,也就是make假装编译目标,但是不是真正编译目标,只是把目标变成已编译的状态。

        “-q”

        "--question"

如果目标存在,那就什么都不输出,什么也不执行,如果目标不存在,打印一条出错信息。

        "-W <file>"

        “--what-if=<file>”

        "--assume-new=<file>"

        "--new-file=<file>"

这个参数需要制定一个文件,一般是源文件(或者是依赖文件),Make根据推到规则来运行依赖于这个文件的命令,一般来说,可以和"-n"参数一同使用,来查看这个依赖文件所发生的规则命令。

make参数:

频率较高:

"-b"

"-m"

用于忽略和其他版本的兼容性。

"-B"

"--always-make"

认为所有的目标都需要更新(重编译)

“-C  <dir>”

“--directory=<dir>”

指定读取makef的目录。若有多个"-C "参数,make的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定的目录。如"make -C ~hchen/test -C  prog"相当于"make -C ~hchen/test/prog"

“-debug[=<options>]”

输出make的调试信息,它有集中不同的级别可供选择,如果没有参数,那就是输出最简单的调试信息。<option>的取值:

a——也就是all,输出所有的调试信息。

b——basic,只输出简单的调试信息。

v——也就是verbose,在b的选项级别上。输出的I信息包括那个makefile被解析,不需要被重新编译的依赖文件(或依赖目标)等。

...........................(其他省略)




自动化变量:

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

    (1)$@

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

    (2)$%

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

    (3)$<

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

     (4)$?

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

    (5)$^

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

    (6)$+

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

    (7)$*

    这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",那么目标的模式是"a.%.b",那么,"$*"值就是"dir/a.foo"。这个变量对于构造有关的文件名是比较快的。如果目标中没有模式的定义,那么"$*"也就不能推到出来。但是如果目标文件的后缀是make所识别的,name"$*"就是除了后缀的那一部分。例如:如果目标是"foo.c",因为".c"是make所能识别的后缀名,所以,"$*"的值就是"foo",这个特性是GNU   make的,很有可能不能兼容其他版本的make。所以,应该尽量避免使用"$* ",除非是在隐含规则或者静态模式中。如果目标中的后缀是make不能识别的,那么"$*"就是空值。