彻底掌握Makefile(一)

介绍

makefile就是一个可以被make命令解析的文件,他定义了一系列编译的规则,帮助我们更加方便、简洁的去完成编译的过程。在一个大工程当中我们会有各种各样的文件,我们可能会分模块去存放各种文件,可能有些文件还依赖其他的文件,因此我们在编译的时候需要先将被依赖的文件先编译,其他文件后编译,而我们使用makefile就可以更好的去完成这件事儿。

Makefile基础

在很多情况下我们在C/C++的项目当中使用makefile,其实在其他语言的项目也可以使用make和makefile,它不局限于语言的,可以是C也可以是Java。现在写一个基本的例子:

demo: demo.c
gcc demo.c -o demo clean:
rm demo

上面是一个makefile的例子,我们简要说明一下makefile的书写规则:

编译目标:依赖文件
编译命令

然后我们使用make命令去解释执行makefile,我们可以使用make 编译目标去执行特定的语句,比如在上面的例子当中我们执行make demo的话就会执行gcc demo.c -o demo命令。

其实上面我们也可以直接使用make命令,不需要指定编译目标,因为make会自己寻找第一个目标作为被执行的目标。

在上面的代码当中我们当我们执行make的时候寻找到makefile文件的第一个目标demo,但是因为我没有改动demo.c这个文件,而且这个文件已经编译过了,因此我们没有必要再去编译这个文件,这也是make给我们提供的一个非常好的特性,我们不需要重新编译已经编译好的文件,这在一个大型项目当中是非常有用的,当我们的项目当中有成千上万的文件的时候,如果我们重新编译每一个文件的话,那么编译的时间消耗是非常大的。因此我们在执行make执行执行clean编译目标先删除demo这个编译结果,然后在执行make这次它再找到demo目标,而此时demo已经被删除了,因此会重新编译。

Make命令的工作流程

当我们在命令行当中输入make的时候他的执行流程如下:

  • make命令首先会在当前目录下面寻找makefile或者Makefile文件。
  • 寻找到makefile文件之后,他会在文件当中寻找到一个编译目标,比如在上面的makefile文件当中他会找到demo这个编译目标,而不是clean这个目标,因为clean是第二个编译目标。
  • 然后make会解析编译目标的依赖,如果这个依赖是其他的编译目标A的话,那么make会先完成它依赖的编译目标A的命令,如果它依赖的编译目标A也存在依赖B的话,make就会去执行依赖的B的编译命令,如此的递归下去,知道有所得依赖目标都存在了,才会完成第一个编译目标的编译,这个也很好理解,只有依赖文件都存在了我们才能够完成正确的编译过程。

make编译的过程为寻找编译目标,依赖关系,如果依赖的文件还存在依赖那么make会一层一层的寻找下去,只要所有的依赖都被成功解析了,才会最终执行第一个编译目标的编译命令。但是在makefile当中不被第一个编译目标的目标的编译命令是不会被执行的,比如上面我们执行make的时候会执行demo编译目标,但是不会执行clean编译目标。

下面我们写一个例子了解make解析依赖的过程:

main: demo.o myprint.o
gcc demo.o myprint.o -o out
echo make 解析编译完成 demo.o: demo.c
gcc -c demo.c -o demo.o myprint.o: myprint.c
gcc -c myprint.c -o myprint.o clean:
rm myprint.o demo.o out

执行make之后的结果如下图所示:

因为我们没有指定编译的目标,因此make会寻找一个编译目标,也就是main,但是在main当中依赖两个文件demo.omyprint.o,因此make会再去执行demo.omyprint.o两个编译目标,当这两个编译目标被完成之后才能执行main的编译,根据make的输出结果我们可以得到这一个结果的印证。

Makefilex小技巧

Makefile当中的变量

在makefile当中我们可以定义一些我们的变量这样就可以避免反复输入了。比如我们经常会变编译文件的时候增加一些编译选项,比如像下面这样:

cflags=-c
main: demo.o myprint.o
gcc demo.o myprint.o -o out demo.o: demo.c
gcc $(cflags) demo.c -o demo.o myprint.o: myprint.c
gcc $(cflags) myprint.c -o myprint.o clean:
rm myprint.o demo.o out

在上面的makefile当中我们定义了一个变量cflags并且在编译命令当中使用,我们定义变量的方法其实和shell差不多,我们直接使用=可以定义变量,然后使用$(变量名)可以使用变量,因为上面的例子当中cflag=-c比较短,比较简单,但是如果当我们的编译参数很多很长的时候使用变量就非常有效了,而且如果在一个项目当中如果有成千上万个文件我们像统一改变编译时候的参数的话,我们一个一个改是很麻烦的,但是如果我们使用变量就可以做到一改全改。

Makefile当中的include命令

在makefile当中我们也可以使用include命令去包含其他的makefile文件,比如我们将上面的makefile文件分成两个部分makefilesubmakefile:

makefile:

include submakefile

demo.o: demo.c
gcc $(cflags) demo.c -o demo.o myprint.o: myprint.c
gcc $(cflags) myprint.c -o myprint.o clean:
rm myprint.o demo.o out

submakefile:

cflags=-c
main: demo.o myprint.o
gcc demo.o myprint.o -o out

然后在目录下执行make命令, 得到的效果和前文当中提到的makefile结果是一样的,这就相当于将submakefile的内容放到include语句的位置。

Makefile中的PHONY

在上面谈到的makefile当中有一个clean的编译目标用于清除我们编译的结果文件,现在我们在当前的目录下面增加一个文件clean在执行make命令,我们的makefile文件如下:

cflags=-c
main: demo.o myprint.o
gcc demo.o myprint.o -o main demo.o: demo.c
gcc $(cflags) demo.c -o demo.o myprint.o: myprint.c
gcc $(cflags) myprint.c -o myprint.o clean:
rm myprint.o demo.o main

然后执行下面的命令(touch命令是新增一个文件,touch clean 就是往目录中增加一个名字为clean的文件):

我们可以看到当目录下面增加一个clean文件之后,我们使用make clean命令的时候,make给我们的提示信息为clean文件是最新的了。这是因为当执行make 编译目标,make首先会检查当前目录下面是否存在clean文件如果不存在则执行编译目标clean的命令。如果存在clean文件的话,make会检查编译目标clean的依赖文件是否发生更改,如果发生更改了那么就会执行clean对应的命令,但是在上面的makefile当中clean没有依赖文件,因此相当于他的依赖文件没有发生改变,make不会执行编译目标clean对应的命令了。

但是我们的需求是仍然希望执行clean之后的命令,这个时候我们就可以使用PHONY了,他可以保证即使存在clean文件的时候,make命令依然会执行编译目标clean对应的命令。

cflags=-c
main: demo.o myprint.o
gcc demo.o myprint.o -o main demo.o: demo.c
gcc $(cflags) demo.c -o demo.o myprint.o: myprint.c
gcc $(cflags) myprint.c -o myprint.o clean:
rm myprint.o demo.o main
.PHONY: clean # 增加这一行

执行结果如下图所示:



我们现在来测试一下当我们命令生成的文件和编译目标不同名的时候,make是如何解释执行makefile的,makefile的内容如下:

cflags=-c
main: demo.o myprint.o
gcc demo.o myprint.o -o out # 这里的输出文件是 out 而不是 main 输出文件名字和目标不同名 demo.o: demo.c
gcc $(cflags) demo.c -o demo.o myprint.o: myprint.c
gcc $(cflags) myprint.c -o myprint.o clean:
rm myprint.o demo.o main
.PHONY: clean

执行结果:

从上面的结果我们可以发现,当我们编译的结果文件(out)和编译目标(main)不同名的时候make每次都回去执行这个目标,因为make在进行检测的时候没有发现main这个文件,因此每次执行make命令的时候都会去执行这个编译目标。

Makefile的通配符

我们现在修改前面的makefile,修改的结果如下(当前目录下面有两个文件demo.c和myprint.c):

cflags=-c

main: demo.o myprint.o
gcc demo.o myprint.o -o main %.o: %.c
gcc $(cflags) $<
clean:
rm myprint.o demo.o main
.PHONY: clean

上面的makefile当中有一个通配符%,其中%.c表示当前目录下面的所有的以.c结尾的文件。上面的makefile与下面的makefile的效果是一样的:

cflags=-c

main: demo.o myprint.o
gcc demo.o myprint.o -o main demo.o:demo.c
gcc $(cflags) demo.c myprint.o:myprint.c
gcc $(cflags) myprint.c clean:
rm myprint.o demo.o main
.PHONY: clean

在上面的makefile当中 $< 表示第一个依赖文件,上面的%就是一个通配符,%.c可以匹配任何以.c结尾的文件,你可能会有疑问%.c匹配不是所有的.c文件吗?那么等价的结果不应该是:

cflags=-c

main: demo.o myprint.o
gcc demo.o myprint.o -o main demo.o myprint.o:demo.c myprint.c
gcc $(cflags) demo.c clean:
rm myprint.o demo.o main
.PHONY: clean

事实上你可以认为make会将通配符匹配的文件一一展开,有几个文件就将产生对应数目的编译目标,而不是将他们都放在一起。

Makefile文件自动搜索

在一个工程项目当中我们可以会有许多的目录以及源文件,而make命令只会自动搜索当前目录下的文件,如果当前目录下没有那么make命令就会产生错误。因此make也给我们提供了一种文件搜索的功能。

在makefile当中,我们可以使用VAPTH指定make去搜索文件的路径。我们先来测试一下当我们没有使用VPATH的时候指定其他目录下的文件会出现什么情况,我们的文件目录结构如下图所示:

我们的makefile内容如下(先把VPATH的那一行注释掉):

cflags=-c

# VPATH=./files

main: demo.o myprint.o a.o b.o
gcc demo.o myprint.o a.o b.o -o main demo.o:demo.c
gcc $(cflags) demo.c myprint.o:myprint.c
gcc $(cflags) myprint.c
a.o: a.c
gcc $(cflags) a.c
b.o: b.c
gcc $(cflags) b.c clean:
rm myprint.o demo.o main
.PHONY: clean

执行结果如下图所示:

我们现在修改我们的makefile文件如下:

cflags=-c

VPATH=./files

main: demo.o myprint.o a.o b.o
gcc demo.o myprint.o a.o b.o -o main demo.o:demo.c
gcc $(cflags) demo.c myprint.o:myprint.c
gcc $(cflags) myprint.c a.o: a.c
gcc $(cflags) $<
b.o: b.c
gcc $(cflags) $< clean:
rm myprint.o demo.o main
.PHONY: clean

再次执行make命令,执行结果如下图所示:

我们可以看到仍然出错了,这是因为虽然make可以找到a.c和b.c的位置,但是在执行编译命令的时候我们执行的依然是gcc -c a.c,这条命令是会在当前目录下去寻找a.c的,而不会在其他路径下去寻找a.c,在这里我们可以使用$<去解决这个问题,$<表示第一个依赖的文件。

修改之后的makefile文件如下所示:

cflags=-c

VPATH=./files

main: demo.o myprint.o a.o b.o
gcc demo.o myprint.o a.o b.o -o main demo.o:demo.c
gcc $(cflags) demo.c myprint.o:myprint.c
gcc $(cflags) myprint.c a.o: a.c
gcc $(cflags) $<
b.o: b.c
gcc $(cflags) $< clean:
rm myprint.o demo.o main a.o b.o
.PHONY: clean

执行结果如下图所示:

如果你想设置多条目录的话,设置的规则为VPATH=dir1:dir2:dir3:dir4

总结

在本篇文章当中主要介绍了一些makefile的基础用法,和一些常用的小技巧,虽然不是很难,但是掌握了可以很大的跳高我们的工作效率,也可以方便我们阅读别人写的makefile文件。


以上就是本篇文章的所有内容了,我是LeHung,我们下期再见!!!更多精彩内容合集可访问项目:https://github.com/Chang-LeHung/CSCore

关注公众号:一无是处的研究僧,了解更多计算机(Java、Python、计算机系统基础、算法与数据结构)知识。

彻底掌握Makefile(一)的更多相关文章

  1. 说说Makefile那些事儿

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

  2. 编写一个通用的Makefile文件

    1.1在这之前,我们需要了解程序的编译过程 a.预处理:检查语法错误,展开宏,包含头文件等 b.编译:*.c-->*.S c.汇编:*.S-->*.o d.链接:.o +库文件=*.exe ...

  3. 编写简单的Makefile文件

    makefile中的编写内容如下: www:hello.c x.h gcc hello.c -o hello clean: rm hello www:hello.c  x.h 表示生成www这个文件需 ...

  4. 简单编写Makefile

    相信很多朋友都有过这样的经历,看着开源项目中好几页的makefile文件,不知所云.在日常学习和工作中,也有意无意的去回避makefile,能改就不写,能用ide就用ide.其实makefile并没有 ...

  5. [转]Linux中configure/makefile

    本文教你如何使用autoconf.automake等来制作一个以源代码形式(.tar.gz)发布的软件.并可在执行configure时使用自定义参数. 一.概述和基础知识 在Linux下得到一个以源代 ...

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

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

  7. make 查找的文件名顺序为:“GNUmakefile”、“makefile”、“Makefile”

    默认的情况下,make会在工作目录(执行make的目录)下按照文件名顺序寻找makefile文件读取并执行,查找的文件名顺序为:“GNUmakefile”.“makefile”.“Makefile”. ...

  8. 实例:对2个Makefile的备注

    实例1:Makefile编译链接简单.c函数 example.c Makefile exe: example.c gcc example.c -o exe clean: rm exe 执行效果: 实例 ...

  9. Linux中C程序调试、makefile

    gcc基本语法格式:gcc [-选项] 源文件 [-选项] 目标文件,GCC编译C程序的过程: 预处理:gcc -E hello.c hello.i.-E指定执行到预处理结束,下面类似. 编译:gcc ...

  10. Linux工具入门:make工具与Makefile文件

    1. make工具 利用make工具可以自动完成编译工作,这些工作包括: 如果修改了某几个源文件,则只重新编译这几个源文件 如果某个头文件被修改了,则重新编译所有包含该头文件的源文件 利用这种自动编译 ...

随机推荐

  1. 如何参与开源项目 - 细说 GitHub 上的 PR 全过程

    目录 一.概述 二.为什么要参与开源项目 三.为什么我想介绍如何 PR 四.我想参与开源项目,怎么开始? 4.1.寻找一个合适的开源项目 4.2.寻找贡献点 五.我要提交 PR,怎么上手? 5.1.第 ...

  2. Tomcat深入浅出——最终章(六)

    前言 利用了六天的时间,对Tomcat的内容进行了整理. 学习不易.整理也不易,自己坚持的更了下来,很不错. 希望每一位在努力前行的小伙伴,都要相信坚持就会有收获. 本次Tomcat就探寻到这里,明年 ...

  3. 【docker专栏4】使用docker安装nginx提供web服务

    一般学习一项技术,会先用一个最简单的例子或最典型的例子来向大家讲解入门内容,所以此文为大家介绍使用docker安装nginx容器服务.从基础使用的角度来讲,此文几乎涵盖了docker最核心的内容:镜像 ...

  4. 4-5 Spring Boot

    1. 关于Spring Boot Spring Boot是Spring官方的一个产品,其本质上是一个基于Maven的.以Spring框架作为基础的进阶框架,很好的支持了主流的其它框架,并默认完成了许多 ...

  5. AtCoder Beginner Contest 253 F - Operations on a Matrix // 树状数组

    题目传送门:F - Operations on a Matrix (atcoder.jp) 题意: 给一个N*M大小的零矩阵,以及Q次操作.操作1(l,r,x):对于 [l,r] 区间内的每列都加上x ...

  6. TCP协议调试工具TcpEngine V1.3.0使用教程

    简介   这里说的TCP协议调试定义是在开发长连接TCP协议应用时,为了验证代码流程或查找bug,需要与对端交互数据过来,当需要时可以暂停发送:单条发送:跳过发送:正常发送:发送时修改数据等.   T ...

  7. 1269: 求最长上升子序列(LIS)

     题目描述: LIS问题(longest increasing subsequence),即:最长上升子序列问题,是动态规划中一个比较经典的问题.具体描述为:一个有n个整数的序列:A[1],A[2], ...

  8. websocket心跳实现

    简介 在实际项目中可能会使用到websocket,在使用过程中可能会存在一种问题就是,当网络异常断开时.或者websocket服务波动时,websocket会断开,导致异常,正常情况下,我们会采用心跳 ...

  9. websocket、socket、http对比

    简介 在之前的理解中,讲述了socket.websocket等相关的理解,本文就socket.websocket.http理解一下其对应的联系和区别. HTTP 协议 http 为短连接:客户端发送请 ...

  10. SpringBoot集成文件 - 如何使用POI导出Word文档?

    前文我们介绍了通过Apache POI导出excel,而Apache POI包含是操作Office Open XML(OOXML)标准和微软的OLE 2复合文档格式(OLE2)的Java API.所以 ...