Here’s why you might want thunks in a Makefile.
Let’s say you keep all your build tools in a central location. To be able to build on multiple platforms, you have multiple versions of each tool stored in that central location.
Let’s say you have a tool named “
bork
”, with separate binaries for the different host platforms: “
bork.OSF1.Alpha
”, “
bork.Linux.x86
”, “
bork.Linux.PPC
”, etc. To choose between these binaries, you have a shell script (“
detect.sh
”) that detects the host system type and prints out the name of the appropriate binary.
BORK = $(shell detect.sh)
a.bork: a.txt
$(BORK) -o a.bork a.txt
b.bork: b.txt
$(BORK) -o b.bork b.txt
That code will work just fine. The problem is that the “
detect.sh
” script will be executed twice (once for each use of the
$(BORK)
variable). To get around this, you could use “
:=
” assignment.
BORK := $(shell detect.sh)
The problem with this is that the “
detect.sh
” script will be executed all the time, even when we don’t need to use the “
$(BORK)
” variable. For example, “
detect.sh
” will be run even for “
make clean
” or if we’re using the Makefile to build something totally unrelated.
What we want here is a thunk. The first time the “
$(BORK)
” variable is used, run the “
detect.sh
” script and save the result. Any subsequent accesses to “
$(BORK)
” should use the saved result. To do that, we have to abuse GNU Make’s “
eval
” function:
BORK_GEN = $(shell detect.sh)
BORK = $(eval BORK := $(BORK_GEN))$(BORK)
When “
$(BORK)
” is accessed for the first time, it will call the “
$(BORK_GEN)
” function and save the result in “
$(BORK)
”. Subsequent accesses will just return the saved value.
You can create a function to generate thunks:
THUNK_EXPR = $$(eval $(1) := $$($(1)_GEN))$$($(1))
DEF_THUNK = $(eval $(1) = $(call THUNK_EXPR,$(1)))
Now, creating a new thunk involves less work:
BORK_GEN = $(shell detect.sh)
$(call DEF_THUNK,BORK)