Makefile目标汇总和变量的高级用法

Makefile目标汇总和变量的高级用法

规则中的目标形式是多种多样的,它可以是一个或多个的文件、可以是一个伪目标,这是我们之前讲到过的,也是经常使用的。其实规则目标还可以是其他的类型,下面是对这些类型的详细的说明。

强制目标

如果一个目标中没有命令或者是依赖,并且它的目标不是一个存在的文件名,在执行此规则时,目标总会被认为是最新的。就是说:这个规则一旦被执行,make 就认为它的目标已经被更新过。这样的目标在作为一个规则的依赖时,因为依赖总被认为更新过,因此作为依赖在的规则中定义的命令总会被执行。看一个例子:

clean:FORCE
    rm $(OBJECTS)
FORCE:

这个例子中,目标 "FORCE" 符合上边的条件。它作为目标 "clean" 的依赖,在执行 make 的时候,总被认为更新过。因此 "clean" 所在的规则而在被执行其所定义的那个命令总会被执行。这样的一个目标通常我们将其命名为 "FORCE"。
例子中使用 "FORCE" 目标的效果和将 "clean" 声明为伪目标的效果相同。

空目标文件

空目标文件是伪目标的一个变种,此目标所在的规则执行的目的和伪目标相同——通过 make 命令行指定将其作为终极目标来执行此规则所定义的命令。和伪目标不同的是:这个目标可以是一个存在的文件,但文件的具体内容我们并不关心,通常此文件是一个空文件。
空目标文件只是用来记录上一次执行的此规则的命令的时间。在这样的规则中,命令部分都会使用 "touch" 在完成所有的命令之后来更新目标文件的时间戳,记录此规则命令的最后执行时间。make 时通过命令行将此目标作为终极目标,当前目标下如果不存在这个文件,"touch" 会在第一次执行时创建一个的文件。
通常,一个空目标文件应该存在一个或者多个依赖文件。将这个目标作为终极目标,在它所依赖的文件比它更新时,此目标所在的规则的命令行将被执行。就是说如果空目标文件的依赖文件被改变之后,空目标文件所在的规则中定义的命令会被执行。看一个例子:

print:foot.c bar.c
    lpr -p $?
    touch print

执行 "make print" ,当目标文件 "print" 的依赖文件被修改之后,命令 "lpr -p $?" 都会被执行,打印这个被修改的文件。

特殊的目标

名称功能
.PHONY: 这个目标的所有依赖被作为伪目标。伪目标是这样一个目标:当使用 make 命令行指定此目标时,这个目标所在的规则定义的命令、无论目标文件是否存在都会被无条件执行。
.SUFFIXES: 这个目标的所有依赖指出了一系列在后缀规则中需要检查的后缀名
.DEFAULT: Makefile 中,这个特殊目标所在规则定义的命令,被用在重建那些没有具体规则的目标,就是说一个文件作为某个规则的依赖,却不是另外一个规则的目标时,make 程序无法找到重建此文件的规则,这种情况就执行 ".DEFAULT" 所指定的命令。
.PRECIOUS: 这个特殊目标所在的依赖文件在 make 的过程中会被特殊处理:当命令执行的过程中断时,make 不会删除它们。而且如果目标的依赖文件是中间过程文件,同样这些文件不会被删除。
.INTERMEDIATE: 这个特殊目标的依赖文件在 make 执行时被作为中间文件对待。没有任何依赖文件的这个目标没有意义。
.SECONDARY: 这个特殊目标的依赖文件被作为中过程的文件对待。但是这些文件不会被删除。这个目标没有任何依赖文件的含义是:将所有的文件视为中间文件。
.IGNORE 这个目标的依赖文件忽略创建这个文件所执行命令的错误,给此目标指定命令是没有意义的。当此目标没有依赖文件时,将忽略所有命令执行的错误。
.DELETE_ON_ERROR: 如果在 Makefile 中存在特殊的目标 ".DELETE_ON_ERROR" ,make 在执行过程中,荣国规则的命令执行错误,将删除已经被修改的目标文件。
.LOW_RESOLUTION_TIME: 这个目标的依赖文件被 make 认为是低分辨率时间戳文件,给这个目标指定命令是没有意义的。通常的目标都是高分辨率时间戳。
.SILENT: 出现在此目标 ".SILENT" 的依赖文件列表中的文件,make 在创建这些文件时,不打印出此文件所执行的命令。同样,给目标 "SILENT" 指定命令行是没有意义的。
.EXPORT_ALL_VARIABLES: 此目标应该作为一个简单的没有依赖的目标,它的功能是将之后的所有变量传递给子 make 进程。
.NOTPARALLEL: Makefile 中如果出现这个特殊目标,则所有的命令按照串行的方式执行,即使是存在 make 的命令行参数 "-j" 。但在递归调用的子make进程中,命令行可以并行执行。此目标不应该有依赖文件,所有出现的依赖文件将会被忽略。

多规则目标

Makefile 中,一个文件可以作为多个规则的目标。这种情况时,以这个文件为目标的规则的所有依赖文件将会被合并成此目标一个依赖文件列表,当其中的任何一个依赖文件比目标更新时,make 将会执行特定的命令来重建这个目标。
对于一个多规则的目标,重建这个目标的命令只能出现在一个规则中。如果多个规则同时给出重建此目标的命令,make 将使用最后一个规则中所定义的命令,同时提示错误信息。某些情况,需要对相同的目标使用不同的规则中所定义的命令,我们需要使用另一种方式——双冒号规则来实现。
一个仅仅描述依赖关系的描述规则可以用来给出一个或者时多个目标文件的依赖文件。例如,Makefile 中通常存在一个变量,就像我们以前提到的 "objects" ,它定义为所有的需要编译的生成 .o 文件的列表。这些 .o 文件在其源文件中包含的头文件 "config.h" 发生变化之后能够自动的被重建,我们可以使用多目标的方式来书写 Makefile:

objects=foo.o bar.o
foo.o:defs.h
bar.o:defs.h test.h
$(objects):config.h

这样做的好处是:我们可以在源文件增加或者删除了包含的头文件以后不用修改已存在的 Makefile 的规则,只需要增加或者删除某一个 .o 文件依赖的头文件。这种方式很简单也很方便。
我们也可以通过一个变量来增加目标的依赖文件,使用 make 的命令行来指定某一个目标的依赖头文件,例如:
extradeps=
$(objects):$(exteradeps)
它的意思是:如果我们执 "make exteradeps=foo.h" 那么 "foo.h" 将作为所有的 .o 文件的依赖文件。当然如果只执行 "make" 的话,就没有指定任何文件作为 .o 文件的依赖文件。

变量的高级用法

之前已经学习过变量的定义和基本的赋值运算,我们可以更深入的了解一下变量的一些高级的用法。高级使用方法有两种:一种是变量的替换引用,一种是变量的嵌套引用。这是我们在使用的时候比较常见的两种使用方法。

变量的替换引用

我们定义变量的目的是为了简化我们的书写格式,代替我们在代码中频繁出现且冗杂的部分。它可以出现在我们规则的目标中,也可以是我们规则的依赖中。我们使用的时候会经常的对它的值(表示的字符串)进行操作。遇到这样的问题我们可能会想到我们的字符串操作函数,比如 "patsubst" 就是我们经常使用的。但是我们使用变量同样可以解决这样的问题,我们通过下面的例子来具体的分析一下。
实例:

foo:=a.c b.c d.c
obj:=$(foo:.c=.o)
All:
    @echo $(obj)

这段代码实现的功能是字符串的后缀名的替换,把变量 foo 中所有的以 .c 结尾的字符串全部替换成 .o 结尾的字符串。我们在 Makefile 中这样写,然后再 shell 命令行执行 make 命令,就可以看到打印出来的是 "a.o b.o d.o" ,实现了文件名后缀的替换。

注意:括号中的变量使用的是变量名而不是变量名的引用,变量名的后面要使用冒号和参数选项分开,表达式中间不能使用空格。第二个变量 obj 是对整体的引用。

上面的例子我们可以换一种更加通用的方式来写,代码展示如下:

foo:=a.c b.c d.c
obj:=$(foo:%.c=%.o)
All:
    @echo $(obj)

我们在 shell 中执行 make 命令,发现结果是相同的。
对比上面的实例我们可以看到,表达式中使用了 "%" 这个字符,这个字符的含义就是自动匹配一个或多个字符。在开发的过程中,我们通常会使用这种方式来进行变量替换引用的操作。
为什么这种方式比第一种方式更加实用呢?我们在实际使用的过程中,我们对变量值的操作不只是修改其中的一个部分,甚至是改变其中的多个,那么第一种方式就不能实现了。我们来看一下这种情况:

foo:=a123c a1234c a12345c
obj:=$(foo:a%c=x%y)
All:
    @echo $(obj)

我们可以看到这个例子中我们操作的是两个不连续的部分,我们执行 make 后打印的值是 "x123y x1234y x12345y",这种情况下我们使用第一种情况就不能实现,所以第二种的使用更全面。

变量的嵌套使用

变量的嵌套引用的具体含义是这样的,我们可以在一个变量的赋值中引用其他的变量,并且引用变量的数量和和次数是不限制的。下面我们通过几个实例来说明一下。
实例 1:

foo:=test
var:=$(foo)
All:
    @echo $(var)

这种用法是最常见的使用方法,打印出 var 的值就是 test。我们可以认为是一层的嵌套引用。
实例 2:

foo=bar
var=test
var:=$($(foo))
All:
    @echo $(var)

我们再去执行 make 命令的时候得到的结果也是 test,我们可以来分析一下这段代码执行的过程:$(foo) 代表的字符串是 bar,我们也定义了变量 bar,所以我们可以对 bar 进行引用,变量 bar 表示的值是 test,所以对 bar 的引用就是 test,所以最终 var 的值就是 test。这是变量的二层嵌套执行,当然我们还可以使用三层的嵌套执行,写法跟上面的方式是一样的。嵌套的层数也可以更多,但是不提倡使用。
我们再去使用变量的时候,我们并不是只能引用一个变量,可以有多个变量的引用,还可以包含很多的变量还可以是一些文本字符。我们可以通过一些例子来说明一下。
实例 4:

first_pass=hello
bar=first
var:=$(bar)_pass
all:
    @echo $(var)

在命令行执行 make 我们可以得到 var 的值是 hello。这是变量嵌套引用的时候可以包含其它字符的使用情况。
实例 5:

first_pass=hello
bar=first
foo=pass
var:=$(bar)_$(foo)
all:
    @echo $(var)

这个实例跟上面实例的运行结果是一样的。我们可以看到这个实例中使用了两个变量的引用还有其它的字符。
变量的嵌套引用和我们的变量的递归赋值的区别:嵌套引用的使用方法就是用一个变量表示另外一个变量,然后进行多层的引用。而递归展开的变量表示当一个变量存在对其它变量的引用时,对这变量替换的方式。递归展开在另外一个角度描述了这个变量在定义是赋予它的一个属性或者风格。并且我们可以在定义个一个递归展开式的变量时使用套嵌引用的方式,但是建议你的实际编写 Makefile 时要尽量避免这种复杂的用法。
在实际使用的过程中变量的第一种用法经常使用的,第二种用法我们很少使用,应该说是尽量避免使用变量的嵌套引用。在必须要使用的时候我们应该做到嵌套的层数是越少越好的。因为使用这种方法表达会比较的复杂,如果条理不清楚的话我们就会出错。并且在给其他人看的时候也会不容易理解。

 
 
原文地址:https://www.cnblogs.com/jkin/p/13767624.html