Makefile介绍:
在Linux中使用make命令来编译程序,特别是大的程序;而make命令所执行的动作依赖于Makefile文件。最简单的Makefile文件如下:
hello:hello.c gcc -o hello hello.cclean: rm -f hello
将上述4行存为Makefile文件(注意必须以Tab键缩进第2、4行,不能以空格键缩进),放入/work/hardware/hello目录下,然后直接执行make命令即可编译程序,执行“make clean”即可清除编译出来的结果。
make命令根据文件更新的时间戳来决定哪些文件需要重新编译,这使得可以避免编译已经编译过的、没有变化的程序,可以大大提高编译效率。
要想完整地了解Makefile的规则,请参考《GNU Make使用手册》,以下仅粗略介绍。
1、Makefile规则
一个简单的Makefile文件包含一系列的“规则”,其样式如下:
目标(target)...:依赖(prerequiries)....
<Tab>命令(command)
目标(target)通常是要生成的文件的名称,可以是可执行文件或OBJ文件,也可以是一个执行的动作的名称,诸如“clean”。
依赖是用来产生目标的材料,一个目标经常有几个依赖。
命令是生成目标时执行的动作,一个规则可以包含有几个命令,每个命令占一行。
注意:每个命令前面必须是一个Tab字符,即命令行第一个字符时Tab,这是容易出错的地方。
通常,如果一个依赖发生了变化,就需要规则调用命令以更新或创建目标。但是并非所有的目标都有依赖,例如,目标“clean”的作用是清除文件,它没有依赖。
规则一般是用于解释怎样和何时重建目标。make首先调用命令处理依赖,进而才能创建或更新目标。当然,一个规则也可以是用于解释怎样和何时执行一个动作,即打印提示信息。
一个Makefile文件可以包含规则以外的其他文本,但一个简单的Makefile文件仅仅需要包含规则。虽然真正的规则比这里展示的例子复杂,但格式是完quan一样的。
对于上面的Makefile,执行“make”命令时,仅当hello.c文件比hello文件新,才执行命令“arm-linux-gcc -o hello hello.c”生成可执行文件hello;如果还没有hello文件,这个命令也会执行。
运行“make clean”时,由于目标clean没有依赖,它的命令“rm -f hello”将被强制执行。
2、Makefile文件里的赋值方法
变量的定义语法形式如下:
immediate = deferredimmediate ?= deferredimmediate := immediateimmediate += deferred or immediatedefine immediatedeferredendef
在GNU make中对变量的赋值有两种方式:延时变量、立即变量。区别在于它们的定义方法和扩展的方式不同,前者在这个变量使用时才扩展开,意即当真正使用时这个变量的值才确定;后者在定义时它的值就已经确定了。使用“=”、“?=”定义或使用define指令定义的变量是延时变量;使用“:=”定义的变量是立即变量。需要注意的一点是“?=”仅仅在变量还没有定义的情况下有效,即“?=”用来定义第一次延时变量。
对于附加操作符“+=”,右边变量如果在前面使用(:=)定义为立即变量则它是立即变量,否则均为延时变量。
3、Makefile常用函数
函数调用的格式如下:
$(function arguments)
这里“function”是函数名,“arguments”是该函数的参数。参数和函数名之间用空格或Tab隔开,如果有多个参数,它们之间用逗号隔开。这些空格或逗号不是参数值的一部分。
内核的Makefile中用到大量的函数,现在介绍一些常用的。
3.1、字符串替换和分析函数
$(subst from, to, string)
在文本“text”中使用“to”替换每一处“from”。
比如:
$(subst ee, EE, feet on the street)
结果为fEEt on the street。
$(patsubst pattern, replacement, text)
寻找“text中符合格式“pattern”的字,用“replacement”替换它们。“pattern”和“replacement”中可以使用通配符。
比如:
$(patsubst %.c, %.o, x.c.c bar.c)
结果为:x.c.o bar.o
$(strip string)
去掉前导和结尾空格,并将中间的多个空格压缩为单个空格。
比如:
$(strip a b c )
结果为“a b c”。
$(findstring find, in)
在字符串“in”中搜寻“find”,如果找到,则返回值是“find”,否则返回值为空。
比如:
$(findstring a, a b c)
$(findstring a, b c)
将分别产生值“a”和“ ”。
$(filter pattern..., text)
返回在“text”中由空格隔开且匹配格式“pattern...”的字,去除不符合格式“pattern...”的字。
比如:
$(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的反函数。
比如:
$(filter-out %.c %.s, foo.c bar.c baz.s ugh.h)
结果为“ugh.h”。
$(sort list)
将“list”中的字按字母顺序排序,并去掉重复的字。输出由单个空格隔开的字的列表。
比如:
$(sort foo bar lose)
返回值是“bar foo lose”。
3.2、文件名函数
$(dir names....)
抽取“names....”中每一个文件名的路径部分,文件名的路径部分包括从文件名的首字符到最后一个斜杠之前的一切字符。
比如:
$(dir src/foo.c hacks)
结果为“src/ ./”。
$(notdir names....)
抽取“names....”中每一个文件名中除路径部分外一切字符。
比如:
$(notdir src/foo.c hacks)
结果为“foo.c hacks”。
$(suffix names....)
抽取“names....”中每一个文件名的后缀。
比如:
$(suffix src/foo.c src-1.0/bar.c hacks)
结果为“.c .c”。
$(basename names....)
抽取“names...”中每一个文件名中除后缀外一切字符。
比如:
$(basename src/foo.c src-1.0/bar hacks)
结果为“src/foo src-1.0/bar hacks”。$(addsuffix suffix, names....)
参数“names....”是一系列的文件名,文件名之间用空格隔开;suffix是一个后缀名。将suffix的值附加在每一个独立文件名的后面,完成后将文件名串联起来,它们之间用单个空格隔开。
比如:
$(addsuffix .c, foo bar)
结果为“foo.c bar.c”。
$(addprefix prefix, names....)
参数“names...”是一系列的文件名,文件名之间用空格隔开;prefix是一个前缀名。将prefix的值附加在每一个独立文件名的前面,完成后将文件名串联起来,它们之间用单个空格隔开。
比如:
$(addprefix src/, foo bar)
结果为“src/foo src/bar”。
$(wildcard pattern)
参数“pattern”是一个文件名格式,包含有通配符。函数wildcard的结果是一列和格式匹配且真实存在的文件的名称,文件名之间用一个空格隔开。
比如当前目录下有文件1.c、2.c、1.h、2.h,则:
c_src :=$(wildcard *.c)
结果为“1.c 2.c”。
3.3、其他函数
$(foreach var, list, text)
前两个参数,“var”和“list”将首先扩展,最后一个参数“text”此时不扩展;接着,“list”扩展所得的每个字都赋给“var”变量;然后“text”引用该变量进行扩展,因此“text”每次扩展都不相同。
函数的结果是由空格隔开的“text”在“list”中多次扩展后,得到的新的“list”,就是说:“text”多次扩展的字符串联起来,字与字之间由空格隔开,如此就产生了函数foreach的返回值。
下面是一个简单的例子,将变量“files”的值设置为“dirs”中的所有目录下的所有文件的列表:
dirs := a b c d
files := $(foreach dir, $(dirs), $(wildcard $(dir) /*))
这里“text”是$(wildcard $(dir)/*),它的扩展过程如下。
第一个赋给变量dir的值是“a”,扩展结果为“$(wildcard a/*)”;
第一个赋给变量dir的值是“b”,扩展结果为“$(wildcard b/*)”;
第一个赋给变量dir的值是“c”,扩展结果为“$(wildcard c/*)”;
如此继续扩展。
这个例子和下面的例子有共同的结果:
files := $(wildcard a/* b/* c/* d/*)
$(if condition, then-part, else-part)
首先把第一个参数“condition”的前导空格、结尾空格去掉,然后扩展。如果扩展为非空字符串,则条件“condition”为真;如果扩展为空字符串,则条件“condition”为假。
如果条件“condition”为真,那么计算第二个参数“then-part”的值,并将该值作为整个函数if的值。
如果条件“condition”为假,并且第三个参数存在,则计算第三个参数“else-part”的值,并将该值作为整个函数if的值;如果第三个参数不存在,函数if将什么也不计算,返回空值。
注意:仅能计算“then-part”和“else-part”二者之一,不能同时计算,这样有可能产生副作用。
$(origin variable)
变量“variable”是一个查询变量的名称,不是对该变量的引用。所以,不能采用$和圆括号的格式书写该变量,当然,如果需要使用非常量的文件名,可以在文件名中使用变量引用。
函数origin的结果是一个字符串,该字符串变量的定义如下:
undefined 变量“variable”从来没有定义;
default 变量“variable”是默认定义;
environment 变量“variable”作为环境变量定义,选项-e没有打开;
file 变量“variable”在Makefile中定义;
command line 变量“variable”在命令行中定义;
automatic 变量“variable”是自动变量;
$(shell command arguments)
函数shell是make与外部环境的通信工具。函数shell的执行结果和在控制台上执行“command arguments”的结果相似。不过如果“command arguments”的结果含有换行符,则在函数shell的返回结果中将把它们处理为单个空格,若返回结果最后是换行符则被去掉。
比如当前目录下有文件1.c、2.c、1.h、2.h,则:
c_src := $(shell ls *.c)
结果为“1.c 2.c”。
本节可以在阅读内核、bootloader、应用程序的Makefile文件时作为手册来查询。