Chinaunix首页 | 论坛 | 博客
  • 博客访问: 902537
  • 博文数量: 113
  • 博客积分: 3160
  • 博客等级: 少校
  • 技术积分: 1801
  • 用 户 组: 普通用户
  • 注册时间: 2011-08-19 10:09
文章分类

全部博文(113)

分类: LINUX

2012-05-28 17:07:29


8 Makefile的基本应用



当你要编译一个比较大的工程时,makefile就能派上用场了。因为,一个工程的源文件可能很多,而且会分散在多个目录中,你需要大量的命令来编译链接这些文件。当你更新其中一些源文件的时候,就不得不再次输入命令重新编译链接,这样就使得编译的效率很低,并且重复多次输入大量的命令很容易出错。而makefile就是通过定义一系列的规则,来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件在更新之后需要重新编译,从而省去了多次输入命令的麻烦。总之一句话,makefile就是一个能实现自动化编译链接的文件,它能大大提高软件开发的效率。


我先总结一点关于makefile的知识和概念,而后讲解一个比较复杂的makefile。下面的东西,基本涉及了makefile的基础应用,只要你能理解下面这些东东,查看makefile文件或者自己编写makefile做常规的工程也就够了。当然,能深入了解是更好的。


8.1makefile主要语法总结


1target.... : prerequisitie...

command

....

注释:target是目标文件,也可以是执行文件,还可以是标签(label);

prerequisities是要生成target所需要的文件或者目标;

commmandmake执行的命令,其前面必须有TAB键!!!

解释:target这一个或者多个目标文件依赖prerequisites中的文件,其生成规则

定义在command中。这就是makefile的规则和核心。


2 .PHONY : 定义一个标记为“伪目标”

例如:清空目标文件的规则

.PHONY : clean

clean :

-rm edit $(objects)

.PHONY表示clean是一个伪目标。clean不能在文件的开头,否则会成make的默认目标。


3:自动化变量

$< 表示所有的依赖目标集(例如foo.c,bar.c

$@ 表示目标集(foo.o,bar.o


4: 声明一个变量

例如:objects = main.o kdb.o command.o display.o \

insert.o search.o file.o util.o

我们可以在makefile中以$(objects)的方式来使用这个变量。


5:自动生成依赖关系

当我们编写的源代码文件越来越多的时候,每个文件之间的依赖关系可能会记不清楚,我们可以利用编译器来实现。大多数C/C++编译器支持一个“-M的选项,即自动寻找源文件包含的头文件,并生成一个依赖关系。

例如: gcc -M main.c

其输出为:main.o : main.c defs.h

如果使用GNU编译器,得使用“-MM参数。否则“-M参数会把一些标准库头文件包含进来。


6GNUmake工作方式

1:读入所有的makefile

2:读入被include的其它makefile

3:初始化文件中的变量;

4:推导隐晦规则,并分析所有规则;

5:为所有的目标文件创建依赖关系链;

6:根据依赖关系决定那些目标要重新生成;

7:执行命令。

8.2:一个makefile实例的解析


这个复杂的Makefile实例来自维基,这段Makefile是为GNU tar程序设计的代码,相当的复杂。之所以开始就罗列这么“复杂”的例子,一方面是因为,具有实际应用的例子更有学习的价值,完成会它比我自己写一个专为讲解makefile的教学代码更有挑战性;另一方面是因为,makefile其实并不难,当你读完这个复杂的makefile时,你会发现,所谓的makefile也不过如此。下面每行代码我会仔细注释和讲解。


# makefile中,’#后面的为注释,和bochs的配置文件bochsrc注释方式差不多

# 这是第一个概念,下面的语句是一个变量的定义。

# 它类似于C语言的宏定义,你可以以$()形式引用,

# 它会在所使用的地方原模样的展开。

# 例如下边的代码把/bin/sh定义成SHELL

# 当下文以$(SHELL)出现的时候,makefile在执行的时候会将其展开成/bin/sh

SHELL = /bin/sh

# 同理,下面这些都一样。

srcdir = .

CC = gcc -O

YACC = bison -y

INSTALL = /usr/local/bin/install -c

INSTALLDATA = /usr/local/bin/install -c -m 644

DEFS = -DSIGTYPE=int -DDIRENT -DSTRSTR_MISSING \

-DVPRINTF_MISSING -DBSD42


RTAPELIB = rtapelib.o

# 等号后边什么都没有表示空格的意思

LIBS =

DEF_AR_FILE = /dev/rmt8

DEFBLOCKING = 20

CDEBUG = -g


# 看到这里的$(CDEBUG) $(srcdir)了吧

# makefile执行的时候它们都会原样展开在使用的地方

# 还有一个比较奇怪的就是每行最后的那个‘\

# 它和C语言里的'\'是一个意思,都是接续行

# 注意\后面不能有空格之类的,这个容易出错。之所以用它完全是为了美观。

CFLAGS = $(CDEBUG) -I. -I$(srcdir) $(DEFS) \

-DDEF_AR_FILE=\"$(DEF_AR_FILE)\" \

-DDEFBLOCKING=$(DEFBLOCKING)

LDFLAGS = -g

prefix = /usr/local


binprefix =

# 这个展开之后就变成了 bindir = /usr/local/bin,就是一个目录

bindir = $(prefix)/bin


infodir = $(prefix)/info

# 下边这一大堆都是在定义变量,之所以这么做,主要是为了简化后边的程序

SRC1 = tar.c create.c extract.c buffer.c \

getoldopt.c update.c gnu.c mangle.c

SRC2 = version.c list.c names.c diffarch.c \

port.c wildmat.c getopt.c

SRC3 = getopt1.c regex.c getdate.y

SRCS = $(SRC1) $(SRC2) $(SRC3)

OBJ1 = tar.o create.o extract.o buffer.o \

getoldopt.o update.o gnu.o mangle.o

OBJ2 = version.o list.o names.o diffarch.o \

port.o wildmat.o getopt.o

OBJ3 = getopt1.o regex.o getdate.o $(RTAPELIB)


# 这个属于变量的嵌套定义,跟上边的差不多

OBJS = $(OBJ1) $(OBJ2) $(OBJ3)

AUX = README COPYING ChangeLog Makefile.in \

makefile.pc configure configure.in \

tar.texinfo tar.info* texinfo.tex \

tar.h port.h open3.h getopt.h regex.h \

rmt.h rmt.c rtapelib.c alloca.c \

msd_dir.h msd_dir.c tcexparg.c \

level-0 level-1 backup-specs testpad.c

# .PHONY 它定义一个伪目标,具体意思等我讲完下面的再说

# 注意,makefile的第一个目标(不管伪不伪的),将成为最终的目标文件

.PHONY: all

all: tar rmt tar.info

.PHONY: tar

tar: $(OBJS)

$(CC) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)

############################################################################

# 下面这段代码是makefile的核心规则:确定依赖关系和编译规则

# target : prerequisities

# command

# 上面列的这段代码就是下面的始祖。。。。

# 其中target就是目标文件,下面的rmt就是target

# prerequisities是生成target所需要的文件,rmt.c就是prerequisities

# command就是生成target所需要执行的命令。

# $(CC) $(CFLAGS) $(LDFLAGS) -o $@ rmt.c展开的意思就是 gcc -g -o rmt rmt.c(简单吧)

# 其中那个很古怪的$@代表的是target,本段代码中就是rmt,它是一个自动化变量,关于自动化变量,我等会说

rmt: rmt.c

$(CC) $(CFLAGS) $(LDFLAGS) -o $@ rmt.c

#############################################################################

tar.info: tar.texinfo

makeinfo tar.texinfo

############################################################################

# 好了,现在就说说.PHONY的意思吧,在两行‘#围起来的都属于它的管辖范围

# 定义它们相当与为人操作makefile提供了一个接口。当你需要执行某项特定的任务时,你就可以make加上伪目标的名字,而不是从头到尾执行一遍makefile

# .PHONY定义了一个伪目标(下面的install就是一个伪目标)

# 为什么叫伪目标,因为它和前面的“target不一样,它不需要依赖文件,就是所谓的prerequisities

# 并且,也不会生成一个叫install的文件,它只是一个标签。当make它的时候,它只是执行后面那一堆命令,这有点类似变量的定义。

# 但是,这里很显然在‘:’后多了一个依赖文件:all(其实它也是一个伪目标,就是一个标签,请看前面的代码)

# 这说明伪目标可以有依赖文件,但是这个依赖文件稍微有点不同。

# 当你执行make install的时候,如果它依赖的那个也是个伪目标,它就会首先执行伪目标的命令,然后才执行自己的命令。

.PHONY: install

install: all

$(INSTALL) tar $(bindir)/$(binprefix)tar

-test ! -f rmt || $(INSTALL) rmt /etc/rmt

$(INSTALLDATA) $(srcdir)/tar.info* $(infodir)

# 当你make install 的时候,makefile文件会找到install后面那堆命令,并执行它

# 由于这些命令的执行还需要all,所以它会先make all,然后再来make install


############################################################################

$(OBJS): tar.h port.h testpad.h

regex.o buffer.o tar.o: regex.h


# 这个比较有趣,要生成testpad.h,必须先执行testpad,而这个testpad是由teatpad.c生成的,可能这里面testpad.h不是testpad.c的头文件吧。

testpad.h: testpad

./testpad


# 好了,有碰到$@这个自动化变量了

# 自动化变量,就是它会把模式中所定义的一系列的文件自动地挨个取出,

# 直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。

# 有点不明白吧,其实说白了就是$@代表目标文件集合,就是target

# 另一个比较常用的$<表示所有的依赖文件集合,就是prerequisities

# 并且这种变量只能出现在command中,不能跑到其它的地方

testpad: testpad.o

$(CC) -o $@ testpad.o

TAGS: $(SRCS)

etags $(SRCS)

# 又碰到了一个伪目标,当make clean的时候,就会老老实实执行后面那个命令

.PHONY: clean

clean:

rm -f *.o tar rmt testpad testpad.h core


# 下面这个伪目标有依赖文件,说明,当你要make distclean之前,

# 它会先执行make clean,因为clean也是个伪目标

.PHONY: distclean

distclean: clean

rm -f TAGS Makefile config.status

.PHONY: realclean

realclean: distclean

rm -f tar.info*


# 最后这三个的形式差不多,都是定义伪目标,而且伪目标还有依赖关系。

# 后面那些稀奇古怪的不用管,这个东东先不说了,一般很少用,俺也不熟悉

.PHONY: shar

shar: $(SRCS) $(AUX)

shar $(SRCS) $(AUX) | compress \

> tar-`sed -e '/version_string/!d' \

-e 's/[^0-9.]*\([0-9.]*\).*/\1/' \

-e q

version.c`.shar.Z

.PHONY: dist

dist: $(SRCS) $(AUX)

echo tar-`sed \

-e '/version_string/!d' \

-e 's/[^0-9.]*\([0-9.]*\).*/\1/' \

-e q

version.c` > .fname

-rm -rf `cat .fname`

mkdir `cat .fname`

ln $(SRCS) $(AUX) `cat .fname`

tar chZf `cat .fname`.tar.Z `cat .fname`

-rm -rf `cat .fname` .fname

tar.zoo: $(SRCS) $(AUX)

-rm -rf tmp.dir

-mkdir tmp.dir

-rm tar.zoo

for X in $(SRCS) $(AUX) ; do \

echo $$X ; \

sed 's/$$/^M/' $$X \

> tmp.dir/$$X ; done

cd tmp.dir ; zoo aM ../tar.zoo *

-rm -rf tmp.dir

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