Makefile 详解

Makefile 原理

  • make 命令会在当前目录下找名字叫 Makefilemakefile 的文件。
  • 如果找到,它会找文件中的第一个目标文件(target),并把这个文件作为最终的目标文件。
  • 如果这个目标文件不存在,或是它所依赖的后面的 .o 文件的文件修改时间要比这个文件新,那么,他就会执行后面所定义的命令来生成这个文件。
  • 如果这个文件所依赖的 .o 文件也不存在,那么 make 会在当前文件中找目标为 .o 文件的依赖性,如果找到则再根据那一个规则生成 .o 文件。
  • 当然,你的 .c 文件和 .h 文件是存在的,于是 make 会生成 .o 文件,然后再用 .o 文件生成 make 的终极任务,也就是可执行文件了。

清空目标文件的规则

.PHONY : clean
clean :
​   rm $(objects)

.PHONY 是伪目标的意思

make 的工作方式

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

变量

变量在声明时需要给予初值,而在使用时,需要给在变量名前加上 $ 符号,但最好用小括号 () 或是大括号 {} 把变量给包括起来。如果你要使用真实的 $ 字符,那么你需要用 $$ 来表示。

Makefile 变量的赋值

立即变量:定义的时候就已经确定了该变量的值。( :=+=

延时变量:使用该变量的时候,才展开该变量,并确定该变量的值。( =?=define )

符号 含义 变量类型
= 递归赋值,将整个 Makefile 文件展开之后,再决定变量的值 延时变量
:= 直接为变量进行赋值 立即变量
+= 在变量后面追加值 立即变量
?= 若前面没有定义该变量,则此处赋值,如果前面已经定义了,则此处不再赋值 延时变量

自动化变量

  • $@:表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,$@ 就是匹配于目标中模式定义的集合。
  • $%:仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是foo.a(bar.o),那么,$%就是bar.o$@就是foo.a。如果目标不是函数库文件(Unix 下是.a,Windows 下是.lib),那么,其值为空。
  • $<:依赖目标中的第一个目标名字。如果依赖目标是以模式(即%)定义的,那么$<将是符合模式的一系列的文件集。注意,其是一个一个取出来的。(就是依赖中的第一个)
  • $?:所有比目标新的依赖目标的集合。以空格分隔。
  • $^:所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
  • $+:这个变量很像$^,也是所有依赖目标的集合。只是它不去除重复的依赖目标。

变量值替换

格式: $(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)

Makefile 关键字

override

如果一个变量的值需要在编译选项中指定或由系统传入,那么 makefile 中可以使用 override 关键字来设置,使这个变量的赋值被忽略

define

使用 define 关键字可以定义多行变量

define two-lines
    echo foo
    echo $(bar)
endef

wildcard

作用是让通配符 (Makefile 中的通配符就是 * , % 算 pattern,不是通配符)在变量或函数中展开,通常用于提取指定目录的某一类型文件。因为在 Makefile 的规则中,函数中的通配符是不会被展开的。

all:$(subst .c, .o, $(wildcard *.c))
%.o:%.c
    gcc -o $@ $<

export

将变量导出,以便于所有的子 makefile 都可以使用

include

和 C 语言的 #include 一样,将后面的文件展开到当前位置

Makefile 函数

字符串替换函数 subst

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

属性 解释
名称 字符串替换函数 subst
功能 把字串 <text> 中的字符串 <from> 替换成 <to>
返回 函数返回被替换过后的字符串

示例:
$(subst ee, EE, feet on the street)

结果值是 fEEt on the strEEt

模式字符串替换函数 patsubst

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

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

示例:

$(patsubst *.c,*.o,foo.c bar.c)

结果值是 foo.o bar.o

查找字符串函数 findstring

$(findstring <find>, <in>)

属性 解释
名称 查找字符串函数 findstring
功能 在字串 <in> 中查找字串 <find>
返回 如果找到,那么返回,否则返回空字符串

示例:

$(findstring a,a b c) 
$(findstring a,b c) 

第一个函数返回 a 字符串
第二个返回空字符串

过滤函数 filter

$(filter <patterns>, <text>)

属性 解释
名称 过滤函数 filter
功能 以模式 <patterns> 过滤字符串 <text> 中的单词,保留符合模式 <patterns> 的单词。可以有多个模式
返回 返回符合模式 <patterns> 的字串

示例:

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

去空格函数 strip

$(strip <string>)

属性 解释
名称 去空格函数 strip
功能 去掉字符串 <string> 开头和结尾的空字符
返回 返回被去掉空格的字符串值

示例:

$(strip a b c )

结果值是 a b c

排序函数 sort

$(sort <list>)

属性 解释
名称 排序函数 sort
功能 给字符串 <list> 中的单词排序(升序)
返回 返回排序后的字符串

示例:

$(sort foo bar lose)

结果值是 bar foo lose

备注:sort 函数会去掉其中重复的单词

取单词函数 word

$(word <n>, <text>)

属性 解释
名称 取单词函数 word
功能 取字符串 <text> 中第 <n> 个单词(从 1 开始)
返回 返回:返回字符串 <text> 中第 <n> 个单词。如果 n 比其中的单词数要大,返回空字符串。

示例:

$(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> 开始,到结束的单词串

示例:

$(wordlist 2, 3, foo bar baz)

返回值是 bar baz

单词个数统计函数 words

$(words <text>)

属性 解释
名称 单词个数统计函数 words
功能 统计 <text> 中字符串中的单词个数
返回 返回 <text> 中的单词数

示例:

$(words,foo bar baz)

返回值是 3

备注:如果我们要取中最后的一个单词,我们可以这样: $(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> 多出来的单词将被复制到 <list1>
返回 返回连接过后的字符串

示例:

$(join aaa bbb,111 222 333)

返回值是 aaa111 bbb222 333

遍历函数 foreach

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

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

示例:

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

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


转载规则

《Makefile 详解》Konata 采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。
  目录