makefile 及其工作原理

Make程序最初设计是为了维护C程序文件防止不必要的重新编译。在使用命令行编译器的时候,修改了一个工程中的头文件,如何确保包含这个头文件的所有文件都得到编译?现在10机的版本生成是使用批处理程序,编译那些文件依赖于程序的维护者,在模块之间相互引用头文件的情况下,要将所有需要重新编译的文件找出来是一件痛苦的事情;在找到这些文件之后,修改批处理进行编译。实际上这些工作可以让make程序来自动完成,make工具对于维护一些具有相互依赖关系的文件特别有用,它对文件和命令的联系(在文件改变时调用来更新其它文件的程序)提供一套编码方法。Make工具的基本概念类似于Proglog语言,你告诉make需要做什么,提供一些规则,make来完成剩下的工作。
make工作自动确定工程的哪部分需要重新编译,执行命令去编译它们。虽然make多用于C程序,然而只要提供命令行的编译器,你可以将其用于任何语言。实际上,make工具的应用范围不仅于编程,你可以描述任和一些文件改变需要自动更新另一些文件的任务来使用它。



规则简介
makefile中的规则是这样的:
TARGET … : DEPENDENCIES …
COMMAND

目标(TARGET)程序产生的文件,如可执行文件和目标文件;目标也可以是要执行的动作,如“clean”。
依赖(DEPENDENCIES)是用来产生目标的输入文件,一个目标通常依赖于多个文件。
命令(COMMAND)是make执行的动作,一个可以有多个命令,每个占一行。注意:每个命令行的起始字符必须为TAB字符!
有依赖关系规则中的命令通常在依赖文件变化时负责产生target文件,make执行这些命令更新或产生target。规则可以没有依赖关系,如包含target “clean”的规则。
规则解释如何和何时重做该规则中的文件,make根据依赖关系执行产生或更新目标;规则也说明如何和何时执行动作。有的规则看起来很复杂,但都符合上述模式。



.make工作原理
缺省make从第一个target开始(第一个非 ’.’ 开始的target),这称作缺省目标。



Makefile中包含五种内容:显式规则,隐式规则,变量定义,指令(directive)和注释。
1.显式规则:描述如何生成规则的目标,它列出了目标依赖的文件,指定了产生或更新目标的命令
2.隐式规则:描述如何生成基于文件名的一类文件,说明目标可能依赖于和其文件名类似的文件,指定了相应的命令。
3.变量定义:定义一个变量
3.指令:类似与编译器的伪指令,包含:指示make读入另一个makefile,决定是否忽略makefile中的一部 分
4.注释:以‘#’开始直到行末,除非遇到续行符号。在’define’和命令中不能有注释,其它情况下注 释可出现在任何地方。



wildcard、notdir、patsubst的意思:
  wildcard : 扩展通配符
  notdir : 去除路径
  patsubst :替换通配符
 
当你需要在一些源文件改变后运行或更新一个任务时,通常会用到 make 工具。make 工具需要读取一个 Makefile(或 makefile)文件,在该文件中定义了一系列需要执行的任务。你可以使用 make 来将源代码编译为可执行程序。大部分开源项目会使用 make 来实现最终的二进制文件的编译,然后使用 make install 命令来执行安装。



本文将通过一些基础和进阶的示例来展示 make 和 Makefile 的使用方法。在开始前,请确保你的系统中安装了 make。



基础示例



依然从打印 “Hello World” 开始。首先创建一个名字为 myproject 的目录,目录下新建 Makefile 文件,文件内容为:



say_hello:



    echo "Hello World"


在 myproject 目录下执行 make,会有如下输出:



$ make



echo “Hello World”



Hello World



在上面的例子中,“say_hello” 类似于其他编程语言中的函数名。这被称之为目标target。在该目标之后的是预置条件或依赖。为了简单起见,我们在这个示例中没有定义预置条件。echo ‘Hello World’ 命令被称为步骤recipe。这些步骤基于预置条件来实现目标。目标、预置条件和步骤共同构成一个规则。



总结一下,一个典型的规则的语法为:



目标: 预置条件



步骤

作为示例,目标可以是一个基于预置条件(源代码)的二进制文件。另一方面,预置条件也可以是依赖其他预置条件的目标。

final_target: sub_target final_target.c

Recipe_to_create_final_target



sub_target: sub_target.c

Recipe_to_create_sub_target

目标并不要求是一个文件,也可以只是步骤的名字,就如我们的例子中一样。我们称之为“伪目标”。

再回到上面的示例中,当 make 被执行时,整条指令 echo "Hello World" 都被显示出来,之后才是真正的执行结果。如果不希望指令本身被打印处理,需要在 echo 前添加 @。

say_hello:
@echo "Hello World"

重新运行 make,将会只有如下输出:

$ make

Hello World

接下来在 Makefile 中添加如下伪目标:generate 和 clean:

say_hello:

@echo "Hello World"

generate:

@echo "Creating empty text files..."

touch file-{1..10}.txt

clean:

@echo "Cleaning up..."

rm *.txt

随后当我们运行 make 时,只有 say_hello 这个目标被执行。这是因为Makefile 中的第一个目标为默认目标。通常情况下会调用默认目标,这就是你在大多数项目中看到 all 作为第一个目标而出现。all 负责来调用它他的目标。我们可以通过 .DEFAULT_GOAL 这个特殊的伪目标来覆盖掉默认的行为。

在 Makefile 文件开头增加 .DEFAULT_GOAL:

.DEFAULT_GOAL := generate

make 会将 generate 作为默认目标:

$ make

Creating empty text files...

touch file-{1..10}.txt

顾名思义,.DEFAULT_GOAL 伪目标仅能定义一个目标。这就是为什么很多 Makefile 会包括 all 这个目标,这样可以调用多个目标。

下面删除掉 .DEFAULT_GOAL,增加 all 目标:

all: say_hello generate

say_hello:

@echo "Hello World"

generate:

@echo "Creating empty text files..."

touch file-{1..10}.txt

clean:

@echo "Cleaning up..."

rm *.txt

运行之前,我们再增加一些特殊的伪目标。.PHONY 用来定义这些不是文件的目标。make 会默认调用这些伪目标下的步骤,而不去检查文件名是否存在或最后修改日期。完整的 Makefile如下:

.PHONY: all say_hello generate clean

all: say_hello generate

say_hello:

@echo "Hello World"

generate:

@echo "Creating empty text files..."

touch file-{1..10}.txt

clean:

@echo "Cleaning up..."

rm *.txt

make 命令会调用 say_hello 和 generate:

$ make

Hello World

Creating empty text files...

touch file-{1..10}.txt

clean 不应该被放入 all 中,或者被放入第一个目标中。clean 应当在需要清理时手动调用,调用方法为 make clean。

$ make clean

Cleaning up...

rm *.txt

现在你应该已经对 Makefile 有了基础的了解,接下来我们看一些进阶的示例。

进阶示例

变量

在之前的实例中,大部分目标和预置条件是已经固定了的,但在实际项目中,它们通常用变量和模式来代替。

定义变量最简单的方式是使用 = 操作符。例如,将命令 gcc 赋值给变量 CC:

CC = gcc

这被称为递归扩展变量,用于如下所示的规则中:

hello: hello.c

${CC} hello.c -o hello

你可能已经想到了,这些步骤将会在传递给终端时展开为:

gcc hello.c -o hello

${CC} 和 $(CC) 都能对 gcc 进行引用。但如果一个变量尝试将它本身赋值给自己,将会造成死循环。让我们验证一下:

CC = gcc

CC = ${CC}

all:

@echo ${CC}

此时运行 make 会导致:

$ make

Makefile:8: *** Recursive variable 'CC' references itself (eventually). Stop.

为了避免这种情况发生,可以使用 := 操作符(这被称为简单扩展变量)。以下代码不会造成上述问题:

CC := gcc

CC := ${CC}

all:

@echo ${CC}

模式和函数

下面的 Makefile 使用了变量、模式和函数来实现所有 C 代码的编译。我们来逐行分析下:

# Usage:

# make # compile all binary

# make clean # remove ALL binaries and objects

.PHONY = all clean

CC = gcc # compiler to use

LINKERFLAG = -lm

SRCS := $(wildcard *.c)

BINS := $(SRCS:%.c=%)

all: ${BINS}

%: %.o

@echo "Checking.."

${CC} ${LINKERFLAG} $< -o $@

%.o: %.c

@echo "Creating object.."

${CC} -c $<

clean:

@echo "Cleaning up..."

rm -rvf *.o ${BINS}

◈ 以 # 开头的行是评论。◈ .PHONY = all clean 行定义了 all 和 clean 两个伪目标。◈ 变量 LINKERFLAG 定义了在步骤中 gcc 命令需要用到的参数。◈ SRCS := $(wildcard *.c):$(wildcard pattern) 是与文件名相关的一个函数。在本示例中,所有 “.c”后缀的文件会被存入 SRCS 变量。◈ BINS := $(SRCS:%.c=%):这被称为替代引用。本例中,如果 SRCS 的值为 'foo.c bar.c',则 BINS的值为 'foo bar'。◈ all: ${BINS} 行:伪目标 all 调用 ${BINS} 变量中的所有值作为子目标。◈
规则:

%: %.o

@echo "Checking.."

${CC} ${LINKERFLAG} $< -o $@

下面通过一个示例来理解这条规则。假定 foo 是变量 ${BINS} 中的一个值。% 会匹配到 foo(%匹配任意一个目标)。下面是规则展开后的内容:

foo: foo.o

@echo "Checking.."

gcc -lm foo.o -o foo

如上所示,% 被 foo 替换掉了。$< 被 foo.o 替换掉。$<用于匹配预置条件,$@ 匹配目标。对 ${BINS} 中的每个值,这条规则都会被调用一遍。


规则:

%.o: %.c

@echo "Creating object.."

${CC} -c $<

之前规则中的每个预置条件在这条规则中都会都被作为一个目标。下面是展开后的内容:

foo.o: foo.c

@echo "Creating object.."

gcc -c foo.c


最后,在 clean 目标中,所有的二进制文件和编译文件将被删除。

下面是重写后的 Makefile,该文件应该被放置在一个有 foo.c 文件的目录下:

# Usage:

# make # compile all binary

# make clean # remove ALL binaries and objects

.PHONY = all clean

CC = gcc # compiler to use

LINKERFLAG = -lm

SRCS := foo.c

BINS := foo

all: foo

foo: foo.o

@echo "Checking.."

gcc -lm foo.o -o foo

foo.o: foo.c

@echo "Creating object.."

gcc -c foo.c

clean:

@echo "Cleaning up..."

rm -rvf foo.o foo

Makefile中的四个有用的特殊符号意义和使用,他们分别是@、$@、$^、$<
一、@

这个符串通常用在“规则”行中,表示不显示命令本身,而只显示它的结果,例如Makefile中的内容为:

DIR_OBJ=./obj
CMD_MKOBJDIR=if [ -d ${DIR_OBJ} ]; then exit 0; else mkdir ${DIR_OBJ}; fi

mkobjdir:
@${CMD_MKOBJDIR}
命令行执行如下:
make mkobjdir
此时不会显示在命令行不会显示出if [ -d ${DIR_OBJ} ]; then exit 0; else mkdir ${DIR_OBJ}; fi,但如果规则行的TAB后没有以@开头,则会显示,不信可以试试。
二、$@、$^、$<

这三个分别表示:

$@ --代表目标文件(target)

$^ --代表所有的依赖文件(components)

$< --代表第一个依赖文件(components中最左边的那个)。

好了,知道了他们的意义后,如果使用上面三个变量,那么简化的Makefile文件为:

main.out:main.o line1.o line2.o
g++ -o $@ $^
main.o:main.c line1.h line2.h
g++ -c $<
line1.o:line1.c line1.h
g++ -c $<
line2.o:line2.c line2.h
g++ -c $<



.PHONY的作用
Phony Targets

PHONY 目标并非实际的文件名:只是在显式请求时执行命令的名字。有两种理由需要使用PHONY 目标:避免和同名文件冲突,改善性能。

如果编写一个规则,并不产生目标文件,则其命令在每次make 该目标时都执行。例如:
  clean:
  rm *.o temp
因为"rm"命令并不产生"clean"文件,则每次执行"make clean"的时候,该命令都会执行。如果目录中出现了"clean"文件,则规则失效了:没有依赖文件,文件"clean"始终是最新的,命令永远不会 执行;为避免这个问题,可使用".PHONY"指明该目标。如:
  .PHONY : clean
  这样执行"make clean"会无视"clean"文件存在与否。

已知phony 目标并非是由其它文件生成的实际文件,make 会跳过隐含规则搜索。这就是声明phony 目标会改善性能的原因,即使你并不担心实际文件存在与否。
  完整的例子如下:
  .PHONY : clean
  clean :
  rm *.o temp

shell 命令


每个目标都可以具有与其关联的一系列 shell 命令,这些命令通常用来创建目标。此脚本中的每一条命令都必须以制表符开始。虽然任何目标都能够显示在相关性行上,但除非使用 :: 操作符,否则这些相关性中只有一个能够通过创建脚本来跟随。


如果命令行的第一个或前两个字符是 @ (at 符号)、-(连字符)和 +(加号)这几个符号之一或全部,那么将特别处理该命令,如下:
@ 使命令在被执行前不被回显。
- 使任何命令行的任何非零退出状态都被忽略。
+ 使命令行可以通过指定 -n、-q 或 -t 选项来执行。

2.1.makefile名字
缺省情况下,make以下列名字查找makefile:’GNUmakefile’,’makefile’和’Makefile’(注意大小写)。通常你的makefile应叫做’makefile’或’Makefile’。’GNUmakefile’不推荐,除非你的makefile是为GNU的make定制的,其它的make不认为该名字是一个makefile的名字。
如果你使用非标准命名的makefile,必须用命令开关’-f ’ 或 ’—file’。参数’–f NAME’或’—file NAME’告诉make读入NAME作为makefile。如果使用多个该开关,所有的文件将按顺序连接起来。如果使用该选项,标准的makefile名字不会自动检测。

2.2.包含
‘include’指令告诉make暂停处理余下的内容,读入其它makefile。语法如下:
include FILENAMES …
这一行起始可以有空格,但TAB字符不允许。如果文件名包含变量或函数,这些将被扩展。

2.3.‘MAKEFILE’变量
如果环境变量’MAKEFILE’已定义,make认为它的值是一系列空格隔开的文件名,这些文件在处理其它makefile前被make程序读入。这类似于include指令;这些文件中的目标不会影响缺省目标,而且如果文件未找到的话,make并不认为是错误。这个变量的主要用途是递归引用make程序时通讯

2.4.如何重新生成makefile
有时候makefile是从其它文件生成的,比如RCS或SCCS文件。如果makefile是由其它文件生成的,需要make读入最新版本的makefile。在读入所有makefile之后,make认为每个makefile是一个目标,试图去更新它;如果makefile中有一条如何更新它的规则,或者有适用的隐式规则,需要的更新会进行。所有的makefile检查完之后,如果有的改变了,make重新开始再读入(make会试图再做更新,但通常不会再改变了,因为已经是最新的了)。
如果一个文件使用双冒号规则,提供了命令但没有依赖关系,文件始终会被更新。在makefile的情况下,如果makefile双冒号规则,提供了命令但没有依赖关系,这样makefile始终会重新生成,这会导致循环:make只是在不断更新makefile,却不干活。为避免这种情况,make不会重新生成那些只有命令没有依赖关系的双冒号规则的makefile。
如果没有使用’-f’或’--file’选项,make会尝试缺省的makefile文件名。和指明’-f’或’--file’选项不同,make不能确定这些文件是否应当存在。然而,如果缺省makefile不存在但可以通过运行make规则生成,你可能希望这些规则被运行使得makefile可以使用。因此,如果没有缺省makefile,make试图按照makefile名查找的顺序生成它,直到成功或名字用完。注意如果make 不能找到或生成makefile,这并不是错误;makefile不总是必需的。
当使用’-t’或’--touch’选项时,不希望使用过时的makefile来决定那个目标来touch。所以’-t’选项对makefile更新不起作用;类似’-q’(or ‘—question’)和’-n’(or ’—just-print’)不阻止makefile的更新,因为过时的makefile会产生错误的输出。这样’make –f mfile –n foo’会更新’mfile’,读入它,打印出更新’foo’需要执行的命令但不运行这些命令。与’foo’有关的命令是更新过的’mfile’中的内容。
但是有时不希望更新makefile,可以将makefile作为命令行的目标,当makefile被显式指定为目标时,’-t’选项也适用于它们。这样’make –f mfile –n mfile foo’会读入’mfile’,打印出更新执行的命令,’foo’的命令是当前的’mfile’中的内容。

2.5.重载makefile
可以使用’include’指令来包含其它makefile,增加目标的变量定义。然而,make不允许同一个目标有不同的命令,有其它的途径可以达到目的。
假设有’makefile’ 和’mfile’,’makfile’要包含’mfile’,但都有对于目标’foo’的规则。这是可以在’makefile’中写一条匹配任意模式的规则,指明当make在’makefile’中未找到目标时,搜索’mfile’:
foo:
frobnicate > foo
%: force
@$(MAKE) -f mfile $@
force: ;
当执行’make foo’时,make找到’makefile’,执行命令’ frobnicate > foo’;执行’make bar’时,在’makefile’中未找到相应的规则,这时模式规则适用,执行命令’make –f mfile bar’,’makefile’中未提及的其它目标也是类似的。
这种方法之所以工作是因为模式规则的模式是’%’,可以匹配任何的目标;这条规则的依赖是’force’,保证即使目标存在命令也会执行;’force’规则的命令为空防止’make’为其搜索隐式规则-这样会导致依赖循环。

makefile中的规则描述如何生成特定的文件,即规则的目标。规则列出了目标的依赖文件,指定生成或更新目标的命令。规则的次序是不重要的,除非是确定缺省目标:缺省目标是第一个makefile中的第一个规则;如果第一个规则有多个目标,第一个目标是缺省的。有两个例外:以’.’开头的目标不是缺省目标;模式规则对缺省目标没有影响。通常我们所写的地一个规则是编译整个或makefile中指定的所有程序。

规则的语法

语法如下:
TARGETS : DEPENDENCIES
COMMAND
...
或者
TARGETS : DEPENDENCIES ; COMMAND
COMMAND
...
TARGETS是以空格隔开的文件名,统配符可以使用。通常一个规则只有一个目标,偶尔也有多个。
命令行以TAB键开始。第一条命令可在依赖关系的下一行;或者在同一行,在分号后面;两种方式效果相同。
因为’$’符号被用做变量引用,如果要在规则中使用’$’符号,必须写两个:’$$’。可以用’’符号来分割一个长行,这不是必须的,因为make对行的长度没有限制。

3.3.通配符

规则中的文件名可以包含统配符,如’*’,’?’。
文件名前的字符'~’有特殊的含义。单独使用,或跟随一个’/’,代表用户的home目录,比如’~/bin’扩展为/home/you/bin’;如果’~’跟随一个单词,表示单词指示的那个用户的home目录,如’~john/bin’扩展为’/home/john/bin’。
通配符在目标,依赖关系,命令中自动扩展,其它情况下,统配符的扩展除非显式使用’wildcard’函数。通配符的特殊意义可以使用’’符号关闭。
例子:
clean:
rm -f *.o

print: *.c
lpr -p $?
touch print
通配符在定义变量时并不扩展,例如:
objects = *.o
则objects的值是字符串’*.o’;但是如果你将objects用于目标,依赖或命令中,扩展会进行。要将objects设置成扩展过的内容,使用:
objects := $(wildcard *.o)

3.3.1.通配符的缺陷
这是一个使用通配符的例子,但结果不是你所期望的。假设可执行文件’foo’是从当前目录中的所有’.o’文件生成的:
objects = *.o
foo : $(objects)
cc -o foo $(CFLAGS) $(objects)
objects变量的值是字符串’*.o’。通配符扩展在规则’foo’中进行,于是所有存在的’.o’文件成为’foo’的依赖而且在需要时重新编译。但如果删除了所有的’.o’文件呢?当通配符不匹配任何文件时,一切都保持原样:则’foo’依赖于一个叫做’*.o’的文件;由于这个文件不大可能存在,’make’程序会报告一个无法生成’*.o’文件的错误,这不是期待的结果。实际上可以用通配符获得期望结果,但是需要复杂的技术,包括’wildcard’函数和字符串替换函数。

3.3.2.wildcard函数
通配符自动在规则中进行。但是在变量赋值的和函数的参数中通配符不会扩展,如果在这些情况下需要通配符扩展,必须使用’wildcard’函数。语法如下:
$(wildcard PATTERN...)
这个在makefile任何地方出现的字符串,会被匹配任何一个文件名格式的以空格隔开的现有文件列表替换。如果没有任何文件匹配一个模式,这个模式从’wildcard’的输出中忽略,注意,这和上述的通配符的处理是不一样的。‘wildcard’函数的一个功能是找出目录中所有的’.c’文件:
$(wildcard *.c)
可以通过替换后缀’.c’为’.o’从C文件列表得到目标文件的列表:
$(patsubst %.c,%.o,$(wildcard *.c))
这样,上节中的makefile改写为:
objects := $(patsubst %.c,%.o,$(wildcard *.c))
foo : $(objects)
cc -o foo $(objects)
这个makefile利用了编译C程序的隐含规则,所以不需要对编译写出显式的规则。(’:=’是’=’的一个变体)注意:’PATTERN’是大小写敏感的。

3.4.目录搜索
对于大的系统,通常将源文件和目标文件放在不同的目录中。目录搜索功能可以让make自动在多个目录中搜寻依赖文件,当你将文件重新分布是,不需要改变规则,更改搜索路径即可。

3.4.1.‘VPATH’
make变量’VPATH’列出make应当搜索的目录列表。很多情况下,当前目录不包含依赖文件,’VPATH’描述一个对所有文件的搜索列表,包含那些是规则的目标的文件。
如果一个目标或者依赖文件在当前目录没找到的话,’make’在’VPATH’中列出的目录中查找同名的文件。如果找到的话,那个文件成为依赖文件;规则可以象这些文件在当前目录中一样来使用他们。
在’VPATH’变量中,目录名以冒号或空格隔开;目录列出的顺序决定make查找的顺序。(注:在pSOSystem 2.5移植到Win32的GNU make目录名必须使用分号隔开,以下均简称Win32 GNU make)。举例说明:
VPATH = src:../headers 则规则
foo.o : foo.c
被解释为
foo.o : src/foo.c
假设’foo.c’在当前目录不存在,在’src’目录中可以找到。

3.4.2.选择性搜索
与’VPATH’变量相似但更具选择性的是’vpath’指令(注意是小写),可以指定对于符合特定模式文件的查找路径。这样可以为不同类型的文件指定不同的搜索路径。
‘vpath’指令共有三中形式:
a).‘vpath PATTERN DIRECTORIES’
为匹配PATTERN的文件名指定搜索路径DIRECTORIES,目录的分隔和’VPATH’的相同
b).‘vpath PATTERN’
清除为匹配PATTERN的文件名指定的搜索路径
c).‘vpath’
清除所有以前用’vpath’指定的搜索路径
‘vpath’的模式是包含’%’的字符串:这个字符串必须匹配需要搜索的依赖文件名,’%’字符匹配0个或多个任意字符。例如:’%.h’匹配任何以’.h’结尾的文件(如果没有%,则PATTERN必须和依赖文件完全一致,这种用法不太多)。
当当前目录中不存在依赖文件时,如果’vpath’中的PATTERN匹配依赖文件名,则指令中DIRECTORIES列出的目录和’VPATH’中同样处理。举例:
vpath %.h ../headers
告诉make在当前目录中未找到的’.h’文件在../headers目录中查找。
如果多个’vapth’的模式匹配依赖文件名,make将逐一处理,在所有指定的目录中搜索。Make按照’vapth’在makefile中的次序;来处理它们,多个相同模式的’vapth’是相互独立的。
vpath %.c foo
vpath %.c blish
vpath %.c bar
将按照’foo’,‘blish’,’bar’的次序查找’.c’文件。而
vpath %.c foo:bar
vpath % blish
按照’foo’,’bar’,’blish’的顺序搜索。

3.4.3.使用自动变量
目录搜索的结果并不改变规则中的命令:命令按原样被执行。因此,必须写出与目录搜索功相适应的命令。这可以通过使用’$^’这样的自动变量来完成。’$^’表示规则中的所有依赖文件,包含它们所在的目录名(参见目录搜索);’$@’表示目标。例如:
foo.o : foo.c
cc -c $(CFLAGS) $^ -o $@
通常情况下,依赖文件也包含头文件,但命令中并不提及这些文件:变量’$<’表示第一个依赖文件:
VPATH = src:../headers
foo.o : foo.c defs.h hack.h
cc –c $(CFLAGS) $< -o $@

3.4.4.目录搜索和隐含规则
使用’VPATH’和’vpath’指定目录搜索也会影响隐含规则。例如:文件’foo.o’没有显式规则,make会考虑隐式规则:如果’foo.c’存在则编译它;如果这个文件不存在,则在相应的目录中查找;如果’foo.c’在任一的目录中存在,则C编译的隐式规则被应用。
隐式规则的命令使用自动变量通常是必要的,这样无需其它努力即可以使用目录搜索得到的文件名。

3.5.PHONY目标
Phony目标并非实际的文件名:只是在显式请求时执行命令的名字。有两种理由需要使用phony目标:避免和同名文件冲突,改善性能。
如果编写一个规则,并不产生目标文件,则其命令在每次make该目标时都执行。例如:
clean:
rm *.o temp
因为’rm’命令并不产生’clean’文件,则每次执行’make clean’的时候,该命令都会执行。如果目录中出现了’clean’文件,则规则失效了:没有依赖文件,文件’clean’始终是最新的,命令永远不会执行;为避免这个问题,可使用’.PHONY’指明该目标。如:
.PHONY : clean
这样执行’make clean’会无视’clean’文件存在与否。
已知phony目标并非是由其它文件生成的实际文件,make会跳过隐含规则搜索。这就是声明phony目标会改善性能的原因,即使你并不担心实际文件存在与否。完整的例子如下:
.PHONY : clean
clean :
rm *.o temp
phony目标不应是真正目标文件的依赖。如果这样,每次make在更新此文件时,命令都会执行。只要phony目标不是真正目标的依赖,规则的命令只有在指定此目标时才执行。
Phony目标可以有依赖关系。当一个目录中有多个程序是,将其放在一个makefile中会更方便。因为缺省目标是makefile中的第一个目标,通常将这个phony目标叫做’all’,其依赖文件为各个程序:
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
这样,使用’make’将可以将三个程序都生成了。
当一个phony目标是另一个的依赖,其作用相当于子程序,例如:
.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff

3.6.FORCE目标
当规则没有依赖关系也没有命令,而且其目标不是存在的文件名,make认为此规则运行时这个目标总是被更新。这意味着如果规则依赖于此目标,其命令总是被执行。
clean: FORCE
rm $(objects)
FORCE:
例中目标’FORCE’满足这种特殊条件,这样依赖于它的目标’clean’被强制执行其命令。名字’FORCE’没有特殊含义,只不过通常这样用而已。这种方式使用’FORCE’和’.PHONY : clean’效果相同。使用’.PHONY’更加明确高效,担不是所有的’make’都支持;这样许多makefile中使用’FORCE’。

3.7.空目标
空目标(empty target)是phony目标的变种:用来执行显式请求的一个动作。和phony目标不同的是:这个目标文件可以真实存在,担文件的内容无关紧要,通常是空的。空目标文件的目的是利用其最后修改时间来记录命令最近一次执行的时间,这是通过使用’touch’命令更新目标文件来达到的。
print: foo.c bar.c
lpr -p $?
touch print
利用这条规则,执行’make print’时如果自上次’make print’之后任一文件改变了,’lpr’命令会执行。自动变量’$?’是为了只打印出那些变化了的文件。

3.8.内建的特殊目标
某些名字作为目标存在时有特殊含义。
a) .PHONY 该目标的依赖被认为是phony目标,处理这些目标时,命令无条件被执行,不管文件名是否 存在及其最后修改时间
b) .SUFFIXES 该目标的依赖被认为是一个后缀列表,在检查后缀规则时使用
c) .DEFAULT 该目标的规则被使用在没有规则(显式的或隐含的)的目标上。如果’DEFAULT’命令定 义了,则对所有不是规则目标的依赖文件都会执行该组命令
d).PRECIOUS 该目标的依赖文件会受到特别对待:如果make被kill或命令的执行被中止,这些目标并不 删除;而且如果该目标是中间文件,在不需要时不会被删除。可以将隐含规则的目标模式(如%.o) 做为’.PRECIOUS’的依赖文件,这样可以保存这些规则产生的中间文件。
e).INTERMEDIATE 该目标的依赖文件被当作中间文件;如果该目标没有依赖文件,则makefile中所有的 目标文件均被认为是中间文件。
f).IGNORE 在执行该目标的依赖规则的命令时,make会忽略错误,此规则本身的命令没有意义。如果该 规则没有依赖关系,表示忽略所有命令执行的错误,这种用法只是为了向后兼容;由于会影响到所 有的命令,所以不是特别有用,推荐使用其它更有选择性忽略错误的方法。
g).SILENT 在执行该目标的依赖规则的命令时,make并不打印命令本身。该规则的命令没有意义。 在’.SILIENT’没有依赖关系时,表示执行makefile中的所有命令都不会打印,该规则只是为了向 后兼容提供的。
h).EXPORT_ALL_VARIABLES 只是作为一个目标存在,指示make将所有变量输出到子进程中。
定义的隐含规则的后缀作为目标时,也认为它是特殊目标;两个后缀的连接也是一样,比 如’.c.o’。这些目标是后缀规则,一中定义隐式规则的过时方法(但仍然广泛使用)。后缀通常 以’.’开始,所以特殊目标也以’.’开始。

3.9.一个规则多个目标
一条有多个目标的规则和写多条规则,每条一个目标作用是等同的。同样的命令应用于所有目标,但其效用会因将实际目标以’$@’代替而不同。规则中所有目标的依赖关系是一样的。这在两种情况下有用:
a).只有依赖关系,不需要命令。例如:
kbd.o command.o files.o: command.h
b).所有的目标同样的命令。命令不需要完全相同,因为在命令中可以使用’$@’:
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
等同。这里假设程序’generate’产生两种输出:一种使用’-big’选项,一种使用’-little’选项。如果想象使用’$@’变化命令那样来变化依赖关系,不能通过多目标的普通规则实现,但是可以通过模式规则来实现。

3.10.一个目标多条规则
一个文件可以是多条规则的目标,所有规则的依赖关系被合并。如果目标比任一个依赖文件旧,命令被执行。
一个文件只能有一组命令执行。如果多个规则对于同一个文件都给出了命令,make使用最后一组并打印错误信息(特殊情况:如果文件名以’.’开始,并不打印错误信息,这一点是为了和其它make兼容)。没有任何理由需要将makefile写成这样,这是make给出错误信息的理由。
一条只有依赖关系的附加规则可以一次给出许多文件的附加依赖文件。例如’objects’变量表示系统中编译器的所有输出.,说明当’config.h’更改时所有文件必须重做的简单方法如下:
objects = foo.o bar.o
foo.o : defs.h
bar.o : defs.h test.h
$(objects) : config.h
不用改变实际目标文件生成的规则,这条规则可以在需要增删附加的依赖关系时插入或提出。另一个诀窍是附加的依赖关系可以用变量表示,在make执行时,可以给变量赋值:
extradeps=$(objects) : $(extradeps)
当命令`make extradeps=foo.h'执行时会认为’foo.h’是每个目标文件的依赖文件,但简单的’make’命令不是这样。

3.11.静态模式规则
静态模式规则(static pattern rules)可以指定多个目标,并且使用目标名字来建议依赖文件的名字;比普通多目标规则更通用因为不需要依赖关系是相同的:依赖关系必须类似但不需要相同。

3.11.1.语法
TARGETS ...: TARGET-PATTERN: DEP-PATTERNS ...
COMMANDS
...
TARGETS列表指出规则应用的目标,可以包含通配符,于普通规则的目标相同。TARGET-PATTERN和DEP-PATTERNS来表明目标的依赖关系如何计算:匹配TARGET-PATTERN的目标从名字中抽出一部分,叫做词干(stem),词干被替换到DEP-PATTERNS来形成依赖文件名。
每个模式通常包含一个’%’字符。当TARGET-PATTERN匹配一个目标时,’%’字符可以匹配目标名中的任何部分;这部分即是词干,模式的其余部分必须完全匹配。例如’foo.o’匹配’%.o’,’foo’是词干;目标’foo.c’和’foo.out’并不匹配这个模式。
目标的依赖文件名通过将DEP-PATTERNS中的’%’替换为词干形成:如果依赖模式为’%.c’,在替换词干’foo’可以得到’foo.c’。依赖模式中不包含’%’也是合法的,此依赖文件对所有的目标均有效。
如果需要在模式规则中使用’%’字符,必须在其前面加’’字符,如果’%’前的’’字符是有实际意义的,必须在其前面加’’,其它的’’不必如此处理。如’the\%weird\%pattern’在有效的’%’前是’the%weird’,其后是’pattern’。最后的’’保持原样是因为其并不影响’%’字符。
以下例子从相应的’.c’文件编译’foo.o’和’bar.o’:
objects = foo.o bar.o
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
每个目标必须匹配目标模式,对于不匹配的目标会给出警告。如果列表中只有部分文件匹配模式,可以使用filter函数移去不匹配的文件名:
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
例子中`$(filter %.o,$(files))' 结果是`bar.o lose.o’; `$(filter %.elc,$(files))' 的结果是`foo.elc'。以下例子说明’$*’的使用:
bigoutput littleoutput : %output : text.g
generate text.g -$* > $@
命令`generate'执行时,’$*’扩展为词干’big’或’little’。

3.11.2.静态模式规则和隐式规则
静态模式规则和隐式规则在作为模式规则是具有很多共同点,都有目标模式和构造依赖文件名的模式,不同之处在于make决定何时应用规则的方法。
隐式规则可应用于匹配其模式的任何目标,但只限于没有指定命令的目标,如果有多条可应用的隐式规则,只有一条被使用,取决于规则的顺序。反之,静态模式规则适用于规则中明确目标列表,不适用于其它目标且总是适用于指定的每个目标。如果有两条冲突的规则,且都有命令,这是一个错误。
静态模式规则比隐式规则优越之处如下:
a).可为一些不能按句法分类,但可以显式列出的文件重载隐式规则
b).不能判定目录中的精确内容,一些无关的文件可能导致make适用错误的隐式规则;最终结果可能依赖 于隐式规则的次序。适用静态模式规则时,这种不确定性是不存在的:规则适用于明确指定的目标。

3.12.双冒号规则
双冒号规则(Double-colon rules)的目标后是’::’而不是’:’,当一个目标出现在多条规则中时,其处理和普通规则的处理不同。
当一个目标出现在多条规则中时,所有规则必须是相同类型的:都是普通的或者都是双冒号的。如果是双冒号,规则之间相互独立;如果目标需要更新,则规则的命令被执行;结果可能是没有执行,或者执行了其中一些,或者所有的规则都执行了。
同一目标的双冒号规则事实是完全孤立的,每条规则被被单独处理,就象不同目标的规则一样;规则按照在makefile中出现的次序被处理,此类规则真正有意义的是那些于命令执行次序无关的。
这种规则有时比较晦涩不是特别有用;它提供了一种机制:通过不同依赖文件的更新来对目标进行不同的处理,这种情形很罕见。每个这种规则应当提供命令,如果没有,适用的隐式规则将使用。

3.13.自动生成依赖关系
在makefile中,许多规则都是一些目标文件依赖于一些头文件。例如:’main.c’ 通过’#include’使用’defs.h’,这样规则:
main.o: defs.h
告诉make在’defs.h’变化时更新’main.o’。在程序比较大时,需要写许多这样的规则;而且当每次增删’#include’时,必须小心的更新makefile。许多现代的编译器可以帮你写这些规则,通常这是通过编译器的’-M’选项,例如命令:
cc –M main.c
输出以下内容:
main.o : main.c defs.h
这样就不必写这些规则,有编译器代劳了。
注意这样的依赖关系中提及’main.o’,不会被隐式规则认为是中间文件,这意味这make在使用过它之后不会将其删除。使用老的’make’程序时,习惯做法是使用’make depend’命令利用编译器的功能产生依赖关系,该命令会产生一个’depend’文件包含所有自动产生的依赖关系,然后在makefile中使用’include’将其读入。
使用GNU的make时,重新生成makefile的功能使得这种做法变得过时:从不需要显式请求更新依赖关系,因为它总是重新生成任何过时的makefile。
自动依赖关系生成推荐的做法是对每个源文件做一个makefile。对每个源文件’NAME.c’,有一个makefile ’NAME.d’,其中列出了目标文件’NAME.o’依赖的所有文件,这样在源文件更新时,需要扫描来产生新的依赖关系。例子是一个从’NAME.c’产生依赖关系文件’NAME.d’的模式规则:
%.d: %.c
$(SHELL) -ec '$(CC) -M $(CPPFLAGS) $<
| sed '''s/($*).o[ :]*/1 $@/g''' > $@'
-e选项是当$(CC)命令失败时(exit状态非0),shell立刻退出。通常shell的返回值是管道中最后一条命令(sed)的返回值,这样make不会注意到编译器出错。
使用GNU的C编译器时(gcc),可以用’-MM’选项来代替’-M’选项,这样省略系统头文件的依赖关系。’sed’命令的目的是将
main.o : main.c defs.h
转换为
main.o main.d : main.c defs.h
这样使得每个’.d’文件依赖于’.o’文件相应源文件和头文件,make则可以在原文间或头文件变化时更新依赖关系文件。如果定义了生成’.d’文件的规则,可以使用’include’指令来读入所有的文件:
sources = foo.c bar.c
include $(sources:.c=.d)
例中使用替换变量来将源文件列表’ foo.c bar.c’转换为依赖关系文件的列表。因为’.d’文件和其它文件一样,不需要更多工作,make会在需要时重新生成它们。




make分中预定义变量表
$* 不包含扩展名的目标文件名称。
$+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件。
$< 第一个依赖文件的名称。
$? 所有的依赖文件,以空格分开,这些依赖文件的修改日期比目标的创建日期晚。
$@ 目标的完整名称。
$^ 所有的依赖文件,以空格分开,不包含重复的依赖文件。
$% 如果目标是归档成员,则该变量表示目标的归档成员名称。例如,如果目标名称
为 mytarget.so(image.o),则 @为mytarget.so,而% 为 image.o。
AR 归档维护程序的名称,默认值为 ar。
ARFLAGS 归档维护程序的选项。
AS 汇编程序的名称,默认值为 as。
ASFLAGS 汇编程序的选项。
CC C 编译器的名称,默认值为 cc。
CCFLAGS C 编译器的选项。
CPP C 预编译器的名称,默认值为 $(CC) -E。
CPPFLAGS C 预编译的选项。
CXX C++ 编译器的名称,默认值为 g++。
CXXFLAGS C++ 编译器的选项。
FC FORTRAN 编译器的名称,默认值为 f77。
FFLAGS FORTRAN 编译器的选项。

常见赋值操作的含义
= 是最基本的赋值
:= 是覆盖之前的值
?= 是如果没有被赋值过就赋予等号后面的值
+= 是添加等号后面的值

include、-include、sinclude的区别
如果指示符“include”指定的文件不是以斜线开始(绝对路径,如/usr/src/Makefile...),而且当前目录下也不存在此文件;make将根据文件名试图在以下几个目录下查找:首先,查找使用命令行选项“-I”或者“--include-dir”指定的目录,如果找到指定的文件,则使用这个文件;否则继续依此搜索以下几个目录(如果其存在):“/usr/gnu/include”、“/usr/local/include”和“/usr/include”。

当在这些目录下都没有找到“include”指定的文件时,make将会提示一个包含文件未找到的告警提示,但是不会立刻退出。而是继续处理Makefile的后续内容。当完成读取整个Makefile后,make将试图使用规则来创建通过指示符“include”指定的但未找到的文件,当不能创建它时(没有创建这个文件的规则),make将提示致命错误并退出。会输出类似如下错误提示:

Makefile:错误的行数:未找到文件名:提示信息(No such file or directory)

Make:*** No rule to make target ‘’. Stop

通常我们在Makefile中可使用“-include”来代替“include”,来忽略由于包含文件不存在或者无法创建时的错误提示(“-”的意思是告诉make,忽略此操作的错误。make继续执行)。像下边那样:

-include FILENAMES...

使用这种方式时,当所要包含的文件不存在时不会有错误提示、make也不会退出;除此之外,和第一种方式效果相同。以下是这两种方式的比较:

使用“include FILENAMES...”,make程序处理时,如果“FILENAMES”列表中的任何一个文件不能正常读取而且不存在一个创建此文件的规则时make程序将会提示错误并退出。

使用“-include FILENAMES...”的情况是,当所包含的文件不存在或者不存在一个规则去创建它,make程序会继续执行,只有真正由于不能正确完成终极目标的重建时(某些必需的目标无法在当前已读取的makefile文件内容中找到正确的重建规则),才会提示致命错误并退出。

为了和其它的make程序进行兼容。也可以使用“sinclude”来代替“-include”(GNU所支持的方式)。


Category linux