nowox
nowox

Reputation: 29096

Force variable expansion once and when used only

In this example, I have a process that takes some time. Let's says 1 second. If I write the following Makefile, FOO will be expanded 3 times for make all and none for make clean.

If I want to save some execution time for all I can assign FOO using := instead of =. However this will cause FOO to be expanded for the target clean even if it doesn't use it.

FOO = $(shell echo -e "+1" >> foo && echo "Hello" && sleep 1)

all:
    @echo $(FOO)
    @echo $(FOO)
    @echo $(FOO)
    @cat foo

clean:
    rm foo

The output:

$ make
Hello
Hello
Hello   
+1
+1
+1

I would like to force Make to expand a variable only once only if required.

Is it possible to do it so?

Upvotes: 2

Views: 788

Answers (2)

MadScientist
MadScientist

Reputation: 100836

Try this:

FOO = $(eval FOO := $(shell echo "+1" >> foo && echo "Hello" && sleep 1))$(value FOO)

The first time make expands $(FOO) it will first expand the eval, which resets the variable FOO using :=. Then it resolves the value of the FOO variable. In subsequent expansions, due to the eval, FOO expands directly to the value.

I should also point out that if you have at least GNU make 4.0 you can use a new feature added to the POSIX standard for make recently, the != operator:

FOO != echo "+1" >> foo && echo "Hello" && sleep 1

which does exactly what you want here.

Upvotes: 2

Etan Reisner
Etan Reisner

Reputation: 80931

The "best" solution I can come up with looks like this:

$ cat coin.mk
FOO = FOO:=$(shell echo -e "+1" >> foo && echo "Hello" && sleep 1)

defined = $(and $(filter-out undefined,$(origin $1)),$($1))

all:
        echo $(eval $(FOO))$(FOO)
        echo $(FOO)
        echo $(FOO)
        cat foo

clean:
        rm foo
$ time make -f coin.mk clean
rm foo
rm: cannot remove `foo': No such file or directory
make: *** [clean] Error 1

real    0m0.003s
user    0m0.003s
sys     0m0.000s
$ time make -f coin.mk
echo Hello
Hello
echo Hello
Hello
echo Hello
Hello
cat foo
+1

real    0m1.009s
user    0m0.002s
sys     0m0.004s
$ time make -f coin.mk clean
rm foo

real    0m0.003s
user    0m0.000s
sys     0m0.003s

Which works but requires special-casing the first use of the variable in the make run ($(eval $(FOO)) run a second time will cause a make error).

I tried briefly to encapsulate the eval logic inside the value of FOO but most attempts were blocked by make complaining that *** Recursive variable 'FOO' references itself (eventually). Stop.

Upvotes: 2

Related Questions