原文作者:Suminder S. Ahuja
翻译:艾艾
正文:
概述
makefile介绍
makefile组成
注释
显式规则
make的执行
使用make的好处
定义变量
引用替换和计算变量名
递归扩展和简单扩展变量
给变量添加文本
自动变量
特定目标和特定模式变量值
隐含规则
指示
"Define" 指示
"Include" 指示
"Override" 指示
命令回显
伪目标
在文件名中使用通配符
命令中的错误
并行执行
用make更新归档文件
结论
附加参考
关于作者
概述
make是UNIX®下的一个工具,用来自动创建程序并且进行优化. 当你开发的程序由许多组件或源文件组成时它特别有用.
一个叫makefile的描述符文件阐明了源文件之间的关系,并给出更新每一个文件的命令. 任何时候当源文件之一有所改动,make就调用 makefile自动重建这个程序. 因为仅重编译那些受改动影响的文件,因此节约了编译时间.同时也有助于降低用命令行建立这些条目时发生人为错误的可能性.
除了编译以外, make也是一个可用于程序安装和更改系统配置的有效工具.
makefile介绍
为每一个工程创建一个名为makefile的配置文件. 每次修改源程序后, 用简单的命令make调用makefile来执行必要的重编译. make在当前工作目录中按以下的顺序查找这个描述文件:
makefile
Makefile
缺省条件下, 简单的make命令只创建文件中第一个目标. make命令的其它一般用法如下.
make prog1 建立目标 "prog1"
make -f mymakefile 用 "mymakefile" 作配置文件建立源文件中的第一个目标
make -f mymakefile prog1 用 "mymakefile" 作配置文件建立目标 "prog1"
makefile包含的五个主要部分是: 注释, 显式规则, 变量定义, 隐式规则以及指示.
注释
makefile中以'#'开始到行末的内容是注释.一个注释可以延续到多行,只要在行末加上一个('/'),但这个反斜线不能被另一个反斜线所转义. 注释可以放在一行中的任何位置,在'#' 之前的内容不属于注释. 缺省的/bin/sh shell允许命令行注释,但不允许在定义指示的时候加注释.
显式规则
makefile由'规则'(rules)组成. 规则说明在何时和如何重建某些文件,这些文件又可能是其他一些特定文件的目标. 规则由三部分组成: 一个或多个目标(target), 零个或多个先决条件(prerequisites), 零个或多个命令(command).
target ... : prerequisites
command
...
...
目标是make创建的域的通常叫法;例如可执行文件或目标文件. 目标也可以是要执行的动作的名字, 例如, clean (见伪目标). 一旦任何先决条件变化将引起目标被创建. 如果目标存在且比它的先决条件新,则认为目标是"最新"的.
先决条件是一个或多个文件,作为创建目标的输入. 先决条件的目的是定义某些目标对某些源文件的依赖属性.
命令是在有先决条件的规则之中. 它是当任何先决条件改变时make创建或更新目标采取的动作. 一条规则可包含多个命令. 每个在规则中执行的命令由shell解释执行. 缺省情况下, make用/bin/sh shell. 宏SHELL = bin/sh将覆盖缺省shell.
make的执行
make遵循"依赖规则"概念; make 读取当前目录下的makefile,从处理第一个目标开始. make查找每个目标的依赖(先决条件)以知道它们是否也作为目标列了出来 .
make沿依赖链遍历整个递归链直到发现一个没有先决条件的目标,或者其先决条件没有规则. 一旦到达依赖链的末端, make便返回递归,在返回的过程执行每条目标规则中的命令. 对于遇到的所有有规则的先决条件, make采用同样的模式处理 .
一旦所有先决条件规则运行完了, make最后返回第一个目标 . 如果目标不存在, 或者目标比它现在的先决条件生成的早, make运行命令来生成目标, 如以下是一个makefile的例子 (例 1.0).
# Example 1.0
# Linking object files
prog1 : main.o file1.o /
file2.o display.o
cc -o prog1 main.o file1.o /
file2.o display.o
# Compiling source files
main.o : main.c mydefs.h
cc -c main.c
file1.o : file1.c mydefs.h
cc -c file1.c
file2.o : file2.c command.h
cc -c file2.c
display.o : display.c command.h
cc -c display.c
# Compiling source files
..PHONY : clean
clean :
rm prog1 *.o
# End of makefile
注意: 参考命令回显.
在前面的例子中, 我们已经给变量objs赋了值.
objs = main.o file1.o file2.o display.o
一旦变量定义好了, 它的值可以用$(variable)或 ${variable}提取出来. 然而, 如果圆括号遗漏了, 那么只有变量名的第一个字符被用到. 因此:
•$(objs)得到变量objs的值.
•$objs 得到变量 o 的值(如果有变量o).
make能灵活的使用未定义的变量; 这种情况下值返回空串. 在设定一个变量前, make可以用快捷符'?='检查它是否已被设置. 下面的例子检查 foo是否已经被设置:
foo ?= bar
如果没有, make 将分配给它一个值.
引用替换和计算变量名
还有两个引用变量的高级特性是引用替换和计算变量名.
•引用替换
引用替换用你指定的值替换变量的值. 它的格式象这样:'$(var:x=y)', 意思是取得变量 var的值, 用y替换变量值中每个单词结尾处的x, 并取代结果字串. 下面的例子设定bar为a.c b.c c.c:
foo := a.o b.o c.o
bar := $(foo:.o=.c)
•计算变量名
在变量名内部可以引用另一个变量; 这称为计算变量名.
例:
a = b
b = c
x := $($(a))
本例赋x值为'c'. $(a)展开为 'b', 所以$($(a))即$(b); 而 $(b)展开为'c'.
递归扩展与简单扩展变量
变量有两种方法获得值, 递归扩展变量和简单扩展变量.
递归扩展变量 简单扩展变量
行赋值用'=' 或 用 "define" 指示 用行':='赋值
值的指定是逐字被装配的. 变量在定义时一次扫描获得其值
不论何时变量被替代都展开对其他变量的参考. 不包含对其它变量的参考; 包含它们被定义的值.
例:
foo = $(bar)
bar = $(yep)
yep = hello
命令:
all :;echo $(foo)
将显示'hello'. $(foo)将展开为$(bar), $(bar)展开为$(yep)最终展开为'hello'. 例:
a := foo
b := $(a) bar
a := hello
等价于:
b := foo bar
a := hello
执行是有目的的,却导致make运行速度很慢, 因为每当变量扩展时在此定义中的引用都要执行一遍. 更容易预测复杂的makefile的编写.
给变量添加更多文本
我们常需要给一个已存在的变量添加更多的文本. 快捷操作符 '+=' 提供了这种灵活性. 在下面这个例子中, 先取得objs的值然后给这个值加上文本file3.o :
objs += file3.o
这样我们就能够设定objs为main.o file1.o file2.o display.o file3.o:
objs = main.o file1.o file2.o display.o
objs += file3.o
自动变量
基于规则的目标和先决条件,此类变量的值在每条已执行的规则中都各不相同. 在下一节隐式规则的例1.2中, '$@'用于目标文件名, '$+' 用于源文件名.
通用的自动变量包括以下这些.
$@
规则目标的文件名
$%
目标成员名, 此时目标是一个归档成员
$<
第一个先决条件名
$?
所有比目标新的先决条件名
$^
全体用空格符分隔的先决条件名
$+
类似于'$^', 但是重复的先决条件会以它们在 makefile中列出的次序多次列出。
特定目标和特定模式的变量值
•特定目标变量值
根据make所建目标的不同,这个功能可以为相同的变量赋不同的值. 该值在目标的命令脚本的上下文中局部可用. 格式如下:
target ... : variable-assignment
下面的语句在prog1以及它的先决条件的命令脚本中设置CFLAGS为-g :
prog1 : CFLAGS = -g
prog1 : main.o file1.o file2.o display.o
•特定模式变量值
此特征让你为任何匹配这种特别模式的目标定义一个变量. 模式表示为 '%'. 接下来的例子中,对于所有匹配模式%.o的目标分配给CFLAGS值-o:
%.o : CFLAGS = -o
(更多信息请参考 附加参考.)
隐式规则
隐式规则告诉make如何采用惯例方法,这样就不必在每次用到时都要详细指定它们. 其中一条隐式规则是用cc -c命令更新文件'.o',其源文件是相应的'.c'文件. 用隐式规则, 前一个例子中的makefile可以写成下面的形式(例 1.2).
# Example 1.2
#1
# Defining the compiler
CC=gcc
#2
# Defining the object (objs) variable
objs = main.o file1.o file2.o display.o
#3
# Linking object files
prog1 : $(objs)
$(CC) -o $@ $+
echo prog1 : make complete
#4
# Tell make how to build .o from .c files
%.o:%.c
$(CC) -c $+
#5
# Compiling source files.
main.o : mydefs.h
file1.o : mydefs.h
file2.o : command.h
display.o : command.h
#6
# Removing the executable and object files
..PHONY : clean
clean :
rm prog1 $(objs)
echo clean : make complete
# End of makefile
注意:
1.编译器变量提供了对同一个makefile使用不同的编译器的灵活能力.
2.变量objs定义为全体目标文件.
3.'$@'自动变量意思是目标, '$+' 意思是全体以空格分隔的先决条件.
4.模式规则告诉 make如何转换*.c 文件为 *.o 文件.
5.make有一个内建模式将*.h文件转换为依赖*.o文件.
6.make删除现有目录下的prog1和全部对象文件 (见 伪目标).
以隐含规则建立的命令用到某些预定义的变量, 这些变量分成两类: 一类是程序名 (象 CC) 另一类是对应这些程序的特定变量 (如 CFLAGS).
如下是一些内建规则中用作程序名的变量.
AR
存档文件维护程序, 缺省是 ar
AS
做汇编的程序, 缺省是 as
CC
编译C程序的程序, 缺省是 cc
CXX
编译C++程序的程序 , 缺省是 g++
RM
删除文件命令, 缺省是 rm -f
下列一些变量它们的值是程序附加的参数.
ARFLAGS
给归档维护程序的标志; 缺省是 rv
ASFLAGS
给汇编器的额外标志
CFLAGS
给C编译器的额外标志
CXXFLAGS
给C++编译器的额外标志
指示
"Define" 指示
define指示给变量赋值. define指示与跟着的变量名在同一行. 变量值在下一行. 最后一行的endef表示define结束. define作用类似于'=', 举例如下:
define two-lines
echo foo
echo $(bar)
endef
普通变量赋值和define指示的区别在于普通赋值不能含换行符, 而在define的值里分割行的换行符成为变量值的一部分.
以上例子功能等同于:
two-lines = echo foo; echo $(bar)
以分号分开的两条命令作用很象两条分别的shell命令. 然而, 注意到分在两行make将调用两次shell, 对每一行运行一次独立的子shell.
"Include" 指示
指示include告诉make暂停读取当前makefile转而读取每一个列出的文件. 读完以后, make 在指示中出现的地方重新读取makefile. 格式如下:
include filenames...
filenames可以含shell文件名模式.
举个例子, 如果你有三个 '.mk' 文件, 'x.mk', 'y.mk', 及 'z.mk', 并且$(bar)是展开为 bish bash, 那么下列表达式:
include foo *.mk $(bar)
等价于:
include foo x.mk y.mk z.mk bish bash
include指示的一个用法是分配一个公共变量定义集给被各自"makefile"文件处理的程序 .
"Override" 指示
override 指示可以设置makefile中已经用命令参数设置过的变量. 格式如下:
override variable = value
给在命令行中定义的变量添加文本, 用:
override variable += more text
此指示被发明来做更改或增加用户在命令行中指定的值. 举例来说, 假定你总是希望C编译器用-g开关, 但是你又不想让用户象通常一样用命令参数指定其他开关. 你可以用override指示:
override CFLAGS += -g
命令回显
make在一行被执行前先显示它, 叫做回显. 以 '@'起始的命令则不回显 . echo也可以用来指示makefile的进度. 如下所示:
echo prog1 : make complete
加上-n标志, make只回显命令但不真正执行它们. 只有这种情况, 即使命令以 '@' 开头亦会显示. 为防止一切回显, 可以用-s标志. 输出就如同每条命令都以 '@'开头.
伪目标
伪目标不是一个文件名而是由一个显式请求引起的动作. 在伪目标中的命令不创建任何目标, 但每次目标发生重建时命令都要执行. 举例如下:
clean :
rm prog1 *.o
当命令象下面这样显式的调用时,将删除 prog1及所有目标文件:
make clean
因为rm不是创建一个名为 clean的文件, 可能这个文件根本不曾存在. 但如果某人确实在这个目录下建了名为clean的文件, 伪目标将停止工作. 文件clean在没有先决条件时将不可避免的被认为是最近更新的, 它的命令也不会执行.
为避免这个问题, 目标被定义为 .PHONY. 在下面的例子中, make clean 将运行命令而不论是否真有名clean 的文件:
.PHONY : clean
clean :
rm prog1 *.o
在文件名中用通配符
单个文件名用通配符可指代多个文件. make在目标先决条件, 命令和变量中用到通配符 '*', '?', 以及'[...]'. 通配符可以用在规则命令中, 这些命令被shell展开. 例如, 这是删除所有目标文件的规则:
clean:
rm *.o
当你定义变量时通配符是不会展开的. 即, 如果你这样写:
objects = *.o
那么变量对象的值是真实的字符串 '*.o'.
命令中的错误
命令执行完以后, make 检查退出状态以验证其已经成功完成, 同时发送下一条命令到新shell. 如果返回错误, make 放弃当前规则,很可能放弃所有规则. 要忽略命令行中的错误, 在行开头加上'-'号 (在初始的tab后). '-'号在命令传递给shell执行前丢弃. 下面的例子即使rm不能删除文件它也继续运行, :
clean:
-rm -f *.o
类似的特性也可以通过在make后加-i参数实现; 全体规则中的全部命令的错误都被忽略.
并行执行
缺省情况下, make一次只运行一条命令, 在执行下一条命令前等待上一条命令执行结束. make用'-j'选项可以同时执行多条命令. '-j'选项后缀的整数指定一次可以运行作业的数量.
用make更新归档文件
被叫做成员的归档文件通常用作链接的子程序库. 它们由程序ar维护. 格式如下:
archive(member)
make中独立的文件成员可以作为目标或先决条件. 在下面的例子中, 是要通过复制文件foo.o在归档sublib中创建成员foo.o :
sublib(foo.o) : foo.o
ar cr sublib foo.o
结论
本文是使用makefile入门教程, 内容涉及所有知识面. make还有许多特性可以在下列参考中发现. 用你自己的新makefile试一试.
更多参考
1.GNU Make, 一个用于指导重编译的程序
2.Sun的make官方参考页(Sun official reference page on make)
关于作者
Suminder Singh Ahuja 是一位有四年经验的软件工程师, 他工作的领域是密集事务技术和数据库,包括TPF, IBM 370/390, VM/CMS, 以及UNIX. 他在Galileo工作了三年, 主管航空订票解决方案. 联系他 Suminder: suminder@rediffmail.com
July 2002