Makefile 简述

定义

Linux 环境下的程序员若是不会使用GNU make来构建和管理本身的工程,应该不能算是一个合格的专业程序员,至少不能称得上是 Unix程序员。在 Linux(unix )环境下使用GNU 的make工具可以比较容易的构建一个属于你本身的工程,整个工程的编译只须要一个命令就能够完成编译、链接以致于最后的执行。不过这须要咱们投入一些时间去完成一个或者多个称之为Makefile 文件的编写。


make是一个命令工具,它解释Makefile 中的指令(应该说是规则)。在Makefile文件中描述了整个工程全部文件的编译顺序、编译规则。Makefile 有本身的书写格式、关键字、函数。像C 语言有本身的格式、关键字和函数同样。并且在Makefile 中可使用系统shell所提供的任何命令来完成想要的工做。Makefile(在其它的系统上多是另外的文件名)在绝大多数的IDE 开发环境中都在使用,已经成为一种工程的编译方法。


下面都用C语言举例子程序员


程序编译连接过程

C语言的编译和连接就是要把编写的C程序转换成可执行程序。编译是把文本形源代码翻译为机器语言形式的目标文件的过程。连接是把目标文件、操做系统的启动代码和用到的库文件进行组织造成最终生成可执行代码的过程。shell

编译

编译是读取源程序,并进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,编译程序所要做得工做就是经过词法分析和语法分析,在确认全部的指令都符合语法规则以后,将其翻译成等价的中间代码表示或汇编代码。函数

连接

连接程序的主要工做就是将有关的目标文件彼此相链接,也即将在一个文件中引用的符号同该符号在另一个文件中的定义链接起来,使得全部的这些目标文件成为一个可以被操做系统装入执行的统一总体。工具


Makfile

make执行命令的时候须要一个Makefile文件,来告诉make命令如何编译连接程序。


这里先给一个例子来简单的理解Makefile的书写规则,在示例中,工程有11个C文件,和3个头文件。咱们要写一个Makefile来告诉make命令如何编译连接这几个文件。编译规则以下:
一、若是全部的C文件都没有编译过,那么要将全部C文件都编译和连接。
二、若是有一个以上的C文件被修改,那么咱们只编译被修改的C文件,并连接目标程序。
二、若是头文件被修改,那么编译引用头文件的C文件,并连接目标程序。


咱们只要写好Makefile文件,告诉make命令如何编译连接,咱们就能够很简单的用一个make命令完成这个工做。ui


Makefile 规则

咱们仍是先大致的看一下Makefile的基本编写规则。

操作系统

target ... : prerequisites ...
command
...

target 是一个目标文件,这个目标文件能够是Object File,也能够是可执行文件,还能够是一个标签(伪目标)。
prerequisites 是要生成 target 所须要的文件或是目标。
command 是要执行的命令(任意Shell命令)
这其实就是一个依赖关系, target 依赖于prerequisites 中的文件,它生成的规则定义在 command 中。若是 prerequisites 有被修改的文件,那么command 中的命令就会被执行。翻译


Makefile 示例

咱们前面提到要举一个例子,这个例子中有11个C文件和两个头文件,为了可以实现前面提到的规则,咱们编写了一个Makefile。unix

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 是最终生成的可执行文件,冒号后面的code

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 中能够写不少与编译无关或者不用编译的命令,好比程序的打包,备份,清除等等。ip


make工做过程

当你输入 make ,会在当前目录寻找名字叫 makefileMakefile 的文件,通常仍是使用 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的简单的概述,都是一些基础,若是须要更多的细节请见:

相关文章
相关标签/搜索