大型软件在依赖关系复杂的时候,编辑—编译—测试的周期将变化的很长。如果仅仅改动了一个源文件,而去把整个工程重新编译是开销太大了。make命令由此应运而生。其实不仅仅是由源文件得到可执行程序,任何有一组文件得到另外一组文件的应用都可以用make命令。下面简单介绍下make和makefile文件。
Make命令虽然内置了很多机制,但是不肯能完全凭借该命令自身来为用户创建所需要的应用程序。Make命令根据makefile文件来创建应用程序。Makefile文件说明了该如何构造应用程序。
其实makefile文件很简单,(我一直喜欢L下的这种简单)。Makefile由依赖关系和规则两部分构成。依赖关系说明了目标文件可由哪些文件来创建。而规则,则说明了如何来创建。就好像一个是原料,一个是烹饪方法。美味佳肴就出来了。
Make是比较智能的。它会根据目标文件所依赖的源文件的时间和日期来决定采用哪条规则来创建构造目标。这一点很重要,假如你修改了某个源文件,在此之前make已经创建了依赖于这个源文件的目标文件。那么make会智能的发现该源文件的事件和日期已经改变,就会重新创建该目标文件。(这一点很重要)另外在创建目标文件的过程中常常需要创建一些中间文件,make会智能的确定正确的创建顺序和规则调用顺序!(假设A依赖于B和C,C又依赖与D和E,那么make会先用D和E创建C,再用B和C创建A。)
make命令的选项和参数:
-k: 它的作用是让make命令在发现错误的时候仍然继续执行,而不是在检查到第一个错误时就停下来。可以用这个选项在一次操作中发现所有未编译成功的源文件。
-n: 它的作用是让make命令输出将要执行的操作而不真正的执行这些操作。(军事演习)
-f<filename>: 它的作用是告诉make命令哪个文件作为makefile文件!优先级: 先找makefile——>>>Makefile
为了使用make创建一个目标文件,通常需要把该文件名作为一个参数传送给make命令。否则,Make命令默认创建列在makefile文件中的第一个目标,一般牛逼的程序员们把该目标约定为all,然后再列出其他从属目标。这样明确的告诉make命令,在未指定特定目标时候应该创建哪个目标。强烈推荐这么做,因为这样看起来你也很牛逼。
依赖关系:
依赖关系定义了最终应用程序里每个文件与源文件之间的关系。例如最终的myapp依赖于文件main.o、2.o、3.o。同样mian.o依赖于main.c和a.h,以下同理。如果mian.C或者a.h有所改变,则需要重新生成mian.o,同时要需要重新生成myapp。依赖关系的写法是:
myapp:(此处为空格或者制表符)main.o(用空格和制表符分隔) 2.o (用空格和制表符分隔)3.o
以下同上。
如果想一次创建多个最终的目标程序,应该用伪目标all,例如all:myapp myapp1
规则:
规则就是定义了创建目标文件的方式。其实make命令内置了许多有用的规则,它可以识别一些类型的文件而自动给出创建该类型文件的规则。但是总会遇到不识别的文件类型,或者需要指定头文件的位置或者加入调试信息。所以还是需要明确定义一些规则。
另外,规则所在的行必须以tab开头!空格是不行滴!而且规则以空格结尾,也可能导致make命令执行失败。
一个例子:
//Makefile1
myapp: main.o 2.o 3.o
gcc –o myapp main.o 2.o 3.o
main.o: main.c a.h
gcc –c mian.c
2.o: 2.c a.h b.h
gcc –c 2.c
3.o: 3.c b.h c.h
gcc –c 3.c
//Makefile1
执行命令:
$make –f Makefile1
gcc –c main.c //创建了main.o
gcc –c 2.c
gcc –c 3.c
gcc –o myapp main.o 2.o 3.o
$
假如我们修改了某个头文件会发生什么?
$touch b.h
$make –f Makefile1
gcc –c 2.c //2.o: 2.c a.h b.h
gcc –c 3.c //3.o: 3.c b.h c.h
gcc –c myapp main.o 2.o 3.o
$
b.h改变了,则重新编译了依赖于2.o和3.o,同理2.o和3.o都变化了,则重新编译了myapp!多他妈好的命令啊,智能!
注释和宏:
makefile中用#作注释,跟C语言中的//差不多。
makefile文件还允许定义强大的宏!MACRONAME=value来定义宏,引用宏的方法为$(MARCRONAME)或者${ MARCRONAME },很像shell脚本中的变量啊!
宏常常设置为编译器的选项!例如是gcc还是cc等!可以再命令中直接给出宏定义,例如
make “CC = 89”
eg:
//Makefile2
all: myapp
#Which compiler
CC = gcc
#Where are include files kept
INCLUDE = .
#Options for development
CFLAGS = -g –Wall -ansi
#Options for development
#CFLAGS = -o –Wall –ansi
myapp: main.o 2.o 3.o
$(CC) –o myapp main.o 2.o 3.o
main.o: main.c a.h
$(CC) -I$(INCLUDE) $(CFLAGS) –c main.c
…
…
…
//Makefile2
$make –f Makefile2
gcc –I . –g Wall –ansi –c main.c
…
..
…
make命令将宏展开。可以做到一改全改。
还有另外一些有用的宏:
宏 | 定义 |
$? | 当前目标所依赖的文件列表中比当前目标文件还要新的文件 |
$@ | 当前目标的名字 |
$< | 当前依赖文件的名字 |
$* | 不包括后缀名的当前依赖文件的名字 |
两个用在命令之前的特殊字符:
-:告诉make命令忽略所有错误。
@:告诉make在执行某条命令前不要将该命令显示在标准输出上。
多个目标:
扩展的makefile文件可以同时制作多个目标,例如该例子增加了clean和install选项。
//Makefile3
all:app
#Wherr to install
INSTDIR = /usr/local/bin
…
clean: #不依赖任何文件,则被认为总是过时的!所以执行make时,如果#指定目标clean,则该目标对应的规则将总被执行。
-rm main.o 2.o 3.o #以减号开头,说明忽略该命令执行过程中的所有错误
install : myapp
@if [ -d $(INSTDIR) ];\ #反斜杠保证这些命令会在一个shell中执行,@保证这些命令不#会在标准输出上显示。
then \
cp myapp $(INSTDIR);\
chmod a+x $(INSTDIR)/myapp;\
chmod og-w $(INSTDIR)/myapp;\
echo “Installed in $(INSTDIR)”;\
else \
echo “Sorry…”;\
fi
$make –f Makefile3
//只会执行创建myapp的脚本
$make –f Makefile3 clean
rm mian.o 2.o 3.o
$make –f Makefile3 install
Installed in /usr/local/bin
内置规则(推导规则((推倒谁?邪恶…))):
其实make命令内置了大量的内置规则,可是大大简化makefile文件的内容。这些内置规则都使用了宏,因此可以通过给宏赋新值来改变其默认的行为。
$ rm foo
$ make CC=gcc CFLAGS=”-Wall -g” foo
gcc –Wall –g foo.c –o foo
$
用make -p命令可以显示所有的规则…如果你感兴趣,研究一下,其实是如果你用得着,再去研究就行。
考虑到这些内置规则的存在,在你的makefile文件中你甚至不需要任何制作目标的规则,只需要指定依赖关系即可。
后缀和模式规则:
内置规则是用文件后缀名来完成目标文件到制作规则的映射的。例如最常见的是从一个.c为后缀名的文件创建出一个以.o为后缀名的文件。也就是:main.o: mian.c a.h
那么我们需要新建规则的时候该怎么办呢,例如我们需要从.cpp文件生成.o文件。推荐的作法是为make指定一条新的规则,而不是在makefile文件里每处加入这样一条规则。
S1:首先在makefile中增加一行语句,告诉make命令这个新的后缀名,然后才能用这个新的后缀名来定义规则。
.<old_suffix>.<new_suffix>:
该例子用一个新的通用规则将.cpp文件编译为.o文件
eg:
.SUFFIXES: .cpp #这是一个新的后缀
.cpp.o: #由.cpp制作.o文件
$(CC) –xc++ $(CFLAGS) –I$(INCLUDE) –c $< #$<将被展开起始文件的名字,-xc++
#告诉编译器这是一个C++源文件
另外最新版本的make命令还包含了新的语法来实现同样的效果。模式规则可以用%通配符语法来匹配文件名,而不是仅依赖于文件的后缀名。
可以到达与上例中.cpp规则同样效果的模式规则如下:
%.cpp: %o
$(CC) –xc++ $(CFLAGS) –I$(INCLUDE) –c $<
用make管理函数库:
多个编译号的目标文件常常被归纳在一起管理,那就是函数库。也就是.o文件被归档为.a文件。管理函数库的语法是lib(file.o),它的含义是目标文件file.o是存储在函数库lib.a当中的,make命令有一个内置规则来管理函数库,该规则的常见形式如下所示:
.c.a:
$(CC) –c $(CFLAGS) $<
$(AR) $(ARFLAGS) $@ $*.o
宏$(AR)和$(ARFLAGS)的默认取值通常是命令ar和选项rv。
GNU make和gcc
make命令的-jN(j来自于jobs的首字母)选项,它允许make命令同时执行N条命令。
该命令的意义在于如果项目不同的部分可以彼此独立的进行编译,make命令就可以同时条用几条规则。这样可以极大的缩短重新编译所需要花费的时间。
另外一个牛逼的的选项是gcc的-MM选项。它的作用是产生一个使用于make命令的依赖关系清单。该命令的意义在于如果某个项目包含非常多的源文件,每个源文件又包含很多不同的头文件组合。那么理清他们之间的依赖关系就变得非常困难,但是有非常重要!
eg:
$ gcc –MM main.c 2.c 3.c
main.o :main.c a.h
2.o:2.c a.h b.h
3.o:3.c b.h c.h
$
另外还有一个叫做makedepend的工具,它的功能与-MM选项很类似。但其做法是将依赖关系直接附加到指定的makefile文件的末尾。
其实make的实质就是 它通过一系列命令从某些类型的输入文件得到输出文件。关键点在于make命令可以根据文件的日期和时间信息判断出哪个文件发生了变化。那么直接依赖和间接依赖该文件的文件都要被重新生成。