购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.4.3 初识makefile

通常在对单文件或者比较少量的文件进行编译的时候,只需要通过GCC命令直接编译就可以了。因为在文件数目比较少的情况下,其编译过程中的文件依赖关系还是很简单的,可以通过人工控制命令的顺序来解决文件的依赖关系。

然而在大型项目的开发过程中,编译过程面对的往往是成千上万个源码文件,而源码的相互依赖关系又非常复杂,想通过人力来维护这种编译顺序几乎是一件不可能的事情。当然,可以通过维护一个shell脚本(用于构建)文件来控制整个系统中所有文件的编译过程。但是通过shell脚本来控制项目文件的编译顺序有3个问题。

1)shell脚本语言无法原生支持依赖关系的表达,需要通过复杂的逻辑代码来表达源码的依赖关系。

2)shell脚本语言无法原生支持增量依赖编译(即如果只修改项目中的一个文件,只需要重新编译对该文件依赖的模块即可),要想实现控制逻辑也非常复杂。

3)shell脚本的维护成本相对较高。

GNU make是一个收集文件依赖关系,并根据依赖关系自动进行项目构建的工具。依赖定义在makefile文件中,make工具依据makefile的规则来按顺序执行对应的命令。现在的大型项目都会使用更加智能的构建工具cmake,cmake可以自动分析文件中的依赖关系,从而生成对应的makefile文件,使得项目的构建更加简便。不过本书在实现Linux 0.11项目代码的过程中还是采用make工具,因为这个工程结构相对清晰、简单。

1.makefile基本规则

当我们输入make命令时,make会到当前目录下去查找makefile文件。makefile文件由一系列的规则组成,每条规则的形式如下:

1 target…:prerequisites…

2 recipe…

第一行规定了目标文件以及文件的依赖关系。在makefile里,target和冒号是必不可少的,prerequisites在这一行里边可以没有。第一行之后是一条或多条recipe命令,即要达成这个target需要执行的命令。这里需要注意的是,这些recipe命令必须使用Tab分隔符来进行缩进,相比第一行的target需要多缩进一个制表符。

target一般是指该条规则下最终生成的文件名,如可执行文件或者.o/.so文件等。一个target往往会依赖一个或多个文件,即规则中的prerequisites。多个依赖则用空格进行分隔。例如:

1 foo.o:foo.c

2   GCC foo.c-c-o foo.o

3 bar.o:bar.c

4   GCC bar.c-c-o bar.o

5 a.out:foo.o bar.o

6   GCC bar.o foo.o-o a.out

这里第一条规则的target是foo.o文件,foo.o的生成依赖foo.c。生成foo.o的命令要通过GCC编译。第二条规则表明了bar.o文件依赖bar.c。第三条规则给出a.out同时依赖foo.o以及bar.o。所以,在这个makefile文件里,我们可以通过执行make a.out生成最终的可执行文件。在构建a.out的过程中,根据其依赖关系可知它同时依赖foo.o以及bar.o,make会先构建出它的依赖文件foo.o和bar.o,也就是先执行make foo.o以及make bar.o命令。由此可见,make是根据makefile文件定义的规则,并按照依赖的顺序进行构建的。如果构建完a.out以后又对bar.c文件进行了修改,再次执行make a.out时,make只需要重新构建bar.o以及a.out即可,不需要重新构建foo.o,这样在大型项目中可以大大加快构建的速度。

一般情况下,make命令会将第一条规则作为默认执行的规则,这样直接运行make命令就等价于执行make a.out。

除此之外,target可以用来指代一组命令的名称,常用的target是clean。例如:

1 clean:

2   rm a.out *.o

这种情况下执行make clean会删除当前目录下的.o文件以及a.out文件。此时,clean是一个伪目标。如果当前文件夹下恰好有一个clean文件,会干扰到make的执行。因为make发现这个target并没有依赖文件,所以不需要重新构建,这个target对应的recipe也就不需要执行了。为了消除这种影响,我们最好在这种伪目标下做一些声明:

1 .PHONY:clean

2 clean:

3   rm a.out *.o

这样的话,make便不会认为clean是一个需要生成的文件目标了。

2.makefile的变量

在makefile中,还可以通过定义变量来避免在多处输入重复的命令。如在上面a.out的例子中,可以通过变量进行如下改写:

1  OBJS:=foo.o bar.o

2  CC:=GCC

3  CFLAGS:=-O2

4

5  a.out: $(OBJS)

6   $(CC) $(CFLAGS) $(OBJS)-o a.out

7  foo.o:foo.c

8   $(CC) $(CFLAGS)foo.c-c-o foo.o

9  bar.o:bar.c

10   $(CC) $(CFLAGS)bar.c-c-o bar.o

11

12 .PHONY:clean

13 clean:

14   rm a.out$(OBJS)

这里将目标文件的列表定义为变量OBJS,将编译命令定义为变量CC,将编译选项定义为CFLAGS。之后再修改目标文件或者编译器等只需要修改变量即可,不需要对每个规则下的命令进行修改。

除了用户定义的变量外,makefile中还有一系列自动变量,这些自动变量可以在规则执行时根据规则的target以及prerequisites进行刷新和计算。常用的自动变量主要有以下几个。

变量 $@:指代了当前规则里的target。如果一个规则的target有多个的话,则指代第一个target。例如上述例子可以改为:

1 a.out: $(OBJS)

2   $(CC) $(CFLAGS) $(OBJS)-o$@

3 foo.o:foo.c

4   $(CC) $(CFLAGS)foo.c-c-o$@

5 bar.o:bar.c

6   $(CC) $(CFLAGS)bar.c-c-o$@

变量$<:指代了当前规则里的第一个prerequisites。而$ˆ变量则表示当前规则中的所有prerequisites。由此,我们可以把上述例子继续改写为:

1 a.out: $(OBJS)

2   $(CC) $(CFLAGS)$^-o$@

3 foo.o:foo.c

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

5 bar.o:bar.c

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

变量$?:表示prerequisites列表中所有比target文件更新的文件。

至此,我们对makefile的规则就有了基本的了解。当然makefile还有很多高级的用法,本节只是简单介绍在开发Linux 0.11过程中用到的一些知识。如果读者对makefile的更多用法感兴趣,可以通过man make命令查看GNU make的官方手册。 /n7OFKioA7W+frTPypn8QjULgnnrBwVxVyZaHUTkK7R5whgEE3dMnFJEzTCUvx3F

点击中间区域
呼出菜单
上一章
目录
下一章
×