Features

Although debugging GNU Makefiles is a little different than debugging, procedure-oriented programming languages, this debugger tries similar to other trepanning debuggers and gdb in general. So knowledge gained by learning this is transferable to those debuggers and vice versa.

Profiling

If you want to know where most of the time goes in building your system with Makefiles, there is a --profile option which times the targets.

This option creates Callgrind Profile Format output which can be read by KCachegrind, callgrind_annotate, or gprof2dot or other tools that understand this format.

You can get not only timings, but a graph of the target dependencies checked. Below is an image rendered from a profiling of a remake build:

_images/remake-profiled2.png

Listing and Documenting Makefile Targets

Have you ever wanted rake tasks for GNU Make? That is, you have some strange Makefile and you want to see the targets, that you can run “make target-name” on?

There are two new options added to remake to assist this:

  • --tasks gives a list of targets with remake descriptions
  • --targets gives a list of all targets

A target with a remake description is just a one-line comment before the the target in the Makefile that describes what the target does and starts with #:

If you do this, when either of these options is shown it will also be shown with next to the target name when --tasks is run.

Here is an example. Consider this Makefile:

#: This is the main target
all:
    @echo all here

#: Test things
check:
    @echo check here

#: Build distribution
dist:
    @echo dist here

Let’s run remake --tasks:

$ remake --tasks
all                  This is the main target
check                Test things
dist                 Build distribution

Many legacy [1] Makefiles don’t have descriptive comment in them yet. So you can get a list of all targets using option --targets. But be warned, since GNU Make comes with lots of implicit rule defaults, this list can be quite large.

Here is an example of runnint --targets on the above file:

$ remake --targets -f comment.Makefile
          .C
          .C.o
          .DEFAULT
          ... # about 70 more lines!
          all       # This is the main target
          check     # Test things
          Makefile
          dist      # Build distribution

Searching for a Makefile in Parent Directories

When the -c flag is given (or --search-parent), if a Makefile or goal target isn’t found in the current directory, remake will search in the parent directory for a Makefile. On finding a parent the closest parent directory with a Makefile, remake will set its current working directory to the directory where the Makefile was found.

In this respect the short option -c, is like -C except no directory need to be specified.

Here is a screenshot that shows make behavior versus remake:

_images/remake-search-parent.gif

Improved Execution Tracing

When the -x flag is given (or --trace=normal), any commands that are about to be run are shown as seen in the Makefile along with set -x tracing when run in a POSIX shell. Also, we override or rather ignore, any non-echo prefix @ directive listed at the beginning of target commands.

If different granularity of tracing is desired the --trace option has other settings. See the relevant parts of this manual for more information.

And, if you the most flexibility in tracing there is a built-in debugger.

Here is a screenshot that shows tracing:

_images/remake-trace.gif

Debugger

Features of the debugger:

  • Inspect target properties
  • See the current target stack
  • Set breakpoints
  • Set and expand GNU Make variables
  • Load in Makefiles
  • write a shell script containing the target commands with GNU Make variables expanded away, so the shell code can be run (and debugged) outside of make.
  • Enter debugger at the outset, call it from inside a Makefile, or enter it upon the first error

See debugger for more information on the built-in debugger.

For Developers

If you are interested in learning about how GNU Make works, you might find it easier to start out working with this code.

First, some Doxygen comments have been added.

Second, it has been simplified as a result of the removal of lesser-used OS’s (from the standpoint of GNU Make use).

We don’t even attempt to support:

This is 2020, not the late 1970-80’s. Although GNU make is phasing some of these out, you can find C-preprocessor checks and C code in GNU Make for the above.

By eliminating support for the above, thousands of lines of code in support of the above has been removed.

And the remaining code is easier to read.

Sure, it has annoyed (and still annoys?) those who still work on and develop on the above. I get it. If it is any consolation, there is still GNU Make or GNU Make in older versions for such people.

However the way this code has been added makes already difficult-code to read even more difficult.

For example here is GNU Make 4.3 code from job.c

#if !defined(__MSDOS__) && !defined(_AMIGA) && !defined(WINDOWS32)
        remote_status_lose:
#endif
          pfatal_with_name ("remote_status");
        }
      else
        {
          /* No remote children.  Check for local children.  */
#if !defined(__MSDOS__) && !defined(_AMIGA) && !defined(WINDOWS32)
          if (any_local)
            {
#ifdef VMS
              /* Todo: This needs more untangling multi-process support */
              /* Just do single child process support now */
              vmsWaitForChildren (&status);
              pid = c->pid;

              /* VMS failure status can not be fully translated */
              status = $VMS_STATUS_SUCCESS (c->cstatus) ? 0 : (1 << 8);

              /* A Posix failure can be exactly translated */
              if ((c->cstatus & VMS_POSIX_EXIT_MASK) == VMS_POSIX_EXIT_MASK)
                status = (c->cstatus >> 3 & 255) << 8;
#else
#ifdef WAIT_NOHANG
              if (!block)
                pid = WAIT_NOHANG (&status);
              else
#endif
                EINTRLOOP (pid, wait (&status));
#endif /* !VMS */

Can you spot which code is used in the most-often POSIX unixy case? In some cases such as in the above, the most-often case is indented incorrectly because in of one of less-frequent cases it is say in an else clause (as appears above).

Note: If you have trouble parsing the above, the Pygments parser used in this document has trouble too. Even after adding mismatched braces in the above for context, I couldn’t get Pygments to parse this after specifying C source with C-preprocessor directives. So I gave up, and opted for the slightly shorter source code without some enclosing braces.

I understand how this ugly code hard-to-read code most likely came about in GNU Make. Been there and done that myself too.

In the early days to gain traction and support, a project wants to support lots of different platforms and OS’s, even obscure ones. To get going, you’ll probably do that in the most expedient way.

But again, that was then and this is now.

If there are folks in the affected communities that would like remake added and are willing to code and do the testing, I am open to this. But it needs to be added in a more modular way than was done in the past.

Overall, I view this as a plus for developers who would like to extend GNU Make or understand the code.

[1]As Ryan Davis explains: “legacy code” is any code you didn’t write.