Makefile语法介绍

发布于:2025-07-03 ⋅ 阅读:(21) ⋅ 点赞:(0)

在这里插入图片描述

系列文章目录




介绍

target ... : prerequisites ... 
    command 
    ... 
    ...
  • target 也就是一个目标文件,可以是 Object File,也可以是执行文件。还可以是
    一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。
  • prerequisites 就是,要生成那个 target 所需要的文件或是目标。
  • command 也就是 make 需要执行的命令。(任意的 Shell 命令)

Makefile 里有什么

    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 键开始。

Makefile 的文件名

    可以为:“Makefile”、“makefile”、“GNUmakefile” 。 推荐第一个。

指定特定的 Makefile,使用 make 的 “-f” 和 “–file” 参数。

make -f Make.Linux 

# 或 

make --file Make.AIX。

引用其它的 Makefile

include foo.make *.mk $(bar) 

# 等价于:

include foo.make a.mk b.mk c.mk e.mk f.mk

    make 命令开始时,会把找寻 include 所指出的其它 Makefile,并把其内容安置在当前的位置。如果文件都没有指定绝对路径或是相对路径的话,make 会在当前目录下首先寻找,如果当前目录下没有找到,那么,make 还会在下面的几个目录下找:

  1. 如果 make 执行时,有 “-I”“–include-dir” 参数,那么 make 就会在这个参数所指定的目录下去寻找。
  2. 如果目录 <prefix>/include(一般是:/usr/local/bin 或 /usr/include)存在的话,make 也会去找。
-include <filename> 

    其表示,无论 include 过程中出现什么错误,都不要报错继续执行。和其它版本 make 兼容的相关命令是 sinclude,其作用和这一个是一样的。

make 的工作方式

GNU 的 make 工作时的执行步骤入下:(想来其它的 make 也是类似)

  1. 读入所有的 Makefile。
  2. 读入被 include 的其它 Makefile。
  3. 初始化文件中的变量。
  4. 推导隐晦规则,并分析所有规则。
  5. 为所有的目标文件创建依赖关系链。
  6. 根据依赖关系,决定哪些目标要重新生成。
  7. 执行生成命令。

书写规则

规则的语法

targets : prerequisites 
    command 
    ... 
 
 # 或是这样: 

targets : prerequisites ; command 
    command 
    ...

    targets 是文件名,以空格分开,可以使用通配符。一般来说,我们的目标基本上是一个文件,但也有可能是多个文件。

    command 是命令行,如果其不与 “target:prerequisites” 在一行,那么,必须以 Tab 开头,如果和 prerequisites 在一行,那么可以用分号做为分隔。(见上)

    prerequisites 也就是目标所依赖的文件(或依赖目标)。如果其中的某个文件要比目标文件要新,那么,目标就被认为是 “过时的” ,被认为是需要重生成的。这个在前面已经讲过了。

    如果命令太长,你可以使用反斜框(‘\’)作为换行符。make 对一行上有多少个字符没有限制。规则告诉 make 两件事,文件的依赖关系和如何成成目标文件。

伪目标

PHONY: clean 
clean: 
    rm *.o temp

    “伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以 make 无法生成它的依赖关系和决定它是否要执行。

多目标

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

静态模式

# 语法
<targets ...>: <target-pattern>: <prereq-patterns ...> 
    <commands> 
    ...
  • targets 定义了一系列的目标文件,可以有通配符。是目标的一个集合。
  • target-parrtern 是指明了 targets 的模式,也就是的目标集模式。
  • prereq-parrterns 是目标的依赖模式,它对 target-parrtern 形成的模式再进行一次依赖目标的定义。
objects = foo.o bar.o 
all: $(objects) 
$(objects): %.o: %.c 
    $(CC) -c $(CFLAGS) $< -o $@

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

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)) 表示调用 Makefile 的 filter 函数,只要其中模式为“%.o”的内容。

书写命令

显示命令

@echo 正在编译 XXX 模块...... 

    当 make 执行时,会输出 “正在编译 XXX 模块…” 字串,但不会输出命令,如果没有 “@” ,那么,make 将输出:

echo 正在编译 XXX 模块…
正在编译 XXX 模块…

    如果 make 执行时,带入 make 参数 “-n”“--just-print” ,那么其只是显示命令,但不会执行命令,这个功能很有利于我们调试我们的 Makefile,看看我们书写的命令是执行起来是什么样子的或是什么顺序的。

   而 make 参数 “-s”“--slient” 则是全面禁止命令的显示。

命令执行

   需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比如你的第一条命令是 cd 命令,你希望第二条命令得在 cd 之后的基础上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。

# 示例一:
exec: 
    cd /home/hchen 
    pwd

 # 示例二:
exec: 
    cd /home/hchen; pwd

    当我们执行 “make exec” 时,第一个例子中的 cd 没有作用,pwd 会打印出当前的 Makefile 目录,而第二个例子中,cd 就起作用了,pwd 会打印出 “/home/hchen” 。

命令出错

如果一个规则中的某个命令出错了(命令退出码非零),那么 make 就会终止执行当前规则,这将有可能终止所有规则的执行。如果想忽略命令的出错,我们可以在 Makefile 的命令行前加一个减号 “-”(在Tab 键之后),标记为不管命令出不出错都认为是成功的。如:

clean: 
    -rm -f *.o

    还有一个全局的办法是,给 make 加上 “-i” 或是 “--ignore-errors” 参数,那么, Makefile 中所有命令都会忽略错误。

    还有一个要提一下的 make 的参数的是 “-k” 或是 “--keep-going”,这个参数的意思是,如果某规则中的命令出错了,那么就终目该规则的执行,但继续执行其它规则。

嵌套执行 make

我们有一个子目录叫 subdir,这个目录下有个 Makefile 文件,来指明了这个目录下文件的编译规则。那么我们总控的 Makefile 可以这样书写:

subsystem: 
    cd subdir && $(MAKE)

# 其等价于:

subsystem: 
    $(MAKE) -C subdir    

    定义 $(MAKE) 宏变量的意思是,也许我们的 make 需要一些参数,所以定义成一个变量比较利于维护。这两个例子的意思都是先进入 “subdir” 目录,然后执行 make 命令。

    我们把这个 Makefile 叫做 “总控 Makefile” ,总控 Makefile 的变量可以传递到下级的 Makefile 中(如果你显示的声明),但是不会覆盖下层的 Makefile 中所定义的变量,除非指定了 “-e” 参数。

  • 如果你要传递变量到下级 Makefile 中,那么你可以使用这样的声明:
export <variable ...>    
  • 如果你不想让某些变量传递到下级 Makefile 中,那么你可以这样声明:
unexport <variable ...>   

如:

export variable = value
# 其等价于:
# variable = value  
# export variable
export variable := value
export variable += value

   如果你要传递所有的变量,那么,只要一个 export 就行了。后面什么也不用跟,表示传递所有的变量。

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

   如果你不想往下层传递参数,那么,你可以这样来:

subsystem: 
    cd subdir && $(MAKE) MAKEFLAGS=

定义命令包

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

define run-yacc 
    yacc $(firstword $^) 
    mv y.tab.c $@ 
endef

foo.c : foo.y 
    $(run-yacc)

使用变量

定义变量

  1. = ,递归扩展赋值
# 定义变量
objects = program.o foo.o utils.o
  1. := ,立即扩展赋值
# 变量是可以使用后面的变量来定义
foo = $(bar) 
bar = $(ugh) 
ugh = Huh?

# 前面的变量不能使用后面的变量
x := foo 
y := $(x) bar 
x := later
# y ==> foo bar ; x ==> later 
  1. ?= ,条件赋值
# 如果 FOO 没有被定义过,那么变量 FOO 的值就是 “bar” ,
# 如果 FOO 先前被定义过,那么这条语将什么也不做。
FOO ?= bar
  1. += ,追加赋值
# 追加变量值
objects = main.o foo.o bar.o utils.o 
objects += another.o
# $(objects) ==> “main.o foo.o bar.o utils.o another.o”

变量高级用法

  1. 变量值的替换
$(var:a=b)
# 或
${var:a=b}

# 把变量 “var” 中所有以 “a” 字串 “结尾” 的 “a” 替换成 “b” 字串。

foo := a.o b.o c.o 
bar := $(foo:.o=.c)
# bar ==> a.c b.c c.c

# 静态模式
foo := a.o b.o c.o 
bar := $(foo:%.o=%.c)
# bar ==> a.c b.c c.c
  1. 把变量的值再当成变量
x = y 
y = z 
a := $($(x))

# 推导 a := $($(x)) ==> a := $(y) ==> a := z

override 阻止命令行覆盖

override <变量名> [= | := | ?= | +=] <>

override CC = gcc

多行变量

定义变量包

目标变量

语法:

<target ...> : <variable-assignment> 
<target ...> : overide <variable-assignment>
# <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

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

模式变量

语法:

<pattern ...> : <variable-assignment>
<pattern ...> : override <variable-assignment>
# <variable-assignment> 可以是各种赋值表达式,如 =, :=, +=, ?=

示例:

%.o : CFLAGS = -O

    给所有以 [.o] 结尾的目标定义目标变量。

使用条件判断

  1. 语法
<conditional-directive> 
    <text-if-true> 
endif 

# 以及:

<conditional-directive> 
    <text-if-true> 
else 
    <text-if-false> 
endif

其中 <conditional-directive> 表示条件关键字,可以为 ifeq, ifneq, ifdef, ifndef

  • ifeq
ifeq (<arg1>, <arg2>) 
ifeq '<arg1>' '<arg2>' 
ifeq "<arg1>" "<arg2>" 
ifeq "<arg1>" '<arg2>' 
ifeq '<arg1>' "<arg2>"

ifeq ($(strip $(foo)),) 
	<text-if-empty> 
endif
  • ifneq
ifneq (<arg1>, <arg2>) 
ifneq '<arg1>' '<arg2>' 
ifneq "<arg1>" "<arg2>" 
ifneq "<arg1>" '<arg2>' 
ifneq '<arg1>' "<arg2>"
  • ifdef
ifdef <variable-name>
  • ifndef
ifndef <variable-name>

使用函数

函数的调用语法

$(<function> <arguments>) 

# 或是

${<function> <arguments>}

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

示例:

comma:= , 
empty:= 
space:= $(empty) $(empty) 
foo:= a b c 
bar:= $(subst $(space),$(comma),$(foo))

    在这个示例中,$(comma) 的值是一个逗号。$(space) 使用了 $(empty) 定义了一个空格,$(foo) 的值是 “a b c” ,$(bar) 调用了函数 “subst”,这是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作作用的字串。这个函数也就是把 $(foo) 中的空格替换成逗号,所以 $(bar) 的值是 “a,b,c” 。

字符串处理函数

subst

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

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

patsubst

$(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

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

# 示例:
$(strip a b c ) 
# 把字串 “a b c ” 去掉开头和结尾的空格,结果是 “a b c” 。

findstring

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

# 示例:
$(findstring a,a b c) 
$(findstring a,b c) 
# 第一个函数返回 “a” 字符串,第二个返回 “” 字符串(空字符串)

filter

$(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.c bar.c baz.s”。

filter-out

$(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

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

word

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

wordlist

$(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) 返回值是 “bar baz”。

words

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

firstword

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

文件名操作函数

dir

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

notdir

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

suffix

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

basename

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

addsuffix

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

addprefix

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

join

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

foreach 函数

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

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

示例:

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 函数

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

# 或是
 
$(if <condition>,<then-part>,<else-part>)

call 函数

call 函数是唯一一个可以用来创建新的参数化的函数。

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

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

示例:

reverse = $(2) $(1)
foo = $(call reverse,a,b)
# foo 的值就是 “b a” 

origin 函数

origin 函数只是告诉你你的这个变量是哪里来的。

$(origin <variable>) 

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

  • “undefined”
    如果 <variable> 从来没有定义过,origin 函数返回这个值 “undefined” 。
  • “default”
    如果 <variable> 是一个默认的定义,比如 “CC” 这个变量,这种变量我们将在后面讲述。
  • “environment”
    如果 <variable> 是一个环境变量,并且当 Makefile 被执行时,“-e” 参数没有被打开。
  • “file”
    如果 <variable> 这个变量被定义在 Makefile 中。
  • “command line”
    如果 <variable> 这个变量是被命令行定义的。
  • “override”
    如果 <variable> 是被 override 指示符重新定义的。
  • “automatic”
    如果 <variable> 是一个命令运行中的自动化变量。关于自动化变量将在后面讲述。

示例:

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

如果 bletch 变量来源于环境,那么我们就把之重定义。

shell 函数

   shell 函数把执行操作系统命令后的输出作为函数返回。于是,我们可以用操作系统命令以及字符串处理命令 awk,sed 等等命令来生成一个变量,如:

contents := $(shell cat foo) 
files := $(shell echo *.c) 

控制 make 的函数

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

$(error <text ...>)

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

# 示例一:
ifdef ERROR_001 
	$(error error is $(ERROR_001)) 
endif 

#示例二:
ERR = $(error found an error!) 
.PHONY: err 
err: ; $(ERR) 

    示例一会在变量 ERROR_001 定义了后执行时产生 error 调用,而示例二则在目录 err 被执行时才发生 error 调用。


$(warning <text ...>) 

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

make 的运行

make 的退出码

make 命令执行后有三个退出码:

  • 0 —— 表示成功执行。
  • 1 —— 如果 make 运行时出现任何错误,其返回 1。
  • 2 —— 如果你使用了 make 的“-q”选项,并且 make 使得一些目标不需要更新,那么返回 2。

检查规则

    有时候,我们不想让我们的 makefile 中的规则执行起来,我们只想检查一下我们的命令,或是执行的序列。于是我们可以使用 make 命令的下述参数:

 “-n”
 “--just-print”
 “--dry-run”
 “--recon”

    不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行,这些参数对于我们调试 makefile 很有用处。

 “-t”
 “--touch”

    这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make 假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状态。


“-q”
“--question”

    这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印出一条出错信息。

“-W <file>”
“--what-if=<file>”
“--assume-new=<file>”
“--new-file=<file>

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

隐含规则

隐含变量

代表命令的变量

  • AR
    函数库打包程序,可创建静态库.a文档。默认是“ar”。
  • AS
    汇编程序。默认是“as”。
  • CC
    C编译程序。默认是“cc”。
  • CXX
    C++编译程序。默认是“g++”。
  • CPP
    C程序的预处理器(输出是标准输出设备)。默认是“$(CC) -E”。
  • YACC
    Yacc文法分析器(针对于C程序)。默认命令是“yacc”。
  • RM
    删除命令。默认是“rm -f”

命令参数的变量

    下边的是代表命令执行参数的变量。如果没有给出默认值则默认值为空。

  • ARFLAGS
    执行“AR”命令的命令行参数。默认值是 “rv” 。
  • ASFLAGS
    执行汇编语器“AS”的命令行参数(明确指定 “.s” 或 “.S” 文件时)
  • CFLAGS
    执行 “CC” 编译器的命令行参数(编译 .c 源文件的选项)。
  • CXXFLAGS
    执行 “g++” 编译器的命令行参数(编译 .cc 源文件的选项)。
  • CPPFLAGS
    执行C预处理器 “cc -E” 的命令行参数(C 和 Fortran 编译器会用到)。
  • LDFLAGS
    链接器(如:“ld”)参数。
  • YFLAGS
    Yacc文法分析器参数。

模式规则

模式规则示例

   下面这个例子表示了,把所有的 [.c] 文件都编译成 [.o] 文件。

%.o : %.c 
    $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ 

    其中,“$@” 表示所有的目标的挨个值,“$<” 表示了所有依赖目标的挨个值。这些奇怪的变量我们叫 “自动化变量” 。

自动化变量

  • $@
    表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,“$@”就是匹配于目标中模式定义的集合。
  • $%
    仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是 “foo.a(bar.o)”,那么,“$%” 就是 “bar.o” ,“$@” 就是 “foo.a” 。如果目标不是函数库文件(Unix 下是 [.a] ,Windows 下是 [.lib] ),那么,其值为空。
  • $<
    依赖目标中的第一个目标名字。如果依赖目标是以模式(即 “%” )定义的,那么 “$<” 将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
  • $?
    所有比目标新的依赖目标的集合。以空格分隔。
  • $^
    所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
  • $+
    这个变量很像 “$^”,也是所有依赖目标的集合。只是它不去除重复的依赖目标。
  • $*
    这个变量表示目标模式中 “%” 及其之前的部分。如果目标是 “dir/a.foo.b” ,并且目标的模式是 “a.%.b” ,那么,“$*” 的值就是 “dir/a.foo” 。这个变量对于构造有关联的文件名是比较有较。
       
    • 如果目标中没有模式的定义,那么 “$*” 也就不能被推导出,但是,如果目标文件的后缀是 make 所识别的,那么 “$*” 就是除了后缀的那一部分。例如:如果目标是 “foo.c” ,因为 “.c” 是 make 所能识别的后缀名,所以,“$*” 的值就是 “foo” 。这个特性是 GNU make 的,很有可能不兼容于其它版本的 make,所以,你应该尽量避免使用 “$*” ,除非是在隐含规则或是静态模式中。
    • 如果目标中的后缀是 make 所不能识别的,那么 “$*” 就是空值。

重建内嵌隐含规则

    一个隐含规则,我们可以对它进行重建。重建一个隐含规则时,需要使用相同的目标和依赖模式,命令可以不同(重新指定规则的命令)。这样就可以替代有相同目标和依赖的那个 make 内嵌规则,替代之后隐含规则可能被使用的顺序由它在Makefile中的位置决定。例如通常 Makefile 中可能会包含这样一个规则:

%.o : %.c 
    $(CC) $(CFLAGS) –D__DEBUG__ $< -o $@

   它替代了编译.c文件的内嵌隐含规则。

   也可以取消一个内嵌的隐含规则。同样需要定义一个和隐含规则具有相同目标和依赖的规则,但这个规则没有命令行。例如下边的这个规则取消了编译 .s 文件的内嵌规则。

 %.o : %.s