Makefile学习
一直以来对于所学的东西都是会而不精,今天从makefile开始,深入的学习一下一些基础的内容.
参考:gunmake.htm
Makefile
make 可以自动判断一个大程序中哪些文件需要编译,并编译它们.
如何判断?
make程序根据Makefile文件中的数据和每个文件更改的时间戳决定哪些文件需要更新.
使用make 必须要有对应的Makefile文件
它根据makefile文件来决定干什么
makefile的规则:
目标 target : 依赖1 prerequiries ....()
<tab>编译的命令1(cmd)
....
以tab开始的行,make会认为是命令行.
目标是要产生的文件名称.比如可执行文件或者obj文件.当然,目标也可以是一个可执行的动作名称,比如"clean"
依赖是用来输入从而产生目标的文件,一个目标可以有很多依赖
命令是make执行的具体动作,一个规则可以含有多个命令
注意:每个命令行前面必须是一个tab字符,这里比较容易出错
通常,如果一个依赖发生变化,make会根据makefile的依赖关系,自动更新需要更新的目标
但是目标不一定都需要依赖,比如"clean", 它只包含命令
规则即用来解释怎样创建文件.比如编译顺序,编译内容.
一个Makefile可以包含规则以外的其他文本,但一个简单的makefile文件仅仅需要包含规则.
具体例子:
一个文本编译器(名字为edit)的可执行文件生成方法,其依赖8个obj文件(.o文件),这8个obj依赖8个C文件和3个头文件.
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的过程
默认状态下,make开始于第一个目标,如上例,自动执行于第一个目标edit.
使用变量简化makefile文件
上例中每个文件都被列举了两次,这样,如果对于一个大的工程,很有可能在需要列举的地方都要列举出来
很容易遗漏.而使用变量则可以简化makefile文件并排除这种出错可能,变量是定义一个字符串一次,而能多处替代改字符串使用
一般在makefile中使用名为objects,OBJECTS,objs,OBJS,obj,OBJ的变量代表所有obj文件.此处我们定义obj变量,如下
obj = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
然后再每个需要列举obj的地方,使用$(obj)来代替
objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
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 $(objects)
2.5 让make推断命令
编译单独的c语言并不需要写出命令,因为make可以把它推断出来:
make有一个使用cc -c把C语言源程序编译更新为相同文件名的obj文件的隐含规则.
例如
main.o : main.c
cc -c main.c -o main.o
可写作
main.o:
2.7 在目录中删除文件的规则
make 可以执行一个清除工作.如clean
实际应用中,应该编写较为复杂的规则以防不能预料的情况发生.更接近实用的规则样式如下:
.PHONY: clean
clean:
-rm edit $(obj)
这样可以预防因为存在clean的文件而发生混乱,并导致它在执行rm命令时发生错误.
3 编写makefile
命名: make可以自动寻找一下文件'GNUmakefile'、‘makefile’和‘Makefile’
推荐命名为Makefile
如果要使用自定义名字的makefile,可以使用-f name或 --file=name 来告诉make读名字为name的文件为makefile文件.
如果指定多个文件,则按顺序执行.
3.1 包含其他的makefile文件
include指令告诉make暂停读取当前makefile文件,先读完include指令指定的makefile文件后再继续,指令在makefile文件占单独一行,其格式如下:
include filenames.... //该行不能以tab开始,可以有空格,两个文件名也以空格隔开
使用include指令的一种情况: 几个程序分别有单独的makefile文件
另一种使用include指令情况是需要自动从源文件为目标产生依赖的情况
如果makefile文件名不以'/'开头,且在当前目录找不到,则会搜寻以'-I'或'--include-dir'参数指定的目录,最后会一次搜寻下面的目录(如果存在)
‘prefix/include' (通常为 ‘/usr/local/include') ‘/usr/gnu/include',‘/usr/local/include', ‘/usr/include'。
4 通配符
make中的通配符和shell中的通配符一样是"*","?","[...],例如*.c指当前目录中所有以.c结尾的文件. 注意,当 定义一个变量时通配符不会扩展,如obj = *.o,并不会扩展.
在目标、依赖和命令中的通配符自动扩展.
4.1 函数wildcard
通配符在规则中可以自动扩展,但设置在变量中活在函数的参数中通配符一般不能正常扩展,如果需要扩展,可以使用如下格式:
$(wildcard pattern...)
可以在makefile文件的任何地方使用该字符串,应用时该字符串被一列在指定目录下存在的且
文件名和给出的文件名的格式相符合的文件所代替,文件名中间由空格隔开.
如果没有指定格式一致的文件,则函数wildcard的输出将会省略.
如使用函数wildcard得到指定陌路下所有的c语言源程序文件名的命令格式为:
$(wildcard *.c)
我们可以把所获得的C语言源程序文件名的字符串通过".C"后缀变为".o"转换为obj文件名的字符串,其格式为:
$(patsubst %.c,%.o,$(wildcard *.c))
这里使用了另一个函数patsubst(字符串替换和分析函数)
这样,一个变异特定目录下所有C语言源程序,并把它们连接在一起的makefile文件可以写成如下格式:
obj:=$(patsubst %.c,%.o,$(wildcard *.c))
foo : $(obj)
cc -o foo $(obj)
4.4 假想目标
假想目标不是一个真正存在的文件名,它仅仅是您制定的一个具体规则所执行的一些命令的名称.使用假想目标有两个原因:
避免和具有相同名称的文件冲突和改善性能.
如果您写一个其命令不创建目标文件的规则,一旦由于重建而提及该目标,则该规则的命令就会执行.比如:
clean:
rm *.o temp
因为rm命令不创建名为clean的文件,所以不应有名为clean的文件存在,
注意:但如果目标clean文件存在,则因为其没有依赖,所以文件clean始终会认为已经更新,因此它的命令将永不会执行.
为了避免这种情况,应该使用.PHONY目标格式声明其为假想目标
.PHONY : clean
clean:
rm *.o temp
另一个使用假想目标的例子是使用make的递归调用进行连接的情况:此时,makefile文件常常包含列举一系列需要创建的子目录的变量.
4.7 内建的特殊目标名
.PHONY
特殊目标.PHONY的依赖是假想目标.
假想目标是这样一些目标:make 无条件的执行它命令,和目录下是否存在该文件以及它最后一次更新的时间没有关系.
.SUFFIXES
特殊目标.SUFFIXES的依赖是一列用于后缀规则检查的后缀.
.IGNORE
如果您特别为目标.IGNORE指明依赖,则make将会忽略处理这些依赖文件时执行命令产生的错误.
4.11 双冒号规则
双冒号规则是在目标名后使用::代替:的规则,当同一个目标在一条以上的规则中出现时,双冒号规则和平常的规则处理有所差异.
4.12 自动生成依赖
在为一个程序编写的makefile文件中,常常需要写许多仅仅是说明一些obj文件依靠头文件的规则.例如如果main.c通过一条include使用defs.h,则您需要写入下列规则:
main.o:defs.h
您需要这条规则让make知道如果defs.h一旦改变必须重构main.o.所以在一个大的工程中,必须多次注明,且如果删除一个include则makefile也要改写.
为避免这种烦恼,现代C编译器根据源程序中include语句可以为您编写这些规则,即如果需要这种功能,通常在编译源程序是加入 '-M'开关,如下命令
cc -M main.c
产生如下输出:
main.o : main.c defs.h
这样您就不必再亲自写这些规则,编译器可以为您完成这些工作.
我们推荐使用自动生成依赖的习惯是把makefile文件和源文件程序文件一一对应起来.
如,对每一个源程序文件"name.c"有一名为name.d的makefile文件和它对应,该makefile文件中列出了名为name.o的obj文件所依赖的文件.这种方式的优点是仅在源程序文件改变的情 况下才有必要重新扫描生成新的依赖.
这里有一个根据C语言源程序name.c生成名为name.d依赖文件的格式规则:
%.d:%.c
set -e: $(CC) -M $(CPPFLAGS) $< | sed 's/\($*\)\.o[ :]*/\l.o $@ : /g' > $@: [ -s $@] || rm -f $@
常用的自动变量
下面是自动变量列表:
$@
规则的目标文件名。如果目标是一个档案成员,则变量‘$@’ 档案文件的文件名。对于有多个目标的格式规则(参阅格式规则简介),变量‘$@’是那个导致规则命令运行的目标文件名。
$<
第一个依赖的文件名。如果目标更新命令来源于隐含规则,该变量的值是隐含规则添加的第一个依赖。参阅使用隐含规则。
$?
所有比目标‘新’的依赖名,名字之间用空格隔开。对于为档案成员的依赖,只能使用已命名的成员。参阅使用make更新档案文件。
$^
所有依赖的名字,名字之间用空格隔开。对于为档案成员的依赖,只能使用已命名的成员。参阅使用make更新档案文件。对同一个目标来说,一个文件只能作为一个依赖,不管该 文件的文件名在依赖列表中出现多少次。 所以,如果在依赖列表中,同一个文件名出现多次,变量‘$^’的值仍然仅包含该文件名一次。