Chinaunix首页 | 论坛 | 博客
  • 博客访问: 198871
  • 博文数量: 69
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 720
  • 用 户 组: 普通用户
  • 注册时间: 2005-08-03 11:35
文章分类

全部博文(69)

文章存档

2011年(13)

2010年(46)

2009年(10)

我的朋友

分类:

2010-10-08 23:00:38

 Make100928

GNU Make的使用简记 1

 

 

Email:

zcatt@163.com

Blog

http://zcatt.cublog.cn

 

 

 


文档简要整理了GNU Make的语法,函数,和命令。备忘和参考。本文的pdf格式已经发布到百度wenku.

 

命令行使用

用法

GNUmakefilemakefile,和Makefilemake的默认处理的脚本文件。直接使用make命令就会处理当前目录下的makefile脚本

make

如果你的make脚本文件名不是makefile,则应当使用下面三种形式之一.

make -f your_make_file

make --file=your_make_file

make --makefile=your_make_file

 

make后面不指定target时,将会更新default targetmake后面当然也可以指定要更新的target,脚本中经常定义的target

all 完成所有的编译,安装等工作

clean  清楚中间的和生成的文件, 例如objexe

install    将生成的结果文件copy到指定的系统目录下,完成安装工作

tar 打包源码,生成tar文件

dist   生成发布版本。

test   执行测试相关的动作。

 

默认的执行脚本中的第一个target, 若指定target时,使用如下。

make your_target

make --makefile=your_make_file your_target

 

make返回的exit status如下

0   make成功执行,正常推出

2   make执行中发生了error,退出

1   使用'-q'运行make, 并发现有target没有更行。

make命令行可以带一些选项option.

 

'-n', '--just-print'

仅打印出要执行的recipe,而不实际执行

 

'-t''--touch'

仅更新target文件的时标timestamp,满足时间依赖关系,而不实际执行recipe生成/改变target。例如源文件变了,而不想实际编译产生对应的obj,可以使用这个选项。

 

'-C dir', '--directory=dir'

变更路径到dir,然后读入makefile

 

'-I', '--include-dir=dir'

指定搜寻的dir,用于查找included makefile

 

'-s', '--silent', '--quiet'

recipe执行时不要打印输出提示。

概念

规则

makefile脚本文件最基本的元素是rule(规则)。 Rule的形式如下

targets: prerequisites

    recipe

   

   

 

or

 

targets : prerequisites ; recipe

    recipe

   

 

目标 : 依赖项

    动作

   

   

目标 : 依赖项 ; 动作

    动作

   

 

target时间依赖prerequisites, 也就是说如果prerequisites发生变动了(时间新于target),破坏了target时间依赖于prerequisites的条件,那么这个rule将被选中,执行recipe定义的动作,通常是生成时间更新的target文件。

注意, recipe前是一个tab字符, 而不是空格。makefile脚本中长行需要折行时可以行尾使用'\'

 

Rule中如果prerequisites是空,这样的rule,总是认为依赖条件满足,recipe不会被选中执行。除非在make中明确指明的调用。

 

makefile脚本文件通常由多个rule组成, 第一个rule称为default rulemake默认从default ruletarget开始, 除非make特别指定其他target

简单的例子

HelloWorld : helloworld.o display.o

       gcc -o HelloWorld helloworld.o display.o

 

helloworld.o : helloworld.c helloworld.h display.h

       gcc -c helloworld.c

 

display.o : display.c display.h

       gcc -c display.c

 

clean :

       rm HelloWorld helloworld.o display.o

 

 

小结

1Rule三要素: target, prerequisites, recipe

2prerequesites为空的rule,不会被选择执行,除非make特别指明它执行。

3make默认从default ruletarget开始执行。

 

变量

变量的定义

varName=varValue1 varValue2 …

变量的使用

$(varName)

 

or

 

${varName}

 

例如

Object=helloworld.o display.o

那么碰到$(Object),将被展开称helloworld.o display.o

 

脚本的元素

makefile脚本有五种元素组成

1.       explicit rule, 显示规则, 具体列出的方式指定target

2.       implicit rule, 隐式规则, 针对具有相同文件名特征的文件的规则。

3.       variable definition, 变量定义语句

4.       directive, 命令语句,定义make应当采取的特别处理。

5.       comment, 注释,'#'开始直到行尾。

 

make处理脚本分成两个阶段:

阶段1 read-in phase: 读入脚本,展开显示和隐式的变量,分析各个Rule,构造target的依赖图(dependency graph)。

阶段2target-update phase: 根据依赖图结构, 调用相应的recipe,生成相关的需要创建和更新的target.

 

变量和函数的扩展处理发生在阶段1的,称作是立即的,immediate;不是immediate的称作是延迟扩展,deferred

简而言之, target, prerequisite的变量和函数的扩展处理是immediaterecipe中是deferred的。因此,写makefile时应小心recipe中指定的filetargetprerequisite中指定的是否是一致

rule各部分的immediatedeferred

immediate : immediate

    deferred

   

variable定义的情况

immediate=deferred

immediate=deferred

immediate:=immediate

immediate+=deferred or immediate

(+=的右项如果是':='设置的简单变量,则是immediate,否则是deferred.)

 

小结

1  变量和函数的扩展在targetprerequisite中是立即的immediate recipe中的是延迟的deferred

2  变量定义中 '='的右端是deferred ':='的右端是immediate的。

通配符)

文件名中使用的通配符'*''?''[…]'位于targetprerequisite时, make负责展开;位于recipe时,由shell负责展开。

注意,变量定义中的通配符实际是在targetprerequisiterecipe中用到时才展开。(todo: deferred的?)

二次 )

使用.SECONDEXPANSION$$

(todo)

1)当前路径下target file不存在时,会执行其他路径的搜寻,例如VPATHvpath指定的路径。

2)如果找到,则路径会暂时记下,作为target file的路径。

3)如果target不需要重建和更新,则这个路径将作为target file的路径。

4)如果target需要重建和更新,则这个路径将被废弃,而使用makefile中指定的target file的路径。

 

因此,写makefile时应小心target file到底路径是哪个。

如果target的名字不是文件名,这样的target称作Phony Target。尤其如果phony targetprerequisite是空时,rule的时间依赖关系总是认为是满足的,默认不会被make选中执行它的recipe,除非特别指明的调用。

为了防止phony target的名字同文件名的无意冲突, 可以使用.PHONY声明phony target.

例如

.PHONY: clean

 

clean:

    rm *.o temp

因为phony target指称的不是实际文件,所以make处理phony targetrule时,会忽略对implicit rule(隐式rule)的搜索。这就是phony target拥有较高性能的原因。

 

一个例子,phony的方式makefile1优于makefile2

#makefile1

SUBDIRS = foo bar baz

.PHONY: subdirs $(SUBDIRS)

subdirs: $(SUBDIRS)

$(SUBDIRS):

    $(MAKE) -C $@

 

 

#makefile2

SUBDIRS = foo bar baz

subdirs:

    for dir in $(SUBDIRS); do \

        $(MAKE) -C $$dir; \

    done

 

.PHONY

声明phony targets

 

.SUFFIXES

声明后缀,这些后缀将使用后缀规则suffix rule

 

.

声明中间文件

 

.SECONDEXPANSION

这个声明后的所有ruleprerequisite在说有makefile读入后还会执行第二次展开。

 

.IGNORE

如果一个rule规则的recipe执行发生错误,但想忽略发生的错误,可以用.IGNORE声明。

 

.SILENT

声明的rule规则在执行recipe时将不输出提示。

 

.EXPORT_ALL_VARIABLES

输出所有的变量定义到子进程。

 

.DELETE_ON_ERROR

如果声明,则某个rulerecipe执行错误,则删除它的target文件。

 

通常一个rule规则仅有一个target目标。但也允许一个rule规则有多个target目标和一个target目标作为多个rule规则的target..

对于后者,只能有一个rulerecipe执行。有多个rulerecipe时,会报错。如果希望多处定义recipe,应当使用'::'

 

静态模式规则

静态模式规则用在多个target时,根据target的名字构建prerequisite

targets : target-pattern : prereq-patterns

    recipe

   

targets中的每个target都匹配target-pattern,提取出匹配部分,称作stem,替换prereq-patterns中的标记部分,形成prerequisite.

匹配符使用'%', 在一个pattern中只能使用一次。

例子

objects = foo.o bar.o

all: $(objects)

$(objects):%.o:%.c

    $(CC) -c $(CFLAGS) $< -o $@

 

targets中不符合target-patterntarget可以使用filter函数滤除。

files = foo.elc bar.o lose.o

$(filter %.o,$(files)):%.o:%.c

    $(CC) -c $(CFLAGS) $< -o $@

$(filter %.elc,$(files)):%.elc:%.el

    emacs -f batch-byte-compile $<

 

双冒号的使用

一个target出现在多个rule中时,这些rule要么都使用单冒号':',要么都使用双冒号':'

双冒号的不同之处在于,每一个rule都将被独立处理,不会影响其他的rule。哪一个rule不满足依赖关系了,哪一个rulerecipe被执行。

Prerequisite为空的Rule

如果一个ruleprerequisiterecipe都是空的,并且target不是存在的文件(phony target),那么每次make执行时,这个rule都将被选中,执行更新。依赖这个targetrule自然也都被选中,执行更新。

文件

使用touch创建和更新一个空文件,用于make时间依赖关系中。

一个例子

print:foo.c bar.c

    lpr -p $?

    touch print

 

的语法

Rulerecipe部分,已tab开头,make只做很少的处理就转交给shell处理。

1)  recipe中的comment不是makecomments,而会原封不动转交给shell.

2)  recipe中的变量定义(不是变量引用)被认为是shell的内容,而不是make的变量定义, 会直接转交给shell

3)  recipe中的条件表达式(ifdef,ifeq,etc)也被视作shell的内容,直接转交给shell处理。

默认目标)

脚本的第一个target就是default goal。但'.'开始的target(不含'/')不能做default goal, 同样, 定义pattern ruletarget也不能做default goal

第一个rule有多个target时,第一个targetdefault goal

targetprerequisite中,如果需要用到'$'符号, 应该使用'$$', 因为变量使用了'$'

 

当需要prerequisite发生更新却又想不引起rulerecipe执行,仅当target不存在才执行recipe生成target时,可以使用order-only prerequisite,也就是仅反映存在依赖,而不反映时间依赖时,可以使用order-only prerequisite.

targets : normal-prerequisites | order-only-prerequisites

一个典型例子:

OBJDIR := objdir

OBJS := $(addprefix $(OBJDIR)/,foo.o bar.o baz.o)

$(OBJDIR)/%.o : %.c

$(COMPILE.c) $(OUTPUT_OPTION) $<

all: $(OBJS)

$(OBJS): | $(OBJDIR)

$(OBJDIR):

mkdir $(OBJDIR)

这个例子中,o文件的更新会导致dir更新,因此dir的更新不应要求o文件再更新。

 

中的变量引用

Recipe中变量引用的展开发生在makefile全部读入完毕,也就是deferred。并且仅是那些需要更新targetrule中的变量引用才会被展开。参见2.4

makefile其他部分的使用方法一样,实际的'$'字符需要用'$$'表示。

中的Echo

makeecho功能是指,make在执行recipe中的语句前会打印出要执行的语句。若不想打印输出,可以在句首前缀'@'make传递给shell的语句中会自动去除'@'符号。

'-s'命令选项可以禁断所有的make echo

的执行

recipe的每一行,make都会调用一个新的子shell执行。如果设置了.ONESHELL,则会在一个shell中执行recipe的所有行。

因此,对于recipe中的命令行,每个子shell的环境是相对独立,不互相影响的。例如,第一个语句的cd命令变更了当前路径,但这不会影响到第二句的当前路径。一个解决的办法是用'&&',将两个命令写到一行中,这已经是shell的语法了。

foo : bar/lose

    cd $(@D) && gobble $(@F) > ../$@

另外,通常make一次仅执行一个recipe,等执行完后,才执行下一个recipe。当然,可以配置make并行执行,具体见文档[1]

recipe每句话执行完毕(每个子shell)后,make都会检查返回的状态,如果错误,就会放弃recipe的执行,甚至终止make的执行。出于某种需要,如果想忽略这样的错误,继续执行,可以在recipe的语句前加'-'.IGNORE也有类似效果。

clean:

    -rm -f *.o

的递归

脚本中出现make命令,是为make的递归。递归使用中,makefile脚本中建议使用$(MAKE)而不要直接使用make命令,因为运行环境的不一样,直接使用make跨平台性不好。

父子之间的环境变量传递,可以使用export/unexport命令。默认的MAKEFLAGSMAKEFILES都会被传递到子make中。MAKELEVEL是嵌套的深度,从0开始。

 

的复用

如果命令序列会被重复使用,则可以使用define…endef定义成recipe块,使用的时候简单引用就可以了。这实际上是一种特殊形式的变量定义而已。

define run-yacc =

yacc $(firstword $^)

mv y.tab.c $@

endef

 

 

foo.c : foo.y

    $(run-yacc)

注意块名实际是变量名,因此不要与已有的变量名冲突。

recipe的目的是为了避免implicit rule的影响。建议的形式是';'后跟一个空格

target: ;

 

隐式规则

隐式规则通常是作用于一类target的规则。当make找不到某个target的显式规则时,就会尝试寻找适用的隐式规则,创建和更新target

隐式规则的通常形式是根据文件后缀名决定处理的工具,生成的文件。例如.c文件用c编译器,生成.o文件。

模式规则

同一般规则相比,pattern ruletarget中含有通配符'%'

%.o:%.c

    $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@


Locations of visitors to this page

阅读(1422) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~