我正在为一个业余操作系统项目编写make脚本。在玩这个脚本时,我注意到(几乎)同一个变量的两个不同的变量展开会产生不同的结果(即使它们"直接"放在一起)。我将提供makefile的重要部分以及运行时的结果。
Makefile:
####################
# KERNEL #
####################
.PHONY: kernel
KERNEL_OBJS = $(patsubst %.c,%.o,$(wildcard kernel/*.c))
KERNEL_OBJS += $(patsubst %.asm,%.o,$(wildcard kernel/*.asm))
KERNEL_OBJS += $(DRIVER_OBJS)
KERNEL_NAME = kernel32.elf
kernel: $(KERNEL_OBJS)
@echo $^
@echo $(KERNEL_OBJS)
####################
# DRIVERS #
####################
.PHONY: drivers
DRIVER_OBJS := $(patsubst %.c,%.o,$(wildcard drivers/*/*.c))
通过终端以以下方式执行Makefile(GNU Make 4.2.1):
make kernel
这产生了以下结果:
kernel/kmain.o kernel/boot.o
kernel/kmain.o kernel/boot.o drivers/vga/vga.o
输出行当然来自内核配方中的两条"回显行"。值得一提的是,此代码片段中使用的所有变量在此处使用,在其他更大的make脚本中仅在此处使用。两个常规后缀规则用于构建KERNEL_OBJS,但它们无论如何都不应该改变这里的输出。除了后缀规则之外,这个片段及其变量与脚本的其余部分完全分离。
你知道为什么这两个变量展开式不同吗?你的,迈克尔。
这两个上下文之间的最大区别是,在解析makefile时,先决条件列表会立即展开,但只有在稍后make即将构建该目标时,才会展开配方。
当make第一次解析你的makefile时,它会发现这一行:
kernel: $(KERNEL_OBJS)
它会立即展开此变量。当变量被展开时,DRIVER_OBJS
变量还没有被设置,所以它是空字符串,你会得到这个:
kernel: kernel/kmain.o kernel/boot.o
然后make完成对所有makefile的解析,作为解析的一部分,DRIVER_OBJS
变量被设置。。。但这对上面的行来说并不重要,因为它已经被扩展了。
现在make决定要构建kernel
目标,为了做到这一点,它必须扩展配方:
@echo $^
@echo $(KERNEL_OBJS)
这里$^
是先决条件列表:kernel/kmain.o kernel/boot.o
。现在KERNEL_OBJS
已展开,DRIVER_OBJS
已设置,因此您可以获得完整的列表。
有关何时进行扩展的完整详细信息,请参阅How make Reads a Makefile。
您可以激活第二次扩展,然后在依赖项中使用它:
.SECONDEXPANSION:
kernel: $$(KERNEL_OBJS)