Makefile Optimization: $(shell) and := go Together


Ask Mr. Make discusses Makefile optimization: ow $(shell) and := go together.

$(shell) explained
$(shell) is GNU Make's equivalent of the back tick (`) operator in the shell. $(shell) executes a command, flattens the result (i.e. turns all line endings into spaces) and returns the resulting string.

For example, if you want to get the output of the date command into a variable called NOW you'd write

NOW = $(shell date) 

Or if you want to count the number of files in the current directory and get that number into FILE_COUNT do

FILE_COUNT = $(shell ls | wc -l ) 

Note that since $(shell) flattens output you can get the names of all the files in the current directory into a macro with:

FILES = $(shell ls) 

The newline between files is replaced with a single space making FILES a space-separated list of filenames.

You'll commonly see in Makefiles an execution of the pwd command to get the current working directory into a variable (in this case CWD):

CWD = $(shell pwd) 

It's this command we'll take a look at later on when considering how to optimize an example Makefile that wastes time getting the working directory over and over again.

The difference between = and := — 99% of the time you'll see macro definitions in Makefiles that use the = form:

FOO = foo
 BAR = bar
 all: $(FOOBAR)
 	@echo $@ $(FOOBAR)
 FOO = fooey
 BAR = barney 

In the example above macros FOO, BAR and FOOBAR are “recursively expanded” macros. That means that when we ask for the value of a macro any macros that it references are expanded at that point. For example, if we ask for the value of $(FOOBAR) it will get the value of $(FOO) and $(BAR) put them together with the space in between and return foo bar. Expansion through as many levels of macros as necessary is done only when the variable is used.

In the Makefile above this has the interesting side effect that FOOBAR
appears to have two different values. Running it prints out:

foo fooey barney
 bar fooey barney 

The value of FOOBAR is used to define the list of prerequisites to the all rule and is expanded as foo bar, the same thing happens for the next rule which defines rules for foo and bar.

But when the rules are run, the value of FOOBAR as used in the echo comes out as fooey barney. (You can verify that the value of FOOBAR was foo bar when the rules were defined by looking at the value of $@---the target being built---when the rules are run).

There are two cases to remember:

  1. When a rule is being defined in a Makefile, macros will evaluate to their value at that point in the Makefile.
  2. Macros used in rule bodies (i.e. in the commands) have their final value: whatever value the macro had at the end of the Makefile.

If we change the definition of FOOBAR to use a := instead of = running the Makefile gives a very different result.

foo foo bar
 bar foo bar  

Now FOOBAR has the same value everywhere. That because := forces the right-hand side of the definition to be expanded at that moment. Rather than storing $(FOO) $(BAR) as the definition of FOOBAR, GNU Make stores the expansion of $(FOO) $(BAR) which at that point is foo bar. The fact that FOO and BAR are redefined later in the Makefile is irrelevant, FOOBAR has already been expanded and set to a fixed string. GNU Make refers to variables defined in this way as “simply expanded.”

Once a macro has become simply

About the author

AgileConnection is a TechWell community.

Through conferences, training, consulting, and online resources, TechWell helps you develop and deliver great software every day.