说说Makefile那些事儿

|扬说|透过现象看本质

  

  工作至今,一直对Makefile半知半解。突然某天幡然醒悟,觉得此举极为不妥,只得洗心革面从头学来,以前许多不明觉厉之处顿时茅塞顿开,想想好记性不如烂笔头,便来说说Makefile那些事儿。

  • Makefile到底是个啥玩意儿

  Makefile就是一文本文件。

-----------------------------------------------

$ file Makefile

Makefile: ASCII make commands text

-----------------------------------------------

  

  一般来说,我们平时所称的Makefile是指make命令以及Makefile文件,Makefile文件中记录着各种规则,make命令通过分析Makefile执行规则中的操作。

   

  看看百度百科的定义:

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

  再看看官方文档的定义:

You need a file called a makefile to tell make what to do. Most often, the makefile tells makehow to compile and link a program.

 

  • Makefile的由来

  任何一种技能或知识都是源之于某种社会需求,那为什么要用Makefile呢?

  当项目源文件很少的时候,我们也许还可以手动使用gcc命令来进行编译,但是当项目发展到一个庞大的规模时,再手动敲gcc命令去编译就变得不可能的事情。所以呢,在这样的历史背景下,就出现了某个大牛(斯图亚特·费尔德曼),在某年(1977年)在某地(贝尔实验室)制作了这样一个软件,它的名字就叫做make。

  用一句话来说明为啥用Makefile:为了实现自动化(当然大多数场景都是用在自动化编译中)。

  另外在编译过程中,为了节省时间,希望仅编译修改过的文件,这也是Makefile在设计时一个重要的设计观点。

  • Makefile的组成部分

  Makefile包含五个东西:显示规则,隐式规则,变量定义,文件指示,注释。具体含义还是直接引用网上的版本吧| | |

1、显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。

2、隐式规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。

3、变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。

4、文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。

5、注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“/#”。

最后,还值得一提的是,在Makefile中的命令,必须要以[Tab]键开始。

  这里说说规则(Rules):

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

  一条规则由三部分组成,目标(target)、先决条件(prerequisites)、命令(commands)。

  target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label)。

  prerequisites就是,要生成那个target所需要的文件或是目标。

  command也就是make需要执行的命令。(任意的Shell命令)

  

  这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。

  说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。

  这就是Makefile的规则。也就是Makefile中最核心的内容。

  • Makefile的核心思想

四个字,依赖关系

  • Makefile之执行过程

  1. 依次读取变量“MAKEFILES”定义的makefile文件列表

  2. 读取工作目录下的makefile文件(根据命名的查找顺序“GNUmakefile”,“makefile”,“Makefile”,首先找到那个就读取那个)

  3. 依次读取工作目录makefile文件中使用指示符“include”包含的文件

  4. 查找重建所有已读取的makefile文件的规则(如果存在一个目标是当前读取的某一个makefile文件,则执行此规则重建此makefile文件,完成以后从第一步开始重新执行)

  5. 初始化变量值并展开那些需要立即展开的变量和函数并根据预设条件确定执行分支

  6. 根据“终极目标”以及其他目标的依赖关系建立依赖关系链表

  7. 执行除“终极目标”以外的所有的目标的规则(规则中如果依赖文件中任一个文件的时间戳比目标文件新,则使用规则所定义的命令重建目标文件)

  8. 执行“终极目标”所在的规则

 

  • Makefile之模式规则

模式规则其实也是普通规则,但它使用了如%这样的通配符。如下面的例子:

%.o : %.c

    $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

此规则描述了一个.o文件如何由对应的.c文件创建。规则的命令行中使用了自动化变量“$<”和“$@”,其中自动化变量“$<”代表规则的依赖,“$@”代表规则的目标。此规则在执行时,命令行中的自动化变量将根据实际的目标和依赖文件取对应值。

 

  • Makefile之隐式规则

  如果发现某变量在shell和makefile中未找不到其定义,那么恭喜你,你极大可能遇到隐式规则了。当然隐式规则中的变量只是隐式规则的一部分。

  1.  隐式规则中的变量

  隐式规则中使用的变量分成两种:一种是命令相关的,如“CC”;一种是参数相关的,如“CFLAGS”。

  1)与命令相关的变量

变量

含义

AR

函数库打开包程序。默认命令是“ar”

AS

汇编语言编译程序。默认命令是“as”

CC

C语言编译程序。默认命令是“cc”

CXX

C++语言编译程序。默认命令是“g++”

CO

从RCS文件中扩展文件程序。默认命令是“co”

CPP

C程序的预处理器(输出是标准输出设备)。默认命令是“$(CC) -E”

FC

Fortran和Ratfor的编译器和预处理程序。默认命令是”f77”

GET

从SCCS文件扩展文件的程序。默认命令是“get”

LEX

Lex方法分析器程序(针对于C或Ratfor)。默认命令是”lex”

PC

Pascal语言编译程序。默认命令是”pc”

YACC

Yacc文法分析器(针对C程序)。默认命令是“yacc”

YACCR

Yacc文法分析器(针对Ratfor程序)。默认命令是“yacc -r”

MAKEINFO

转换Texinfo源文件(.texi)到info文件程序。默认命令是“makeinfo”

TEX

从TeX源文件创建TeX DVI文件的程序。默认命令是“tex”

WEAVE

转化Web到TeX的程序。默认命令是“weave”

TEXI2DVI

从Texinfo源文件创建TeX DVI文件的程序。默认命令是“texi2dvi”

CWEAVE

转化C Web到TeX的程序。默认命令是“cweave”

TANGLE

转换Web到Pascal语言的程序,默认命令是”tangle“

CTANGLE

转换C Web到C。默认命令是”ctangle“

RM

删除文件命令。默认命令是”rm -f“

  2)与参数相关的变量

变量

含义

ARFLAGS

函数库打包程序AR命令的参数。默认值是“rv”

ASFLAGS

汇编语言编译参数(当明显地调用”.s”或”.S”文件时)

CFLAGS

C语言编译器参数

CXXFLAGS

C++语言编译器参数

COFLAGS

RCS命令参数

CPPFLAGS

C预处理器参数(C和Fortran编译器也会用到)

FFLAGS

Fortran语言编译器参数

GFLAGS

SCCS ”get“程序参数

LDFLAGS

连接器参数(如“ld”)

LFLAGS

Lex文法分析器参数

PFLAGS

Pascal语法编译器参数

RFLAGS

Ratfor程序的Fortran编译器参数

YFLAGS

Yacc文法分析器参数

  2.  使用模式规则

  可以使用模式规则定义一个隐式规则。和一般规则类似,只是在模式规则中,目标的定义需要有“%”字符。“%”定义对文件名的匹配,表示任意长度的非空字符串。在依赖目标中同样可以使用“%”,只是依赖目标中“%”的取值,取决于其目标。

模式规则中“%”的展开和变量与函数的展开是有区别的,“%”的展开发生在变量和函数的展开之后。变量和函数的展开发生在make载入Makefile时,而“%”的展开则发生在运行时。

  1)  模式规则举例

  模式规则中,至少在规则的目标中要包含“%”符号。

  %.o : %.c ; <command ......>

  其含义是,字指出了从所有的.c文件生成相应的.o文件的规则。如果要生成的目标是”a.o b.o”,那么

  %.c”就是”a.c b.c”。

  %.o : %.c

  $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

  表示把所有的.c文件都编译成.o文件。

  其中,“$@”表示所有目标的集合,”$<”表示所有依赖目标的集合(在模式定义规则的情形下)。

  2)  自动化变量

  自动化变量只应出现在规则的命令中。

变量

含义

$@

表示规则中的所有目标文件的集合。在模式规则中如果有多个目标,“$@”就是匹配于目标中模式定义的集合

$%

仅当目标是函数库文件时,表示规则中的目标成员名,如果目标不是函数库文件(UNIX下是

.a,Windows是.lib),其值为空。

$<

依赖目标中的第一个目标名字,如果依赖目标是以模式(即”%“)定义的,则”$<”是符合模式的一系列的文件集

$?

所有比目标新的依赖目标的集合,以空格分隔

$^

所有依赖目标的集合,以空格分隔。如如果在依赖目标中有多个重复的,则自动去除重复的依赖目标,只保留一份

$+

同”$^”,也是所有依赖目标的集合,只是它不去除重复的依赖目标。

$*

目标模式中“%”及其之前的部分

$(@D)

“$@”的目录部分(不以斜杠作为结尾),如果”$@”中没有包含斜杠,其值为“.”(当前目录)

$(@F)

“$@”的文件部分,相当于函数”$(notdir $@)”

$(*D)

同”$(@D)”,取文件的目录部分

$(*F)

同”$(@F)”,取文件部分,但不取后缀名

$(%D)

函数包文件成员的目录部分

$(%F)

函数包文件成员的文件名部分

$(<D)

依赖目标中的第一个目标的目录部分

$(<F)

依赖目标中的第一个目标的文件名部分

$(^D)

所有依赖目标文件中目录部分(无相同的)

$(^F)

所有依赖目标文件中文件名部分(无相同的)

$(+D)

所有依赖目标文件中的目录部分(可以有相同的)

$(+F)

所有依赖目标文件中的文件名部分(可以有相同的)

$(?D)

所有被更新文件的目录部分

$(?F)

所有被更新文件的文件名部分

  • Makefile那些稀奇古怪的符号

这些稀奇古怪的符号是前面隐式规则中出现过,单独拎出来是因为我们会经常用到它们。

这些符号也就是我们常说的自动变量:

$@ :规则中的目标集

$^ :规则中的所有先决条件

$< :表示规则中的第一个先决条件

再来说说$VAR和$$VAR的区别:

makefile文件中的规则绝大部分都是使用shell命令来实现的,这里就涉及到了变量的使用,包括makefile中的变量shell命令范畴内的变量。在makefile的规则命令行中使用$var就是在命令中引用makefile的变量,这里仅仅是读取makefile的变量然后扩展开,将其值作为参数传给了一个shell命令;而$$var是在访问一个shell命令内定义的变量,而非makefile的变量。如果某规则有n个shell命令行构成,而相互之间没有用';'和'\'连接起来的话,就是相互之间没有关联的shell命令,相互之间也不能变量共享。

 

  • Makefile之伪目标

使用其原因一:避免和同名文件冲突

在现实中难免存在所定义的目标与所存在的目标是同名的,采用Makefile如何处理这种情况呢?Makefile中的假目标(phony target)可以解决这个问题。

假目标可以使用.PHONY关键字进行声明,对于假目标,可以想象,因为不依赖于某文件,make该目标的时候,其所在规则的命令都会被执行。

如果编写一个规则,并不产生目标文件,则其命令在每次make 该目标时都执行。

例如:

clean:

rm *.o temp

因为"rm"命令并不产生"clean"文件,则每次执行"make clean"的时候,该命令都会执行。如果目录中出现了"clean"文件,则规则失效了:没有依赖文件,文件"clean"始终是最新的,命令永远不会执行;为避免这个问题,可使用".PHONY"指明该目标。如:

.PHONY : clean

这样执行"make clean"会无视"clean"文件存在与否。

已知phony 目标并非是由其它文件生成的实际文件,make 会跳过隐含规则搜索。这就是声明phony 目标会改善性能的原因,即使你并不担心实际文件存在与否。

完整的例子如下:

.PHONY : clean

clean :

rm *.o temp

使用其原因二:提高执行make的效率

当一个目标被声明为伪目标后,make在执行此规则时不会试图去查找隐含规则来创建这个目标。这样也提高了make的执行效率,同时我们也不用担心由于目标和文件名重名而使我们的期望失败。

 

  • Makefile那些赋值的事儿

面试中经常被问到的问题: [=]和[:=]符号的区别。

=

  可以先使用后定义,这就导致makefile在全部展开后才能决定变量的值。

  有可能出现循环递归,无法暂开的问题。

:=

  必须先定义然后再使用,在当前的位置就可以决定变量的值。

再补充两种符号?= 、+=,如果熟悉C语言那对这两种符号理解会很容易。

?=

  相当于选择疑问句,如果前面的变量没被赋值,那就做赋值操作

+=

  相当于递加操作

看一个例子就明白了。新建一个Makefile,内容如下:

ifdef DEFINE_VRE

    VRE = “Hello World!”

else

endif

ifeq ($(OPT),define)

    VRE ?= “Hello World! First!”

endif

ifeq ($(OPT),add)

    VRE += “Kelly!”

endif

ifeq ($(OPT),recover)

    VRE := “Hello World! Again!”

endif

all:

    @echo $(VRE)

敲入以下make命令:

make DEFINE_VRE=true OPT=define 输出:Hello World!

make DEFINE_VRE=true OPT=add 输出:Hello World! Kelly!

make DEFINE_VRE=true OPT=recover  输出:Hello World! Again!

make DEFINE_VRE= OPT=define 输出:Hello World! First!

make DEFINE_VRE= OPT=add 输出:Kelly!

make DEFINE_VRE= OPT=recover 输出:Hello World! Again!
  • 杂谈

  以下,摘止采铜大大的《开放的智力》,献给那些不甘平庸的人。

  在今天这个时代,很多人疯狂地追求着很多东西,却没有反思过自己为什么要追求这些。我们争先恐后地买房,买车,我们笑谈着「卖肾」买iPhone,我们为各种名牌神魂颠倒,我们在微博、微信这些社交媒体上八面玲珑,我们在知乎上写答案然后等着赞同票一点点刷上去。这些都构成了米兰·昆德拉笔下的「媚俗」的生活。

  在一个钟鼓齐鸣的地方,你会失去驻足倾听的能力;在一个霓虹闪烁的地方,你就无法发现事物本身的光泽;在一个人流如梭的闹市,你会忘记原本行进的方向。以旁人的眼光作为自己人生的参考线,你会为追逐一个内心并不想要的东西,而气喘吁吁地奔跑;更糟糕的是,你的人生会被割裂开来,变成很多碎片,每一块碎片都用来讨好特定的一群人。也许,以一个较小的几率,你最终获得了世俗意义上的成功,但这种成功也很可能是平庸的。如果你做不成自己,再大的成功又有何意义?

  趋同的益处简单而直接,你可以很容易的融入一个集体,与别人有更多共同的兴趣,聊天时有更多的谈资,获得长辈更多的称赞,甚至,你会成为一个小群体中的明星。而它的代价是缓慢而隐蔽的,它会让你不去努力发现自己的天赋和才能,让你失去变得与众不同的所有可能性。曾经有一个日本小说家,从小学开始,成绩就很不好,是老师同学眼中的大笨蛋,当然他自己也这么觉得。不过他很早就有一个爱好,就是喜欢看小说。到了高一时,他的成绩在全年级四百多人中都排到了倒数几名,可他对此却是不急不躁,不仅不迎头赶上,而是变本加厉,开始做一件同学老师家长玩玩没想到的事:写小说。每天晚上,他用小伙伴们做作业的时间伏案写作,一上来就是长篇,一下就是几十万字,这样写了多年,遭遇了几次挫折之后,终于成名,他是东野圭吾。

  所以,内心坚定的人,从来不忌惮做一些不寻常之事。那些在别人看来疯狂的举动,对自己来说却可能是最好最安宁的选择。逃脱献媚于他人的牢笼,才能真正获得自由。

说说Makefile那些事儿的更多相关文章

  1. makefile那些事儿

    一.好处 自动化编译,一条make命令,整个工程可以完全自动编译,make命令是构建大型项目的首选方案. makefile就像一个shell脚本一样,用来定义规则,一个名称包含一条或多条命令,在终端m ...

  2. 关于makefile的那些事儿

    最近越来越感觉到,在linux下面身为一个程序员,不会makefile就不是一个合格的程序员,所以今天我们介绍下常用的makefile编写. 了解知识: 编译:把高级语言书写的代码转换为机器可识别的机 ...

  3. Linux就这个范儿 第8章 我是Makefile

    Linux就这个范儿 第8章 我是Makefile P287 Makefile的作用就是——自动化编译,一旦写好,只需要一个make命令(解析Makefile,执行Makefile中描述的操作),整个 ...

  4. 如何自己编译apue.3e中代码 &amp; 学习写makefile

    本来是搜pthread的相关资料,看blog发现很多linux程序员都看的一本神书<APUE>,里面有系统的两章内容专门讲pthread(不过是用c语言做的代码示例,这个不碍事,还是归到原 ...

  5. [apue] linux 文件系统那些事儿

    前言 说到 linux 的文件系统,好多人第一印象是 ext2/ext3/ext4 等具体的文件系统,本文不涉及这些,因为研究具体的文件系统难免会陷入细节,甚至拉大段的源码做分析,反而不能从宏观的角度 ...

  6. 总结iOS开发中的断点续传那些事儿

    前言 断点续传概述 断点续传就是从文件赏赐中断的地方重新开始下载或者上传数据,而不是从头文件开始.当下载大文件的时候,如果没有实现断点续传功能,那么每次出现异常或者用户主动的暂停,都会从头下载,这样很 ...

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

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

  8. 编写简单的Makefile文件

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

  9. 简单编写Makefile

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

随机推荐

  1. yum命令安装mysql

    linux下使用yum安装mysql,以及启动.登录和远程访问. 1.安装 查看有没有安装过: yum list installed mysql* rpm -qa | grep mysql* 查看有没 ...

  2. 瓷砖铺放 (状压DP+矩阵快速幂)

    由于方块最多涉及3行,于是考虑将每两行状压起来,dfs搜索每种状态之间的转移. 这样一共有2^12种状态,显然进行矩阵快速幂优化时会超时,便考虑减少状态. 进行两遍bfs,分别为初始状态可以到达的状态 ...

  3. Python编程软件的安装与使用——Windows、Linux和Mac

    Python版本:3.6.2  操作系统:Windows  作者:SmallWZQ 最近,有读者透露:Python软件如何安装?为什么自己安装的软件会有各种"奇怪"的问题?据此,本 ...

  4. 每天学点SpringCloud(五):如何使用高可用的Eureka

    前几篇文章我们讲了一下Eureka的基础使用,但是呢有一个很重要的问题,我们讲的都是单机版的情况,如果这个时候Eureka服务挂了的话,那么我们的服务提供者跟服务消费者岂不是都废了?服务提供者和消费者 ...

  5. MVC报错:找到多个与名为“Home”的控制器匹配的类型。

    错误原因是:在根目录中的Controller中有HomeController,而在Areas中也有一个HomeController,只是他们的命名空间不一样. 这样的话,只需要在对应的路由注册中加入命 ...

  6. ICM Technex 2017 and Codeforces Round #400 (Div. 1 + Div. 2, combined) D. The Door Problem 2-SAT

    题目链接:http://codeforces.com/contest/776/problem/D D. The Door Problem time limit per test 2 seconds m ...

  7. JQuery 同时获取多个标签的指定内容并储存为数组

    在网页开发中经常会碰到获取同种类型的 标签 的值得问题,比如下面的两种情况. 当需要批量获取同种标签的指定值时,新人就会碰上一点小麻烦. 比如 id=problem1的demo var list1=$ ...

  8. MySQL基于mysqldump快速搭建从库

    MySQL主从搭建总的来说大致分为3个步骤: 1. 为主从实例添加复制所需参数以及创建复制用的账户 2. 需要 […]

  9. 证书制作 z

    一.WCF中的安全方式 说到安全就会涉及到认证,消息一致性和机密性,WCF的安全方式分为两种,即传输安全和消息安全. 传输安全和消息安全的区别:传输安全提供点对点的安全: 比如 A 提供服务,B和C直 ...

  10. python基于yield实现协程

    def f1(): print(11) yield print(22) yield print(33) def f2(): print(55) yield print(66) yield print( ...