make命令与makefile文件
一、多个源文件带来的问题
在编写c/c++测试程序时,我们习惯每次修改一处代码,然后就马上编译运行来查看运行的结果。这种编译方式对于小程序来说是没有多大问题的,可对于大型程序来说,由于包含了大量的源文件,如果每次改动一个地方都需要编译所有的源文件,这个简单的直接编译所有源文件方式对程序员来说简直是噩耗。
我们看一个例子:
1 | // main.c |
如果程序员只修改了头文件c.h
,则源文件main.c
和2.c
都无需编译,因为它们不依赖这个头文件。而对3.c
来说,由于它包含了c.h
,所以在头文件c.h
改动后,就必须得新编译。
而如果改动了b.h
可是忘记编译了2.c
,那么最终的程序就可能无法正常工作。
make 工具就是为了解决上述问题而出现的,它会在必要时重新编译所有受改动影响的源文件。
二、make 命令
make
命令本身支持许多选项,最常用的是-f
选项。如果我们直接运行
make
那么make
命令会首先在当前目录查找名为makefile
的文件,如果找不到,就会查找名为Makefile
的文件。
为了指示make
命令将哪个文件作为makefile文件,可以使用 -f
选项:
make -f Makefile1
三、makefile 文件
上面提到makefile文件,那么什么是makefile文件呢?make
命令功能虽然十分强大,但是光凭其自身无法了解如何构建应用程序的。这时,makefile就出来了,它告诉make
应用程序如何构建的。make
命令和makefile文件的结合提供了一个在管理项目的十分强大的工具,它们不仅用于控制源文件的编译,而且还提供了将应用程序安装到目标目录等其他功能。
3.1 依赖关系
依赖关系定义了应用程序里面每个文件与其他源文件之间的关系。例如在上面的例子中,我们可以定义最终应用程序依赖于目标文件main.o
,2.o
和3.o
。同样,main.o
依赖于main.c
和a.h
,2.o
依赖于2.c
,a.h
和b.h
,3.o
依赖于3.c
,b.h
和c.h
。
在makefile文件中,依赖关系的写法是:先写目标的名称,然后紧跟一个冒号,接着是空格或者制表符tab,最后是用空格或者制表符tab隔开的文件列表。上面的例子的依赖关系如下:
1 | myapp: main.o 2.o 3.o |
这组依赖关系形成一个层次结构,展示了源文件之间的关系。例如,如果源文件b.h
发生改变,就需要重新编译2.o
和3.o
,接下来还需要重新编译myapp
。
3.2 规则
makefiel文件中的规则定义了目标的创建方式。在上面的例子中,我们使用gcc -c 2.c
创建2.o
。这个gcc
命令即是目标2.o
的创建方式,也即是规则。
在makefile文件中,规则都必须以tab开头。
在源文件所在的目录下创建Makefile1
文件,其内容如下。
1 | myapp: main.o 2.o 3.o |
三个头文件a.h
,b.h
,c.h
内容都为空,源文件的内容如下:
1 | /* main.c */ |
1 | /* 2.c */ |
1 | /* 3.c */ |
执行make
命令,:
$ make -f Makefile1
gcc -c main.c
gcc -c 2.c
gcc -c 3.c
gcc -o myapp main.o 2.o 3.o
运行应用程序:
$ ./myapp
function two
function three
从输出可以说明应用程序已被正确构建。
如果改变b.h
头文件,makefile能够正确处理这一变化,只有2.c
和3.c
发生重新编译:
$ touch b.h
$ make -f Makefile1
gcc -c 2.c
gcc -c 3.c
gcc -o myapp main.o 2.o 3.o
3.3 注释
makefile文件使用#
来表示注释,一直延续到这一行的结束。
3.4 宏
不同的平台下可能使用不同的编译器,不同的环境(例如开发与线上环境)也可能使用不同的编译器选项,为了便于修改makefile这些可变的参数,我们可以使用宏来实现makefile。
makefile引用宏定义的方法为$(MACRONAME)
。我们来看如何使用宏来改写上面的makefile文件。
1 | all: myapp |
我们习惯在makefile文件中将第一个目标定义为all
,然后再列出其他从属的目标,上面的makefile也遵循这个约定。
运行make
命令:
$ make -f Makefile2
gcc -I. -g -Wall -ansi -c main.c
gcc -I. -g -Wall -ansi -c 2.c
gcc -I. -g -Wall -ansi -c 3.c
gcc -o myapp main.o 2.o 3.o
同样也正确构建了应用程序myapp
。
3.5 多个目标
makefile文件除了定义编译的目标外,还可以定义其他的目标。例如,增加一个clean
选项来删除不需要的目标文件,增加一个install
选项来将编译成功的应用程序安装到另一个目录下,等等。
1 | all: myapp |
上面的makefile文件有几点需要注意的。
(1)特殊目标all
只指定了myapp
这个目标,因此,在执行make
命令时未指定目标,它的默认行为就是创建目标myapp
。
(2)目标clean
用来测试编译过程中产生的中间文件。
(3)目标install
用于将应用程序安装到指定目录,它依赖于myapp
,即执行install
前须先创建myapp
。install
目标由shell脚本组成,由于make
命令在执行规则时会调用一个shell,并且会针对每个规则使用一个新的shell,所以必须在上面每行代码的结尾加上一个\
,让所有的shell脚本都处于同一行。
脚本以@
开头,说明make
在执行这些规则之前不会在标准输出显示命令本身。
创建myapp
:
$ make -f Makefile3
gcc -I. -g -Wall -ansi -c main.c
gcc -I. -g -Wall -ansi -c 2.c
gcc -I. -g -Wall -ansi -c 3.c
gcc -o myapp main.o 2.o 3.o
将myapp
安装到指到目录:
$ make -f Makefile3 install
Install in /usr/local/bin
然后可以直接执行myapp
:
$ myapp
function two
function three
删除中间文件:
$ make -f Makefile3 clean
rm main.o 2.o 3.o
四、参考资料
- Linux程序设计(第4版),Neil Matthew等著,人民邮电出版社,2010年
- http://mrbook.org/blog/tutorials/make/
- http://www.cs.colby.edu/maxwell/courses/tutorials/maketutor/