Makefile 语法简介

  http://tetralet.luna.com.tw/index.php?op=ViewArticle&articleId=185

有稍稍在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展开后最终的值。

x := foo 
y := $(x) bar 
x := xyz 
# y的值为foo bar 
在上例中, y的值将会是foo bar ,而不是xyz bar了。
?= 语法: 
?= 是一个简化的语法:若变数未定义,则替它指定新的值。   否则,采用原有的值。   例:  
FOO ?= bar 
FOO未定义,则FOO = bar ;若FOO已定义,则FOO的值维持不变。  
+= 语法:
例:  
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> Commands

Rule指示了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的语法请参考: 

GNU `make': 4.9 Special Built-in Target Names
另外,如果某个非fake项目的targetdependencies包含了fake项目的话,因为make一定会执行fake项目,这样一来,这个非fake项目的target一定也会被执行。   这可能不是理想的做法。  
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 

而以下是错误示范:

all: 
cd subdir; $(MAKE) 
这时因为make只会检查最后一个指令的传回值,所以在以上指令中,即使subdir不存在,但make并不会因而中断执行,并会继续执行$(MAKE)指令,而产生了不可预期的结果。  

为了避免这个问题,可以利用&&来检查其中某个指令是否成功执行,再决定是否执行下个指令。   例:  
all: 
cd subdir && $(MAKE) 
特别字元:
@:不要显示执行的指令。  

-:表示即使该行指令出错,也不会中断执行。 

例: 

.PHONY: clean 
clean: 
@echo "Clean..." 
-rm *.o
因为make会一行一行将正在执行的Commands显示在萤幕上,但您可以利用@来暂时关闭这个功能。

而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所指定的档案,但不包含副档名。

例:  
print: foo1.c foo2.c foo3.c 
lpr -p $? 
touch print 
这样会将foo1.c foo2.c foo3.c中已有更新的内容印至印表机。

内部函数:

您可以在Makefile 使用make 所支援的一些内部函数。   详情请参考:  
GNU `make': 8 Functions for Transforming Text  

条件判断:

可以在Makefile 中使用以下的条件判断语法。 但由于它们不是rule,所以不可以<Tab>开头;但其后要执行的指令则必须以<Tab>开头,make才会视其为Shell指令。 

ifeq: (检查value1 , value2是否相等)  
ifeq (value1, value2) 
... 
else 
... 
endif 
ifneq: (提供和ifeq相反的功能)
ifneq (value1, value2) 
... 
else 
... 
endif 
ifdef: (检查variable变数是否为空的)  
ifdef variable 
... 
else 
... 
endif 

ifndef: (提供和ifdef相反的功能)  
ifdef variable 
... 
else 
.... 
endif

引入档案:

将外部档案引入Makefile 中。 可以视为直接在此将该档案内容全数插入Makefile 中。

例:

include foo.in 
foo.in的内容全数引入Makefile里。  

可以同时引入多个档案、使用变数$(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 里,用变数所指定的参数。 例: 

make CFLAGS="-g -O2" 
您可以在Makefile里使用override来避免变数的值被make的参数所取代。   例:  
override CFLAGS = -Wall -g 
可以在make后指定要重新建立的target 。   例:  
make clean 
以上会执行Makefile中的clean区段。  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值