Makefile 简述
定义
Linux 环境下的程序员如果不会使用GNU make来构建和管理自己的工程,应该不能算是一个合格的专业程序员,至少不能称得上是 Unix程序员。在 Linux(unix )环境下使用GNU 的make工具能够比较容易的构建一个属于你自己的工程,整个工程的编译只需要一个命令就可以完成编译、连接以至于最后的执行。不过这需要我们投入一些时间去完成一个或者多个称之为Makefile 文件的编写。
make是一个命令工具,它解释Makefile 中的指令(应该说是规则)。在Makefile文件中描述了整个工程所有文件的编译顺序、编译规则。Makefile 有自己的书写格式、关键字、函数。像C 语言有自己的格式、关键字和函数一样。而且在Makefile 中可以使用系统shell所提供的任何命令来完成想要的工作。Makefile(在其它的系统上可能是另外的文件名)在绝大多数的IDE 开发环境中都在使用,已经成为一种工程的编译方法。
下面都用C语言举例子
程序编译链接过程
C语言的编译和链接就是要把编写的C程序转换成可执行程序。编译是把文本形源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作系统的启动代码和用到的库文件进行组织形成最终生成可执行代码的过程。
编译
编译是读取源程序,并进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
链接
链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。
Makfile
make执行命令的时候需要一个Makefile文件,来告诉make命令如何编译链接程序。
这里先给一个例子来简单的理解Makefile的书写规则,在示例中,工程有11个C文件,和3个头文件。我们要写一个Makefile来告诉make命令如何编译链接这几个文件。编译规则如下:
1、如果所有的C文件都没有编译过,那么要将所有C文件都编译和链接。
2、如果有一个以上的C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
2、如果头文件被修改,那么编译引用头文件的C文件,并链接目标程序。
我们只要写好Makefile文件,告诉make命令如何编译链接,我们就可以很简单的用一个make命令完成这个工作。
Makefile 规则
我们还是先大体的看一下Makefile的基本编写规则。
target ... : prerequisites ...
command
...
target 是一个目标文件,这个目标文件可以是Object File,也可以是可执行文件,还可以是一个标签(伪目标)。
prerequisites 是要生成 target 所需要的文件或是目标。
command 是要执行的命令(任意Shell命令)
这其实就是一个依赖关系, target 依赖于prerequisites 中的文件,它生成的规则定义在 command 中。如果 prerequisites 有被修改的文件,那么command 中的命令就会被执行。
Makefile 示例
我们前面提到要举一个例子,这个例子中有11个C文件和两个头文件,为了能够实现前面提到的规则,我们编写了一个Makefile。
wordSystem : wordSysPat.o watchRecited.o wordSqlLogin.o \
wordSysSetPlan.o wordSysExit.o wordSysShow.o \
my_dil.o wordRecite.o wordSysLogin.o \
wordSysMain.o wordTest.o
cc -o wordSystem wordSysPat.o watchRecited.o wordSqlLogin.o \
wordSysSetPlan.o wordSysExit.o wordSysShow.o \
my_dil.o wordRecite.o wordSysLogin.o \
wordSysMain.o wordTest.o
wordSysPat.o : wordSysPat.c wordSystem.h
cc -c wordSysPat.c
watchRecite.o : watchRecite.c mydil.h
cc -c watchRecite.c
wordSysSetPlan.o : wordSysSetPlan.c mydil.h
cc -c wordSysSetPlan.c
wordSysShow.o : wordSysShow.c wordSystem.h
cc -c wordSysShow.c
wordRecite.o : wordRecite.c mydil.h
cc -c wordRecite.c
wordSysLogin.o : wordSysLogin.c wordSystem.h mydil.h
cc -c wordSysLogin.c
wordSysMain.o : wordSysMain.c wordSystem.h
cc -c wordSysMain.c
clean :
rm wordSystem wordSysPat.o watchRecited.o wordSqlLogin.o \
wordSysSetPlan.o wordSysExit.o wordSysShow.o \
my_dil.o wordRecite.o wordSysLogin.o \
wordSysMain.o wordTest.o
只要在保存文件的目录中执行
make
就可以生成最终的可执行文件 wordSystem ,执行make clean
可以删除所有可执行文件和中间目标文件。
Makefile 中的 \ 是换行的意思,这样让 Makefile 看起来整洁了一些,wordSystem 是最终生成的可执行文件,冒号后面的
wordSysPat.o watchRecited.o wordSqlLogin.o \
wordSysSetPlan.o wordSysExit.o wordSysShow.o \
my_dil.o wordRecite.o wordSysLogin.o \
wordSysMain.o wordTest.o
是它生成所需要的依赖文件,其实就是 target 是由 prerequisites 中的文件生成的。 command 就是生成 target 要执行的命令。这里还要说一下书写的格式,
command 的前面必须要有一个Tab键作为开头(不可以是其他的空白符,用空格凑出来一个类似Tab键的效果是不可以的),否则就会出现类似这样的提示,Makefile:10: *** 遗漏分隔符 。 停止。
同时不是命令的前面不可以用Tab键,如果使用了Tab键开头会出现类似这样的提示,Makefile:1: *** recipe commences before first target。 停止。
Makefile 最后的 clean 它并不是一个文件,只是一个动作的名称,它并有依赖的文件,所以冒号后面没有 prerequisites ,它只有要执行的命令,但是这个命令并不会自动的执行,这个命令要在make命令的后面写出这个标签来执行命令,
这样就非常有用了,Makefile 中可以写很多与编译无关或者不用编译的命令,比如程序的打包,备份,清除等等。
make工作过程
当你输入 make ,会在当前目录寻找名字叫 makefile 或 Makefile 的文件,一般还是使用 Makefile 这样更容易区分,
当然如果你使用其它的名称也可以,只要加上-f
选项,如:make -f Linux_make
如果找到了文件,它会找到第一个目标作为最终的生成目标,例子中的就是 wordSystem ,其实最后生成的文件的名称是和第一目标生成的命令中的文件名决定的,如下:
wordSystem : wordSysPat.o watchRecited.o wordSqlLogin.o \
wordSysSetPlan.o wordSysExit.o wordSysShow.o \
my_dil.o wordRecite.o wordSysLogin.o \
wordSysMain.o wordTest.o
cc -o my_wordSystem wordSysPat.o watchRecited.o wordSqlLogin.o \
wordSysSetPlan.o wordSysExit.o wordSysShow.o \
my_dil.o wordRecite.o wordSysLogin.o \
wordSysMain.o wordTest.o
......
最后生成可执行文件的名称是 my_wordSystem ,这也就说明其实 target 真的就只是一个目标 ,只是编译链接的中间的一个过程而已,再如下:
wordSystem : wordSysPat_new.o watchRecited.o wordSqlLogin.o \
wordSysSetPlan.o wordSysExit.o wordSysShow.o \
my_dil.o wordRecite.o wordSysLogin.o \
wordSysMain.o wordTest.o
cc -o wordSystem wordSysPat.o watchRecited.o wordSqlLogin.o \
wordSysSetPlan.o wordSysExit.o wordSysShow.o \
my_dil.o wordRecite.o wordSysLogin.o \
wordSysMain.o wordTest.o
wordSysPat_new.o : wordSysPat.c wordSystem.h
cc -c wordSysPat.c
watchRecite.o : watchRecite.c mydil.h
cc -c watchRecite.c
wordSysSetPlan.o : wordSysSetPlan.c mydil.h
cc -c wordSysSetPlan.c
wordSysShow.o : wordSysShow.c wordSystem.h
cc -c wordSysShow.c
wordRecite.o : wordRecite.c mydil.h
cc -c wordRecite.c
wordSysLogin.o : wordSysLogin.c wordSystem.h mydil.h
cc -c wordSysLogin.c
wordSysMain.o : wordSysMain.c wordSystem.h
cc -c wordSysMain.c
clean :
rm wordSystem wordSysPat.o watchRecited.o wordSqlLogin.o \
wordSysSetPlan.o wordSysExit.o wordSysShow.o \
my_dil.o wordRecite.o wordSysLogin.o \
wordSysMain.o wordTest.o
我将 wordSysPat.o 的目标改成了 wordSysPat_new.o ,最后的效果都是一样,生成的中间文件也是 wordSysPat.o 而不是 wordSysPat_new.o ,
当然不要这么写,不然你自己也可能写蒙了,只是理解一下标签的实际效果。
继续说工作的过程,如果 wordSystem 不存在那么就寻找它的依赖,并执行命令,如果 wordSystem 已经存在了,并且它依赖的文件的修改时间要新,那么就执行命令。
如果头文件被修改了,那么所有依赖了这个头文件的目标都会被重新编译,然后再和目标进行链接。
如果这个依赖文件也不存在,那么就去找这个依赖文件的目标,并重复这个操作。
make 只会在文件中一层一层的去寻找文件依赖关系,而编译的错误就不去关心,如果寻找依赖关系出现问题,那么它就会直接退出并报错。
Makefile 使用变量
我们再看一下 wordSystem 的依赖
wordSystem : wordSysPat.o watchRecited.o wordSqlLogin.o \
wordSysSetPlan.o wordSysExit.o wordSysShow.o \
my_dil.o wordRecite.o wordSysLogin.o \
wordSysMain.o wordTest.o
cc -o wordSystem wordSysPat.o watchRecited.o wordSqlLogin.o \
wordSysSetPlan.o wordSysExit.o wordSysShow.o \
my_dil.o wordRecite.o wordSysLogin.o \
wordSysMain.o wordTest.o
我们看到这里很多的 [.o] 的文件重复的出现了两次,这个例子中的文件还比较少,而且重复的次数比较少,但是如果是一个大的工程,我们就不能保证重复多次这样的事情不会出错,而且显得很繁琐,那么有没有简化的方法那?
当然是有的,这就使用到了 Makefile 中的变量。就是定义一段固定的字符串,其实就像是C语言的宏定义。
那么我们就要声明这个变量,这个变量你可以给它起各种你喜欢的名称了,只有不发生冲突就好,我们就暂时给这个变量起名叫 objects 。
我们用 $(objects) 来使用这个变量,那么我们的将 Makefile 就可以改良成下面这样。
objects = wordSysPat.o watchRecited.o wordSqlLogin.o \
wordSysSetPlan.o wordSysExit.o wordSysShow.o \
my_dil.o wordRecite.o wordSysLogin.o \
wordSysMain.o wordTest.o
wordSystem : $(objects)
cc -o wordSystem $(objects)
wordSysPat_new.o : wordSysPat.c wordSystem.h
cc -c wordSysPat.c
watchRecite.o : watchRecite.c mydil.h
cc -c watchRecite.c
wordSysSetPlan.o : wordSysSetPlan.c mydil.h
cc -c wordSysSetPlan.c
wordSysShow.o : wordSysShow.c wordSystem.h
cc -c wordSysShow.c
wordRecite.o : wordRecite.c mydil.h
cc -c wordRecite.c
wordSysLogin.o : wordSysLogin.c wordSystem.h mydil.h
cc -c wordSysLogin.c
wordSysMain.o : wordSysMain.c wordSystem.h
cc -c wordSysMain.c
clean :
rm wordSystem $(objects)
是不是看起来就简化了一些,以后在增减或减少一些目标文件都简单了很多,只要更改变量就可以了。
make自动推导
GNU的make还是很强大的,它能够自动推导文件和依赖关系后的命令,所以我们就没有必要在每个目标后面都写的那么复杂,于是就可以修改成如下:
objects = wordSysPat.o watchRecited.o wordSqlLogin.o \
wordSysSetPlan.o wordSysExit.o wordSysShow.o \
my_dil.o wordRecite.o wordSysLogin.o \
wordSysMain.o wordTest.o
wordSystem : $(objects)
cc -o wordSystem $(objects)
wordSysPat_new.o : wordSystem.h
watchRecite.o : mydil.h
wordSysSetPlan.o : mydil.h
wordSysShow.o : wordSystem.h
wordRecite.o : mydil.h
wordSysLogin.o : wordSystem.h mydil.h
wordSysMain.o : wordSystem.h
clean :
rm wordSystem $(objects)
这就是make的 “隐晦规则” ,所以之前那种玩笑的写法是万万不可的。
##清除目标文件的的规则 >每一个 Makefile 都应该写一个清除目标文件和可执行文件的规则,这不仅便于重编译,也很利于保持文件的清洁。 一般的风格都是这样的: ``` clean : rm wordSystem $(objects) ``` >更稳妥的方法: ``` .PHONY : clean clean : -rm wordSystem $(objects) ``` >`.PHONY` 的意思是 clean 是个 ‘伪目标’ ,而 rm 命令前面的 `-`是如果某些文件出现问题先不管它,继续向后执行,clean 有一个不成文的规则 “clean从来都放在文件最后” 。
>>这些就是Makefile的简单的概述,都是一些基础,如果需要更多的细节请见: