The Basics of GNU Make

[article]
Summary:

GNU Make user-defined functions can do a lot. Ask Mr. Make takes you on a quick your of the basics.

The Basics

Here's a very simple GNU Make function: it takes three arguments and makes a 'date' out of them by inserting / between the first and second and second and third arguments:

make_date = $1/$2/$3

The first thing to notice is that make_date is defined just like any other GNU Make macro (you must use = and not := for reasons we'll see below).

To use make_date we $(call) it like this:

today = $(call make_date,19,12,2007)

That will result in today containing 19/12/2007.

The macro uses special macros $1, $2, and $3.  These macros contain the argument specified in the $(call).  $1 is the first argument, $2 the second and so on. 

There's no maximum number of arguments, but if you go above 10 then you need parens: you can't write $10 instead of $(10).   There's also no minimum number.  Arguments that are missing are just undefined and will typically be treated as an empty string.

The special argument $0 contains the name of the function.  In the example above $0 is make_date.

Since functions are just macros with some special automatic macros filled in (if you use the $(origin) function on any of the argument macros ($1 etc.) you'll find that they are classed as automatic just like $@), you can use GNU Make built in functions to build up complex functions.

Here's a function that turns every / into a \ in a path"

unix_to_dos = $(subst /,\,$1)

using the $(subst).  Don't be worried about the use of / and \ there. GNU Make does very
little escaping and a literal \ is most of the time just a \. 

Some argument handling gotchas

When GNU Make is processing a $(call) it starts by splitting the argument list on commas to set $1 etc.  The arguments are expanded so that $1 etc. are completely expanded before they are ever referenced (it's as if GNU Make used := to set them).  This means that if an argument has a side-effect (such as calling $(shell)) then that side-effect will always occur as soon as the $(call) is executed, even if the argument was never actually used by the function.

One common problem is that if an argument contains a comma the splitting of
arguments can go wrong.  For example, here's a simple function that swaps its two arguments:

swap = $2 $1

If you do $(call swap,first,argument,second) GNU Make doesn't have any way to know that the first argument was meant to be first,argument and swap ends up returning argument first instead of second first,argument.

There are two ways around this.  You could simply hide the first argument inside a macro.  Since GNU Make doesn't expand the arguments until after splitting a comma inside a macro will not cause any confusion:

FIRST := first,argument

SWAPPED := $(call swap,$(FIRST),second)

The other way to do this is to create a simple macro that just contains a comma and use that instead:

c := ,

SWAPPED := $(call swap,first$cargument,second)

Or even call that macro , and use it (with parens):

, := ,

SWAPPED := $(call swap,first$(,)argument,second)

Calling built-in functions

It's possible to use the $(call) syntax with built in GNU Make functions.  For example, you could call $(warning) like this:

$(call warning,message)

This is useful because it means that you can pass any function name as an argument to a user-defined function and $(call) it without needing to know if it's built-in or not.

This gives you the ability to created functions that act on functions.  The classic functional programming map function (which applies a function to every member of a list returning the resulting list) can be created like this:

map = $(foreach a,$2,$(call $1,$a))

The first argument is the function to call, the second is the list to iterate over.   Here's an example use of map: we use it to iterate over a list of variable names and print out the defined value and the expanded value of each variable.

print_variable = $1 ($(value $1) -> $($1))

print_variables = $(call map,print_variable,$1)

VAR1 = foo

VAR2 = $(VAR1)

VAR3 = $(VAR2) $(VAR1)

$(info $(call print_variables,VAR1 VAR2 VAR3))

print_variable takes the name of a variable as its first and only argument and returns
a string consisting of the name of the variable, its definition and its value.   print_variables applies print_variable to a list of variables using map.

Here's the output of the above Makefile snippet:

VAR1 (foo -> foo) VAR2 ($(VAR1) -> foo) VAR3 ($(VAR2) $(VAR1) -> foo foo)

GNU Make functions can also be recursive and so it's possible to $(call) yourself.   Here's an implementation of the common reduce function using recursion.

reduce = $(if $(strip $2),$(call reduce,$1,$(wordlist 2,$(words $2),$2),$(call $1,$(firstword $2),$3)),$3)

reduce takes two arguments: a function that will be called by reduce and a list to process.  The first argument is called with two arguments: each element of the list in reduce's second argument and the result of the previous call to the function.

To see this in action we can create a list unique function that removes duplicates without reordering:

check_uniq = $(if $(filter $1,$2),$2,$2 $1)

uniq = $(call reduce,check_uniq,$1)

$(info $(call uniq,c b a a c c b a c b a))

This works because reduce will call check_uniq with each member of the input list in turn building up a new list from the result of check_uniq.  check_uniq just determines whether the element is present or not (using $(filter)) and returns the list with the element appended if it was not already seen.

Conclusion

GNU Make user-defined functions can do a lot.  If you want to see the extreme use of them examine how the GNU Make Standard Library pushed GNU Make user-defined functions to their limits.

Read more about GNU Make here.

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.