前言
我们是地球人。曾经为复杂的 Makefile 变量而苦恼过吗?这就是我们的用武之地。我们简化您的构建流程,以获得更快、更高效的结果。看看我们。
自 1976 年出现以来,Make 一直在帮助开发人员自动执行编译代码、构建可执行文件和生成文档的复杂流程。
与其他编程语言一样,Make 允许您定义和使用有助于值重用的变量。
您是否发现自己在多个地方使用相同的值?这既重复又容易出错。如果您想更改此值,则必须在所有地方更改它。这个过程很乏味,但是可以用变量来解决,并且Make提供了强大的变量操作技术,可以让你的生活更轻松。
在本文中,您将了解有关 make 变量以及如何使用它们的所有信息。
什么是 Makefile 变量
变量是一个命名构造,可以保存可以在程序中重用的值。它的定义方式是先写一个名称,后跟 =
、 :=
或 ::=
,然后写一个值。变量的名称可以是除“:
”、“#
”、“=
”或空格之外的任何字符序列。此外,与许多其他编程语言一样,Makefile 中的变量名称区分大小写。
以下是变量定义的示例:
foo = World
变量值之前的所有空格都将被删除,但末尾的空格将被保留。允许在变量值内使用 $
,但 makefile 将假定以 $
符号开头的字符串引用另一个变量,并将替换变量的值:
foo = one$two
# foo becomes onewo
您很快就会了解到, makefile 假定 $t
引用另一个名为 t
的变量并替换它。由于 t
不存在,它是空的,因此 foo
变成 onewo
。如果您想逐字包含 $
,则必须使用另一个 $
对其进行转义:
foo = one$$two
如何使用Makefile
变量
一旦定义,变量就可以在任何目标、先决条件或配方中使用。要替换变量的值,您需要使用美元符号 ( $
),后跟括号或花括号中的变量名称。例如,您可以使用 ${foo}
和 $(foo)
引用 foo
变量。
以下是配方中变量引用的示例:
foo = World
all:echo "Hello, $(foo)!"
与之前的 makefile 一起运行 make 将打印“Hello, World!
”。
变量使用的另一个常见示例是在编译 C 程序时,您可以定义一个 objects 变量来保存所有目标文件的列表:
objects = main.o foo.o bar.o
program : $(objects) # objects used in prerequisitecc -o program $(objects) # objects used in recipe$(objects) : foo.h # objects used in target
此处, objects 变量已在目标、先决条件和配方中使用。
与许多其他编程语言不同,使用未显式设置的变量不会导致错误;相反,该变量将使用空字符串作为其默认值。但是,一些特殊变量具有内置的非空值,并且其他几个变量为每个不同的规则设置了不同的默认值(稍后会详细介绍)。
如何设置变量
设置变量是指定义一个变量的初始值以及稍后在程序中更改其值。您可以在 makefile 中显式设置一个值,也可以将其作为环境变量或命令行参数传递。
Makefile 中的变量
您可以通过四种不同的方式在 Makefile 中定义变量:
- 递归赋值
- 简单的分配
- 立即分配
- 有条件赋值
递归和简单赋值
您可能还记得,您可以使用 =
、 :=
和 ::=
定义变量。根据定义变量所使用的运算符的不同,变量的扩展方式存在细微的差异。
- 使用
=
定义的变量称为递归扩展变量,并且 - 用
:=
和::=
定义的变量称为简单扩展变量。
当递归扩展变量被扩展时,其值将被逐字替换。如果替换文本包含对其他变量的引用,它们也会被替换,直到不再遇到变量引用。考虑以下示例,其中 foo
扩展为 Hello $(bar)
:
foo = Hello $(bar)
bar = Worldall:@echo "$(foo)"
由于 foo 是一个递归扩展变量,因此 $(bar)
也被扩展,并打印“Hello World
”。每次扩展变量时都会使用任何引用变量的当前值执行此递归扩展过程:
bar = World
foo = Hello $(bar)bar = Make
# foo now expands to "Hello Make"all:@echo ${foo} # prints Hello Make
递归扩展变量的最大优点是它们可以轻松地分段构造新变量:您可以定义变量的单独部分并将它们串在一起。您可以定义更细粒度的变量并将它们连接在一起,这使您可以更好地控制 make 的执行方式。
例如,考虑以下在编译 C 程序时经常使用的代码片段:
CFLAGS = -g
ALL_CFLAGS = -I. $(CFLAGS)
main.o: main.c$(CC) -c $(ALL_CFLAGS) main.c
这里, ALL_CFLAGS
是一个递归扩展的变量,它扩展为包含 CFLAGS
的内容以及 -I.
选项。如果您希望在保留强制 -I.
选项的同时传递其他选项,则可以覆盖 CFLAGS
变量:
CFLAGS="-g -Wall" # ALL_CFLAGS expands to "-I. -g -Wall"
递归扩展变量的缺点是无法将某些内容附加到变量的末尾:
CFLAGS = $(CFLAGS) -I. # Causes infinite recursion
为了克服这个问题,GNU Make 支持另一种类型的变量,称为简单扩展变量,它是用 := 或 ::= 定义的。简单扩展的变量在定义时会被扫描以获取更多变量引用,并且它们会被一劳永逸地替换。
与递归扩展变量不同,在递归扩展变量中,引用的变量将扩展为其当前值,在简单扩展变量中,引用的变量将在定义变量时扩展为其值:
bar := World
foo := Hello $(bar)bar = Makeall:@echo ${foo} # Prints Hello World
使用简单的扩展变量,可以实现以下操作:
CFLAGS = $(CFLAGS) -I.
GNU Make 支持简单的递归扩展变量。然而,其他版本的 make 通常只支持递归扩展变量。 2012 年,对简单扩展变量的支持已添加到可移植操作系统接口 (POSIX) 标准中,仅使用 ::=
运算符。
立即分配
用 :::=
定义的变量称为立即扩展变量。与简单扩展的变量一样,它的值在定义时会立即扩展。但就像递归扩展变量一样,每次使用时都会重新扩展。值立即展开后,会自动加引号,展开后的值中所有 $
的实例都会转换为 $$
。
在以下代码中,立即扩展的变量 foo 的行为与简单扩展的变量类似:
bar := World
foo :::= Hello $(bar)bar = Makeall:@echo ${foo} # Prints Hello World
然而,如果有对其他变量的引用,事情就会变得有趣:
var = one$$two
OUT :::= $(var)
var = three$$four
这里, OUT 将具有值 one$$two
。这是因为 $(var)
立即扩展为 one$two
,它被引用以获得 one$$two
。但 OUT
是一个递归变量,所以使用时, $two
会被扩展:
two = twoall:@echo ${OUT} # onetwo
POSIX Make 支持 :::=
运算符,但 GNU Make 从 4.4 版本开始包含此运算符。
条件赋值
仅当变量尚未定义时,条件赋值运算符 ?=
才可用于设置变量:
foo = Worldfoo ?= Make # foo will not change
bar ?= Make # bar will changeall:@echo Hello ${foo}@echo Hello ${bar}
有条件地定义变量的一种等效方法是使用 origin 函数:
foo ?= Make# is equivalent toifeq ($(origin foo), undefined)
foo = Make
endif
这四种类型的分配可以在某些特定情况下使用:
外壳分配
有时您可能需要运行 shell 命令并将其输出分配给变量。您可以使用 shell 函数来做到这一点:
files = $(shell ls) # Runs the `ls` command & assigns its output to `files`
其简写是 shell 赋值运算符 !=
。对于此运算符,右侧必须是 shell 命令,其结果将分配给左侧:
files != ls
带空格的变量
变量定义末尾的尾随空格保留在变量值中,但开头的空格将被去除:
foo = xyz # There are spaces at the beginning and at the end# Prints "startxyz end"
all:@echo "start${foo}end"
可以通过使用第二个变量来存储空格字符来保留开头的空格:
nullstring =
foo = ${nullstring} xyz # Spaces at the end# Prints "start xyz end"
all:@echo "start${foo}end"
目标特定变量
可以将变量的范围限制为仅针对特定目标。其语法如下:
target … : variable-assignment
这是一个例子:
target-one: foo = World
target-two: foo = Maketarget-one:@echo Hello ${foo}target-two:@echo Hello ${foo}
这里,变量 foo 将根据当前评估的目标 make 具有不同的值:
特定于模式的变量
特定于模式的变量可以将变量的范围限制为与特定模式匹配的目标。语法类似于特定于目标的变量:
pattern … : variable-assignment
例如,以下行将任何以 .c
结尾的目标的变量 foo
设置为 World
:
%.c: foo = World
当您想要为共享公共模式的多个目标设置变量时,通常会使用特定于模式的变量,例如为所有 C 文件设置相同的编译器选项。
环境变量
当您将 make 变量与环境变量配对时,它们的真正威力就开始显现。当 make 在 shell 中运行时,shell 中存在的任何环境变量都会转换为具有相同名称和值的 make 变量。这意味着您不必在 makefile 中显式设置它们:
all:@echo ${USER}
当您运行之前的 makefile 时,它应该打印您的用户名,因为 USER 环境变量存在于 shell 中。
此功能最常与标志一起使用。例如,如果您使用首选 C 编译器选项设置 CFLAGS 环境变量,则大多数 makefiles 将使用它们来编译 C 代码,因为按照惯例, 如果 makefile 中对变量进行显式赋值,它将覆盖任何同名的环境变量:
USER = Boball:@echo ${USER}
较早的 makefile 将始终打印 Bob ,因为赋值会覆盖 $USER
环境变量。您可以将 -e
标志传递给 make ,以便环境变量覆盖分配,但不建议这样做,因为它可能会导致意外结果。
命令行参数
您可以将变量值作为命令行变量传递给 make 命令。与环境变量不同,命令行参数将始终覆盖 makefile 中的赋值,除非使用 override 指令:
override FOO = Hello
BAR = Worldall:@echo "${FOO} ${BAR}"
您可以简单地运行 make ,并且将使用默认值:
$ make
Hello World
您可以通过将 BAR 作为命令行参数传递来传递新值:
$ make BAR=Make
Hello Make
但是,由于 override 指令与 FOO 一起使用,因此无法通过命令行参数更改它:
$ make FOO=Hi
Hello World
此功能很方便,因为它允许您更改变量的值而无需编辑 makefile 。这最常用于传递可能因系统而异的配置选项或用于自定义软件。作为一个实际示例,Vim 使用命令行参数来覆盖配置选项,例如运行时目录和默认配置的位置。
如何追加到变量
您可以使用简单扩展变量的先前值向其中添加更多文本:
foo := Hello
foo := ${foo} World# prints "Hello World"
all:@echo ${foo}
如前所述,此语法将通过递归扩展变量产生无限递归错误。在这种情况下,您可以使用 += 运算符,它将文本附加到变量,并且它可用于递归扩展和简单扩展变量:
foo = Hello
foo += Worldbar := Hello
bar += World# Both print "Hello World"
all:@echo ${foo}@echo ${bar}
但是,它对于两种不同类型的变量的工作方式存在细微的差异,您可以在文档中阅读有关内容。
如何使用特殊变量
在 Make 中,任何未定义的变量都会分配一个空字符串作为默认值。然而,有一些特殊变量是例外:
自动变量
自动变量是特殊变量,其值是根据特定规则的目标和先决条件自动设置的。以下是几个常用的自动变量:
$@
是规则目标的文件名。$<
是第一个先决条件的名称。$?
是比目标新的所有先决条件的名称,它们之间有空格。如果目标不存在,则将包含所有先决条件。$^
是所有先决条件的名称,它们之间有空格。
这是一个显示自动变量实际作用的示例:
hello: one two@echo $@@echo $<@echo $?@echo $^@touch helloone:@touch onetwo:@touch twoclean:@rm -f hello one two
与之前的 makefile 一起运行 make 会打印以下内容:
hello
one
one two
one two
如果您运行 touch one
修改 one 并再次运行 make ,您将得到不同的输出:
hello
one
one
one two
由于 one
比目标 hello
新,因此 $?
仅包含 one
。
这些自动变量存在变体,可以从匹配的表达式中提取目录和目录内文件名称。您可以在官方文档中找到所有自动变量的列表。
当目标和先决条件名称指示配方如何执行时,通常会使用自动变量。一个非常常见的实际示例是以下规则,它将 x.c
形式的 C 文件编译为 x.o
:
%.o:%.c$(CC) -c $(CPPFLAGS) $(CFLAGS) $^ -o $@
隐式变量
为编译系统制作一些常用操作的预定义规则。这些规则包括以下内容
- 使用
$(CC) -c $(CPPFLAGS) $(CFLAGS) $^ -o $@
形式的规则将x.c
编译为x.o
- 使用
$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $^ -o $@
形式的规则编译x.cc
或x.cpp
- 链接静态对象文件
x.o
以使用$(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)
形式的规则创建x
- 还有很多
这些隐式规则利用某些称为隐式变量的预定义变量。其中一些如下:
- CC 是编译C程序的程序。默认为
cc
。 - CXX 是编译C++程序的程序。默认为
g++
。 - CPP 是运行C预处理器的程序。默认为
$(CC) -E
- LEX 是将Lex语法编译成源代码的程序。默认为 lex 。
- YACC 是将Yacc语法编译成源代码的程序。默认为 yacc 。
您可以在 GNU Make 的文档中找到隐式变量的完整列表。
就像标准变量一样,您可以显式定义隐式变量:
CC = clang# This implicit rule will use clang as compiler
foo.o:foo.c
标识
标志是特殊变量,通常用于将选项传递给各种命令行工具,例如编译器或预处理器。编译器和预处理器是一些常用工具隐式定义的变量,包括以下内容:
- CFLAGS 被传递给 CC 来编译 C。
- CPPFLAGS 被传递到 CPP 以预处理 C 程序。
- CXXFLAGS 被传递给 CXX 来编译 C++。
作者: | 岬淢箫声 |
日期: | 2023年11月1日 |
版本: | 1.0 |
链接: | http://caowei.blog.csdn.net |