前言

在嵌入式工程的编译中,make经常与gcc配合使用,用于对工程进行编译。当然,这只是一份GUN Make文档的阅读笔记,我并不打算在这篇笔记中说太多与文档阅读本身无关的东西,因为我懒。一来我接触Make工具没多久,并没有把握说清楚它的由来,二来我不想浪费太多口舌在与笔记本身无关的事情上。对于Make和makefile完全没有概念的你如果看到的这篇文章,建议先去阅读知乎上一位大佬写的《Makefile概念入门》,以下为传送门:https://zhuanlan.zhihu.com/p/29910215。不过这篇文章后面的内容一点都不入门,起码对我来说是这样,所以我当时只是看懂make和makefile到底是什么鬼就把它关掉然后啃官方文档去了。

一、《GUN Make》给小白的阅读建议

《GUN Make》真是写的非常好,连各种层次的人该怎么阅读文档都在开头给写的明明白白。文档中对于小白选手的阅读意见总共只有两条:
1. Read the first few sections of each chapter, skipping the later sections.
2. The exception is Chapter2[An Introduction to Makefiles], page 3, all of which is introductory.

所以我在读这篇文档的时候,认真拜读了Chapter2以及其他章的第一节,对于其他部分,我是打算等到自己以后用上了才去补齐的,这篇笔记因此只能称之为粗读笔记。

二、Makefile的入门介绍

1、Make的工作原理

总的来说,Make工具需要一个称为makefile的文件来告诉Make如何工作。一般来说,makefile告诉Make如何去编译并且链接一个程序。

我们知道,Make这个工具可以让我们在编译一个大工程时,不必因为一个小改动就重新编译整个工程的源文件,我们只需要重新编译被修改的部分。总的来说,Make依据下面4个原则来更新项目中的文件(因为大多数时候Make用于C项目,本篇文章中默认项目都是使用C语言编写的)。

Make更新文件的原则

了解以上四个原则是很有必要的,当我们编写makefile的时候,应该依照以上原则编写。

2、Makefile的基本规则语法

最基本的Makefile规则语法形式如下:

target... : prerequisites...recipe......

每条规则一般都由一个或者多个target(目标)、prerequisites(依赖)以及recipe(处方?)组成。当Make通过比较target和prerequisites的新旧来决定是否执行本条规则内的recipe,如果prerequisites比target的修改日期更新,则recipe被执行,否则不执行规则内的recipe。有一条值得注意:每一个recipe前面一定要先加一个tab制表符

对于这三者,我在此摘录文档内对它们的英文原文解释:

target usually is the name of a file that is generated by a program…can also be the name of an action to carry out, such as ‘clean’, see Section 4.5[Phony Targets], page 29.

prerequisites is a file that is used as input to create the target. A target often depends on several files.

recipe is an action that male carries out, it may hace more than one command either on the same line or eah on its own line. You need to put a tab character at the beginning of every recipe line!

《GUN Make》

下面是一个官方手册中的Makefile例子,在这个编辑器工程的Makefile例子中,所有的C文件都包含defs.h,不过只有那些定义编辑命令的文件包含command.h,最后,只有那些会更改编辑器缓存的底层文件会包含buffer.h。

简单的Makefile例子

在这个Makefile中有几个点值得关注:
1、在目标为edit的规则中,使用了’\’将一个长行分隔为两行以方便阅读。
2、默认情况下,Make默认从Makefile的第一条规则开始执行,也就是说,当我们在shell中只敲下”make”并回车,make将会自动寻址当前目录下的Makefile并从其中的第一条规则开始执行。当然,我们也可以指定开始执行的规则,如下所示:

make clean //从目标为clean的规则开始执行

3、在默认开始执行第一条规则edit时,由于目标依赖一些.o文件,而这些.o文件又有自己的更新规则,于是会先触发执行.o文件自己的更新规则,最后再回过头来执行edit。你可以将它理解为一个递归的过程。
4、很明显,由于clean这个目标并不是任何其他目标的依赖同时也不是第一条规则的目标,那么除非指定从这个规则开始执行,否则这个规则永远不会被执行;同样的,由于clean的规则没有任何依赖,这个规则执行时永远不会触发其他规则的执行。

3、make如何看待自己要执行的recipes

Bear in mind that make does not know anything about how the recipe work. It is up to you to supply recipes that will update the target file properly. All make does is execate the recipe you have sepecifed when the target file needs to be updated.

《GUN Make》

从上面引文我们可以看出,Make并不确保执行的recipe会正确更新target,编写正确的recipe是编写者需要确保的事,make只是简单的执行recipe,并不会检查recipe是否正确更新了target。

4、改进前面的例子

使用变量(variables)和隐含规则(implicit rules)可以简化前面的例子,变量在后文有粗略的介绍,而隐含规则感兴趣的可以参考《GUN Make》的第十章“ Using Implicit Rules”。

改进后的例子

主要改进了两点:
1、使用变量及那话重复的依赖列表。一般标准的Makefile文件中都至少有一个变量用于列出所有Object文件的名字(一般命名为:objects, OBJECTS, objs, OBJS, obj, OBJ其中的一个)。

2、使用隐含规则让make自己推导出recipe。这里我直接摘录原文了:

It is not necessay to spell out the recipes for compiling the individual C source file, because make can figure them out: it has an implicit rule for updating a ‘.o’ file from a correspondingly named ‘.c’ file using a “cc -c” command.

when a ‘.c’ file is used automatically in this way, it is also automatically added to the list of prerequisites.(这里要注意,也就是说可以不在生成.o文件的依赖列表中明确列出生成用到的.c文件,make可以自己推导出同名的.c文件来生成.o文件,所以你会发现前面的例子里依赖项只有一堆.h的头文件)

《GUN Make》

3、使用.PHONY 来指定clean为虚目标,防止目录下真的有一个叫clean的文件时,clean中的recipe不被执行。想一想就知道了,recipe被执行的条件是target不存在或者prerequisites比target新,因为clean目标没有prerequisites,所以只有clean文件不存在时clean的recipe才会被执行。这样如果目录下以及有了叫clean的文件,clean中的recipe就不会被执行。使用.PHONY指定clean为虚目标,可以避免这个问题。不管目录下有没有clean文件,clean中的recipe都会被执行。

三、如何编写Makefile

1、总体说明

一个基本的Makefile文件,一般会包括以下内容:

makefile主要包含内容

在文档中还给出了一些很有用的tips,一并列出在下面:

  1. 如果希望打一个’#’而不是添加注释,使用’\#’
  2. 借助’\’可以让#开头的注释在多行有效,而不是一行,原因见第3条
  3. 当希望将一行过长的语句分隔为两行,而make处理时还当作一行看待,使用’\’来分隔一行为两行

在某些特殊情况下,使用’#’做注释时应该谨慎或者避免,下面摘录原文的说明:

You cannot use comments within variable references or function calls: any instance of # will be treated literally (rather than as the start of a comment) inside a variable reference or function call.

Comments within a recipe are passed to the shell, just as with any other recipe text. The shell decides how to interpret it: whether or not this is a comment is up to the shell.

Within a define directive, comments are not ignored during the defnition of the variable, but rather kept intact in the value of the variable. When the variable is expanded they will either be treated as make comments or as recipe text, depending on the context in which the variable is evaluated.

《GUN Make》

2、编写规则

规则是Makefile的重要组成部分,Make通过读取Makefile中的规则来决定如何更新工程文件。一般来说,一条规则分为三个部分:target、prerequisites和recipe,这个在前面有介绍,不再赘述。

默认情况下,如果没有特别指定从哪条规则开始执行,Make总是执行文件中的第一条规则,除此之外,其他规则的书写顺序对执行的先后没有影响(Make只在需要的时候执行需要的规则,而不是按书写顺序的先后执行)。

一般来说,虽然Makefile中的第一条规则是最先执行的,却往往是最后执行完的,因为第一条规则中的依赖部分经常有自己的更新规则,而那更新规则里的依赖又有对应的更新规则,这样层层递归,第一条规则触发了其他规则的执行,其他规则执行完后,再转回第一条规则继续执行。我们编译一份程序的时候也是这样,总是先编译每个子文件,再回过头将它们连接起来组成程序,因此,Makefile中的第一条规则一般是用于触发整个程序的编译(并且第一条规则的目标一般起名为:all,意指更新整个工程)。

all目标触发其他规则

对于规则的书写语法,在前文的 第二章第2节:Makefile基本规则语法 一节已经叙述了,这里只补充一些东西。

在规则中,可以通过’$’来调用定义好的变量(如果希望真的打一个’$’而不是做变量引用,需要打”$$”,如下所示:

OBJ = test1.c test2.call : test1.c test2.ccc -o all $(OBJ)

一般来说,一条规则一般只有一个目标,但也可以允许有多个,官方文档是推荐一条规则只有一个目标。

3、编写Recipes

一般来说,每条规则都有自己的recipes(实在不知道怎么翻译recipe好,翻译成 处方 总感觉怪怪的),其由一条或者多条shell指令组成,按照书写顺序执行。通常执行的结果是使得target被更新,默认调用/bin/sh来执行shell指令,可以手动更改。

也就是说,在Makefile中既有shell的指令,又有makefile自己的语句,所以说makefile遵循两种语法:在recipes中使用shell的语法,在其他部分遵循makefile自己的语法。make对于makefile中的recipes只进行有限的处理,然后就会丢给shell执行。

以下是一些编写recipes时需要注意的事项:

  1. 每个recipe一般用[TAB]字符开头,当然还有其他写法,不过我觉得目前知道这个就够用了
  2. 以[TAB]开头的空行同样算一条recipe,称为 empty recipe
  3. 在recipe中‘#’后的内容不会被作为注释,而是会原封不动的传递给shell
  4. 以[TAB]开头,在recipe中定义的变量同样会被当作shell变量原封不动传递给shell,而不是作为makefile的变量
  5. A conditional expression (ifdef, ifeq, etc. see Section 7.2 [Syntax of Conditionals], page 78) in a “rule context” which is indented by a tab as the frst character on the line, will be considered part of a recipe and be passed to the shell.(这句我不是很理解,暂时先用原文放着,以后去探究)

A、分隔Recipes中的长行

虽然说recipes遵循shell的语法,可是一般情况下我们还是可以使用 ‘\’ 来将recipes中的过长的一行分隔为两行而不改变原意。前面说过make在将recipes传递给shell前会做有限的处理,其中一项就是检测行尾的 ‘\’并做出相应的处理(one of the few way in which make does interpret recipes is checking for a backslash just before the newline)。但是与一般情况下还是有点不同,如下图所示:

make对于recipes中的’\’的处理

下面是《GUN Make》中所举的例子:

B、在Recipes中使用Variables

前面说过make在将recipes传递给shell前会做有限的处理,其中的另一项就是展开在recipes中引用的makefile中的变量,对于变量的引用很简单,常用的有两种:

$(var)
${var}

下面截取了《GUN Make》文档中的一个例子:

四、如何使用变量

变量是什么:A variable is a name defined in a makefile to represent a string of text, called the value of the variable.

变量的命名规则:A variable name may be any sequence of characters not containing ‘:’, ‘#’, ‘=’ or whitespace.(建议只用 字母、数字、下划线,变量名区分大小写)

变量的命名风格建议:
1、Using lower case letters for variable names that serve internal purposes in the makefile.
2、Reserving upper case for parameters that control implicit rules or for parameters that the user should override with command options ( see Section 10.5.3, page 120 ).

定义与引用变量:直接一个例子解决

#定义一个变量
objects = program.o foo.o utils.o#两种引用变量的方法,所谓变量引用,其实就是把变量代表的字符串插入引用处,make会在处理时展开它们
$(objects)
${objects}

五、Makefile中的条件语句

条件语句可以用于控制当一条规则执行时,其中的哪些recipes应该被执行,哪些不需要执行。对分支判断的处理会在make读入makefile之后,make执行makefile之前完成。

如上例,比较变量CC中的字符串是否等于”gcc”,如果相等,则执行else前的语句,否则执行else后的语句。

六、文本处理函数

下面是文档中对于文本处理函数的解释说明

Functions allow you to do text processing in the makefle to compute the fles to operate on or the commands to use in recipes. You use a function in a function call, where you give the name of the function and some text (the arguments) for the function to operate on. The result of the function’s processing is substituted into the makefle at the point of the call, just as a variable might be substituted.

《GUN Make》

在Makefile中调用函数和引用变量的方法差不多:

#make中预置了很多函数,参数与函数用空格分隔,参数之间用逗号分隔,函数会将处理结果插入调用处
$(function argument1,argument2...)
${function argument1,argument2...}

Make中支持的函数列表见:[ Index of Functions, Variables, & Directives]一节。

七、如何运行Make

最简单的方法就是切换到Makefile所在的目录下,然后在shell中敲下make并回车。但是有一些选项是常用的:

make -f altmake #指定名为altmake的文件作为makefile并处理它
make goal_name  #指定从规则 goal_name开始执行
make -q         #检查是否makefile中的target是最新的,但是不执行recipes

make运行完成后会返回对应的状态值:

后记

终于在自己定下的deadline到来前的几个小时完成了这篇笔记,其实厚着脸皮推迟了几次deadline,Make和Makefile是很有用的东西,这篇笔记的完成,只能说自己勉强站在了门槛上,以后还要慢慢在使用中深化对其的了解,就这样吧,完结。

《GUN Make》文档粗读笔记相关推荐

  1. openEuler 文档捉虫 2.0 上线啦,一键式提交 PR,成为开源贡献者,你也可以参与,文档伴读方案正式开源!

    hi~ 各位小伙伴 openEuler 文档捉虫 1.0 活动自 4 月开展以来,将 openEuler 官网和 Gitee 平台连结,自动创建 issue,解决了之前需要在两个平台之间来回跳转,提交 ...

  2. 电脑上PDF文档怎么做笔记?

    电脑上PDF文档怎么做笔记?当我们在电脑上阅读PDF文档的时候,经常会需要给PDF文件做一些笔记或批注,比如给PDF文件添加高亮.下划线.删除线和各种图像以及给文档内容添加可隐藏式备注等,那如何给PD ...

  3. 13、《Libevent中文帮助文档》学习笔记13:Linux下集成、运行libevent

    Linux下编译libevent的指导可以参考<4.<Libevent中文帮助文档>学习笔记4:Linux下编译libevent>,完成编译.安装,生成so库后,其他程序即可依 ...

  4. Grails 1.2参考文档速读(19):插件

    Grails是一个插件架构,这一点我们已经在前面体会到了,最典型的就是GORM一节中,我们明明没有定义crud操作,但在运行时却可以使用它,造成这一结果的"元凶"就是我们预先安装的 ...

  5. 《用Castor 处理XML文档》学习笔记

    --Castor可以完成Java和XML的相互转换 前面有介绍过json-lib这个框架,在线博文: [url]http://www.cnblogs.com/hoojo/archive/2011/04 ...

  6. spring文档怎么读

    http://spring.cndocs.tk/index.html 再配上谷歌翻译   读spring文档  太美了

  7. Grails 1.2参考文档速读(15):验证

    在前面的几篇中,我们已经看到了Grails的验证框架带来的便利,现在让我们深入对其进行了解. 和Grails的大多数特性一样,Grails的验证框架同样也是建立在Spring之上的,不同的是它是以Sp ...

  8. 18离线帮助文档_VIM学习笔记 Zeavim离线文档查看器

    Zeal是开源的跨平台软件,用于离线浏览各种开发文档.Zeal使用Dash提供的文档集(Docsets),涵盖近200种开发语言. Zeavim插件,可以在Vim中调用Zeal来查看离线文档. 安装配 ...

  9. Grails 1.2参考文档速读(4):第3章剩余内容

    本系列的2.3部分对参考文档第3章的配置基础.环境和日志配置部分进行了快速扫描,在这一篇中,让我们完成第3章的剩余部分. GORM无疑是Grails中的亮点,在第3章中关于它的配置选项有2个: 1. ...

最新文章

  1. 学习flask的网址
  2. nginx https反向代理tomcat
  3. 免费天气预报短信服务
  4. 理解 Java 核心基础精髓
  5. 7、Cocos2dx 3.0游戏开发找小三之3.0版本号的代码风格
  6. 软件工程复习提纲——第一章
  7. Security+ 学习笔记47 事件响应方案
  8. Java调用MQ队列
  9. python编写端口扫描器_端口扫描器编写 python
  10. 《东周列国志》第四十七回 弄玉吹箫双跨凤 赵盾背秦立灵公
  11. 关于monitor模式
  12. mysql 硬盘死机_磁盘空间不够导致mysql崩溃重启
  13. sqlServer简单建数据库,建表操作
  14. 【开关电源一】电源拓扑之buck、boost、buck-boost
  15. Python(17)python使用tkinter实现一个简单的CSGO幸运转盘抽奖游戏
  16. 测试过程中,遇到开发不认为是bug的bug,该怎么办
  17. 大学物理实验 载流圆线圈轴线上的磁场分布
  18. latex 行间公式大小(批量设置)
  19. 续订Office365E5订阅
  20. 苹果应用商店广告评论删除

热门文章

  1. 一键复制查询内容分享给他人链接url打开,他人对应的查询内容给分享者一致
  2. 如何在NS2中产生和使用Poisson Traffic
  3. 用java写一个超级详细的网银管理系统并附带代码注释
  4. 电信aep平台是什么意思_5G能给双创带来什么?技术赋能产业升级和双创舞台
  5. DVWA靶场SQL Injection(SQL注入)
  6. 2 Keys Keyboard 只有两个键的键盘
  7. struts2 如何接收input name[] 的数组或者集合
  8. 编程机器人考级证书有用吗_机器人等级考试将来有什么用
  9. 加密解密(字符串处理)
  10. linux下使用openssl和md5sum加密文件或者字符串