Makefile 笔记(2)— 使用变量
admin 于 2021年11月16日 发表在 嵌入式linux开发

1. Makefile 内容

一个完整的 Makefile ,包含了五个东西:显式规则、隐含规则、变量定义、指示符和注释。

  1. 显式规则

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

  2. 隐式规则

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

  3. 变量定义

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

  4. Makefile 指示符

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

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

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

    • 定义一个多行变量。

  5. 注释 Makefile 中" #"字符后的内容被作为是注释内容(和 shell 脚本一样)处理。如果此行的第一个非空字符为" #",那么此行为注释行。注释行的结尾如果存在反斜线( \),那么下一行也被作为注释行。

2. 解析Makefile

GNU make 的执行过程分为两个阶段:

  • 第一阶段:

    读取所有Makefile文件(包含 "MakefileS" 变量指定的、指示符 "include" 指定的、以及命令行选项 "-f(--file)" 指定的 Makefile 文件),内建所有的变量、明确规则和隐含规则,并建立所有目标和依赖关系结构链表。

  • 第二阶段:

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

3. 使用变量

当定义了一个变量后,就可以在 Makefile 的很多地方使用这个变量。变量的引用方式是: $(VARIABLE_NAME) 或 ${VARIABLE_NAME} 。

举例如下:

objects = program.o foo.o utils.o
program : $(objects)
	cc -o program $(objects)

$(objects) : defs.h

注意:美元符号 "$" 在 Makefile 中有特殊的含义,因此,在命令或者文件名中使用 "$" 时,需用两个美元符号 "$$" 来代替。

变量引用的展开过程是严格的文本替换,即变量的字符串被精确的展开在变量被引用的地方,如下:

foo = c
prog.o : prog.$(foo)
	$(foo)$(foo) -$(foo) prog.$(foo)

展开后就是:

prog.c : prog.c
	cc -c prog.c

3.1 赋值变量

赋值方式,主要有以下四种:

IMMEDIATE = DEFERRED
IMMEDIATE := IMMEDIATE
IMMEDIATE ?= DEFERRED
IMMEDIATE += DEFERRED

延迟变量 "="

采用 "IMMEDIATE = DEFERRED" 方式定义变量。这种风格的变量采用递归展开方式。变量的引用采用严格的文本替换方式,如果此变量定义在其他变量的引用,这些被引用的变量会在它被展开的同时被展开。例如:

foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:
    @echo $(foo)

输出结果为 'Huh?' ,且输出结果与变量定义的位置无关。

立即变量 ":="

采用 "IMMEDIATE := IMMEDIATE" 方式定义变量 。这种风格的变量采用直接展开方式。变量值中对其他量或者函数的引用在定义变量时被展开,所以变量定义后就是一个实际需要的文本串,其中不再包含变量的引用例如:

x := foo
y := $(x) bar
x := later

等价于:
y := foo bar
x := later

和递归展开不同,此风格变量在定义时就完成了对所有变量和函数的展开,因此若将x定义在y之后,则得到 "y := bar"。

条件赋值 "?="

采用 "IMMEDIATE ?= DEFERRED" 方式定义变量 。

符号"?=",被称为条件赋值,因为,只有此变量在之前没有赋值的情况下才会对这个变量进行赋值。如:

FOO ?= bar

等价于:
ifeq ($(orign FOO), undefined)
	FOO = bar
endif

追加赋值 "+="

采用 "IMMEDIATE += DEFERRED or IMMEDIATE" 方式定义变量。实现对一个变量值的追加操作。例如:

objects += another.o

这个操作把字符串 "another.o" 添加到变量 "objects" 原有值的末尾,使用空格和原有值分开。

3.2 make 环境变量

MAKEFILES

如果当前环境定义了一个 "MAKEFILES" 环境变量,make执行时首先将变量的值作为需要读入的 Makefile 文件,多个文件之间使用空格分开。类似使用指示符 "include" 包含其它 Makefile 文件一样。

MAKEFILES_LIST

make程序在读取多个 Makefile 文件时,包括由环境变量 "MAKEFILES" 指定、命令行指定、当前工作下默认以及使用指示符"include" 指定包含的,在对这些文件进行解析执行之前 make 读取的文件名将会被自动依次追加到变量 "MAKEFILES_LIST" 的定义域中。

这样可以通过测试此变量的最后一个字获取当前 make 程序正在处理的 Makfile 文件名。

name1 := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
include inc.mk
name2 := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))

all:
    @echo name1 = $(name1)
    @echo name2 = $(name2)

输出结果为:

name1 = Makefile
name2 = inc.mk

VPATH

GNU make 可以识别到一个特殊变量 "PATH"。通过变量 "PATH" 可以指定依赖文件的搜索路径,当规则的依赖文件在当前目录不存在时,make 会在此变量所指定的目录下去寻找这些依赖文件。其实,"VPATH" 变量所指定的是 Makefile 中所有文件的搜索路径,包括了规则的依赖文件和目标文件。

定义变量 "VPATH" 时,使用空格或者冒号(:)将多个需要搜索的目录分开。例如:

VPATH = src:../headers

这样,就为所有规则的依赖指定了两个搜索目录,"src" 和 "../headers"。

SHELL

make 对所有规则命令的解析使用环境变量 "SHELL" 所指定的那个程序,在GNU make中,默认的程序是 "/bin/sh"。

MAKE

在使用 make 的递归调用时,在 Makefile 规则的命令行中应该使用变量 "MAKE" 来代替直接使用 "make"。例如:

subsystem:
    cd subdir && $(MAKE)

变量 "MAKE" 的值是 "make"。如果其值为 "/bin/make" 那么上边规则的命令就是 "cd subdir && /bin/make"。好处是,当我们使用一个其它版本的make程序时,可以保证最上层使用的make程序和其子目录下执行的make程序保持一致。

MAKELEVEL

在多级递归调用的 make 执行过程中,变量 "MAKELEVEL" 代表了调用的深度。在 make 一级级的执行过程中变量 "MAKELEVEL" 的值不断发生变化。

最上一级 "MAKELEVEL" 的值是 "0"、下一级是 "1",再下一级是 "2"。

MAKEFLAGS

在 make 的递归过程中,最上层make的命令行选项如 "-k"、"-s" 等会被自动的通过环境变量 "MAKEFLAGS" 传递给子 make 进程。

传递过程中变量 "MAKEFLAGS" 的值会被主控 make 自动的设置为包含执行 make 时的命令行选项的字符串。

执行多级的 make 调用时,当不希望传递 "MAKEFLAGS" 给子 make 时,需要再调用子程序 make 对这个变量进行赋空,例如:

subsystem:
    cd subdir && $(MAKE) MAKEFLAGS=

MAKECMDGOALS

make 在执行时,设置一个特殊变量 "MAKECMDGOALS",此变量记录了命令行参数指定的终极目标列表,没有通过参数指定终极目标时,此值为空。

例如,使用变量 "MAKECMDGOALS" 来判断命令行参数是否指定终极目标为 "clean",如果不是才包含。

sources = foo.c bar.c
ifneq($(MAKECMDGOALS),clean)
    include $(sources:.c=.d)
endif

CURDIR

在 make 递归调用中,变量 "CURDIR" 代表 make 的工作目录。当使用 "-C" 选项进入一个子目录后,此变量将被重新赋值。

.SUFFIXES

特殊目标 ".SUFFIXES" 的所有依赖指出了一系列在后缀规则中需要检查的后缀名。例如,把后缀 ".hack" 和 ".win" 加入到可识别后缀列表的末尾。

.SUFFIXES:.hack .win

若需要重设置默认的可识别后缀,如下

.SUFFIXES            #删除所有已定义的可识别后缀
.SUFFIXES:.c .o .h   #重新定义

.LIBPATTERNS

变量 ".LIBPATTERNS" 就是告诉链接器在执行链接过程中,对于出现 "-INAME" 的文件如何展开。

".LIBPATTERNS" 的值一般是多个包含模式字符(%)的字(一般不包含空格的字符串),多个字之间使用空格分开。

也可以将此变量置空,取消链接器对 "-INAME"格式的展开。

3.3 make 自动化变量

$@

表示规则的目标文件名。如果目标是一个文档文件(linux下,一般称.a文件为文档文件,也称为静态库文件),那么它代表这个文档的文件名。在多目标模式规则中,它代表的是哪个触发规则被执行的目标文件名。

$%

当规则的目标文件时一个静态库时,代表静态库的一个成员名。例如,规则的目标是 "foo.a(bar.o)",那么, "$%" 的值就为 "bar.o","$@" 的值就是 "foo.a"。如果目标文件不是静态库文件,其值为空。

$<

目标依赖列表中的第一个依赖文件名。如果是一个目标文件使用隐含规则来重建,则它代表由隐含规则加入的第一个依赖文件。

$?

所有比目标文件更新的依赖文件列表,即最新被修改过的文件。空格分割。如果目标是静态库文件名,代表的是库成员(.o文件)。

$^

规则的所有依赖文件列表,使用空格分隔。如果目标是静态库文件,它所代表的只能是所有库成员(.o文件)名。一个文件可重复的出现在目标的依赖中,变量 "$^" 只记录它的一次引用情况。就是说变量 "$^" 会去掉重复的依赖文件。

$+

类似 "$^",但是它保留了依赖文件中重复出现的文件。主要用在程序链接时库的交叉引用场合。

$*

在模式规则和静态模式规则中,代表“茎”。“茎”是目标模式中“%”所代表的部分(当文件名中存在目录时,“茎”也包含目录(斜杠之前)部分),例如:文件 "dir/a.foo.b",当目标的模式是 “a.%.b” 时,"$*" 的值为 "dir/a.foo"。“茎” 对于构造相关文件名非常有用。

$(@D)

表示目标文件的目录部分(不包括斜杠)。如果 "$@" 是 "dir/foo.o",那么 "$(@D)"的值为 "dir"。如果 "$@" 不存在斜杠,其值就是 "."(当前目录)。

$(@F)

目标文件的完整文件名中除目录以来的部分(实际文件名)。如果 "$@" 为 "dir/foo.o",那么 "$(@F)" 只就是 "foo.o"

$(*D)

$(*F)

分别代表目标 "茎" 中的目录部分和文件名部分。

$(%D)

$(%F)

当以如 "archive(member)" 形式静态库为目标时,分别表示库文件成员 "member" 名中的目录部分和文件名部分。它仅对这种形式的规则目标有效。

$(<D)

$(<F)

分别表示规则中第一个依赖文件的目录部分和文件名部分。

$(^D)

$(^F)

分别表示所有依赖文件的目录部分和文件部分(不存在同一文件)。

$(+D)

$(+F)

分别表示所有依赖文件的目录部分和文件部分(可存在重复文件)。

$(?D)

$(?F)

分别表示被更新的依赖文件的目录部分和文件部分。

下一篇:《Makefile 笔记(3)— GNU make 指示符》

注意:本站所有文章除特别说明外,均为原创,转载请务必以超链接方式并注明作者出处。 标签:makefile,gnumake,make使用变量