分类: LINUX
2012-05-28 17:07:29
当你要编译一个比较大的工程时,makefile就能派上用场了。因为,一个工程的源文件可能很多,而且会分散在多个目录中,你需要大量的命令来编译链接这些文件。当你更新其中一些源文件的时候,就不得不再次输入命令重新编译链接,这样就使得编译的效率很低,并且重复多次输入大量的命令很容易出错。而makefile就是通过定义一系列的规则,来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件在更新之后需要重新编译,从而省去了多次输入命令的麻烦。总之一句话,makefile就是一个能实现自动化编译链接的文件,它能大大提高软件开发的效率。
我先总结一点关于makefile的知识和概念,而后讲解一个比较复杂的makefile。下面的东西,基本涉及了makefile的基础应用,只要你能理解下面这些东东,查看makefile文件或者自己编写makefile做常规的工程也就够了。当然,能深入了解是更好的。
1:target.... : prerequisitie...
command
....
注释:target是目标文件,也可以是执行文件,还可以是标签(label);
prerequisities是要生成target所需要的文件或者目标;
commmand是make执行的命令,其前面必须有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”参数会把一些标准库头文件包含进来。
6:GNU的make工作方式
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