Makefile规则
规则解析
一个简单的 Makefile 文件包含一系列的“规则”,样式如下:
目标(target) : 依赖(prerequiries)…
<tab>命令(command)
- 目标(target):可以是要生成的文件的名称,如可执行文件或OBJ文件;也可以是一个执行的动作名称,诸如'clean'
- 依赖(prerequiries):是用来产生目标的材料(比如源文件),一个目标经常有几个依赖
一个规则可以含有几个命令,每个命令占一行,命令被执行的 2 个条件:
- 依赖文件比目标文件新
- 目标文件还没生成
例1
hello: hello.c
gcc -o hello hello.c
clean:
rm -f hello
注意必须以 Tab 键缩进第 2、4 行,不能以空格键缩进,直接执行 make 命令即可编译程序,执行 make clean 即可清除编译出来的结果,make 命令根据文件更新的时间戳来决定哪些文件需要重新编译,这使得可以避免编译已经编译过的、没有变化的程序,可以大大提高编译效率,下面是执行逻辑:
- 执行 make 命令时,仅当 hello.c 文件比 hello 文件新,才会执行 gcc -o hello hello.c 命令
- 运行 make clean 时,由于目标 clean 没有依赖,它的命令 rm -f hello 将被强制执行
这里要说明一点的是,clean不是一个文件,它只不过是一个动作名字,有点像C语言中的lable一样,其冒号后什么也没有,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。
执行顺序
执行 make 命令时如果不指定目标,那么它默认是去生成第 1 个目标。所以"第 1 个目标",位置很重要。有时候不太方便把第 1 个目标完整地放在文件前面,这时可以在文件的前面直接放置目标,在后面再完善它的依赖与命令。比如:
First_target: // 这句话放在前面
.... // 其他代码,比如 include 其他文件得到后面的 xxx 变量
First_target : $(xxx) $(yyy) // 在文件的后面再来完善
command
例1:由于只生成第 1 个目标,所以下面只生成了hello.o没有生成hello
PROM = hello
CXX = ./../../buildroot/output/rockchip_rk3308_release/host/bin/aarch64-rockchip-linux-gnu-g++
hello.o : hello.c
$(CXX) -c hello.c
hello : hello.o
$(CXX) -o $(PROM) hello.o
例2:如果想生成多个可执行目标,我们可以使用 make all 命令来编译所有的目标(如果把all置成第一个目标,那么只需执行 make )
.PHONY: all
all: pro1 pro2 pro3 pro4
Makefile的命令参数
执行 make 命令时,它会去当前目录下查找名为“Makefile”的文件,并根据它的指示去执行操作,生成第一个目标。(GNU make找寻默认的Makefile的规则是在当前目录下依次找三个文件“GNUmakefile”、“makefile”和“Makefile”。其按顺序找这三个文件,一旦找到,就开始读取这个文件并执行)
我们可以使用 "-f " 选项指定文件,不再使用名为" Makefile "的文件,比如:
make -f Makefile.build
make -f hchen.mk
我们可以使用 "-C" 选项指定目录,切换到其他目录里去,比如:
make -C a/ -f Makefile.build
我们可以指定目标,不再默认生成第一个目标:
make -C a/ -f Makefile.build other_target
- -f:指定 "makefile" 文件;
- -i:忽略命令执行返回的出错信息;
- -s:沉默模式,在执行之前不输出相应的命令行信息;
- -r:禁止使用build-in规则;
- -n:非执行模式,输出所有执行命令,但并不执行;
- -t:更新目标文件;
- -q:make操作将根据目标文件是否已经更新返回"0"或非"0"的状态信息;
- -p:输出所有宏定义和目标文件描述;
- -d:Debug模式,输出有关文件和检测时间的详细信息。
- -C dir:在读取makefile 之前改变到指定的目录dir;
- -I dir:当包含其他makefile文件时,利用该选项指定搜索目录;
假想目标.PHONY
我们的 Makefile 中有这样的目标:
clean:
rm -f $(shell find -name "*.o")
rm -f $(TARGET)
如果当前目录下恰好有名为" clean "的文件,那么执行" make clean "时它就不会执行删除命令。关键字.PHONY可以解决这问题,告诉make该磁盘上的"clean"是“假的”,这时make为生成这个目标就会将其规则执行一次。.PHONY 修饰clean后,make clean一定会执行rm命令。
.PHONY : clean
Makefile中的变量
变量赋值
在 GNU make 中对变量的赋值有两种方式:延时变量、立即变量
A = xxx // 延时变量
B ?= xxx // 延时变量,只有第一次定义时赋值才成功;如果曾定义过,此赋值无效
C := xxx // 立即变量
D += yyy // 如果 D 在前面是延时变量,那么现在它还是延时变量;
// 如果 D 在前面是立即变量,那么现在它还是立即变量
上列变量中,变量 A 是延时变量,它的值在使用时才展开、才确定。比如:
A = $@
test:
@echo $A
上述 Makefile 中,变量 A 的值在执行时才确定,它等于 test,是延时变量。如果使用" A:= $@ ",这是立即变量,这时$@为空,所以 A 的值就是空,以下三个变量称为自动变量:
- $@ -- 目标文件
- $^ -- 所有的依赖文件
- $< -- 第一个依赖文件
- $? -- 更新过的依赖文件
变量导出
在编译程序时,我们会不断地使用 make -C dir 切换到其他目录,执行其他目录里的Makefile。如果想让某个变量的值在所有目录中都可见,要把它 export 出来,如下所示:CC 变量表示编译器,在整个过程中都是一样的。定义它之后,要使用 "export CC" 把它导出来。
export CC = $(CROSS_COMPILE)gcc
赋值shell命令
这是个立即变量,TOPDIR 等于 shell 命令 pwd 的结果
TOPDIR := $(shell pwd)
常用变量名
变量名虽然可以随意命名,但也有以下规范(大多数人都这样进行命名):
- all:这个伪目标是所有目标的目标,其功能一般是编译所有的目标
- clean:这个伪目标功能是删除所有被make创建的文件
- install:这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去
- print:这个伪目标的功能是列出改变过的源文件
- tar:这个伪目标功能是把源程序打包备份。也就是一个tar文件
- dist:这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件
- TAGS:这个伪目标功能是更新所有的目标,以备完整地重编译使用
- check和test:这两个伪目标一般用来测试makefile的流程
Makefile命令前缀
@
通常,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用"@"字符在命令行前,那么,这个命令将不被make显示出来,最具代表性的例子是,我们用这个功能来像屏幕显示一些信息。如:
@echo 正在编译XXX模块......
结果输出:
正在编译XXX模块
如果命令前没有带@,那么会输出:
echo 正在编译XXX模块
正在编译XXX模块
- 如果make执行时,带入make参数" -n "或" --just-print ",那么其只是显示命令,但不会执行命令,这个功能很有利于我们调试我们的Makefile,看看我们书写的命令是执行起来是什么样子的或是什么顺序的。
- 而make参数" -s "或" --slient "则是全面禁止命令的显示
减号-
其表示,无论include过程中出现什么错误,都不要报错继续执行:
-include<filename>
Makefile中通配符%与通配符*的区别
Makefile中常用函数
$(function arguments)
这里'function'是函数名,'arguments'是该函数的参数。参数和函数名之间是用空格或Tab 隔开,如果有多个参数,它们之间用逗号隔开。这些空格和逗号不是参数值的一部分。内核的 Makefile 中用到大量的函数,现在介绍一些常用的:
$(foreach var,list,text)
简单地说,就是 for each var in list, change it to text。 对 list 中的每一个元素,取出来赋给 var,然后把 var 改为 text 所描述的形式。例子:
objs := a.o b.o
dep_files := $(foreach f, $(objs), .$(f).d) // 最终 dep_files := .a.o.d .b.o.d
$(wildcard pattern)
pattern 所列出的文件是否存在,把存在的文件都列出来,例子:
src_files := $( wildcard *.c) // 最终 src_files 中列出了当前目录下的所有.c 文件
$(filter pattern...,text)
把 text 中符合 pattern 格式的内容,filter(过滤)出来、留下来,例子:
$(filter %.c %.s,foo.c bar.c baz.s ugh.h) //结果为'foo.c bar.c baz.s'
$(filter-out pattern...,text)
返回在`text’中由空格隔开且不匹配格式'pattern...'的字,去除符合格式'pattern...’的字
$(filter %.c %.s,foo.c bar.c baz.s ugh.h) //结果为'ugh.h'。
$(patsubst pattern,replacement,text)
寻找 'text' 中符合格式 'pattern' 的字,用'replacement'替换它们。'pattern' 和 'replacement' 中可以使用通配符,比如:
src := $(shell ls *.c)
objs := $(patsubst %.c,%.o,$(src))
$(subst from,to,text)
在文本 'text' 中使用 'to' 替换每一处 'from' ,比如:
$(subst ee,EE,feet on the street) //结果为‘fEEt on the street’
$(strip string)
去掉前导和结尾空格,并将中间的多个空格压缩为单个空格。比如:
$(strip a b c ) //结果为'a b c'
$(findstring find,in)
在字符串 'in' 中搜寻 'find' ,如果找到,则返回值是 'find' ,否则返回值为空,比如:
$(findstring a,a b c) //'a'
$(findstring a,b c) //''空字符串
Makefile中的隐含规则