Writing a Java 8 Build Tool In Elisp

Introduction

A while ago when I was in college, I learned Java. Recently, when making the decision to get good at programming to be able to change careers, I decided I would re-learn Java. I never really learned Java well when I was in school, but I decided I would change that this time around.

My editor of choice is Emacs. It was an editor I was introduced to while in college, since the Operating Systems class I took made us use it. After having come back to coding after a long hiatus, I've tried various editors, including:

  • Vim (this was before Neovim was a thing);

  • Textadept, a wonderfully lightweight editor scriptable in Lua;

  • Qt Creator, which works excellently for C++ development;

  • Geany, the default editor of the Linux distro I use;

  • Joe, a neat command-line editor;

  • VS Code, the excellent and now-ubiquitous editor from Microsoft.

However, none of them quite hit the spot for me as with Emacs. And so, when faced with a choice of having to rely on an IDE, versus continuing with Emacs, I'd rather continue with Emacs, especially since I've gotten really used to using Magit, a powerful and intuitive Git plugin for Emacs.

Going back to what I was talking about, I was recently faced with this choice when taking a Java specialization on Coursera, focusing on data structures, mainly graphs. I had gotten by in the previous courses in the specialization by editing Java files in Emacs, and compiling and running using the command line. Completion? dabbrev-expand. I would even debug using jdb, though I wish jdb were more on par with something like gdb, but I digress.

Eventually, I reached a course in the specialization which revolved around a much bigger project than in previous courses, and this simple "low tech" approach wasn't going to cut it anymore. I needed a build system, but I didn't want to switch away from Emacs and use Eclipse just because I needed a build system. Now, I could've used an existing one (Gradle, Maven, sbt), but I had an idea for writing my own. But before I continue, let me provide some background:

The application was focused on a GUI-based (JavaFX) street map viewer, already written and provided by the course instructors, that used road data to calculate shortest distances between two geographic points, much like how Uber (Uber Eats) might calculate delivery routes for its drivers. The student's goal was to implement the backend graph search algorithms (breadth-first, Dijkstra, and A*) that would actually perform the searches. The student would then compare the respective behavior of these algorithms (for example, A* wanders less from the goal).

The application consisted of quite a few packages. As would normally be the case, the various files among these packages could freely make references to other files within the same application, creating a dependency graph. So, as stated before, I needed a build system to update stale dependencies. For example, if my main function is in A.java, and A.java uses objects of type B defined in B.java, and I edit B.java, I must recompile B.java: this is the essential notion behind the build tool I eventually wrote. Now, if I know offhand that A.java depends on B.java, this isn't a big deal. Alternatively, if A.java isn't that large and complex of a file, I could easily inspect it to see whether it uses objects of type B. However, this becomes intractable when there are a lot of files that can depend on each other, possibly even circularly, in a lot of different ways: import statements, prefixes, and static class-members.

Hydraulic Make

Enter my build tool. I wanted to call it "emake", but it turns out there's already a tool by that name, where "emake" is short for "Electric Make". So, to spin off of that name, I decided to call mine "Hydraulic Make", or hmake. I figured that, since I was currently learning about graph algorithms, it'd be fitting to practice them in Elisp (I ended up implementing depth-first search on an adjacency list as part of the application, the one that actually culls the current dependencies of the file whose class you want to run.)

I was able to write a prototype pretty quickly, but polishing the application took much longer. The basic premise expands on an observation I made previously: I could automate the inspection of uses of the various package units that occur within a given file. More specifically, if I know (via hmake) that geography.GeographicPoint is one of my Java program's classes, then if "geography.GeographicPoint" (or else simply "GeographicPoint", if it's package-local) appears in a file, geography.GeographicPoint must be a dependency of that file. The basic methodology is to insert a file's contents into a temporary buffer and 1. analyze any import statements it uses, and 2. scan the rest of the file for "package mentions".

When writing this, actually navigating certain corner cases (for example, package-local dependencies, and static class-members) made the code tricky to get right. While dogfooding hmake, I ran into quite a few bugs, which I managed to fix as I went along. However, in the end, I was pleased with the result: I was able to consistently able to recompile the entire street map application from scratch correctly (for example, after having deleted all of its .class files); and, as I mentioned, those are a lot files, with a lot of dependencies between them!

The front end is meant to be used from Eshell.

Possible Improvements

  1. Admittedly, the Eshell interface is a bit wonky and unintutive. For one, the command names (e.g. hmake-build-command) can be shortened a bit. I could use EIEIO customize buffers to ameliorate this.

  2. The quality of the Elisp itself could be improved, since I think I might've overused cl-defmethod.

  3. One thing that could concretely be improved is letting the user set the library directory correctly (I make the hard assumption that it's called lib, which bit me a few times.) This ties into point #1, since setting lib could be done from an EIEIO object-customization buffer.

Project Website

It's available on my Github.