4.3 Advanced User -Defined Functions
We'll spend a lot of time writing macro functions. Unfortunately, there aren't many features in make for helping to debug them. Let's begin by trying to write a simple debugging trace function to help us out.
As we've mentioned, call will bind each of its parameters to the numbered variables $1 , $2 , etc. Any number of arguments can be given to call . As a special case, the name of the currently executing function (i.e., the variable name ) is accessible through $0 . Using this information, we can write a pair of debugging functions for tracing through macro expansion:
# $(debug-enter) debug-enter = $(if $(debug_trace),\ $(warning Entering
# $(debug-enter) debug-enter = $(if $(debug_trace),\ $(warning Entering $0($(echo-args)))) # $(debug-leave) debug-leave = $(if $(debug_trace),$(warning Leaving $0)) comma := , echo-args = $(subst ' ','$(comma) ',\ $(foreach a,1 2 3 4 5 6 7 8 9,'$($a)')) ($(echo-args)))) # $(debug-leave) debug-leave = $(if $(debug_trace),$(warning Leaving
# $(debug-enter) debug-enter = $(if $(debug_trace),\ $(warning Entering $0($(echo-args)))) # $(debug-leave) debug-leave = $(if $(debug_trace),$(warning Leaving $0)) comma := , echo-args = $(subst ' ','$(comma) ',\ $(foreach a,1 2 3 4 5 6 7 8 9,'$($a)')) )) comma := , echo-args = $(subst ' ','$(comma) ',\ $(foreach a,1 2 3 4 5 6 7 8 9,'$($a)'))
If we want to watch how functions a and b are invoked, we can use these trace functions like this:
debug_trace = 1 define a $(debug-enter) @echo $(debug-leave) endef define b $(debug-enter) $(call a,,,hi) $(debug-leave) endef trace-macro: $(call b,5,$(MAKE))
By placing debug-enter and debug-leave variables at the start and end of your functions, you can trace the expansions of your own functions. These functions are far from perfect. The echo-args function will echo only the first nine arguments and, worse , it cannot determine the number of actual arguments in the call (of course, neither can make !). Nevertheless, I've used these macros "as is" in my own debugging. When executed, the makefile generates this trace output:
$ make makefile:14: Entering b( '5', 'make', '', '', '', '', '', '', '') makefile:14: Entering a( '5', 'make', 'hi', '', '', '', '', '', '') makefile:14: Leaving a makefile:14: Leaving b 5 make hi
As a friend said to me recently, "I never thought of make as a programming language before." GNU make isn't your grandmother's make !
4.3.1 eval and value
The eval function is completely different from the rest of the built-in functions. Its purpose is to feed text directly to the make parser. For instance,
$(eval sources := foo.c bar.c)
The argument to eval is first scanned for variables and expanded (as all arguments to all functions are), then the text is parsed and evaluated as if it had come from an input file. This example is so simple you might be wondering why you would bother with this function. Let's try a more interesting example. Suppose you have a makefile to compile a dozen programs and you want to define several variables for each program, say sources , headers , and objects . Instead of repeating these variable assignments over and over with each set of variables:
ls_sources := ls.c glob.c ls_headers := ls.h glob.h ls_objects := ls.o glob.o ...
We might try to define a macro to do the job:
# $(call program-variables, variable-prefix, file-list) define program-variables _sources = $(filter %.c,) _headers = $(filter %.h,) _objects = $(subst .c,.o,$(filter %.c,)) endef $(call program-variables, ls, ls.c ls.h glob.c glob.h) show-variables: # $(ls_sources) # $(ls_headers) # $(ls_objects)
The program-variables macro accepts two arguments: a prefix for the three variables and a file list from which the macro selects files to set in each variable. But, when we try to use this macro, we get the error:
$ make Makefile:7: *** missing separator. Stop.
This doesn't work as expected because of the way the make parser works. A macro (at the top parsing level) that expands to multiple lines is illegal and results in syntax errors. In this case, the parser believes this line is a rule or part of a command script but is missing a separator token. Quite a confusing error message. The eval function was introduced to handle this issue. If we change our call line to:
$(eval $(call program-variables, ls, ls.c ls.h glob.c glob.h))
we get what we expect:
$ make # ls.c glob.c # ls.h glob.h # ls.o glob.o
Using eval resolves the parsing issue because eval handles the multiline macro expansion and itself expands to zero lines.
Now we have a macro that defines three variables very concisely. Notice how the assignments in the macro compose variable names from a prefix passed in to the function and a fixed suffix, $1_sources . These aren't precisely computed variables as described previously, but they have much the same flavor.
Continuing this example, we realize we can also include our rules in the macro:
# $(call program-variables,variable-prefix,file-list) define program-variables _sources = $(filter %.c,) _headers = $(filter %.h,) _objects = $(subst .c,.o,$(filter %.c,)) $(_objects): $(_headers) endef ls: $(ls_objects) $(eval $(call program-variables,ls,ls.c ls.h glob.c glob.h))
Notice how these two versions of program-variables illustrate a problem with spaces in function arguments. In the previous version, the simple uses of the two function parameters were immune to leading spaces on the arguments. That is, the code behaved the same regardless of any leading spaces in $1 or $2 . The new version, however, introduced the computed variables $($1_objects) and $($1_headers) . Now adding a leading space to the first argument to our function ( ls ) causes the computed variable to begin with a leading space, which expands to nothing because no variable we've defined begins with a leading space. This can be quite an insidious problem to diagnose.
When we run this makefile , we discover that somehow the .h prerequisites are being ignored by make . To diagnose this problem, we examine make 's internal database by running make with its ”print-data-base option and we see something strange :
$ make --print-database grep ^ls ls_headers = ls.h glob.h ls_sources = ls.c glob.c ls_objects = ls.o glob.o ls.c: ls.o: ls.c ls: ls.o
The .h prerequisites for ls.o are missing! There is something wrong with the rule using computed variables.
When make parses the eval function call, it first expands the user-defined function, program-variables . The first line of the macro expands to:
ls_sources = ls.c glob.c
Notice that each line of the macro is expanded immediately as expected. The other variable assignments are handled similarly. Then we get to the rule:
The computed variables first have their variable name expanded:
Then the outer variable expansion is performed, yielding:
Wait! Where did our variables go? The answer is that the previous three assignment statements were expanded but not evaluated by make . Let's keep going to see how this works. Once the call to program-variables has been expanded, make sees something like:
$(eval ls_sources = ls.c glob.c ls_headers = ls.h glob.h ls_objects = ls.o glob.o :)
The eval function then executes and defines the three variables. So, the answer is that the variables in the rule are being expanded before they have actually been defined.
We can resolve this problem by explicitly deferring the expansion of the computed variables until the three variables are defined. We can do this by quoting the dollar signs in front of the computed variables:
This time the make database shows the prerequisites we expect:
$ make -p grep ^ls ls_headers = ls.h glob.h ls_sources = ls.c glob.c ls_objects = ls.o glob.o ls.c: ls.o: ls.c ls.h glob.h ls: ls.o
To summarize, the argument to eval is expanded twice : once when when make prepares the argument list for eval , and once again by eval .
We resolved the last problem by deferring evaluation of the computed variables. Another way of handling the problem is to force early evaluation of the variable assignments by wrapping each one with eval :
# $(call program-variables,variable-prefix,file-list) define program-variables $(eval _sources = $(filter %.c,)) $(eval _headers = $(filter %.h,)) $(eval _objects = $(subst .c,.o,$(filter %.c,))) $(_objects): $(_headers) endef ls: $(ls_objects) $(eval $(call program-variables,ls,ls.c ls.h glob.c glob.h))
By wrapping the variable assignments in their own eval calls, we cause them to be internalized by make while the program-variables macro is being expanded. They are then available for use within the macro immediately.
As we enhance our makefile , we realize we have another rule we can add to our macro. The program itself depends on its objects. So, to finish our parameterized makefile , we add a top-level all target and need a variable to hold all the programs our makefile can manage:
#$(call program-variables,variable-prefix,file-list) define program-variables $(eval _sources = $(filter %.c,)) $(eval _headers = $(filter %.h,)) $(eval _objects = $(subst .c,.o,$(filter %.c,))) programs += : $(_objects) $(_objects): $(_headers) endef # Place all target here, so it is the default goal. all: $(eval $(call program-variables,ls,ls.c ls.h glob.c glob.h)) $(eval $(call program-variables,cp,...)) $(eval $(call program-variables,mv,...)) $(eval $(call program-variables,ln,...)) $(eval $(call program-variables,rm,...)) # Place the programs prerequisite here where it is defined. all: $(programs)
Notice the placement of the all target and its prerequisite. The programs variable is not properly defined until after the five eval calls, but we would like to place the all target first in the makefile so all is the default goal. We can satisfy all our constrains by putting all first and adding the prerequisites later.
The program-variables function had problems because some variables were evaluated too early. make actually offers a value function to help address this situation. The value function returns the value of its variable argument unexpanded . This unexpanded value can then be passed to eval for processing. By returning an unexpanded value, we can avoid the problem of having to quote some of the variable references in our macros.
Unfortunately, this function cannot be used with the program-variables macro. That's because value is an all-or-nothing function. If used, value will not expand any of the variables in the macro. Furthermore, value doesn't accept parameters (and wouldn't do anything with them if it did) so our program name and file list parameters wouldn't be expanded.
Because of these limitations, you won't see value used very often in this book.
4.3.2 Hooking Functions
User-defined functions are just variables holding text. The call function will expand $1 , $2 , etc. references in the variable text if they exist. If the function doesn't contain any of these variable references, call doesn't care. In fact, if the variable doesn't contain any text, call doesn't care. No error or warning occurs. This can be very frustrating if you happen to misspell a function name. But it can also be very useful.
Functions are all about reusable code. The more often you reuse a function, the more worthwhile it is to write it well. Functions can be made more reusable by adding hooks to them. A hook is a function reference that can be redefined by a user to perform their own custom tasks during a standard operation.
Suppose you are building many libraries in your makefile . On some systems, you'd like to run ranlib and on others you might want to run chmod . Rather than writing explicit commands for these operations, you might choose to write a function and add a hook:
# $(call build-library, object-files) define build-library $(AR) $(ARFLAGS) $@ $(call build-library-hook,$@) endef
To use the hook, define the function build-library-hook :
$(foo_lib): build-library-hook = $(RANLIB) $(foo_lib): $(foo_objects) $(call build-library,$^) $(bar_lib): build-library-hook = $(CHMOD) 444 $(bar_lib): $(bar_objects) $(call build-library,$^)
4.3.3 Passing Parameters
A function can get its data from four "sources": parameters passed in using call , global variables, automatic variables, and target-specific variables. Of these, relying on parameters is the most modular choice, since their use insulates the function from any changes to global data, but sometimes that isn't the most important criteria.
Suppose we have several projects using a common set of make functions. Each project might be identified by a variable prefix, say PROJECT1_ , and critical variables for the project all use the prefix with cross-project suffixes. The earlier example, PROJECT_SRC , might look like PROJECT1_SRC , PROJECT1_BIN , and PROJECT1_LIB . Rather than write a function that requires these three variables we could instead use computed variables and pass a single argument, the prefix:
# $(call process-xml,project-prefix,file-name) define process-xml $(_LIB)/xmlto -o $(_BIN)/xml/ $(_SRC)/xml/ endef
Another approach to passing arguments uses target-specific variables. This is particularly useful when most invocations use a standard value but a few require special processing. Target-specific variables also provide flexibility when the rule is defined in an include file, but invoked from a makefile where the variable is defined.
release: MAKING_RELEASE = 1 release: libraries executables ... $(foo_lib): $(call build-library,$^) ... # $(call build-library, file-list) define build-library $(AR) $(ARFLAGS) $@ \ $(if $(MAKING_RELEASE), \ $(filter-out debug/%,), \ ) endef
This code sets a target-specific variable to indicate when a release build is being executed. In that case, the library-building function will filter out any debugging modules from the libraries.