Rebuild code automatically with "watch" and a makefile

March 31, 2013

While poking around some random projects by the prolific TJ Holowaychuk, I came upon his version of watch, which repeats a command endlessly. You run

$ watch some_command_here

and it runs that command over and over, on an interval. By default, once per second.

The idea is to pair it with make to run tests and automatically rebuild/recompile code. Since make tracks dependencies and looks at file modification times, it won’t do anything unless the relevant source files have changed.

There’s an older program called watch that comes with Linux, but it doesn’t play as nicely with make. In particular, it clears the screen every time the command is run. For pairing this with make, we want new output to be printed whenever make actually does anything, and otherwise we want it to be silent.

To avoid any confusion I patched TJ’s version of the program, changing its binary name to tjwatch. Now if we run tjwatch make, make will be run once per second.

There’s a small problem though. make prints out this status message if it doesn’t have anything to do:

$ make
make: Nothing to be done for `all'.

So by running it once a second we get flooded with the message over and over again.

$ tjwatch make
make: Nothing to be done for `all'.
make: Nothing to be done for `all'.
make: Nothing to be done for `all'.
^C

We don’t want this. We do want output when make actually does something:

$ touch script/app/views.js
$ make
browserify -t hbsfy script/main.js -o static/bundle.js

In this example, I’m using a makefile to rebuild my app’s JavaScript bundle with Browserify. Note how make prints out the command it executed.

GNU make doesn’t have an option to suppress the “Nothing to be done” message, but still show other output. One idea would be to use grep:

$ tjwatch "make | grep -v 'Nothing to be done for'"
exit: 1

exit: 1

exit: 1

^C

But now we keep getting warned by tjwatch about the non-zero exit code, which is the exit code of grep, not of make.

A Stack Overflow answer suggests a better workaround. We can make an “all” task in the Makefile that does nothing, yet makes make think it’s doing something, by changing this:

all: static/bundle.js

to this:

.PHONY: all realAll

all: realAll
        @true

realAll: static/bundle.js

The true(1) command does nothing, and the @ prevents the command itself from being echoed.

Now tjwatch make is silent while idle, but prints commands and output when it actually does anything. Perfect.

You can follow me on Mastodon or this blog via RSS.

Creative Commons BY-NC-SA
Original text and images (not attributed to others) on this page are licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.