Back in ye olde days I used GNU Make to build most of my projects, that is if I used a build script at all. GNU Make, for all of its inscrutable syntax and unexpected complexity, is still a stable and useful tool if I just want to make a small project. A nice minimal Makefile looks like this:
$(CC) main.c -o main
That doesn’t look too bad, right? Don’t worry, some people have managed to write truly frightening Makefiles using this initially friendly-looking system. As evidence I present to you an image of the latex-makefile:
The Makefile above contains over four thousand lines of code. Sure, much of that is comments, but that is still a big whopping load of lines, and a lot of complexity, for something that should be simple. Maybe all of that logic should really be in a plugin to the build system. Then everyone could build LaTeX projects without reproducing the work of the above monstrosity!
When I started working with larger Java projects I was introduced to Apache Ant. This was the way I learned to build Java projects and I didn’t put much thought into it, initially. A simple
build.xml for an Ant build might look like this:
<project name="foo" default="build">
<task name="build" description="compile Java sources">
<javac src="src" dest="bin"/>
Again, a trivial build requires only a simple build script. Unfortunately, Ant build scripts are written in XML. This was a horrible idea, and one of the major flaws of Ant. XML seems great to developers (well, some developers) because it is easy to parse and serialize, so they are happy to use it in their applications. However, the tragedy is that XML is human-readable, so it is used in situations where someone will have to edit that awful syntax.
Anyhow, Ant seemed limited at first, yet it is possible to bend the rules and put some logic into your build despite that it seems like there should be no logic in an Ant script. This was both a blessing and a curse. A blessing because it meant I could use Ant for all kinds of stuff, and a curse because it meant that I used Ant for all kinds of stuff where I would have been better off with a better build system.
One of the projects I have been working on had the following Ant scripts:
Considering the size of this particular Java project, these build scripts are not too bad really. Other Ant builds for projects of similar size could easily be twice as large.
The project above has four variants; there is one main script (the left one), and four scripts for each variant (plus a small configuration file). All variants are quite similar, and there is much duplicated code between the variants. Although the main build script is relatively simple I think the build was overall too verbose and too complex.
Maven is a build system and project description model that has some nice ideas:
- How to build a project is implicitly derived from the specification of the project
- The build system should manage dependencies
- Build-by-convention is great (assume default configurations for source location and output directory, etc.)
In general I think that was a good idea to try to unify project description and build system, but it was taken a bit too far with Maven. Also, regrettably, Maven also uses XML. The flaws of XML are even more apparent in Mavens project object model files (pom.xml) since they often contain large chunks of boilerplate stuff.
I have never really used Maven, though the benefits over Ant are very clear. Being able to remove your libraries from the repository feels super tidy and neat, and build-by-convention means your simplest projects just work!
When I started using Gradle it was amazing, this build system gets almost everything completely right. Finally, a well-maintained build system for Java that does not use XML!!!
Let’s look at a simple build script again:
apply plugin: 'java'
That’s it! Well, you will have to follow the conventions and put your code where Gradle expects it, but then it will just work.
So what happens for larger projects? I converted the build system for my Ant example above to use Gradle, and this is what I ended up with:
The left-most part of the image shows the build script, and the other parts are just configuration files. To be able to simplify the build this much I use a plugin that I developed for building JastAdd projects. Most such projects are very similar, and the plugin will be re-usable for other such projects.
I won’t talk more about the benefits of Gradle here or why I have started to love it so much, I just want to focus on pure code size. Obviously my Ant scripts are larger, but why is that bad? In general, more lines of code means:
- It is more difficult to navigate the code.
- Developers who are unfamiliar with the project will have a harder time to read and/or understand the build script.
- More complex constructs are probably used to circumvent limitations in the build system. For example, in Ant you can have conditional execution and loops but it requires a lot of trickery.
- The script becomes harder to maintain because of such difficult constructs.
The complexity argument may not necessarily be true, for example maybe the build system is just needlessly verbose. Maybe a very concise build script is more difficult to understand because it uses more implicit behavior. I personally do not believe this. I believe that when there is a large difference in number of lines then the shorter solution is mostly more concise and probably a lot easier to understand.
The three images above were all rendered with the same text size. I think it is quite fun to compare them and look at the patterns that are used in the three build systems. Looking at a zoomed-out view of source code is surprisingly effective to visualize the complexity of the code.