Reputation: 15
I am working on a make-script for a hobby OS project. When playing with the script I noticed that two different variable expansions of (almost) the same variable yields different results (even though they are placed 'directly' after each other). I'll provide the important part of the makefile and the result when ran.
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 executed via terminal in the following manner (GNU Make 4.2.1):
make kernel
And this yields the following result:
kernel/kmain.o kernel/boot.o
kernel/kmain.o kernel/boot.o drivers/vga/vga.o
The output lines are of-course from the two 'echo lines' in the kernel recipe. It is worth mentioning that all variables used in this code-snippet are used here and only here in the otherwise larger make-script. Two regular suffix rules are used to build the KERNEL_OBJS but they should not alter the output here in anyway. Apart from the suffix rules, this snippet and it's variables is completely seperated from the rest of the script.
Any ideas why the two variable expansions differ? Yours, Mikael.
Upvotes: 0
Views: 77
Reputation: 137
You can activate second expansion and then use it in the dependency:
.SECONDEXPANSION:
kernel: $$(KERNEL_OBJS)
Upvotes: 0
Reputation: 101051
The big difference between the two contexts is that the prerequisite list is expanded immediately when the makefile is parsed, but the recipe is only expanded later, when make is about to build that target.
When make first parses your makefile it finds this line:
kernel: $(KERNEL_OBJS)
It expands this variable immediately. When the variable is expanded the DRIVER_OBJS
variable has not been set yet, so it is the empty string and you get this:
kernel: kernel/kmain.o kernel/boot.o
Then make finishes parsing all the makefiles and as part of that, the DRIVER_OBJS
variable is set... but that doesn't matter to the above line because it's already been expanded.
Now make decides it wants to build the kernel
target and in order to do that it has to expand the recipe:
@echo $^
@echo $(KERNEL_OBJS)
Here $^
is the prerequisite list: kernel/kmain.o kernel/boot.o
. Now KERNEL_OBJS
is expanded and now DRIVER_OBJS
is set, so you get the full list.
See How make Reads a Makefile for full details on when expansion happens.
Upvotes: 0