皓哥写了一系列的makefile文章后, 就被大家奉为经典。 我自然无法与皓哥相比, 所以仅仅写一下自己对makefile的理解, 自娱自乐, 权当是个人笔记吧。
Windows下的开发, 用IDE, 有所谓的工程的概念, 写好代码,编译、连接运行, 然后看结果就行了。 在这个过程中, IDE直接为我们写好了makefile, 我们啥都不用担心。但在Linux中, 这些makefile文件必须自己去写。 请别说:“我在linux下写了很多代码, 但我不熟悉makefile”
先说一下基本概念吧: makefile是一个文件, make是一个命令(本质也是一个文件, 类似于ls, rm等基本的linux命令)。 makefile是名词, make是动词, make是用来执行makefile的。
如果你是个linux新手, 那不懂makefile也不要紧, 看完本文后, 肯定会入门makefile. 下面, 我们慢慢来说。 先看几个文件:
input.h:
void input(int *pn);
process.h:
int process(int n);
output.h:
void output(int result);
intput.c:
#include "input.h"
void input(int *pn)
{
*pn = 6;
}
process.c:
#include "process.h"
int process(int n)
{
return n * n;
}
output.c:
#include <stdio.h>
#include "output.h"
void output(int result)
{
printf("%d\n", result);
}
main.c:
#include <stdio.h>
#include "input.h"
#include "process.h"
#include "output.h"
int main()
{
int n = 0;
int result = 0;
input(&n);
result = process(n);
output(result);
return 0;
}
相信大家都能看得懂这么基础的代码。好, 现在我们来编译上面的程序:
[taoge@localhost learnMakeFile]$ gcc main.c input.c process.c output.c
[taoge@localhost learnMakeFile]$ ls
a.out input.c input.h main.c output.c output.h process.c process.h
[taoge@localhost learnMakeFile]$ ./a.out
36
[taoge@localhost learnMakeFile]$
结果好, 并不一定真的好。假设main.c有变动, 那么要重新编译吧, 根据上述编译过程可知, 所有的.c文件都需要重新编译。 在实际开发中, 有好多好多.c文件, 全部编译一次, 有时要几个小时, 你自己算一下。 所以, 有必要对上述流程进行优化, 思路是产生中间的目标文件, 如下:
[taoge@localhost learnMakeFile]$ rm a.out
[taoge@localhost learnMakeFile]$ gcc -c main.c
[taoge@localhost learnMakeFile]$ gcc -c input.c
[taoge@localhost learnMakeFile]$ gcc -c process.c
[taoge@localhost learnMakeFile]$ gcc -c output.c
[taoge@localhost learnMakeFile]$ ls
input.c input.o main.o output.h process.c process.o
input.h main.c output.c output.o process.h
[taoge@localhost learnMakeFile]$ gcc main.o input.o process.o output.o
[taoge@localhost learnMakeFile]$ ls
a.out input.h main.c output.c output.o process.h
input.c input.o main.o output.h process.c process.o
[taoge@localhost learnMakeFile]$ ./a.out
36
[taoge@localhost learnMakeFile]$
结果好, 并不一定真的好。 假若改动了main.c文件, 我们的确不用编译其他.c文件了, 但还是要编译main.c生成新的main.o啊, 重新编译生成最后的a.out. 而且, 如果修改了某个头文件, 程序员要自己去查找哪些.c文件有依赖, 郁闷得不行。
那能不能搞个一键编译, 让编译彻底傻瓜化呢? 可以的, 这就是我们要介绍的makefile, 通过makefile我们可以做到:a. 操作简单傻瓜化, 执行make命令即可. b. 编译过的不再编译. c. 无需自己去找变化依赖关系。
我们先建立一个文件, 文件名为makefile, 其内容为(注意, 缩进的时候必须用tab, 不能用空格代替):
a.out : main.o input.o process.o output.o
gcc -o a.out main.o input.o process.o output.o
main.o : main.c input.h process.h output.h
gcc -c main.c
input.o : input.c input.h
gcc -c input.c
process.o : process.c process.h
gcc -c process.c
output.o : output.c output.h
gcc -c output.c
clean :
rm *.o a.out
好, 我们看看结果:
[taoge@localhost learnMakeFile]$ ls
input.c input.h main.c makefile output.c output.h process.c process.h
[taoge@localhost learnMakeFile]$ make
gcc -c main.c
gcc -c input.c
gcc -c process.c
gcc -c output.c
gcc -o a.out main.o input.o process.o output.o
[taoge@localhost learnMakeFile]$ ./a.out
36
[taoge@localhost learnMakeFile]$ make
make: `a.out' is up to date.
[taoge@localhost learnMakeFile]$ make clean
rm *.o a.out
[taoge@localhost learnMakeFile]$ make
gcc -c main.c
gcc -c input.c
gcc -c process.c
gcc -c output.c
gcc -o a.out main.o input.o process.o output.o
[taoge@localhost learnMakeFile]$
我们来看看, 最开始, learnMakeFile这个目录下比较干净, 也就是.c, .h和makefile文件。 执行make命令后, 就执行了对应的编译。 然后, 我们再次make一下, 可以看到, 提示a.out已经是最新的的了, 也就是说, 没有任何修改。 此时, 我们执行make clean来rm中间生成的目标文件和最后的可执行文件, 再次make, 结果发现又重新编译了一次。
好的, 假设我们这个时候在input.h文件中增加一个空格(改变了input.h文件), 得到的结果如下:
[taoge@localhost learnMakeFile]$ vim input.h
[taoge@localhost learnMakeFile]$ make
gcc -c main.c
gcc -c input.c
gcc -o a.out main.o input.o process.o output.o
[taoge@localhost learnMakeFile]$ ./a.out
36
[taoge@localhost learnMakeFile]$ make
make: `a.out' is up to date.
[taoge@localhost learnMakeFile]$
可以看到, 在input.h中增加空格后, 依赖于它的main.c和input.c都自动重新编译, 且a.out也会更新。
由此可见: makefile实现了:
a. 一键傻瓜式编译.
b. 只编译有修改的(此处的修改是广义的, 比如虽然main.c和input.c没有手动修改, 但其依赖的input.h文件改动了, 所以main.c和input.c实际上也修改了)
c. 依赖关系自动识别(由makefile负责, 而不是敲make命令的人负责).
我们还有个疑问, makefile文件中中那些东西都是什么意思啊? 其实, 很简单, 无非就是指定依赖关系, 并且给出对应的编译命令。 其实很简单, 一看就懂。我们知道, shell脚本可以理解为批量的shell命令, 而makefile其实也可以大致理解为批量的编译命令。
我们再次审视一下makefile文件中的内容, 发现main.o input.o process.o output.o是重复的, 那该怎么办呢? 抽象, 对, 抽象就是专门干这事的, 该makefile文件内容为:
x = main.o input.o process.o output.o
a.out : $x
gcc -o a.out $x
main.o : main.c input.h process.h output.h
gcc -c main.c
input.o : input.c input.h
gcc -c input.c
process.o : process.c process.h
gcc -c process.c
output.o : output.c output.h
gcc -c output.c
clean :
rm *.o a.out
编译结果如下:
[taoge@localhost learnMakeFile]$ make
gcc -c main.c
gcc -c input.c
gcc -c process.c
gcc -c output.c
gcc -o a.out main.o input.o process.o output.o
[taoge@localhost learnMakeFile]$ ./a.out
36
[taoge@localhost learnMakeFile]$ make
make: `a.out' is up to date.
[taoge@localhost learnMakeFile]$
可见, makefile中也可以有变量。 我们再次来审视一下上面的makefile文件, 总感觉有什么冗余的部分, 是的, 那些gcc命令顶多是多余的, 根据依赖关系, 强大的make命令肯定有办法自动推到gcc命令, 我们改为如下:
x = main.o input.o process.o output.o
main : $x
main.o : main.c input.h process.h output.h
input.o : input.c input.h
process.o : process.c process.h
output.o : output.c output.h
clean :
rm *.o main
注意, 此时不能再用a.out了, 结果如下:
[taoge@localhost learnMakeFile]$ make
cc -c -o main.o main.c
cc -c -o input.o input.c
cc -c -o process.o process.c
cc -c -o output.o output.c
cc main.o input.o process.o output.o -o main
[taoge@localhost learnMakeFile]$ ./main
36
[taoge@localhost learnMakeFile]$ make
make: `main' is up to date.
[taoge@localhost learnMakeFile]$
可见, make确实可以正确推导。 OK, makefile的基本介绍就先告一段落。
本文只是makefile的入门简介, 就像一个美少女, 你只是看到了脸蛋而已, 但仅仅脸蛋, 就足够有魅力的。 后续我们会继续深入介绍makefile, 不多说, 你懂的。