标签:makefile
简单来说,Makefile就是帮助我们编译工程并生成可执行文件。现在的IDE基本上都做了Makefile的事情,并不需要我们自己去手动编写Makefile,但是在Linux环境下,对于一个大型工程,我们常常要定制自己的编译规则,要编译哪些文件,该如何编译,通过Makefile就可以搞定。有了Makefile,只需要执行一个简单的make命令,即可实现自动化编译,当然有时候执行make命令之前还需要做一些配置工作,比如在android环境下开发时,首先要执行“envsetup.sh”并lunch一个SDK版本,这个需要根据实际情况而定。
在Linux下通过gcc进行编译时,包括四个阶段:预编译、编译、汇编和链接,分别生成.i、.s、.o和可执行文件,前三个阶段可归结为一个过程,即编译过程以生成中间文件.o,链接过程有两种情况,静态链接和动态链接,也就是所谓的库文件,静态库后缀为.a,动态库后缀为.so。了解gcc的编译过程,有助于理解Makefile,下面先来看一个简单的例子。
以c代码为例,这里有四个文件,分别如下。
main.c——调用circle.h头文件声明的length()和area()函数
#include "circle.h"
#include "stdio.h"
int main(void)
{
printf("length = %lf\n", length(5));
printf("area = %lf\n", area(5));
return 0;
}
circle.h——声明length()和area()函数
#ifndef CIRCLE_H
#define CIRCLE_H
double length(int r);
double area(int r);
#endif // CIRCLE_H
circle.c——实现length()和area()函数
#include "circle.h"
#include "def.h"
double length(int r)
{
return 2 * PI * r;
}
double area(int r)
{
return PI * r * r;
}
def.h——圆周率PI的定义
#ifndef DEF_H
#define DEF_H
#define PI 3.1415926
#endif // DEF_H
使用gcc编译以上四个文件,假设目标文件为main,命令如下:
$gcc -o main main.c circle.c
当工程中有n个文件时,还是像上面的命令一样在gcc后面跟着一大堆文件吗,这样就太笨拙了。即便如此,如果只是某个文件修改了一点代码,也要编译整个工程吗,想想就不必这样,有时候我们还要单独编译某个功能模块,这时候就该Makefile发挥作用了。编译上面的四个文件,可使用如下Makefile。
Makefile——
main: main.o circle.o
gcc -o main main.o circle.o
main.o: main.c circle.h
gcc -c main.c
circle.o: circle.c circle.h def.h
gcc -c circle.c
clean:
rm main main.o circle.o
上面的Makefile是一个非常简单的文件,通过这个文件,只需要执行命令“make”就可以生成目标文件main,执行命令“make clean”就可以删除目标文件main和一些.o中间文件,当工程庞大时Makefile的好处就显现出来了,其神奇功效如下:
(1)如果这个工程没有编译过,那么所有的c文件都要被编译和链接。
(2)如果这个工程的某几个c文件被修改了,那么只编译被修改的这几个c文件并链接目标程序。
(3)如果这个工程的某几个头文件被修改了,那么只编译引用了这几个头文件的c文件并链接目标程序。
从上面的Makefile可以看出其一般格式如下:
target: prerequisites
commands
# or
target: prerequisites; commands
target是目标文件,后面跟着一个冒号,prerequisites是生成target所需要的文件,当文件较多时,可使用续行符号“\”进行续行,另起一行commands是make执行的shell命令,有一点必须注意,commands前要留一个Tab键的距离。如果它们在同一行的话commands前以分号进行分隔。Makefile支持通配符,例如“*”,用法同bash。
(1)一般情况下,当文件名为“Makefile”或“makefile”时,也可以是“GNUmakefile”,只需要输入命令“make”即可,但有时候我们要起一个一目了然的文件名,以清楚地知道这个文件是干什么用的,这时执行make命令时就要使用“-f”参数了,以指定具体的文件,例如“make -f main.mk”。
(2)在Makefile中可以引用外部的Makefile,语法是“include filename”,类似于C/C++的“#include”,支持绝对路径和相对路径。make命令执行时,默认在当前目录下查找Makefile,还可以通过“-I”参数指定。
(3)找到Makefile后,它会找文件中的第一个目标文件,即文件中最顶端的target,这个target编译完成后则整个编译过程结束,如果还有其他的target没有编译也不再编译,所以文件中的第一个target格外重要。但是,有时候我们偏偏只要编译文件中的某个target,这也是可以的,只需要在make命令后输入这个target即可,其它不相关的 target将不再编译。
(4)整个编译过程其实就是文件依赖的查找与编译的过程。当目标文件不存在或者依赖文件比目标文件新时,将执行后面的commands命令,然后当前的依赖文件又会去查找它自己的依赖文件,就这样一层一层执行下去。
(5)上面例子中Makefile的clean,它没有被第一个target关联,也没有被其它的target关联,是不会被自动执行的,不过可以像第“(2)”点提到的,使用命令“make clean”来完成。
Makefile执行过程可以简单的概括为:Makefile读取>include的Makefile读取>变量初始化>隐晦规则自动推导>目标文件创建依赖关系>依据依赖关系决定哪些目标文件需要重新生成>执行生成命令。
在上面的例子中,main这个target用到了main.o和circle.o两次,clean这个target也用到了main.o和circle.o一次,这还算好的,如果依赖文件多时,写多次就太麻烦了,我们可以使用变量來简化这个过程,类似于shell脚本中的变量,修改上面的Makefile如下。
objects = main.o circle.o
main: $(objects)
gcc -o main $(objects)
main.o: main.c circle.h
gcc -c main.c
circle.o: circle.c circle.h def.h
gcc -c circle.c
clean:
rm main $(objects)
上面例子中,我们把main.o和circle.o赋值给了变量objects,这个变量名字是可以随意起的,访问变量时使用美元符号$,并把变量用圆括号括起来,使用花括号也是可以的。
make可以自动推导文件及文件依赖关系后面的命令,也就是常说的隐晦规则,与显式规则对比,这样就又可以少写几行代码,以circle.o为例,make会自动推导出依赖文件circle.c和命令“gcc -c circle.c”,再次修改上面的Makefile如下。
objects = main.o circle.o
main: $(objects)
gcc -o main $(objects)
main.o: circle.h
circle.o: circle.h def.h
clean:
rm main $(objects)
每个Makefile都要有一个clean,用于清除文件,一般在文件最下面,常见的做法是使用“.PHONY”把clean变为一个伪目标,因为clean并不是一个真正的目标文件,也可以理解为一个标签,修改上面Makefile如下。
objects = main.o circle.o
main: $(objects)
gcc -o main $(objects)
main.o: circle.h
circle.o: circle.h def.h
.PHONY: clean
clean:
-rm main $(objects)
仔细观察可以发现rm命令前面有个减号,意思是命令执行过程中如果遇到某些文件有问题将忽略,继续执行。
伪目标的另一种功能:执行“make”时,编译的是文件最顶端的目标文件,如果目标文件有多个,最终目标文件也是只有一个,即第一个目标文件,如果我们想一口气编译多个目标文件,就可以使用伪目标,把伪目标放到文件顶端,伪目标的依赖文件为我们真正想要的目标文件。
通过上面的Makefile可以发现,目标文件都是一个,依赖文件则有多个,也就是一对一或一对多的关系,其实多个目标文件也可以写在一起,也就是多对一或多对多的关系。
静态模式可以更加容易地定义多目标的规则,语法如下:
targets: <targets-pattern>: <prerequisites-pattern>
targets指定了一系列的目标文件,是一个目标文件的集合。
targets-pattern指明了targets的模式。
prerequisites-pattern指明了依赖文件的模式,与targets-pattern相关。
下面以一个例子说明,假设我们有dog.c和cat.c两个文件,想要编译生成dog.o和cat.o,Makefile如下:
objects = dog.o cat.o
all: $(objects)
$(objects): %.o: %.c
gcc -c $< -o $@
.PHONY: clean
clean:
-rm *.o
上面的Makefile,最终目标是all,为多目标,“%.o”表示目标集中以“.o”结尾的文件即dog.o和cat.o,“%.c”则是将“%.o”表示的文件的后缀从“.o”替换为“.c”,“$<”和“$@”是两个自动化变量,分别表示依赖文件集和目标文件集。
在Makefile中,依赖文件可能会包含多个头文件,在一个大型的工程中,我们必须知道源文件中包含了哪些头文件,这样才能编写依赖关系,数量庞大时就显得工作量繁重了,而且可维护性差,一个好的做法是使用gcc编译的“-M”或者“-MM”选项,它会自动生成依赖关系,这两个选项的区别是前者包含了标准库的头文件而后者没有。以我们一开始的main.c为例,结果如下:
$gcc -M main.c
main.o: main.c circle.h /usr/include/stdio.h /usr/include/features.h \
/usr/include/stdc-predef.h /usr/include/x86_64-linux-gnu/bits/predefs.h \
/usr/include/x86_64-linux-gnu/sys/cdefs.h \
/usr/include/x86_64-linux-gnu/bits/wordsize.h \
/usr/include/x86_64-linux-gnu/gnu/stubs.h \
/usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
/usr/lib/gcc/x86_64-linux-gnu/4.6/include/stddef.h \
/usr/include/x86_64-linux-gnu/bits/types.h \
/usr/include/x86_64-linux-gnu/bits/typesizes.h /usr/include/libio.h \
/usr/include/_G_config.h /usr/include/wchar.h \
/usr/lib/gcc/x86_64-linux-gnu/4.6/include/stdarg.h \
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
/usr/include/x86_64-linux-gnu/bits/sys_errlist.h
$gcc -MM main.c
main.o: main.c circle.h
从上面的例子可以看出“-M”与“-MM”的区别,我们一般使用后者。自动生成的依赖关系放在“.d”文件中,例如main.c的依赖关系对应于main.d,且看下面的Makefile。
sources:=$(wildcard *.c)
objects=$(sources:.c=.o)
all: main
%.d: %.c
rm -f $@
gcc -MM $< > tmp
sed ‘s,\($*\)\.o,\1.o $@,g‘ < tmp > $@
rm -f tmp
sinclude $(sources:.c=.d)
main: $(objects)
gcc -o main $^
.PHONY: clean
clean:
-rm -f main *.o *.d
上面的Makefile涉及的知识点较多,稍微有点负责,下面详细解释一下。
第一行:“:=”表示覆盖赋值,另外,“=”是最基本的赋值,“+=”是追加赋值,“?=”是变量没有被赋值时才进行等号后面的赋值,Makefile支持通配符,但是给变量赋值时“*.c”并不会被展开,表示的还是其字面值,所以要用“wildcard”进行转换。
第二行:字符串替换,把“sources”中的“.c”替换为“.o”,同样的功能可以使用Makefile中的patsubst函数实现,这里不做详细介绍。
第三行:空白行。
第四行:目标文件为“all”,依赖文件为“main”。
第五行:空白行。
第六行:目标文件为“%.d”,依赖文件为“.c”,“sources”中所有的“.c”文件都会生成一个对应的“.d”文件。
第七行:删除旧的目标文件,目的是准备生成新的目标文件,自动变量“$@”表示目标文件。
第八行:把上面提到的自动生成的文件依赖关系输入到“tmp”临时文件,“-MM”就是用来自动生成依赖关系但不包括标准库,“$<”表示第一个依赖文件,“>”是数据流重定向符号。
第九行:使用了sed命令,功能是把临时文件“tmp”中的“xxx.o”替换为“xxx.o xxx.d”,即增加一个“.d”文件,xxx是一个“.c”文件的名字,sed命令的用法教为简单,不做详细介绍,这里还用到了shell的数据流输入输出重定向。
第十行:删除tmp临时文件。
第十一行:空白行。
第十二行:使用了“sinclude”,用法同“-include”,include前面的减号表示出错时继续执行,这里使用include也可以,sinclude是为了提高Makefile的兼容性。
第十三行:空白行。
第十四行:“main”是最后生成的可执行文件,依赖于“objects”。
第十五行:使用了自动变量“$^”,表示依赖文件列表。
第十六行:空白行。
第十七行:伪目标。
第十八行:clean,没有依赖。
第十九行:使用rm命令执行一些真正的清除工作。
Makefile注释以“#”开始,shell脚本的注释也是如此,如果在文件中使用“#”字符就要进行转义“#”。
执行make命令时,默认会把Makefile中的命令输出到屏幕,如果不想要这样的输出时,可以在Makefile文件中的命令前添加“@”符号,对应的命令就不会输出到屏幕。
make命令的参数众多,可在shell中通过“man”来查看,这里简单介绍几个。
-n:只显示Makefile中的命令,但不真正执行,可查看Makefile的执行过程。
-s:不显示Makefile中的任何命令,但这些命令会执行到。
-i:忽略所有命令的错误。
-k:当前命令有错误时,继续执行其它的。
-w:执行Makefile时,输出目录信息。
如果想要上一条命令的结果作用下一条命令,那么这两条命令就应该写在一行,以分号分隔,如果这两条命令独立成行的话,结果就大不同了,如下Makefile:
.PHONY: all
all:
showcur:
cd /home
pwd
showhome:
cd /home; pwd
当我们“make showcur”时,pwd命令显示的当前路径。
当我们“make showhome”时,pwd命令显示的home路径。
在bash中执行命令时,都有一个回传码,0表示目录正确执行。前面我们介绍过减号“-”的用法,在一个命令的前面,表示这个命令出错时忽略出错信息,继续执行其它的。
大型工程中,代码一般会以功能模块划分,每个功能模块都会有自己的Makefile,例如当前目录有一个Makefile和subdir目录,subdir目录中又有一个Makefile,那么我们如何在当前目录的 Makefile中直接执行subdir目录中的Makefile呢?下面两种方法都是可以的。
submodule:
$(MAKE) -C subdir
# or
submodule:
# cd subdir && $(MAKE)
subdir这个子目录中的Makefile如下:
sub:
@echo "Makefile in subdir"
当我们“make submodule”时,subdir这个子目录中的 Makefile就会执行。
变量传递类似于bash中把当前shell的变量传递到其子shell,这里是传递到子目录中的Makefile,使用“export”关键字,不想传递时使用“unexport”。如下例子所示。
当前目录Makefile:
fathervar = "father"
export fathervar
submodule:
$(MAKE) -C subdir
subdir这个子目录的Makefile:
sub:
@echo "Makefile in subdir"
@echo $(fathervar)
如果想传递所有的变量,export后不加任何东西即可。
在Makefile中美元符号$随处可见,访问变量或者调用函数,当使用真实的美元符号时,类似于转义字符,形式为$$。
变量可以嵌套在一起使用,变量拼接用法同shell脚本,将变量直接连在一起写即可,如下Makefile:
A := a
B := b
ab := "vars"
multivar:
echo $($(A)$(B))
执行结果是“vars”。
如下例子,nullstring为空变量,什么也没有,space为空格。
nullstring :=
space := $(nullstring)
如果有变量是通常make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略,如果你想在Makefile中设置这类参数的值,那么,你可以使用“override”,基本语法如下:
override <variable> = <value>
上面Makefile的例子中我们定义的变量都是全局变量,我们还可以定义局部变量,也叫目标变量,例子如下:
globalvar := "global"
local: globalvar := "local"
local:
echo $(globalvar)
local2:
echo $(globalvar)
globalvar是个全局变量,在local这个目标中进行了重定义,变成了局部变量,然后在local目标中访问时值为“local”,而local2中还是“global”。
Makefile中的函数众多,可以用来处理变量、文件名等,提供变量的灵活性,返回值可以直接当变量来使用,这里只给出函数调用的一般形式及用法。
语法格式:
$(<function> <arguments>)
语法格式一目了然,函数名与参数列表以空格分开,参数列表内部的各个变量之间以逗号分隔,介绍一下subst函数,形式如下:
$(subst <from>,<to>,<text>)
表示在text中,把from全部替换为to,返回新的内容,如下例子:
funcvar := "hello Makefile"
func:
echo $(subst l,L,$(funcvar))
输出结果为“heLLo Makefile”,小写l全部替换为了大些L,要注意参数列表中的各参数与逗号间不要留有空格,否则替换时空格也起了作用。
上面列举了subst函数的用法,用来处理变量,还有其它一些函数,如用来处理文件名的dir函数以及一些特殊的函数如foreach、if、call、origin、shell等,用法大同小异,这里不再赘述。
另外,还有控制make的函数,类似于C/C++的assert,它们是error和warning,error直接终止程序运行,warning产生警告但不影响运行。
关键字:ifeq、inneq、indef、inndef、else、endif。前面两个关键字用于判断相等与否,中间两个变量类似于C/C++中的宏定义,else是个可选的关键字,endif放在条件编译的最后,如下例子:
ifvar :=
ifdef ifvar
ifvar += "defined"
else
ifvar := "not defined"
endif
condition:
echo $(ifvar)
“make condition”时,因为ifvar变量为空,make认为是没有定义的,所以输出结果为“not undefined”。
上面的Makefile例子中提到了许多自动变量,下面做个简单的总结。
$@:目标文件集。
$%:只有目标为函数库文件时,这个变量才有效,如Linux下的“.a”静态库,否则为空,有效时其值为函数库文件中的成员名。
$<:依赖文件中的第一个文件,当依赖文件使用了模式变量即“%”,上面的例子中有使用过,那么其值将是符合该模式的一系列的文件集,结果是一个一个取出来的。
$?:比目标文件新的依赖文件的集合,空格分开。
$^:依赖文件的集合,空格分开,去重。
$+:依赖文件的集合,空格分开,不去重。
$*:当使用了模式匹配即“%”,其值是%代表的值及%之前的部分,如果没有使用模式匹配,最起码文件名要有个后缀,如foo.c,那么这个值为foo,否则为空。
上面的七个自动变量还可以分别加上“D”和“F”,举个例子如下。
$(@D):目标文件集的目录部分,如“dir/foo.o”,其值为“dir”。
$(@F):目标文件集的文件部分,如“dir/foo.o”,其值为“foo.o”。
########## end ##########
Makefile介绍完毕,熟悉以上Makefile规则后,就可以搞定一个大型工程的编译法则喽。
版权声明:本文为博主原创文章,未经博主允许不得转载。
标签:makefile
原文地址:http://blog.csdn.net/ieearth/article/details/47296429