Makefile Debugging: Tracing Macro Values


associated with the tracer is used and the first rule that the Makefile contains is the rule for _trace. Since _trace is the first target encountered, when TRACE is defined its rule will be run by default.

The _trace rule contains a single, complex command:

@$(MAKE) --no-print-directory TRACE= $(TRACE)='$$(warning TRACE $(TRACE))$(shell $(MAKE) TRACE=$(TRACE) _value)'

On the right-hand side of the command is a $(shell) invocation rerunning the Makefile with a different goal. If we are tracing YS then this $(shell) runs the command

make TRACE=YS _value

which will cause the _value rule to get run, which in turn will echo the definition of YS. _value simply echoes the definition of the macro specified by the TRACE macro. So the $(shell) ends up evaluating to $(Y)$(S).

The $(shell) is in fact inside a command-line macro definition (usually called a command-line override):

$(TRACE)='$$(warning TRACE $(TRACE))$(shell $(MAKE)TRACE=$(TRACE) _value)'

which adds the $(warning) needed to output the 'TRACE X' messages. Notice how the name of the macro being defined is a computed value: its name is contained in $(TRACE). When tracing YS this definition turns into:

YS='$(warning TRACE YS)$(Y)$(S)'

The single quotes are used to prevent the shell from seeing the $ sign, the double $ is used to prevent Make from seeing the $: in either case a macro expansion would be prevented which we want to delay until YS is actually used.

Finally the _trace rule runs

make TRACE= YS='$(warning TRACE YS)$(Y)$(S)'

which resets the value of TRACE (since this invocation of Make should actually run the real rules) and overrides the value of YS as defined in the Makefile. Recall that macros defined on the command-line override definitions in the Makefile (see article URL): even though YS is defined in the Makefile the command-line definition will be used.

Now every time $(YS) is expanded a warning is printed.

This technique doesn't work for a macro that is target-specific. GNU Make allows you to define a macro as specific to a target in the following manner:

all: FOO=foo
all: a
@echo $(FOO)

@echo $(FOO)

The macro FOO will have value foo in the rule that builds all and in any prerequisites of all. The Makefile above will print foo and then foo because $(FOO) is defined in the all rule and in the a rule. The tracer is unable to obtain the value of $(FOO) and would in fact cause this Makefile to behave incorrectly.

$(warning) sends its output to STDERR which makes it possible to separate normal Make output from the tracer. Simply redirect STDERR to a trace log file. For example the following command will result in normal Make output being seen and the trace going to the log file:

% gmake TRACE=S 2> trace.log

dog dog
cat cat
dogs hate cats


Next month I'll return with a GNU Make trick that let's you see exactly which rules are run when you type 'make' and in what order.

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.