有稍稍在Linux下碰过程式设计的开发者应该会知道, make是用来将程式码、函式库、标头档及其它资源档build成最终成果(即:最终的应用程式)的超强力辅助工具。
当然了,并不是非得动用到make才能build程式,或许有什么程式设计魔人喜欢什么都自己手动进行;但利用make及其参考档(输入档案) Makefile将会让整个编译工作轻松许多。 若您曾经打包过Debian Package,那么应该会发现debuan/rule这个档案的语法和Makefile几乎是一模一样,所以学习Makefile的语法对于Debian Package Maintainer而言也是一门必要的功课。
Makefile 语法:
以下为Makefile 的基本语法:
注解:
以# 开头的即为注解。变数宣告: (有人称之为巨集)
语法:
MACRO = value 变数名称为大小写相异。 在惯例上,Makefile内部使用的变数名称使用小写 ;而使用者很可能从命令列自行另外指定数值的变数,像是CFLAGS ,则是使用大写 。 利用MACRO =来取消该变数。
在Makefile中,可利用$(MACRO)或${MACRO}来存取已定义的变数。 例:
targets = foo
$(targets) : common.h
gcc -o $(targets) foo.c效果等同:
foo : common.h
gcc -o foo foo.c:= 语法
注意到,make 会将整个Makefile 展开后,再决定变数的值。 也就是说,变数的值将会是整个Mackfile 中最后被指定的值。例:
x = foo
y = $(x) bar
x = xyz
# y的值为xyz bar
在上例中, y的值将会是xyz bar ,而不是foo bar 。
您可以利用:=来避开这个问题。 :=表示变数的值决定于它在Makefile中的位置,而不是整个Makefile展开后最终的值。
在上例中, y的值将会是foo bar ,而不是xyz bar了。
x := foo
y := $(x) bar
x := xyz
# y的值为foo bar?= 语法:
?= 是一个简化的语法:若变数未定义,则替它指定新的值。 否则,采用原有的值。 例:
若FOO未定义,则FOO = bar ;若FOO已定义,则FOO的值维持不变。
FOO ?= bar
+= 语法:
例:
CFLAGS = -Wall -g
CFLAGS += -O2此时CFLAGS的值就变成-Wall -g -O2了。
define 语法:
使用define 语法的唯一优点是它可以让变数直接使用『断行』。 例:
上例可以视同于:
define foo
uname -a
echo $$SHELL
endef
all:
$(foo)
foo = uname -a; echo $$SHELL
all:
$(foo)注意到在上例中使用了$$,让'$' 能传到Shell 中。
在target里另外指定变数的值
可以在target里另外指定变数的值。 例:
foo = abc
all: foo = xyz
all:
echo $(foo)
#此时,foo的值为xyz
以下的语法提供了和上例相同的功能:
all: override foo = xyz
all: export foo = xyz
make 也可以存取环境变数。 例:
all:
@echo $(CFLAGS)在上例中,虽然在Makefile里虽然没有指定CFLAGS的值,但make会试图以环境变数来代出CFLAGS的值。
可搭配wildcard指令在变数里展开* ? [...]等万用字元。 例:
objects=$(wildcard *.o) 规则: (Rule)
指示make 如何进行编译。
主要语法:
target : dependencies
<Tab> Commands或
target : dependencies; Commands
<Tab> CommandsRule指示了make如何建立target ;及何时要重新建立target 。
target :所要建立的档案< /td> dependencies :相依项目。 make会据此决定是否要重新编译target 。 Commands :建立target的指令。 在Makefile 里并没有限定Rule 的先后顺序。 但预设上,make会参考all这个目标项目,并依据它的dependencies来决定要建立哪些项目。 若没有all项目,则会采用Makefile里的第一个项目。
target : (目标项目)
这个项目所要建立的档案,必须以:结尾。 例:
其中, foo.o是这个项目要建立的档案; common.h是相依性的项目/档案;而gcc -c foo.c则为要产生这个项目所要执行的指令。
foo.o: common.h
gcc -c foo.c
make在编译时,若发现target比较新,也就是dependencies都比target旧,那么将不会重新建立target ,如此可以避免不必要的编译动作。
若该项目并非档案,则为fake项目 。 如此一来将不会建立target档案。 但为了避免make有时会无去判断target是否为档案或fake项目,建议利用.PHONY来指定该项目为fake项目。 例:
.PHONY: clean
clean:
rm *.o在上例中,若不使用.PHONY来指定clean为fake项目的话,若目录中同时存在了一个名为clean的档案,则clean这个项目将被视为要建立clean这个档案 ,但clean这个项目却又没有任何的dependencies ,也因此, clean项目将永远被视为up-to-date , 永远不会被执行 。
因为利用了.PHONY来指定clean为fake项目,所以make不会去检查目录中是否存在了一个名为clean的档案。 如此也可以提升make 的执行效率。
其它类以.PHONY的语法请参考:
另外,如果某个非fake项目的target的dependencies包含了fake项目的话,因为make一定会执行fake项目,这样一来,这个非fake项目的target一定也会被执行。 这可能不是理想的做法。
GNU `make': 4.9 Special Built-in Target Names
dependencies : (相依性项目,以空白间隔)
dependencies是指定在建立target之前,必须先检查的项目。 可以不指定。 例:
foo.o: common.h
gcc -c foo.c上例中是指:检查common.h 。 如果它的建立日期比foo.o新,就执行gcc -c foo.c来重新产生foo.o 。 也就是说,可以依需求建立dependencies ,即使它和target一点关系也没有。
相依性项目可以是Makefile中其它的target 。 也因此,在建立该target之前,它会先检查在dependencies里所指定的所有target。
Commands : (即为要执行的Shell指令)
必须以<Tab>开头。 使用Shell Script 语法。 在Makefile里,只要以<Tab>开头都将会被视为Shell Script执行。
每条法则必须写在同一行。 每条Command会启动一个新的 Shell,预设为/bin/sh 。 若执行完某条Command但传回了错误值,make就会中断执行。
因为每条Command会启动一个新的 Shell,所以有时执行的指令必须写在同一行,像是使用if来进行条件判断,此时可以用;来分隔指令。 例:
all:
if [ -f foo ]; then rm foo; fi
而以下是错误示范:
这时因为make只会检查最后一个指令的传回值,所以在以上指令中,即使subdir不存在,但make并不会因而中断执行,并会继续执行$(MAKE)指令,而产生了不可预期的结果。
all:
cd subdir; $(MAKE)
为了避免这个问题,可以利用&&来检查其中某个指令是否成功执行,再决定是否执行下个指令。 例:
all:
cd subdir && $(MAKE)
特别字元:
@:不要显示执行的指令。
-:表示即使该行指令出错,也不会中断执行。
例:
因为make会一行一行将正在执行的Commands显示在萤幕上,但您可以利用@来暂时关闭这个功能。
.PHONY: clean
clean:
@echo "Clean..."
-rm *.o而make 只要遇到任何错误就会中断执行。 但像是在进行clean 时,也许根本没有任何档案可以clean,因而rm 会传回错误值,因而导致make 中断执行。 我们可以利用-来关闭错误中断功能,让make不会因而中断。
隐性法则:
在上例中的:
foo.o: common.h
gcc -c foo.c由于产生foo.o的指令就是gcc -c foo.c ,因此在Makefile里可以将其简化为:
foo.o: common.h
此时make会依据target的副档名来猜测该如何编译target 。 如此可以让Makefile更为简洁。
您可以利用【空白指令】来避免make 依据隐性法则而进行编译。 例:
foo.o: common.h
<Tab>
内部变数:
$?: 代表已被更新的dependencies的值。
也就是dependencies中,比targets还新的值。$@: 代表targets的值。 $<: 代表第一个dependencies的值。 $* :
代表targets所指定的档案,但不包含副档名。
例:
这样会将foo1.c foo2.c foo3.c中已有更新的内容印至印表机。
print: foo1.c foo2.c foo3.c
lpr -p $?
touch print内部函数:
您可以在Makefile 使用make 所支援的一些内部函数。 详情请参考:
GNU `make': 8 Functions for Transforming Text
条件判断:
可以在Makefile 中使用以下的条件判断语法。 但由于它们不是rule,所以不可以<Tab>开头;但其后要执行的指令则必须以<Tab>开头,make才会视其为Shell指令。
ifeq: (检查value1 , value2是否相等)
ifneq: (提供和ifeq相反的功能)
ifeq (value1, value2)
...
else
...
endif
ifdef: (检查variable变数是否为空的)
ifneq (value1, value2)
...
else
...
endif
ifdef variable
...
else
...
endif
ifndef: (提供和ifdef相反的功能)
ifdef variable
...
else
....
endif引入档案:
将外部档案引入Makefile 中。 可以视为直接在此将该档案内容全数插入Makefile 中。
例:
将foo.in的内容全数引入Makefile里。
include foo.in
可以同时引入多个档案、使用变数$(MACRO)或是使用万用字元(* ?或[...]) 。 例:
include foo.in common*.in $(MAKEINCS)
子目录:
如果该专案有多个目录,且每一个目录中都有Makefile,则利用以下指令来进入子目录并进行编译:
例:
cd dir && $(MAKE)
SUBDIRS = dir1 dir2 dir3
all:
for i in $(SUBDIRS); do
(cd $$i && make);
done
clean:
for i in $(SUBDIRS); do
(cd $$i && make clean);
done
install:
for i in $(SUBDIRS); do
(cd $$i && make install);
done
make 参数:
可以用make 的参数来盖过Makefile 里,用变数所指定的参数。 例:
您可以在Makefile里使用override来避免变数的值被make的参数所取代。 例:
make CFLAGS="-g -O2"
可以在make后指定要重新建立的target 。 例:
override CFLAGS = -Wall -g
以上会执行Makefile中的clean区段。
make clean