怎样写Makefile文件(C语言部分)
本文摘抄自“跟我一起写Makefile ”,只是原文中我自己感觉比较精要的一部分,并且只针对C语言,使用GCC编译器。 原文请看这里:http://wiki.ubuntu.org.cn/%E8%B7%9F%E6%88%91%E4%B8%80%E8%B5%B7%E5%86%99Makefile
写完之后才发现基本上都是一些比较枯燥的规则,看看一、二、八三个部分就可以了。当作参考工具吧,什么时候用到了再来看看。
一、概述
我所使用的make 版本是 GNU Make 3.81,使用的系统是 Ubuntu 10.10,GCC版本为 4.4.5。与原文作者使用的Make 版本很相似。
1.1 关于程序的编译和链接
对于C语言的编译,首先要把源文件编译成中间代码文件,即.o文件(在Windows下是.obj文件)。这个动作叫做编译(compile)。然后再把大量Object File合成执行文件,这个动作叫做链接(link)。更加详细的内容可以参考这里:使用gcc编译C程序的详细过程
编译时,编译器主要检查语法、函数与变量的声明是否正确。函数的声明通常放在头文件中(头文件中应该只放声明,对于函数的具体实现则应该放到单独的 .c 源文件中),你告诉编译器头文件所在的位置,编译器就会到相应的地方去找函数声明。只要语法正确,编译器就会生成中间文件。一般来说一个源文件(.c)对应一个目标文件(.o)。
链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们的应用程序,并不需要源文件的存在。在很多时候,由于中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。
二、Makefile 介绍
2.1 Makefile的规则
target ... : prerequisites ... command ... ...
target可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。对于标签这种特性,在后续的“伪目标”章节中会有叙述。
prerequisites就是,要生成那个target所需要的文件或是目标。
command也就是make需要执行的命令。(任意的shell命令)。注意command之前是TAB键,并非空格键。
2.2 一个试例
一个工程有3个头文件,和8个c文件,关系如下图:
我们的makefile应该是下面的这个样子的:
edit : main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o cc -o edit main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o main.o : main.c defs.h cc -c main.c kbd.o : kbd.c defs.h command.h cc -c kbd.c command.o : command.c defs.h command.h cc -c command.c display.o : display.c defs.h buffer.h cc -c display.c insert.o : insert.c defs.h buffer.h cc -c insert.c search.o : search.c defs.h buffer.h cc -c search.c files.o : files.c defs.h buffer.h command.h cc -c files.c utils.o : utils.c defs.h cc -c utils.c clean : rm edit main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o
make命令会找到Makefile文件中第一个target,并且把它当做最终的目标文件。Makefile所指示出来的关系就是我们在上面的图中所展示的关系。
2.3 Makefile中使用变量
makefile的变量也就是一个字符串,理解成C语言中的宏可能会更好。
objects = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o edit : $(objects) cc -o edit $(objects)
2.4 让make自动推导
只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中。
objects = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o cc = gcc edit : $(objects) cc -o edit $(objects) main.o : defs.h kbd.o : defs.h command.h command.o : defs.h command.h display.o : defs.h buffer.h insert.o : defs.h buffer.h search.o : defs.h buffer.h files.o : defs.h buffer.h command.h utils.o : defs.h .PHONY : clean clean : rm edit $(objects)
2.5 清空目标文件的规则
.PHONY : clean clean : -rm edit $(objects)
2.6 Makefile中有什么?
Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
- 显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
- 隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较简略地书写Makefile,这是由make所支持的。
- 变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点像你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
- 文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
- 注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“\#”。
最后,还值得一提的是,在Makefile中的命令,必须要以[Tab]键开始。
2.7 引用其他Makefile
在include前面可以有一些空字符,但是绝不能是[Tab]键开始。你有这样几个Makefile:a.mk、b.mk、c.mk,还有一个文件叫foo.make,以及一个变量$(bar),其包含了 e.mk和f.mk。
include foo.make *.mk $(bar)
如果文件都没有指定绝对路径或是相对路径的话,make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:
- 如果make执行时,有“-I”或“--include-dir”参数,那么make就会在这个参数所指定的目录下去寻找。
- 如果目录<prefix>;/include(一般是:/usr/local/bin或/usr/include)存在的话,make也会去找。
如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成makefile的读取, make会再重试这些没有找到,或是不能读取的文件,如果还是不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以在include前加一个减号“-”,其表示,无论include过程中出现什么错误,都不要报错继续执行。
2.8 make的工作方式
GNU的make工作时的执行步骤入下:(想来其它的make也是类似)
- 读入所有的Makefile。
- 读入被include的其它Makefile。
- 初始化文件中的变量。
- 推导隐晦规则,并分析所有规则。
- 为所有的目标文件创建依赖关系链。
- 根据依赖关系,决定哪些目标要重新生成。
- 执行生成命令。
1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。
看完这些再看 第八部分:隐含规则,就可以了,中间的都不用看了。我怎么写了这么多。
三、书写规则
3.1 文件搜索
使用特殊变量VPATH 来搜索,如果不指定,make只会在当前的目录中去找寻依赖文件和目标文件。指定了这个变量,在当前目录搜索完而找不到之后会搜索相应目录。
VPATH = src:../headers
目录之间用冒号隔开。
还可以使用vpath关键字,它的使用方法有三种:这里只说一种,详情看原文。
- 1、vpath <pattern> <directories>
为符合模式<pattern>的文件指定搜索目录<directories>。
vapth使用方法中的<pattern>需要包含“%”字符。“%”的意思是匹配零或若干字符,(需引用“%”,使用“\%")例如,“%.h”表示所有以 “.h”结尾的文件。<pattern>指定了要搜索的文件集,而<directories>则指定了< pattern>的文件集的搜索的目录。
vpath %.h ../headers
3.2 伪目标
比如刚开始我们指定的
.PHONY : clean clean : rm *.o temp
也可以为伪目标指定依赖文件,常见于all,并且把它放在最前,指定为Makefile的终极目标。由于all是伪目标,所以不会生成all文件。
all : prog1 prog2 prog3 .PHONY : all
3.3 静态模式
静态模式可以更加容易地定义多目标的规则,语法:
<targets ...>: <target-pattern>: <prereq-patterns ...> <commands> ...
如下面的例子:
objects = foo.o bar.o all: $(objects) $(objects): %.o: %.c $(CC) -c $(CFLAGS) $< -o $@
等价于:
foo.o : foo.c $(CC) -c $(CFLAGS) foo.c -o foo.o bar.o : bar.c $(CC) -c $(CFLAGS) bar.c -o bar.o
$(objects): %.o: %.c,这里先把$(object) 在这里展开,表示把所有objects之中的.o文件都用.c文件与之对应起来。
$< 表示依赖文件,这里即.c文件, $@ 表示target文件,这里即.o文件。
3.4 自动生成依赖性
在GCC中,我们用gcc –MM main.c 这条命令就可以查看所有main.o 的依赖关系。于是,可以利用这一特性来让Makefile自动生成依赖关系,不用我们自己写了。
具体看这里。
四、书写命令
每条规则中的命令和操作系统Shell的命令行是一致的。make会按顺序一条一条的执行命令,每条命令的开头必须以[Tab]键开头,除非,命令是紧跟在依赖规则后面的分号后的。
4.1 显示命令
用“@”字符在命令行前,那么,这个命令将不被make显示出来。
make执行时,带入make参数“-n”或“--just-print”,那么其只是显示命令,但不会执行命令。利于调试Makefile。
make参数“-s”或“--slient”则是全面禁止命令的显示。
4.2 命令执行
如果上一条命令执行的结果需要在下一条指令中使用,那么这两个指令应该放在一行,中间用分号隔开。
exec: cd /home/hchen; pwd
4.3 命令出错
如果想要忽略命令的错误,在它之前加 ‘-’即可。
4.4 嵌套执行make
在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的Makefile,这有利于让我们的Makefile变得更加地简洁,而不至于把所有的东西全部写在一个Makefile中,这样会很难维护我们的Makefile,这个技术对于我们模块编译和分段编译有着非常大的好处。
例如,我们有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写:
subsystem: cd subdir && $(MAKE)
等价于:
subsystem: $(MAKE) -C subdir
具体看这里。
五、使用变量
在Makefile中的定义的变量,代表了一个文本字串,在Makefile中执行的时候其会自动原模原样地展开在所使用的地方。变量是大小写敏感的。“$<”、“$@”等,这些是自动化变量。
5.1 变量的基础
变量在定义时要赋初值。使用时在其前加$ 符号,并且用() 或者 {} 把变量名括起来。"$$” 表示真实的 $。
5.2 变量中的变量
这里说变量怎样定义,怎样赋值。
变量赋值有三种方式,一种是 = ,一种是 := ,一种是 ?= 。前一种可以使用延后定义的变量,中间只能按照从上到下的顺序定义变量。最后一种表示如果这个变量已经赋值,就不再对他进行赋值了,如果没有赋值,就对他赋值。详情参看这里。
+= 是追加赋值。
比如定义一个空格,下面的变量space就表示一个空格,其他方法还不好定义空格:
nullstring :=# there is nothing space := $(nullstring) # end of the line
5.3 变量高级用法
· 变量值的替换:
我们可以替换变量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。例如:
foo := a.o b.o c.o bar := $(foo:.o=.c)
这个示例中,我们先定义了一个“$(foo)”变量,而第二行的意思是把“$(foo)”中所有以“.o”字串“结尾”全部替换成“.c”,所以我们的“$(bar)”的值就是“a.c b.c c.c”。
对于静态模式,我们要这样写: bar := $(foo:%.o=%.c)
· 把变量的值再当成变量
即使用变量时,每个变量都展开成一个字符串使用:
x = y y = z a := $($(x))
在这个例子中,$(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就是“z”。(注意,是“x=y”,而不是“x=$(y)”)
更加详细的描述见这里。
5.4 目标变量
为某个target指定只会在这条规则以及它所连带的规则中使用的变量:
prog : CFLAGS = -g prog : prog.o foo.o bar.o $(CC) $(CFLAGS) prog.o foo.o bar.o prog.o : prog.c $(CC) $(CFLAGS) prog.c foo.o : foo.c $(CC) $(CFLAGS) foo.c bar.o : bar.c $(CC) $(CFLAGS) bar.c
在这个示例中,不管全局的$(CFLAGS)的值是什么,在prog目标,以及其所引发的所有规则中(prog.o foo.o bar.o的规则),$(CFLAGS)的值都是“-g”
六、使用函数
在Makefile中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。make所支持的函数也不算很多,不过已经足够我们的操作了。函数调用后,函数的返回值可以当做变量来使用。
6.1 函数调用的语法
函数调用,很像变量的使用,也是以“$”来标识的,其語法如下:
$(<function> <arguments>) 或者 ${<function> <arguments>}
<arguments>为函数的参数,参数间以逗号“,”分隔,而函数名和参数之间以“空格”分隔。下面是一个具体的例子:
comma:= , empty:= space:= $(empty) $(empty) foo:= a b c bar:= $(subst $(space),$(comma),$(foo))
在这个示例中,$(comma)的值是一个逗号。$(space)使用了$(empty)定义了一个空格,$(foo)的值是“a b c”,$ (bar)的定义用,调用了函数“subst”,$(bar)的值是“a,b,c”。
6.2 字符串处理函数
$(subst <from>,<to>,<text>)
- 名称:字符串替换函数——subst。
- 功能:把字串<text>中的<from>字符串替换成<to>。
- 返回:函数返回被替换过后的字符串。
$(patsubst <pattern>,<replacement>,<text>)
- 名称:模式字符串替换函数——patsubst。
- 功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式< pattern>,如果匹配的话,则以<replacement>替换。这里,<pattern>可以包括通配符 “%”,表示任意长度的字串。如果<replacement>中也包含“%”,那么,<replacement>中的这个 “%”将是<pattern>中的那个“%”所代表的字串。(可以用“\”来转义,以“\%”来表示真实含义的“%”字符)
- 返回:函数返回被替换过后的字符串。
这和我们前面“变量章节”说过的相关知识有点相似。如:
“$(var:<pattern>=<replacement>;)” 相当于 “$(patsubst <pattern>,<replacement>,$(var))”,
而“$(var: <suffix>=<replacement>)” 则相当于 “$(patsubst %<suffix>,%<replacement>,$(var))”。
例如有:objects = foo.o bar.o baz.o, 那么,“$(objects:.o=.c)”和“$(patsubst %.o,%.c,$(objects))”是一样的。
$(filter <pattern...>,<text>)
- 名称:过滤函数——filter。
- 功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可以有多个模式。
- 返回:返回符合模式<pattern>;的字串。
其他更多的函数和示例参看这里。
6.3 文件名操作函数
这里列出很多对文件名的操作函数。
还有foreach, if, call这三个比较特殊的函数。
七、make的运行
7.1 指定Makefile
指定某个不叫Makefile的文件运行make命令,加-f选项:
make –f hchen.mk
7.2 指定目标
“all”这个伪目标是所有目标的目标,其功能一般是编译所有的目标。“clean”这个伪目标功能是删除所有被make创建的文件。“install”这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。“print”这个伪目标的功能是例出改变过的源文件。“tar”这个伪目标功能是把源程序打包备份。也就是一个tar文件。“dist”这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件。“TAGS”这个伪目标功能是更新所有的目标,以备完整地重编译使用。“check”和“test”这两个伪目标一般用来测试makefile的流程。
7.3 检查规则
“-n” “--just-print” “--dry-run” “--recon” 不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行,这些参数对于我们调试makefile很有用处。
7.4 make的参数
点击小标题。
八、隐含规则
8.1 C语言编译的隐含规则
“<n>;.o”的目标的依赖目标会自动推导为“<n>;.c”,并且其生成命令是“$(CC) –c $(CPPFLAGS) $(CFLAGS)”。
其他语言的隐含规则看这里。
8.2 隐含规则使用的变量
1、关于命令的变量
CCC语言编译程序。默认命令是“cc”。
2、关于命令参数的变量
CFLAGSC语言编译器参数。
更多变量看这里。
8.3 模式规则
模式规则中都是用%,它表示匹配0个(还是1个?)或多个字符。
%.o : %.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
8.4 自动化变量
所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在模式规则的命令中。
在 a: a.o b.o c.o 这个依赖规则中,a 叫做target,即目标; a.o b.o c.o 都叫做 依赖目标。
- $@
- 表示规则中的目标文件集(target,比如上面例子中的%.o)。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。
- $%
- 仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是 "bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。
- $<
- 依赖目标(比如上例种的%.c)中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
- $?
- 所有比目标新的 依赖目标 的集合。以空格分隔。
- $^
- 所有的 依赖目标 的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
- $+
- 这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。
- $*
- 这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",并且目标的模式是"a.%.b",那么,"$*"的值就是"dir/a.foo"。
多么希望有个实例可以做一下呀,有好多文件,看看怎么把这些文件用Makefile组织起来,并且优化Makefile的文件。只看这些干巴巴的规则,一会儿就累了………………………………