Makefile Debugging: A introduction to remake

[article]
Summary:

remake forked from GNU Make 3.80 and is currently at version 0.62.   This version incorporates some, but not all, of the changes made in GNU Make 3.81. 

remake forked from GNU Make 3.80 and is currently at version 0.62.   This version incorporates some, but not all, of the changes made in GNU Make 3.81. 

Just Print and Trace

To illustrate the operation of remake, here's a sample Makefile:

.PHONY: all

all: foo bar baz

foo: bar

     @touch $@

bar:

     @touch $@

baz: bam

     @touch $@

bam:

     @touch $@

Running the standard GNU Make -n (or --just-print) option against this Makefile produces the output:

touch bar

touch foo

touch bam

touch baz

But remake provides Makefile and line number information for each rule.  The information shows the target (the value of $@) and the commands to be run:

$ remake -n

##>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

/home/jgc/Makefile:8: bar

touch bar

##<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

##>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

/home/jgc/Makefile:5: foo

touch foo

##<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

##>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

/home/jgc/Makefile:14: bam

touch bam

##<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

##>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

/home/jgc/Makefile:11: baz

touch baz

##<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

Of course, any real Makefile has to be actually run to understand its execution.  remake provides a very handy tracing option, -x, which runs the Makefile, while outputting information about why targets are being built and showing the commands executed and their output.   

Reading makefiles...

Updating goal targets....

 /home/jgc/Makefile:2   File `all' does not exist.

   /home/jgc/Makefile:4 File `foo' does not exist.

     /home/jgc/Makefile:7       File `bar' does not exist.

    /home/jgc/Makefile:7        Must remake target `bar'.

##>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

/home/jgc/Makefile:8: bar

touch bar

##<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

+ touch bar

    /home/jgc/Makefile:7        Successfully remade target file `bar'.

  /home/jgc/Makefile:4  Must remake target `foo'.

##>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

/home/jgc/Makefile:5: foo

touch foo

##<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

+ touch foo

  /home/jgc/Makefile:4  Successfully remade target file `foo'.

   /home/jgc/Makefile:10        File `baz' does not exist.

     /home/jgc/Makefile:13      File `bam' does not exist.

    /home/jgc/Makefile:13       Must remake target `bam'.

##>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

/home/jgc/Makefile:14: bam

touch bam

##<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

+ touch bam

    /home/jgc/Makefile:13       Successfully remade target file `bam'.

  /home/jgc/Makefile:10 Must remake target `baz'.

##>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

/home/jgc/Makefile:11: baz

touch baz

##<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

+ touch baz

  /home/jgc/Makefile:10 Successfully remade target file `baz'.

/home/jgc/Makefile:2    Must remake target `all'. Is a phony target.

/home/jgc/Makefile:2    Successfully remade target file `all'.

The trace option really comes into its own when an error occurs.  Here's the output when I added a non-existent option to touch in the commands for target bar:

Reading makefiles...

Updating goal targets....

 /home/jgc/Makefile:2   File `all' does not exist.

   /home/jgc/Makefile:4 File `foo' does not exist.

     /home/jgc/Makefile:7       File `bar' does not exist.

    /home/jgc/Makefile:7        Must remake target `bar'.

##>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

/home/jgc/Makefile:8: bar

touch -x bar

##<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

+ touch -x bar

touch: invalid option -- x

Try `touch --help' for more information.

Makefile:8: *** [bar] Error 1

#0  bar at /home/jgc/Makefile:8

#1  foo at /home/jgc/Makefile:4

#2  all at /home/jgc/Makefile:2

Command-line arguments:

        "-x"

Right at the bottom of that output is the 'call stack' of targets that were dependent on bar building successfully.  Plus, of course, you see the error generated by touch and the actual command that was executed, and where to find it in the Makefile.

Debugging

Since remake contains an interactive debugger we can debug the touch problem above using it.  We simply run remake with the -X option (uppercase X for the debugger, lowercase x for tracing) and the debugger breaks at the first target to be built:

Reading makefiles...

Updating makefiles....

Updating goal targets....

 /home/jgc/Makefile:2   File `all' does not exist.

(/home/jgc/Makefile:2)

all: foo bar baz

mdb<0>

So, the first break is at line 2 of the Makefile and shows that the first target is all (and the complete prereq list is shown).   Typing h gives complete help information:

mdb<0> h

  Command                  Short Name  Aliases

  ----------------------   ----------  ---------

  break *target*                  (b)  L

  cd *dir*                        (C)

  comment *text*                  (#)

  continue                        (c)

  delete breakpoint numbers..     (d)

  down [amount]                   (D)

  eval *string*                   (e)

  examine *string*                (x)

  finish                          (F)

  frame *n*                       (f)

  help [command]                  (h)  ?, ??

  info [thing]                    (i)

  next [amount]                   (n)

  print {*variable* [attrs...]}   (p)

  Print working directory         (P)

  quit [exit-status]              (q)  exit, return

  run                             (R)  restart

  set {*option*|variable} *value* (=)

  setq *variable* *value*         (")

  shell *string*                  (!)  !!

  show [thing]                    (S)

  skip                            (k)

  step [amount]                   (s)

  target                          (t)

  up [amount]                     (u)

  where                           (T)  backtrace, bt

  write [*target* [*filename*]]   (w)

Readline command line editing (emacs/vi mode) is available.

For more detailed help, type h <cmd> or consult online-documentation.

mdb<0>

Since we are debugging the touch problem we'll just continue by single stepping with s:

mdb<0> s

(/home/jgc/Makefile:4)

foo: bar

mdb<0> s

   /home/jgc/Makefile:4 File `foo' does not exist.

(/home/jgc/Makefile:4)

foo: bar

mdb<1> s

(/home/jgc/Makefile:7)

bar:

mdb<2> s

     /home/jgc/Makefile:7       File `bar' does not exist.

(/home/jgc/Makefile:7)

bar:

mdb<3> s

    /home/jgc/Makefile:7        Must remake target `bar'.

(/home/jgc/Makefile:7)

bar:

mdb<4> s

(/home/jgc/Makefile:8)

bar:

mdb<5> s

##>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

touch -x bar

##<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

+ touch -x bar

touch: invalid option -- x

Try `touch --help' for more information.

Makefile:8: *** [bar] Error 1

#0  bar at /home/jgc/Makefile:8

#1  foo at /home/jgc/Makefile:4

#2  all at /home/jgc/Makefile:2

***Entering debugger because we encountered a fatal error.

***Exiting the debugger will exit make with exit code 1.

(/home/jgc/Makefile:8)

bar:

mdb<6>

Whilst in the debugger I can fix the error in the Makefile and then hit R to restart.  Now things work correctly:

mdb<0> R

Changing directory to /home/jgc and restarting...

Reading makefiles...

Updating makefiles....

Updating goal targets....

 /home/jgc/Makefile:2   File `all' does not exist.

(/home/jgc/Makefile:2)

all: foo bar baz

mdb<0> c

   /home/jgc/Makefile:4 File `foo' does not exist.

     /home/jgc/Makefile:7       File `bar' does not exist.

    /home/jgc/Makefile:7        Must remake target `bar'.

+ touch bar

    /home/jgc/Makefile:7        Successfully remade target file `bar'.

  /home/jgc/Makefile:4  Must remake target `foo'.

+ touch foo

  /home/jgc/Makefile:4  Successfully remade target file `foo'.

   /home/jgc/Makefile:10        File `baz' does not exist.

     /home/jgc/Makefile:13      File `bam' does not exist.

    /home/jgc/Makefile:13       Must remake target `bam'.

+ touch bam

    /home/jgc/Makefile:13       Successfully remade target file `bam'.

  /home/jgc/Makefile:10 Must remake target `baz'.

+ touch baz

  /home/jgc/Makefile:10 Successfully remade target file `baz'.

/home/jgc/Makefile:2    Must remake target `all'. Is a phony target.

/home/jgc/Makefile:2    Successfully remade target file `all'.

Makefile terminated.

Use q to quit or R to restart

:

mdb<0>

Targets, Macro Values and Expansion

When stopped in the debugger it's possible to interrogate information about targets in the Makefile, macro values (expanded and unexpanded) and commands.  For example, in the Makefile above, when stopped at a breakpoint we can ask for all the information remake has about the all target using the target command:

./remake -X

Reading makefiles...

Updating makefiles....

Updating goal targets....

 /home/jgc/Makefile:2   File `all' does not exist.

(/home/jgc/Makefile:2)

all: foo bar baz

mdb<0> target all

all: foo bar baz

#  Phony target (prerequisite of .PHONY).

#  Implicit rule search has not been done.

#  Implicit/static pattern stem: `'

#  File does not exist.

#  File has not been updated.

# automatic

# @ := all

# automatic

# % :=

# automatic

# * :=

# automatic

# + := foo bar baz

# automatic

# | :=

# automatic

# < := all

# automatic

# ^ := foo bar baz

# automatic

# ? :=

mdb<0>

Here you can see that all is a phony target, and information about the automatic variables that will be set for this rule.   You are not restricted to asking about the current target, let's see the state of foo:

mdb<0> target foo

foo: bar

#  Implicit rule search has not been done.

#  Implicit/static pattern stem: `'

#  Modification time never checked.

#  File has not been updated.

# automatic

# @ := foo

# automatic

# % :=

# automatic

# * :=

# automatic

# + := bar

# automatic

# | :=

# automatic

# < := bar

# automatic

# ^ := bar

# automatic

# ? :=

#  commands to execute (from `Makefile', line 5):

        @touch $@

Since target foo has commands they are listed at the bottom (with where to find them in
which Makefile).  If we wanted to see the expanded form of the commands we'd use the expand modified to the target command:

mdb<1> target foo expand

foo:

#  commands to execute (from `Makefile', line 5):

        @touch $@

#  commands to execute (from `Makefile', line 5):

        @touch foo

mdb<2>

To get information about a macro we use the print and examine commands: print gives the definition of the macro and examine gives its post-expansion value.   Here we ask for the definition of built-in COMPILE.c macro (which contains the command used to compile .c files):

mdb<0> print COMPILE.c

(origin default) COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c

To see the expanded value we examine it:

mdb<0> examine COMPILE.c

(origin default) COMPILE.c := cc    -c

remake also lets you set macro values using set (which expands a string and sets the macro to that value) and setq (which sets the macro to a string without expansion).  For example, we could change CC from cc to gcc:

mdb<1> print CC

(origin default) CC = cc

mdb<1> setq CC gcc

Variable CC now has value 'gcc'

mdb<3> print CC

(origin debugger) CC = gcc

mdb<2> examine COMPILE.c

(origin default) COMPILE.c := gcc    -c

mdb<5>

Conclusion

In this article I've only touched on the major commands available in remake.  I urge you to take a look at it for your Makefile debugging needs, it's a useful tool to have in your toolbox.

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.