Detached automatic build

I suppose many of you use a full-fledged IDE, which does everything from sock knitting to timing your tea. I'm not like that. I prefer the UNIX way of doing things, where each program does its part, and there are other programs to orchestrate them. This holds true for my development environment as well; I use VIM as a text editor, GIMP as image editor, Blender as a modeler, GCC as a compiler and GNU Make to orchestrate the build. GIMP and Blender actually doubles as background build tools, exporting to game readable files, but no need to be fundamentalist about it. One important feature I've added on top is the automatic build trigger, which immediately tells me the status of the build whenever I change something. Sure Eclipse does this as well, as I'm sure Visual Studio does as well, showing compile errors as soon as you make them, but does it show you the errors in the bones of your 3D model, or automatically generate PDFs of your documents when they've changed? Perhaps it's not actually into sock knitting after all. Yeah, I know, you can probably tie in external builders, and have them triggered, but this is about an alternative solution, not using a monolithic IDE.

Incremental build

If you're doing automatic builds, it is extremely important that you only build what's required to reflect the changes you've made, often known as an incremental build. Why is this important? As soon as your project grows in size, especially if you have compile-time intensive stuff like heavy duty C++ templates, a full build can take several minutes. Now add asset processing to the mix, and you might be looking at a lot more than a minute for each build, clearly not acceptable to see the result of a small change, so be sure to set up your build to correctly track dependencies. GCC works in harmony with Make here by being able to produce Make-readable dependency information as a side product during the build, if you have dependencies in other parts of your build, take a note of how this works, it might be a real practical solution.

If you've set up your Makefile correctly, using dependency information generated from the last build, Make will only build those files where a dependency is newer than the target, satisfying our need for an incremental build. Dependency information from the previous build is sufficient because if you add a dependency (a new #include for example), the file has changed and will be rebuilt anyway, regardless whether the new dependency has changed or not.

The trigger — inotify

As I'm working in Linux, I'm using the inotify system. There are equivalent options for Windows (FileSystemWatcher in .NET and FindFirst- / NextChangeNotification in the Windows API) and Mac OS X (through the FSEvents API), I am unfamiliar with any separate tools exposing this functionality as simple commands however, although I'm sure there must be some.

Look for the package containing inotifywait in your favorite distribution, and fire it up like so:

    inotifywait -r -e close_write src assets
Assuming you have a 'src' and 'assets' directory. The '-r' flag will place triggers on all directories recursively, as well start watching new subdirectories as they appear. We're looking for the event ('-e') 'close_write', meaning any file being closed which was initially opened for writing. Although this does not necessarily mean the file has been written to, this would be a fairly rare occurrence, and the performance impact is negligible. Now save a file in your source tree and see what happens.
    src/ CLOSE_WRITE,CLOSE foo.c
Did you notice the warning saying it might take a while to establish all the watches? To get around that, we add the '-m' switch, telling inotifywait to continuously monitor for changes until interrupted or killed, printing one line per event as they occur.

Now that we're a little familiar with how it works, let's create a script to act as a build trigger. We can specify an output format for the events, to make it easier to parse and trigger builds of that particular file, but Make will check everything anyway, so I just use it as a trigger.

    inotifywait -mr -e close_write src assets |
    while read event; do
      echo   "==== Building ===="
      if make
        echo "======= OK ======="
        echo "====== FAIL ======"
That was easy, right? Let it run and start editing. It'll work, but look closely and you'll notice that it's triggering a lot more often than it should. This is fine in theory, as long as it doesn't impact the performance in a big way, but I prefer to have it not run that often. A closer look at the event being generated will spot the culprit, build results and temporary/backup files (if you place them next to your source files, which is often the case). To combat this we'll use the exclude option to inotifywait, which lets us specify a regular expression matching files to exclude. It appears though, that we can only specify this flag once, so we need to construct a regular expression which covers all cases, which is shame, as it has a tendency to become illegible. I use:
    --exclude '(\.(o|d|swp|blend[0-9])|^[^.]*$)'
Which will exclude object files, Vim swap files, blender backup files, and files with no '.', which is usually executables.

If some of your tools update multiple files simultaneously, it might be a good idea to add a small window for consuming multiple events before starting the build. One options is to add another read-loop, but using a one second (or whatever is appropriate) timeout. Otherwise, the events would queue in the pipe and trigger an extra build, afterwards, stating that there's nothing to do, which might seem confusing. One second is a rather long time though, so if you want a snappy response, leave it out.

Finally, I preload the pipe with a dummy event, to fire off a build when the script is started. I suppose this is a matter of taste. All things combined, this is what I have running:

    subs="src assets"

    { echo INITIAL
      inotifywait -mr -e $ev --exclude "$ex" $subs ;} |
    while read event; do
      while read -t1 event; do true; done
      echo   "==== Building ===="
      if make "$@"
        echo "======= OK ======="
        echo "====== FAIL ======"
Here I've also added a "$@" to the make command, to allow it to be run with different targets (hint, I run it with "check") by simply starting the script with the target as a parameter (or parameters).

Adding bling

There are a lot of things you can do with this, but I settled for adding a bit of color (green and red for success and failure), and a little trick which I really like, adding escape codes to update the title bar with the build status, making it omni-present in my task bar with a quick glance, without having to have the terminal visible.

  • Green:
    echo -e '======= \e[32;1mOK\e[0m ======='
  • Red:
    echo -e '====== \e[31;1mFAIL\e[0m ======'
  • Title bar update:
    echo -ne '\e]0;Build: OK\a'
Note that the title-bar update is will not be echoed to the terminal itself, so you need to output the status twice if you want it to be visible both in the title-bar and as regular output. If you use a separate invocation for updating the title-bar, you might want to append the '-n' flag to suppress the newline, since we don't have any output to the terminal.

You could copy an image of a red or green traffic light into a 'status image' file and have a image viewer show it (assuming it can react on file system changes). The possibilities are endless…

comments powered by Disqus