ChatGPT解决这个技术问题 Extra ChatGPT

GNU Makefile 变量赋值 =、?=、:= 和 += 有什么区别?

任何人都可以清楚地解释变量赋值在 Makefile 中是如何工作的。

和有什么区别:

 VARIABLE = value
 VARIABLE ?= value
 VARIABLE := value
 VARIABLE += value

我已经阅读了 GNU Make 手册中的 section,但它对我来说仍然没有意义。


e
ex1led

懒人集

VARIABLE = value

一个变量的正常设置,但在 value 字段中提到的任何其他变量都使用它们在变量使用点的值递归扩展,而不是它在声明时的值

立即设置

VARIABLE := value

通过内部值的简单扩展来设置变量 - 其中的值在声明时扩展。

不存在时的惰性设置

VARIABLE ?= value

仅当变量没有值时才设置变量。访问 VARIABLE 时始终评估 value。它相当于

ifeq ($(origin VARIABLE), undefined)
  VARIABLE = value
endif

有关详细信息,请参阅 documentation

附加

VARIABLE += value

将提供的值附加到现有值(如果变量不存在,则设置为该值)


A += B 扩展 B 吗?也就是说,如果我执行 A += B,然后执行 B += C,A 会评估为 ${B} 和 ${C} 的串联吗?
正如手册的链接部分所说。 += 根据原始分配所具有的任何简单或递归语义进行操作。所以是的,它将扩展 RHS,但它是立即执行还是以延迟方式执行取决于 LHS 上变量的类型。
当您说变量值被扩展时,您是什么意思?
@СашкоЛихенко 看看这里以了解扩展的含义gnu.org/software/make/manual/make.html#Flavors
“如果不存在则设置”是懒惰的还是立即的?我可以“如果缺席则懒惰设置”和“如果弃权则立即设置”?
s
strager

使用 = 会为变量分配一个值。如果变量已经有值,则将其替换。该值在使用时会被扩展。例如:

HELLO = world
HELLO_WORLD = $(HELLO) world!

# This echoes "world world!"
echo $(HELLO_WORLD)

HELLO = hello

# This echoes "hello world!"
echo $(HELLO_WORLD)

使用 := 类似于使用 =。但是,不是在使用时扩展值,而是在赋值期间扩展它。例如:

HELLO = world
HELLO_WORLD := $(HELLO) world!

# This echoes "world world!"
echo $(HELLO_WORLD)

HELLO = hello

# Still echoes "world world!"
echo $(HELLO_WORLD)

HELLO_WORLD := $(HELLO) world!

# This echoes "hello world!"
echo $(HELLO_WORLD)

使用 ?= 为变量分配一个值 iff 该变量以前未分配。如果变量之前被分配了一个空白值 (VAR=),它仍然被认为是 set我认为。否则,函数与 = 完全相同。

使用 += 与使用 = 类似,但不是替换值,而是将值附加到当前值,中间有一个空格。如果变量之前使用 := 设置,它会被扩展我认为。结果值在使用时会被扩展我认为。例如:

HELLO_WORLD = hello
HELLO_WORLD += world!

# This echoes "hello world!"
echo $(HELLO_WORLD)

如果使用了 HELLO_WORLD = $(HELLO_WORLD) world! 之类的内容,则会导致递归,这很可能会结束您的 Makefile 的执行。如果使用 A := $(A) $(B),结果将与使用 += 不完全相同,因为 B 使用 := 展开,而 += 不会导致 B 展开。


因此,VARIABLE = literalVARIABLE := literal 的结果总是等价的。我做对了吗?
@aiao,是的,因为文字对其用途是不变的
一个细微的区别是:- ?: 可以提高递归调用 makefile 的性能。例如,如果 $? = $(shell some_command_that_runs_long_time)。在递归调用中,这只会被评估一次。导致构建性能的提高。 := 会更慢,因为该命令会不必要地运行多次
a
akhy

我建议你用“make”做一些实验。这是一个简单的演示,展示了 =:= 之间的区别。

/* Filename: Makefile*/
x := foo
y := $(x) bar
x := later

a = foo
b = $(a) bar
a = later

test:
    @echo x - $(x)
    @echo y - $(y)
    @echo a - $(a)
    @echo b - $(b)

make test 打印:

x - later
y - foo bar
a - later
b - later bar

Check more elaborate explanation here


最好在每个配方前使用 @ 以避免这种令人困惑的重复结果。
Make 不支持 /* ... */ 块评论
m
mipadi

使用 VARIABLE = value 时,如果 value 实际上是对另一个变量的引用,则只有在使用 VARIABLE 时才确定该值。最好用一个例子来说明这一点:

VAL = foo
VARIABLE = $(VAL)
VAL = bar

# VARIABLE and VAL will both evaluate to "bar"

当您使用 VARIABLE := value 时,您会得到 value 的值现在。例如:

VAL = foo
VARIABLE := $(VAL)
VAL = bar

# VAL will evaluate to "bar", but VARIABLE will evaluate to "foo"

使用 VARIABLE ?= val 意味着您只设置 VARIABLE 的值 如果 VARIABLE 尚未设置。如果尚未设置,则将延迟设置值,直到使用 VARIABLE(如示例 1 所示)。

VARIABLE += value 只是将 value 附加到 VARIABLE。使用 =:= 确定 value 的实际值与最初设置时一样。


实际上,在您的第一个示例中,VARIABLE 是 $(VAL),VAL 是 bar。 VARIABLE 在使用时展开。
是的,这些评论正在解释使用它们时会发生什么。
啊;我猜你改正了,或者我把“评估”误读为“是”。
p
phs

在上述答案中,了解“值在声明/使用时扩展”的含义很重要。给出像 *.c 这样的值并不需要任何扩展。只有当命令使用此字符串时,它才会触发一些通配符。类似地,像 $(wildcard *.c)$(shell ls *.c) 这样的值不需要任何扩展,并且在定义时完全评估,即使我们在变量定义中使用了 :=

在您有一些 C 文件的目录中尝试以下 Makefile:

VAR1 = *.c
VAR2 := *.c
VAR3 = $(wildcard *.c)
VAR4 := $(wildcard *.c)
VAR5 = $(shell ls *.c)
VAR6 := $(shell ls *.c)

all :
    touch foo.c
    @echo "now VAR1 = \"$(VAR1)\"" ; ls $(VAR1)
    @echo "now VAR2 = \"$(VAR2)\"" ; ls $(VAR2)
    @echo "now VAR3 = \"$(VAR3)\"" ; ls $(VAR3)
    @echo "now VAR4 = \"$(VAR4)\"" ; ls $(VAR4)
    @echo "now VAR5 = \"$(VAR5)\"" ; ls $(VAR5)
    @echo "now VAR6 = \"$(VAR6)\"" ; ls $(VAR6)
    rm -v foo.c

运行 make 将触发创建额外(空)C 文件的规则,称为 foo.c,但 6 个变量的值中没有一个具有 foo.c


这是一个很棒的电话,并且在声明时有很多扩展示例,使用示例和一些单词在使用时扩展答案会很有用
V
Victor Sergienko

最赞成的答案可以改进。

让我参考 GNU Make 手册 "Setting variables""Flavors",并添加一些注释。

递归扩展变量

您指定的值是逐字安装的;如果它包含对其他变量的引用,则只要替换此变量(在扩展某些其他字符串的过程中),就会扩展这些引用。发生这种情况时,称为递归扩展。

foo = $(bar)

捕获foo 将扩展为 $(bar) 的值 每次 foo 被评估,可能导致不同的值。当然,您不能称其为“懒惰”!如果在午夜执行,这可能会让您大吃一惊:

# This variable is haunted!
WHEN = $(shell date -I)

something:
    touch $(WHEN).flag

# If this is executed on 00:00:00:000, $(WHEN) will have a different value!
something-else-later: something
    test -f $(WHEN).flag || echo "Boo!"

简单扩展变量

VARIABLE := value
VARIABLE ::= value

用 ':=' 或 '::=' 定义的变量只是扩展变量。

简单的扩展变量由使用 ':=' 或 '::=' [...] 的行定义。这两种形式在 GNU make 中是等价的;但是,POSIX 标准 [...] 2012 仅描述了 '::=' 形式。

一个简单扩展变量的值被扫描一次,当变量被定义时,扩展对其他变量和函数的任何引用。

没有太多要补充的。它立即被评估,包括递归扩展变量的递归扩展。

问题:如果 VARIABLE 引用 ANOTHER_VARIABLE

VARIABLE := $(ANOTHER_VARIABLE)-yohoho

并且 ANOTHER_VARIABLE 在此分配之前未定义,ANOTHER_VARIABLE 将扩展为空值。

未设置时分配

FOO ?= bar

相当于

ifeq ($(origin FOO), undefined)
FOO = bar
endif

其中 $(origin FOO) 仅在根本未设置变量时才等于 undefined

问题:如果在 makefile、shell 环境或命令行覆盖中将 FOO 设置为空字符串,则将不会分配 bar

追加

VAR += bar

Appending

当所讨论的变量以前没有定义过时,'+=' 的作用就像普通的 '=':它定义了一个递归扩展的变量。但是,当有先前的定义时,'+=' 的确切作用取决于您最初定义的变量的风格。

因此,这将打印 foo bar

VAR = foo
# ... a mile of code
VAR += $(BAR)
BAR = bar
$(info $(VAR))

但这将打印 foo

VAR := foo
# ... a mile of code
VAR += $(BAR)
BAR = bar
$(info $(VAR))

问题+= 的行为会有所不同,具体取决于之前分配的变量 VAR 的类型。

多行值

syntax to assign multiline value to a variable 是:

define VAR_NAME :=
line
line
endef

或者

define VAR_NAME =
line
line
endef

赋值运算符可以省略,然后它创建一个递归扩展变量。

define VAR_NAME
line
line
endef

endef 之前的最后一个换行符被删除。

奖励:shell 赋值运算符 '!='

 HASH != printf '\043'

是相同的

HASH := $(shell printf '\043')

不要使用它。 $(shell) 调用更具可读性,强烈建议不要在生成文件中同时使用这两种方法。至少,$(shell) 遵循了 Joel 的建议和makes wrong code look obviously wrong


在定义旨在被环境变量覆盖的宏(如 CFLAGS/CPPFLAGS/LDFLAGS)时,?== 是否等效?
@shadowtalker,不。使变量默认为环境变量,但 = 会覆盖任何现有值。另请注意,命令行 override 变量(在命令行上 make 之后)会覆盖 Makefile 中的每个单独分配,但 overrides 除外。
这听起来正确吗? =:= 覆盖环境变量,环境变量覆盖 =?,命令行“覆盖变量”覆盖两者,override 指令覆盖以上所有内容。这种与环境变量和命令行覆盖的交互可能是在您已经非常彻底的答案中非常有用的澄清。
这是准确的。谢谢你。我认为这有点超出了问题的范围。另一方面,毫无疑问“Makefile 变量如何获取它们的值?”。也许值得问这个问题。
我采纳了您的建议:stackoverflow.com/a/68825174/2954547