How Does Make Log File Output Relate?
In the previous section, a complete build (for example, where runme, foo.o, and bar.o were missing before typing make) produces no output. That's because all the commands in the Makefile were prefixed by @, which prevents GNU Make from echoing them.
GNU Make's -n option provides a way to echo all the commands without executing them. This is handy for dry runs of the Makefile. The output from make -n on the example Makefile is:
g++ -c -o foo.o foo.c g++ -c -o bar.o bar.c cc foo.o bar.o -o runme
In this example, it's relatively easy to relate these lines to specific rules in the Makefile; however, it can be hard on large, real-world Makefiles. And since -n doesn't actually run these commands, it's no help when trying to figure out why a rule failed to execute correctly.
One solution is to replace every @ with $(AT). Setting AT to @ in the Makefile makes it behave as before. Setting AT to blank means that commands are echoed. For example, the Makefile can be modified to use $(AT) instead of @:
.PHONY: all all: runme runme: foo.o bar.o $(AT)$(LINK.o) $^ -o $@ foo.o: foo.c bar.o: bar.c %.o: %.c $(AT)$(COMPILE.C) -o $@ $< AT := @
This behaves exactly as before, except that it's possible to override the definition of AT on the command line, causing GNU Make to print the commands it is executing. Running make AT= effectively strips the @ from each command and produces the same output as make -n, except that the commands are actually run.
Of course, modifying every command in a Makefile might not be realistic.
The SHELL hack can be modified to cause GNU Make to output command information. Almost all shells have an -x option that causes them to echo the commands that they are about to execute. By modifying SHELL to include -x (and a $(warning...)) that outputs the location of the rule in the Makefile) and wrapping the modification in an ifdef, you can enable/disable detailed debugging information as needed. Here's the SHELL hack for command dumping:
ifdef DUMP OLD_SHELL := $(SHELL) SHELL = $(warning [$@])$(OLD_SHELL) -x endif
If DUMP is defined then SHELL is modified to add the -x option and output the name of the target being built. If foo.o, bar.o, and runme are missing, typing make DUMP=1 produces this output using the SHELL hack:
Makefile:11: [foo.o] + g++ -c -o foo.o foo.c Makefile:11: [bar.o] + g++ -c -o bar.o bar.c Makefile:5: [runme] + cc foo.o bar.o -o runme
Each file being built is in square brackets with the name of the Makefile and the location where the rule to build that file can be found. The shell outputs the exact command to be used to build that file (prepended by +).
In just four lines of GNU Make code, you can turn an empty log file into one with complete file, line number, and command information. This is useful when debugging a failing rule. Just rerun the Makefile with DUMP=1 to find out what was being built and where it was in the Makefile. The only disadvantage to the SHELL hack is that it slows GNU Make. That's because GNU Make tries to avoid using the shell if it can; for example, if GNU Make is doing a simple g++ invocation, then it can avoid the shell and execute the command directly. If SHELL is modified in a Makefile, GNU Make disables this optimization.
Extra Resources
If these ideas whet your appetite for better GNU Make debugging, look at the GNU Make Debugger project (http://gmd.sf.net). GMD is an interactive debugger for GNU Make written entirely using GNU Make functions that provides breakpoints and the ability to examine the value and definition of any macro at runtime.
And if you do something really cool building on the SHELL hack, drop me a line. I'd love to hear about it.