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:
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
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.The quality of the Elisp itself could be improved, since I think I might've overused
cl-defmethod
.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 settinglib
could be done from an EIEIO object-customization buffer.
Project Website
It's available on my Github.