GNU Make手册阅读笔记(2)-- 第三章

第三章  Makefile总述

1.Makefile文件的内容

               显式规则:它描述了在何种情况下如何更新一个或者多个被称为目标的文件( Makefile 的目标文件)。书写 Makefile 时需要明确地给出目标文件、目标的依赖文件列表以及更新目标文件所需要的命令(有些规则没有命令,这样的规则只是纯粹的描述了文件之间的依赖关系)。

    隐含规则:它是 make 根据一类目标文件(典型的是根据文件名的后缀)而自动推导出来的规则。 make 根据目标文件的名,自动产生目标的依赖文件并使用默认的命令来对目标进行更新(建立一个规则)。

    变量定义:使用一个字符或字符串代表一段文本串,当定义了一个变量以后, Makefile 后续在需要使用此文本串的地方,通过引用这个变量来实现对文本串的使用。

         Makefile 指示符:指示符指明在 make 程序读取 makefile 文件过程中所要执行的一个动作。其中包括:

读取一个文件,读取给定文件名的文件,将其内容作为 makefile 文件的一部分。

决定(通常是根据一个变量的得值)处理或者忽略 Makefile 中的某一特定部分。

         定义一个多行变量。

注释: Makefile 中“ # ”字符后的内容被作为是注释内容(和 shell 脚本一样)处理。如果此行的第一个非空字符为“ # ”,那么此行为注释行。注释行的结尾如果存在反斜线( / ),那么下一行也被作为注释行。一般在书写 Makefile 时推荐将注释作为一个独立的行,而不要和 Makefile 的有效行放在一行中书写。当在 Makefile 中需要使用字符“ # ”时,可以使用反斜线加“ # ”( /# )来实现(对特殊字符“ # ”的转义),其表示将“ # ”作为一字符而不是注释的开始标志。

需要注意的地方:

Makefile 中第一个规则之后的所有以 [Tab] 字符开始的的行, make 程序都会将其交给系统 shell 程序去解释执行。因此,以 [Tab] 字符开始的注释行也会被交给 shell 来处理,此命令行是否需要被执行( shell 执行或者忽略)是由系统 shell 程序来判决的。

对一个变量引用的地方 make 所做的就是将这个变量根据定义进行基于文本的展开,展开变量的过程不涉及到任何变量的具体含义和功能分析。

2.Makefile文件命名

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

makefile 文件的命名不是这三个任何一个时,需要通过 make 的“ -f ”或者“ --file ”选项来指定 make 读取的 makefile 文件。给 make 指定 makefile 文件的格式为:“ -f NAME ”或者“ -- file=NAME ”,它指定文件“ NAME ”作为执行 make 时读取的 makefile 文件。也可以通过多个“ -f ”或者“ --file ”选项来指定多个需要读取的 makefile 文件,多个 makefile 文件将会被按照指定的顺序进行链接并被 make 解析执行。当通过“ -f ”或者“ --file ”指定 make 读取 makefile 的文件时, make 就不再自动查找这三个标准命名的 makefile 文件。

3.包含其他Makefile文件

    include foo *.mk $(bar)    

    如果指示符“ include ”指定的文件不是以斜线开始(绝对路径,如 /usr/src/Makefile... ),而且当前目录下也不存在此文件; make 将根据文件名试图在以下几个目录下查找:首先,查找使用命令行选项“ -I ”或者“ --include-dir ”指定的目录,如果找到指定的文件,则使用这个文件;否则继续依此搜索以下几个目录(如果其存在):“ /usr/gnu/include ”、“ /usr/local/include ”和“ /usr/include ”。

使用“ include FILENAMES... ”, make 程序处理时,如果“ FILENAMES ”列表中的任何一个文件不能正常读取而且不存在一个创建此文件的规则时 make 程序将会提示错误并退出。

使用“ -include FILENAMES... ”的情况是,当所包含的文件不存在或者不存在一个规则去创建它, make 程序会继续执行,只有真正由于不能正确完成终极目标的重建时(某些必需的目标无法在当前已读取的 makefile 文件内容中找到正确的重建规则),才会提示致命错误并退出。

4.MAKEFILES变量

        make 在执行时,首先读取的是环境变量“ MAKEFILES ”所指定的文件列表,之后才是工作目录下的 makefile 文件,“ include ”所指定的文件是在 make 发现此关键字的时、暂停正在读取的文件而转去读取“ include ”所指定的文件。

5.MAKEFILE_LIST变量

    在对文件进行解析执行之前 make 读取的文件名将会被自动依次追加到变量“ MAKEFILE_LIST ”的定义域中。 变量“ MAKEFILE_LIST ”的最后一个字只可能是指示符“ include ”指定所要包含的那个文件的名字。

6.Makefile的重建

Makefile 可由其它文件生成 ,例如 RCS SCCS 文件。如果 Makefile 由其它文件重建,那么在 make 在开始解析这个 Makefile 时需要重新读取更新后的 Makefile 、而不是之前的 Makefile make 的处理过程是这样的:

make 在读入所有 makefile 文件之后,首先将所读取的每个 makefile 作为一个目标,寻找更新它们的规则。如果存在一个更新某一个 makefile 文件明确规则或者隐含规则,就去更新对应的 makefile 文件。完成对所有的 makefile 文件的更新之后,如果之前所读取任何一个 makefile 文件被更新,那么 make 就清除本次执行的状态重新读取一遍所有的 makefile 文件(此过程中,同样在读取完成以后也会去试图更新所有的已经读取的 makefile 文件,但是一般这些文件不会再次被重建,因为它们在时间戳上已经是最新的)。读取完成以后再开始解析已经读取的 makefile 文件并开始执行必要的动作。

实际应用中,我们会明确给出 makefile 文件,而并不需要来由 make 自动重建它们。但是 make 在每一次执行时总会自动地试图重建那些已经存在的 makefile 文件,如果需要处于效率考虑,可以采用一些办法来避免 make 在执行过程时查找重建 makefile 的隐含规则。例如我们可以书写一个明确的规则,以 makefile 文件作为目标,规则的命令定义为空。

Makefile 规则中,如果使用一个没有依赖只有命令行的 双冒号规则 去更新一个文件,那么每次执行 make 时,此规则的目标文件将会被无条件的更新(此规则定义的命令会被无条件执行)。如果这样一个规则的目标是 makefile 文件,那么执行 make 时,这个 makefile 文件(双冒号规则的目标)就会被无条件更新,而使得 make 的执行陷入到一个死循环(此 makefile 文件被不断的更新、重新读取、更新再重新读取的过程)。为了防止这种情况的发生, make 在遇到一个目标是 makefile 文件的双冒号规则时,将忽略对这个规则的执行(其中包括了使用“ MAKEFILES ”指定、命令行选项指定、指示符“ include ”指定的需要 make 读取的所有 makefile 文件中定义的这一类双冒号规则)。

执行 make 时,如果没有使用“ -f --file )”选项指定一个文件, make 程序将读取缺省的文件。和使用“ -f --file )”选项不同, make 无法确定工作目录下是否存在缺省名称的 makefile 文件。如果缺省 makefile 文件不存在,但可以通过一个规则来创建它(此规则是隐含规则),则会自动创建缺省 makefile 文件,之后重新读取它并开始执行。

因此,如果在当前目录下不存在一个缺省的 makefile 文件, make 将会按照搜索 makefile 文件的名称顺序去试图创建它,直到创建成功或者超越其缺省的命名顺序。需要明确的一点是:执行 make 时,如果不能成功地创建缺省的 makefile 文件,并不一定会导致错误。一个存在(缺省命名的或者可被创建的)的 makefile 文件并不是 make 正确运行的前提(关于这一点大家会在后续的阅读过程中体会到)。

当使用“ -t --touch )”选项来更新 Makefile 的目标文件(更新规则目标文件的时间戳)时,对于哪些是 makefile 文件的目标是无效的,这些目标文件( makefile 文件)的时间戳并不会被更新。就是说即使在执行 make 时使用了选项“ -t ”,那些目标是 makefile 文件的规则同样也会被执行(重建这些 makefile 文件,而其它的规则不会被执行, make 只是简单的更新规则目标文件的时间戳);类似还有选项“ -q —question )”和“ -n —just-print ”,这主要是因为一个过时的 makefile 文件对其它目标的重建规则在当前看来可能是错误的

正因为如此,执行命令“ make –f mfile –n foo ”首先会试图重建“ mfile 文件”、并重新读取它,之后会打印出更新目标“ foo ”所要执行的命令(但不会真正的执行这些命令)。在这种情况时,如果不希望重建 makefile 文件。我们需要在执行 make 时,在命令行中将这个 makefile 文件作为一个最终目标,这样选项“ –t ”和其它的选项就对这个 makefile 文件目标有效,防止执行这个 makefile 作为目标的规则 (如果是“ -t ”参数,则是简单的更新这个 makefile 文件的时间戳)。同样,命令“ make –f mfile –n mfile foo ”会读取文件“ mfile ”,打印出重建文件“ mfile ”的命令、重建“ foo ”的命令而实际不执行任何命令。并且所打印的用于更新“ foo ”目标的命令是选项“ -f ”指定的、没有被重建的“ mfile ”文件中所定义的命令。

7.Makefile的重载

    有些情况下,存在两个比较类似的 makefile 文件。其中一个( makefile-A )需要使用另外一个( makefile-B )中所定义的变量和规则。通常我们会想到在“ makefile-A ”中使用指示符“ include ”包含“ mkaefile-B ”来达到目的。但使用这种方式,如果在两个 makefile 文件中存在相同目标,而在不同的文件中其描述规则使用不同的命令。这样,相同的目标文件就同时存在两个不同的规则命令,这是 makefile 所不允许的。遇到这种情况,使用指示符“ include ”显然是行不通的。

如果存在一个命名为“ Makefile ”的 makefile 文件,其中描述目标“ foo ”的规则和其他的一些规则,我们也可以书写一个内容如下命名为“ GNUmakefile ”的文件。

 

#sample GNUmakefile

foo:

frobnicate > foo

 

%: force

@$(MAKE) -f Makefile $@

force: ;

 

执行命令“ make foo ”, make 将使用工作目录下命名为“ GNUmakefile ”的文件并执行目标“ foo ”所在的规则,创建目标“ foo ”的命令是:“ frobnicate > foo ”。如果执行另外一个命令“ make bar ”,因为在“ GUNmakefile ”中没有此目标的更新规则。 make 将使用“所有匹配模式”规则,执行命令“ $(MAKE) -f Makefile bar ”。如果文件“ Makefile ”中存在此目标更新规则的定义,那么这个规则会被执行。此过程同样适用于其它 GNUmakefile ”中没有给出的目标更新规则。此方式的灵活之处在于:如果在“ Makefile ”文件中存在同样一个目标“ foo ”的重建规则,由于 make 执行时首先读取文件“ GUNmakefile ”并在其中能够找到目标“ foo ”的重建规则,所以 make 就不会去执行这个“所有模式匹配规则”(上例中目标“ % ”所在的规则)。这样就避免了使用指示符“ include ”包含一个 makefile 文件时所带来的目标规则的重复定义问题。

8.make的执行

第一阶段:读取所有的 makefile 文件(包括“ MAKIFILES ”变量指定的、指示符“ include ”指定的、以及命令行选项“ -f(--file) ”指定的 makefile 文件),内建所有的变量、明确规则和隐含规则,并建立所有目标和依赖之间的依赖关系结构链表。

第二阶段:根据第一阶段已经建立的依赖关系结构链表决定哪些目标需要更新,并使用对应的规则来重建这些目标。

 

make 执行的第一阶段中如果变量和函数被展开,那么称此展开是“立即”的 ,此时所有的变量和函数被展开在需要构建的结构链表的对应规则中(此规则在建立链表是需要使用)。其他的展开称之为“延后”的 。这些变量和函数不会被“立即”展开,而是直到后续某些规则须要使用时或者在 make 处理的第二阶段它们才会被展开。

1. 变量取值

变量定义解析的规则如下:

 

IMMEDIATE = DEFERRED

IMMEDIATE ?= DEFERRED

IMMEDIATE := IMMEDIATE

IMMEDIATE += DEFERRED or IMMEDIATE

define IMMEDIATE

DEFERRED

Endef

 

当变量使用追加符( += )时,如果此前这个变量是一个简单变量(使用 := 定义的)则认为它是立即展开的,其它情况时都被认为是“延后”展开的变量。

2. 条件语句

所有使用到条件语句在产生分支的地方, make 程序会根据预设条件将正确地分支展开。就是说条件分支的展开是“立即”的。其中包括:“ ifdef ”、“ ifeq ”、“ ifndef ”和“ ifneq ”所确定的所有分支命令。

3. 规则的定义

所有的规则在 make 执行时,都按照如下的模式展开:

 

IMMEDIATE : IMMEDIATE ; DEFERRED

DEFERRED

 

其中,规则中目标和依赖如果引用其他的变量,则被立即展开。而规则的命令行中的变量引用会被延后展开。此模板适合所有的规则,包括明确规则、模式规则、后缀规则、静态模式规则。

9.make执行过程总结

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

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

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

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

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

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

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

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值