Makefile知识点
优点
1、简化手动输入gcc编译指令
2、多线程并发编译,缩短编译时间
规则
包括依赖的关系和执行的命令
结构:
targets:prerequisites
command
- targets:规则的目标,可以是 Object File(一般称它为中间文件),也可以是可执行文件,还可以是一个标签;
- prerequisites:是我们的依赖文件,要生成 targets 需要的文件或者是目标。可以是多个,也可以是没有;
- command:make 需要执行的命令(任意的 shell 命令)。可以有多条命令,每一条命令占一行。
目标和依赖文件之间要使用冒号分隔开,命令的开始一定要使用Tab
键。
用法、工作流程
用法:编辑好Makefile文件后输入make即可
工作流程
for example:
main:main.o test1.o test2.o
gcc main.o test1.o test2.o -o main
main.o:main.c test.h
gcc -c main.c -o main.o
test1.o:test1.c test.h
gcc -c test1.c -o test1.o
test2.o:test2.c test.h
gcc -c test2.c -o test2.o
1、第一条规则为终极目标,首先读取第一条,即
main:main.o test1.o test2.o
main依赖于main.o test1.o test2.o
2、对于这些.o文件
- 目标 “.o” 文件不存在,使用其描述规则创建它;
- 目标 “.o” 文件存在,目标 “.o” 文件所依赖的 “.c” 源文件 “.h” 文件中的任何一个比目标 “.o” 文件“更新”(在上一次 make 之后被修改)。则根据规则重新编译生成它;
- 目标 “.o” 文件存在,目标 “.o” 文件比它的任何一个依赖文件(".c" 源文件、".h" 文件)“更新”(它的依赖文件在上一次 make 之后没有被修改),则什么也不做
清除中间文件
.PHONY:clean
clean:
rm -rf *.o test
rm命令: -r 清楚当前目录及以下目录的文件 -f 即使是只读也删除
通配符
shell中的通配符
通配符 | 使用说明 |
---|---|
* | 匹配任意个字符 |
? | 匹配一个字符 |
[] | 指定匹配的字符 |
使用通配符时不能用引用变量的方式,如果要用的话要使用一个函数“wildcard”展开
例
OBJ=$(wildcard *.c)
test:$(OBJ)
gcc -o $@ $^
还有一个通配符%
test:test.o test1.o
gcc -o $@ $^
%.o:%.c
gcc -o $@ $^
“%.o” 把我们需要的所有的 “.o” 文件组合成为一个列表,从列表中挨个取出的每一个文件,"%" 表示取出来文件的文件名(不包含后缀),然后找到文件中和 "%"名称相同的 “.c” 文件,然后执行下面的命令,直到列表中的文件全部被取出来为止。
变量的定义和使用
基本语法:
变量的名称=值列表
例
OBJ=main.o test.o test1.o test2.o
test:$(OBJ)
gcc -o test $(OBJ)
四种基本赋值
1、简单赋值 (:=)常规的赋值
2、递归赋值(=)会改变之前的值
例
x=foo
y=$(x)b
x=new
test:
@echo "y=>$(y)"
@echo "x=>$(x)"
y=>newb
x=>new
3、条件赋值(?=)如果变量未定义,则使用符号中的值定义变量。如果该变量已经赋值,则该赋值语句无效。
4、追加赋值(+=)原变量用空格隔开的方式追加一个新值。
自动化变量
自动化变量 | 说明 |
---|---|
$@ | 表示目标文件 |
$% | 当目标文件是一个静态库文件时,代表静态库的一个成员名。 |
$< | 表示第一个依赖文件。 |
$^ | 代表的是所有依赖文件。 |
$? | 所有比目标文件更新的依赖文件列表。 |
$+ | 类似“$^”,但是它保留了依赖文件中重复出现的文件。主要用在程序链接时库的交叉引用场合。 |
$* | 在模式规则和静态模式规则中,代表“茎”。“茎”是目标模式中“%”所代表的部分(当文件名中存在目录时, “茎”也包含目录部分)。 |
例
test:test.o test1.o test2.o
gcc -o $@ $^
test.o:test.c test.h
gcc -o $@ $<
test1.o:test1.c test1.h
gcc -o $@ $<
test2.o:test2.c test2.h
gcc -o $@ $<
"$@" 代表的是目标文件test,“$^”代表的是依赖的文件,“$<”代表的是依赖文件中的第一个
在这些变量中加入字符 “D” 或者 “F” 就形成了一系列变种的自动化变量
D表示目录部分,F表示文件部分
目标文件搜索
当目标文件放在不同的目录时需要进行搜索
主要有两种:一般搜索VPATH和选择搜索vpath
VPATH 和 vpath 的区别:VPATH 是变量,更具体的说是环境变量,Makefile 中的一种特殊变量,使用时需要指定文件的路径;vpath 是关键字,按照模式搜索,也可以说成是选择搜索。搜索的时候不仅需要加上文件的路径,还需要加上相应限制的条件。
VPATH
VPATH := src car
test:test.o
gcc -o $@ $^
先搜索 src 目录下的文件,再搜索 car 目录下的文件。
make总会先搜索当前目录
vpath
vpath test.c src car
在两个路径里搜索test.c文件
vpath test.c
清除符合文件 test.c 的搜索目录。
vpath
清除所有已被设置的文件搜索路径。
搜索时可包含字符“%”,用于匹配字符
条件判断
关键字 | 功能 |
---|---|
ifeq | 判断参数是否不相等,相等为 true,不相等为 false |
ifneq | 判断参数是否不相等,不相等为 true,相等为 false |
ifdef | 判断是否有值,有值为 true,没有值为 false |
ifndef | 判断是否有值,没有值为 true,有值为 false |
ifeq ifneq用法
ifeq (ARG1, ARG2)
ifeq 'ARG1' 'ARG2'
ifeq "ARG1" "ARG2"
ifeq "ARG1" 'ARG2'
ifeq 'ARG1' "ARG2"
ifdef ifndef用法
ifdef VARIABLE-NAME
伪目标
作用:不会创建目标文件,只是想去执行这个目标下面的命令
例
clean:
rm -rf *.o test
在 shell 中输入 make clean 命令,命令 rm -rf *.o test 总会被执行
当目录下存在文件clean时,命令不会呗执行,需要将其进行声明
.PHONY:clean
clean:
rm -rf *.o test
这样 clean 就被声明成一个伪目标,make 在执行此规则时不会去试图去查找隐含的关系去创建它,提高了 make 的执行效率
另一个例子(没怎么看懂,先放上来)
下面是一段shell循环命令
SUBDIRS=foo bar baz
subdirs:
for dir in $(SUBDIRS);do $(MAKE) -C $$dir;done
在 make 的并行和递归执行的过程中,SUBDIRS定义为需要make的三个子目录,对三个目录进行make,每个目录下都有对应的makefile文件。
但是这种实现方法存在以下几个问题:
- 当子目录执行 make 出现错误时,make 不会退出。就是说,在对某个目录执行 make 失败以后,会继续对其他的目录进行 make。在最终执行失败的情况下,我们很难根据错误提示定位出具体实在那个目录下执行 make 发生的错误。这样给问题定位造成很大的困难。为了解决问题可以在命令部分加入错误检测,在命令执行的错误后主动退出。不幸的是如果在执行 make 时使用了 “-k” 选项,此方式将失效。
- 另外一个问题就是使用这种 shell 循环方式时,没有用到 make 对目录的并行处理功能由于规则的命令时一条完整的 shell 命令,不能被并行处理。
用伪目标方式解决:
SUBDIRS=foo bar baz
.PHONY:subdirs $(SUBDIRS)
subdirs:$(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@
foo:baz #规定三个子目录的编译顺序 baz是foo依赖文件 所以先编译baz 然后foo然后bar
同时生成多个可执行文件
.PHONY:all
all:test1 test2 test3
test1:test1.o
gcc -o $@ $^
test2:test2.o
gcc -o $@ $^
test3:test3.o
gcc -o $@ $^
字符串处理函数
函数的基本结构:
$(<function> <arguments>) 或者是 ${<function> <arguments>}
function 是函数名,arguments 是函数的参数
字符串处理函数:
1、模式字符串替换函数
$(patsubst <pattern>,<replacement>,<text>)
例
OBJ=$(patsubst %.c,%.o,1.c 2.c 3.c)
all:
@echo $(OBJ)
把模式%.c的单词用%.o替换,最后输出1.o 2.o 3.o
2、字符串替换函数
$(subst <from>,<to>,<text>)
例
OBJ=$(subst ee,EE,feet on the street)
all:
@echo $(OBJ)
把feet on the street中的ee替换成EE
3、去空格函数
$(strip <string>)
例
OBJ=$(strip a b c)
all:
@echo $(OBJ)
把连续的空格合并为一个空格
4、查找字符串函数
$(findstring <find>,<in>)
例
OBJ=$(findstring a,a b c)
all:
@echo $(OBJ)
在a b c中找a,返回a
剩下的函数参考[字符串处理函数](Makefile常用字符串处理函数 (biancheng.net))
文件名操作函数
1、取目录函数
$(dir <names>)
例
OBJ=$(dir src/foo.c hacks)
all:
@echo $(OBJ)
返回foo.c 和hacks的路径 src/和./
2、取文件函数
$(notdir <names>)
例
OBJ=$(notdir src/foo.c hacks)
all:
@echo $(OBJ)
返回两个文件的文件名
还有取后缀函数suffix 取前缀函数basename 添加后缀名函数addsuffix 添加前缀名函数addperfix 链接函数join(把文件名进行拼接)
获取匹配模式文件名函数
$(wildcard PATTERN)
列出当前目录下所有符合模式的 PATTERN 格式的文件名
其它常用函数
1、$(foreach <var>,<list>,<text>)
把参数
<list>
中的单词逐一取出放到参数<var>
所指定的变量中,然后再执行<text>
所包含的表达式。
例
name:=a b c d
files:=$(foreach n,$(names),$(n).o)
all:
@echo $(files)
把a b c d放进n 再逐个赋值给files
2、$(if <condition>,<then-part>)或(if<condition>,<then-part>,<else-part>)
如果
condition
为真(非空字符串),那么then-part
会是整个函数的返回值。如果condition
为假(空字符串),那么else-part
将会是这个函数的返回值。
例
OBJ:=foo.c
OBJ:=$(if $(OBJ),$(OBJ),main.c)
all:
@echo $(OBJ)
OBJ不为空,返回foo.c
3、$(call <expression>,<parm1>,<parm2>,<parm3>,...)
expression
参数中的变量$(1)、$(2)、$(3)
等,会被参数parm1
,parm2
,parm3
依次取代
例
reverse = $(1) $(2)
foo = $(call reverse,a,b)
all:
@echo $(foo)
返回a,b
4、$(origin <variable>)
会返回文件的“出生情况”
- “undefined”:如果
<variable>
从来没有定义过,函数将返回这个值。 - “default”:如果
<variable>
是一个默认的定义,比如说“CC”这个变量。 - “environment”:如果
<variable>
是一个环境变量并且当Makefile被执行的时候,“-e”参数没有被打开。 - “file”:如果
<variable>
这个变量被定义在Makefile中,将会返回这个值。 - “command line”:如果
<variable>
这个变量是被命令执行的,将会被返回。 - “override”:如果
<variable>
是被override指示符重新定义的。 - “automatic”:如果
<variable>
是一个命令运行中的自动化变量。
文件包含
- 使用
include <filenames>
,make 在处理程序的时候,文件列表中的任意一个文件不存在的时候或者是没有规则去创建这个文件的时候,make 程序将会提示错误并保存退出。 - 使用
-include <filenames>
,当包含的文件不存在或者是没有规则去创建它的时候,make 将会继续执行程序,只有真正由于不能完成终极目标重建的时候我们的程序才会提示错误保存退出。