https://seisman.github.io/how-to-write-makefile/invoke.html
https://blog.youkuaiyun.com/livelylittlefish/article/details/6448867
https://www.cnblogs.com/turtle-fly/archive/2013/01/09/2851474.html
https://paul.pub/gtest-and-coverage/
makefile介绍
1..PHONY 表示 clean 是一个“伪目标
2.rm 命令前面加了一个小减号的 意思就是,也许某些文件出现问题,但不要管,继续做后面的事
3.Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
4.还值得一提的是,在Makefile中的命令,必须要以 Tab 键开始
foo.o: foo.c defs.h # foo模块
cc -c -g foo.c # cc前面的要是tab键开头
5.文件名最好使用“Makefile” 这个文件名,因为,这个文件名第一个字符为大写,这样有一种显目的感觉。最好不要用“GNUmakefile”, 这个文件是GNU的make识别的
6.引用其它的Makefile: include 的语法是 include <filename>。在 include 前面可以有一些空字符,但是绝不能是 Tab 键开始
### DO NOT EDIT THIS FILE ###
include build/core/main.mk
### DO NOT EDIT THIS FILE ###
7.include foo.make *.mk $(bar) 等价于 include foo.make a.mk b.mk c.mk e.mk f.mk
8.如果make执行时,有 -I 或 --include-dir 参数,那么make就会在这个参数所指定的目 录下去寻找
9.环境变量MAKEFILES,建议不要使用这个环境变量,该定义会影响所有的makefile。这个变量中的值是其它的Makefile,用空格分隔。从这个环境变量中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现 错误,make也会不理。
10.make的工作方式
a.读入所有的Makefile。
b.读入被include的其它Makefile。
c.初始化文件中的变量。
d.推导隐晦规则,并分析所有规则。
e.为所有的目标文件创建依赖关系链。
f.根据依赖关系,决定哪些目标要重新生成。
g.执行生成命令
书写规则
11.Makefile中的目标 可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个,那么 ,第一个目标会成为最终的目标。make所完成的也就是这个目标
12.make支持三个通配符: * , ? 和 ~。~:表示当前用户 的 $HOME 目录下的test目录。
13.文件搜寻,特殊变量 VPATH,如果定义了这个变量,那么,make就会在当前目录找不到的情况下 ,到所指定的目录中去找寻文件了。目录由“冒号”分隔 。
VPATH = src:../headers #src目录的父目录下的headers目录
14.文件搜索路径使用make的“vpath”关键字。它可以指定不同的文件在不 同的搜索目录中。
vpath <pattern> <directories>
为符合模式<pattern>的文件指定搜索目录<directories>。<pattern>需要包含 % 字符。 % 的意思是匹配零或若干字符。
vpath <pattern>
清除符合模式<pattern>的文件的搜索目录。
vpath
清除所有已被设置好了的文件搜索目录
15.伪目标。“伪目标”并不是一个文件,只是一个标签。通过显式地指明这个“目标”才能让其生效。
.PHONY : clean
clean :
rm *.o temp
16.自动生成依赖性gcc -MM main.c。GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一 个 name.c 的文件都生成一个 name.d 的Makefile文件, .d 文件中就存放对应 .c 文件的依赖关系
17.一个模式规则来产生 .d 文件
%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
18.include $(sources:.c=.d)#$(sources:.c=.d) 中的 .c=.d 的意思是做一个替换,把变量 $(sources) 所有 .c 的字串都替换成 .d
书写命令
19.make会一按顺序一条一条的执行命令,每条命令的 开头必须以 Tab 键开头,除非,命令是紧跟在依赖规则后面的分号后的
20.显示命令。@echo 正在编译XXX模块......
21.如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令,不能把这两条命令写在两行上
exec:
cd /home/hchen; pwd
exec:
cd /home/hchen
pwd
22.命令出错。可以在Makefile的命令行前加一个减号 - (在Tab键之后) ,标记为不管命令出不出错都认为是成功的
clean:
-rm -f *.o
23.嵌套执行make。
subsystem:
$(MAKE) -C subdir
24.传递变量到下级Makefile中export <variable ...>;
export variable = value
export variable := value
variable = value
export variable
25.定义命令包。一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令 序列的语法以 define 开始,以 endef 结束
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
foo.c : foo.y
$(run-yacc)
使用变量
26.变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有 : 、 # 、 = 或是空字符(空格、回车等)。变量是大小写敏感的
27.变量在声明时需要给予初值,而在使用时,需要给在变量名前加上 $ 符号,但最好用小括号 () 或是大括号 {} 把变量给包括起来
28.一种用变量来定义变量的方法
x := foo
y := $(x) bar
x := later
其等价于:
y := foo bar
x := later
29.定义一个变量,其值是一个空格.因为在操作符的右边 是很难描述一个空格的,这里采用的技术很管用,先用一个Empty变量来标明变量的值开始了,而后面采 用“#”注释符来表示变量定义的终止,这样,我们可以定义出其值是一个空格的
变量
nullstring :=
space := $(nullstring) # end of the line
dir := /foo/bar # dir这个变量的值是“/foo/bar”,后面还跟了4个空格
30.操作符?=.其含义是,如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语将 什么也不做
FOO ?= bar
其等价于:
ifeq ($(origin FOO), undefined)
FOO = bar
endif
31.替换变量中的共有的部分:其格式一是 $(var:a=b) 或是 ${var:a=b}.格式二是依赖于被替换字串中的有相同的模式,模式中必须包含一个 % 字符
foo := a.o b.o c.o
bar := $(foo:.o=.c)
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
32.把变量的值再当成变量
x = y
y = z
a := $($(x))
33.把变量的值再当成变量”这种技术,同样可以用在操作符的左边
dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_print%.o : CFLAGS = -O
lpr $($(dir)_sources)
endef
34.追加变量值 +=
objects = main.o foo.o bar.o utils.o
objects += another.o
35.override 指示符.如果有变量是通常make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想 在Makefile中设置这类参数的值,那么,你可以使用“override”指示符
override <variable>; = <value>;
override <variable>; := <value>;
override <variable>; += <more text>;
36.模式变量.变量可以定义在某个目标上。模式变量的1.好处就是,我们可以给定一种“模式”,可以把变量定义在符 合这种模式的所有目标上
%.o : CFLAGS = -O
条件判断
37.使用条件判断.三个关键字: ifeq 、 else 和 endif 。 ifeq 的 意思表示条件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,表达式以圆括号括 起。 else 表示条件表达式为假的情况。 endif 表示一个条件语句的结束,任何一个条件表达 式都应该以 endif 结束。
bjects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
38.第四个条件关键字是 ifndef 。其语法是:ifndef <variable-name>
39.最好不要把自动化变量(如 $@ 等)放入条件表达式中,因为自动化变量是在运行时才有的
40.make不允许把整个条件语句分成两部分放在不同的文件中
函数
41.函数调用,很像变量的使用,也是以 $ 来标识的,其语法如下:$(<function> <arguments>)或是:${<function> <arguments>}
comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
42.字符串处理函数subst $(subst <from>,<to>,<text>).
名称:字符串替换函数
功能:把字串 <text> 中的 <from> 字符串替换成 <to> 。
返回:函数返回被替换过后的字符串。
示例:$(subst ee,EE,feet on the street)
把 feet on the street 中的 ee 替换成 EE ,返回结果是 fEEt on the strEEt
43.字符串处理函数patsubst $(patsubst <pattern>,<replacement>,<text>)
名称:模式字符串替换函数
示例:$(patsubst %.c,%.o,x.c.c bar.c)
返回结果是 x.c.o bar.o
44.字符串处理函数strip $(strip <string>)
名称:去空格函数。
示例:$(strip a b c )
返回结果是 把字串 a b c `` 去到开头和结尾的空格,结果是 ``a b c 。
45.findstring $(findstring <find>,<in>)
名称:查找字符串函数。在字串 <in> 中查找 <find> 字串。
示例:$(findstring a,a b c) $(findstring a,b c)
第一个函数返回 a 字符串,第二个返回空字符串
46.filter $(filter <pattern...>,<text>)
名称:过滤函数。以 <pattern> 模式过滤 <text> 字符串中的单词,保留符合模式 <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
47.filter-out $(filter-out <pattern...>,<text>)
名称:反过滤函数。以 <pattern> 模式过滤 <text> 字符串中的单词,去除符合模式 <pattern> 的单词。可以有多个模式
示例:
objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
$(filter-out $(mains),$(objects)) 返回值是 foo.o bar.o
48.sort $(sort <list>)
名称:排序函数。给字符串 <list> 中的单词排序(升序)。sort函数会去掉 <list> 中相同的单词。
示例: $(sort foo bar lose) 返回 bar foo lose
49.word $(word <n>,<text>)
名称:取单词函数。取字符串 <text> 中第 <n> 个单词(从一开始)。如果 <n> 比 <text> 中的 单词数要大,那么返回空字符串
示例: $(word 2, foo bar baz) 返回值是 bar
50.wordlist $(wordlist <ss>,<e>,<text>)
名称:取单词串函数。从字符串 <text> 中取从 <ss> 开始到 <e> 的单词串。 <ss> 和 <e> 是一个数字。返回字符串 <text> 中从 <ss> 到 <e> 的单词字串。
如果 <ss> 比 <text> 中的单词数要大,那么返回空字符串。如果 <e> 大于 <text> 的单词数, 那么返回从 <ss> 开始,到 <text> 结束的单词串。
示例: $(wordlist 2, 3, foo bar baz) 返回值是 bar baz
51.words $(words <text>)
名称:单词个数统计函数。统计 <text> 中字符串中的单词个数
示例: $(words, foo bar baz) 返回值是 3
如果我们要取 <text> 中最后的一个单词》 $(word $(words <text>),<text>)
52.firstword $(firstword <text>)
名称:首单词函数——firstword。
示例: $(firstword foo bar) 返回值是 foo。等价于 $(word,1,<text>)
文件名操作函数
53.dir $(dir <names...>)
名称:取目录函数——dir.从文件名序列 <names> 中取出目录部分。目录部分是指最后一个反斜杠( / )之前 的部分。如果没有反斜杠,那么返回 ./
示例: $(dir src/foo.c hacks) 返回值是 src/ ./
54.notdir $(notdir <names...>)
名称:取文件函数——notdir。从文件名序列 <names> 中取出非目录部分。非目录部分是指最後一个反斜杠( / ) 之后的部分
示例: $(notdir src/foo.c hacks) 返回值是 foo.c hacks
55.suffix $(suffix <names...>)
名称:取后缀函数——suffix。从文件名序列 <names> 中取出各个文件名的后缀
示例: $(suffix src/foo.c src-1.0/bar.c hacks) 返回值是 .c .c
56.basename $(basename <names...>)
名称:取前缀函数——basename
示例: $(basename src/foo.c src-1.0/bar.c hacks) 返回值是 src/foo src-1.0/bar hacks
57.addsuffix $(addsuffix <suffix>,<names...>)
名称:加后缀函数——addsuffix。把后缀 <suffix> 加到 <names> 中的每个单词后面。
示例: $(addsuffix .c,foo bar) 返回值是 foo.c bar.c 。
58.addprefix $(addprefix <prefix>,<names...>)
名称:加前缀函数——addprefix。把前缀 <prefix> 加到 <names> 中的每个单词后面
示例: $(addprefix src/,foo bar) 返回值是 src/foo src/bar
59.join $(join <list1>,<list2>)
名称:连接函数——join。把 <list2> 中的单词对应地加到 <list1> 的单词后面。
示例: $(join aaa bbb , 111 222 333) 返回值是 aaa111 bbb222 333
其他函数
60.foreach函数.是用来做循环用的。$(foreach <var>,<list>,<text>)
名称:循环遍历函数。把参数 <list> 中的单词逐一取出放到参数 <var> 所指定的变量中, 然后再执行 <text> 所包含的表达式。每一次 <text> 会返回一个字符串,循环过程中, <text> 的所返回的每个字符串会以空格分隔,最后当整个循环结束时, <text> 所返回的 每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。
示例:
names := a b c d
files := $(foreach n,$(names),$(n).o)
$(files)的值是 a.o b.o c.o d.o
61.if 函数。$(if <condition>,<then-part>) 或 $(if <condition>,<then-part>,<else-part>)
示例:$(if $(wildcard $(BUILD_DEST)),,mkdir -p $(BUILD_DEST))
62.call函数。唯一一个可以用来创建新的参数化的函数 $(call <expression>,<parm1>,<parm2>,...,<parmn>)
名称:当make执行这个函数时, <expression> 参数中的变量,如 $(1) 、 $(2) 等,会 被参数 <parm1> 、 <parm2> 、 <parm3> 依次取代。而 <expression> 的 返回值就是 call 函数的返回值。参数的次序是可以自定义的,不一定是顺序的。在向 call 函数传递参数时要尤其注意空格的使用。call 函数在处理参数时,第2个及其之后的 参数中的空格会被保留。因而在向call函数提供参数时,最安全的做法是 去除所有多余的空格。
示例:
reverse = $(2) $(1)
foo = $(call reverse,a,b)
此时的 foo 的值就是 b a
63.origin函数。告诉你你的这个变量是哪里来的。$(origin <variable>)
名称:<variable> 是变量的名字,不应该是引用。所以你最好不要在<variable> 中使用$字符。origin返回值:undefined-var从来没有定义;default-var是一个默认的定义;
environment-var是环境变量;file-var被定义在Makefile中;command line-var被命令行定义;override-var被override指示符重新定义;automatic-var是命令运行中的自动化变量。
示例:
ifdef bletch
ifeq "$(origin bletch)" "environment"
bletch = barf, gag, etc.
endif
endif
64.shell函数。参数应该就是操作系统Shell的命令。它和反引号“`”是 相同的功能
名称:shell函数把执行操作系统命令后的输出作为函数返回。我们可以用操作 系统命令以及字符串处理命令awk,sed等等命令来生成一个变量。这个函数会新生成一个Shell程序来执行命令,所以你要注意其运行性能
示例:
contents := $(shell cat foo)
files := $(shell echo *.c)
65.控制make的函数。$(error <text ...>)。检测一些运行Makefile时的运行时信息,并且 根据这些信息来决定,你是让make继续执行,还是停止
名称:产生一个致命的错误, <text ...> 是错误信息。注意,error函数不会在一被使用就会产生错误 信息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么也是可以的
示例:
ifdef ERROR_001
$(error error is $(ERROR_001))
endif
make 的运行
66.make的退出码。0-表示成功执行;1-如果make运行时出现任何错误,其返回1;2-如果你使用了make的“-q”选项,并且make使得一些目标不需要更新,那么返回2
67.指定Makefile。make –f hchen.mk
隐含规则
68.隐含规则一览
编译C程序的隐含规则。
<n>.o 的目标的依赖目标会自动推导为 <n>.c ,并且其生成命令是 $(CC) –c $(CPPFLAGS) $(CFLAGS)
编译C++程序的隐含规则。
<n>.o 的目标的依赖目标会自动推导为 <n>.cc 或是 <n>.C ,并且其生成命令是 $(CXX) –c $(CPPFLAGS) $(CFLAGS) 。(建议使用 .cc 作为C++源文件的后缀,而不是 .C )
链接Object文件的隐含规则。
<n> 目标依赖于 <n>.o ,通过运行C的编译器来运行链接程序生成(一般是 ld ), 其生成命令是: $(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS) 。这个规则对于 只有一个源文件的工程有效,同时也对多个Object文件(由不同的源文件生成)的也有效。例如如下规则:
69.隐含规则使用的变量
关于命令的变量:
CC : C语言编译程序。默认命令是 cc
CXX : C++语言编译程序。默认命令是 g++
关于命令参数的变量:
CFLAGS : C语言编译器参数
CXXFLAGS : C++语言编译器参数。
70.隐含规则链。一个目标可能被一系列的隐含规则所作用