absuu
absuu

Reputation: 377

GNU Make - Variable Expansion in Recipes

Say in the working directory, I have:

$ find . | grep testfile
./testfile1

This is my Makefile:

list_files:
    @echo "Show Files..."
    @echo $(shell find . | grep testfile)
    @touch testfile2
    @echo $(shell find . | grep testfile)
    @rm testfile2

With make, I got this:

$ make list_files
Show Files...
./testfile1
./testfile1

Why this happened? I expected it to be something like this:

Show Files...
./testfile1
./testfile1 ./testfile2

Then my question is:

Why all the variables/function in recipes inside a rule, are expanded likely simultaneously after target is invoked?

I have found an explanation from this answer that is pretty close to the truth:

The reason your attempt doesn't work is that make will evaluate all lines of the recipe before it starts the first line.

But there is no references provided there, I just cannot convinced myself of this working mechanism of GNU Make.

Could anyone give some clues? Thanks!

Upvotes: 2

Views: 1295

Answers (2)

John Bollinger
John Bollinger

Reputation: 180715

Why this happened?

Because, with one caveat that does not apply to your case, make functions such as $(shell ...) are evaluated when the makefile is parsed, not during the execution of recipes.

Why all the variables/function in recipes inside a rule, are expanded likely simultaneously after target is invoked?

They're not. They are expanded before the target's recipe runs. In fact, before make even determines whether the recipe should be run.

But there is no references provided

This is covered in the manual. See in particular section 8.14, The shell function:

The commands run by calls to the shell function are run when the function calls are expanded (see How make Reads a Makefile).

... which refers to section 3.7, How make Reads a Makefile, in particular:

GNU make does its work in two distinct phases. During the first phase it reads all the makefiles, included makefiles, etc. and internalizes all the variables and their values and implicit and explicit rules, and builds a dependency graph of all the targets and their prerequisites. During the second phase, make uses this internalized data to determine which targets need to be updated and run the recipes necessary to update them.

It also relies on section 8.1, Function Call Syntax:

A function call resembles a variable reference. It can appear anywhere a variable reference can appear, and it is expanded using the same rules as variable references.

"The same rules" of course includes the rules for when expansions are performed.


Your recipes should be written in the language of the target shell, usually /bin/sh. All make functions and variable references in each recipe will be expanded before any recipe runs, so their expansions cannot reflect the results of running any recipe during the current make run. It's particularly peculiar to try to use the $(shell ...) function to do that, because you can just use shell code directly in a recipe.

Upvotes: 1

vjalle
vjalle

Reputation: 953

As explained in the comments, all $(shell ...) and other functions are executed (expanded) before executing any lines from the recipe. But you can delay the expansion:

list_files:
    @echo "Show Files..."
    @echo $$(shell find . | grep testfile)
    @touch testfile2
    @echo $$(shell find . | grep testfile)
    @rm testfile2

This yields the expected output. Note the double $$. Make will still expand all variables/functions first before executing the recipe, and remove one of the dollar signs. The expressions will be expanded again, when executing the recipe lines.

Of course, you're better off without the $(shell) in this example. However, it's not inherently a bad idea. Sometimes you need to execute shell commands before expanding other variables, and then $(shell) is the trivial solution.

Upvotes: 1

Related Questions