定义

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 ,会在当前目录寻找名字叫 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的简单的概述,都是一些基础,如果需要更多的细节请见:

Makefile 简述的更多相关文章

  1. Linux内核配置、编译及Makefile简述

    Hi,大家好!我是CrazyCatJack.最近在学习Linux内核的配置.编译及Makefile文件.今天总结一下学习成果,分享给大家^_^ 1.解压缩打补丁 首先是解压缩你获取到的Linux内核. ...

  2. linux内核makefile概览

    linux内核makefile概览 本博客参照内核官方英文文档 linux的内核makefile主要用于编译整个内核源码,按照用户的需求生成各种目标文件,对于用户来说,编译内核时非常简单的,只需要几个 ...

  3. Linux内核

    Linux内核配置.编译及Makefile简述 Hi,大家好!我是CrazyCatJack.最近在学习Linux内核的配置.编译及Makefile文件.今天总结一下学习成果,分享给大家^_^ 1.解压 ...

  4. u-boot 学习系列 1 - SPL

    u-boot这个东西从自我N年前使用到现在,变化好多,今天开始重新研究下,本系列的研究都是基于BeagleBoneBlack(bbb)开发板和 u-boot v201801版本的. SPL介绍 在源代 ...

  5. unix automake 使用,快速生成你的Makefile

    使用automake快速生成编译的Makefile 1,确保自己装有的软件automake autoconf 2, 1)执行autoscan 并将生成的configure.scan重命名为config ...

  6. 简述configure、pkg-config、pkg_config_path三者的关系

    简述configure.pkg-config.pkg_config_path三者的关系 一.什么是configure 源码安装过程中大多会用到configure这个程序,一般的configure都是一 ...

  7. [转帖]Qemu 简述

    Qemu 简述 记得KVM 就是 底层用的qemu https://www.cnblogs.com/bakari/p/7858029.html 本文首发于我的公众号 Linux云计算网络(id: cl ...

  8. ./configure,make,make install的作用(configure一般用来生成 Makefile,相当于qmake)

    这些都是典型的使用GNU的AUTOCONF和AUTOMAKE产生的程序的安装步骤. ./configure是用来检测你的安装平台的目标特征的.比如它会检测你是不是有CC或GCC,并不是需要CC或GCC ...

  9. 说说Makefile那些事儿

    说说Makefile那些事儿 |扬说|透过现象看本质 工作至今,一直对Makefile半知半解.突然某天幡然醒悟,觉得此举极为不妥,只得洗心革面从头学来,以前许多不明觉厉之处顿时茅塞顿开,想想好记性不 ...

随机推荐

  1. JQuery 判断滚动条是否到底部

    BottomJumpPage: function () { var scrollTop = $(this).scrollTop(); var scrollHeight = $(document).he ...

  2. va_list arg_list va_start(arg_list, format) va_end( arg_list ) 原理的理解

    void log( int log_level, const char* file_name, int line_num, const char* format, ... ) { .......... ...

  3. eclipse把局部变量提为全局变量的快捷键是什么

    没有缺省定义的直接快捷键,或者就按Ctrl+1按照melord说的那样做,或者自己在Preference/General/Keys自己对Convert Local Variable to Feild进 ...

  4. Matrix Chain Multiplication (堆栈)

    题目链接:https://vjudge.net/problem/UVA-442 题目大意:输入n个矩阵的维度和一些矩阵链乘表达式,输出乘法的次数.如果乘法无法进行,输出error. 假定A是m*n的矩 ...

  5. 性能测试工具LoadRunner07-LR之Virtual User Generator 参数化设置

    1.Select next row[选择下一行]: 顺序(Sequential):按照参数化的数据顺序,一个一个的取 随机(Random):参数化中的数据,每次随机的从中抽取数据 唯一(Unique) ...

  6. 几种常见的排序算法Java实现总结

    public class MySort { final int MAX=20; int num[]=new int[MAX]; { System.out.print("生成的随机数组是:&q ...

  7. Neutron命令测试3

    1.打开Ubuntu的/etc/network/interfaces文件 .默认内容如下 auto lo iface lo inet loopback 2.如果以DHCP方式配置网卡,则改为:auto ...

  8. 关于数学问题的urls

    一个知乎账号, 分析了很多的数学问题: https://www.zhihu.com/people/matongxue/activities 关于三阶样条的解析: https://blog.csdn.n ...

  9. Tomcat实现Session共享

    一个Tomcat中有多一个web应用,然后想共享session,只需在host中加上 <Valve className="org.apache.catalina.authenticat ...

  10. 初步学习XML的基本代码

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...