My last post was all about the problems inherent with using Java as a builder for Java programs. My conclusion was that it isn't a good idea... mainly because you have to solve a bootstrapping problem - you need to compile your builder before it can work.
Well, I thought about it a bit more, and figured, eh, what the hell? After all, working code wins. So, I sat around and attempted to make my own Java based Java builder. This ultimately turns out to be a two-step process:
- Compile/package your builder
- Call your builder with it's own classloader
So, how does it work?
You put your builder code in it's own source tree. I decided the "builder" folder would be a good choice. So, you put your builder's java source code in "builder/src", dependencies in "builder/lib", and your compiled code will end up in "builder/classes". My "Builder" class will compile everything in builder/src, and call whatever methods in one of your classes.
What?
Maybe it would help if I included an example... Here is how you build the builder:
java -cp builder.jar com.fourspaces.builder.Builder com.fourspaces.builder.BuilderBuilder(or more simply)java -jar builder.jar com.fourspaces.builder.BuilderBuilder
What this does is pretty simple: it uses the com.fourspaces.builder.Builder class to compile all the source code in "builder/src" and then calls the @Default task in com.fourspaces.builder.BuilderBuilder. In this case, the default task compiles the Builder project itself and creates the builder.jar file. You can call any method (that has no parameters) in the class that you call. So, for example, this will clean-up any artifacts from a previous build (clean method) and create the builder.jar (jar method):
java -jar builder.jar com.fourspaces.builder.BuilderBuilder clean jar
Not only can you call any method, you can also configure @Depends task-dependencies. So, in the above case, the "jar" task depends on "compile", which depends on "init". So, the methods that are called are (in order) clean, init, compile, jar.
The final little feature is that you can optionally create a single custom-builder jar file. If you do this, your command-line can be as simple as:
java -jar custom-builder.jar clean jar
Okay, so you can successfully create a bootstrapping builder in Java. Woohoo :) The real question is: is it worth it?
After much deliberation, I can say this: maybe. It doesn't suck as much as I thought it would, and you can really do some amazing things that you can't do in Ant. You can create whatever type of building infrastructure that you want without having to adapt your process to your build tool. The downside is that you have to do your own coding, so there isn't much already done for you.
As an example of what you can do, here is the BuilderBuilder class. I wrote three helper classes as well "JavaCompile", "JarBuilder", and "FileSupport". These helper tasks make compiling and jar building quite nice... from this code you can see how to define task dependencies, how to configure the default task, how to compile, how to jar up files, and how to do some basic file operations.
public class BuilderBuilder {
private String srcDir = "src/java";
private String buildDir = "build/classes";
@Depends("init")
public void compile() throws IOException {
new JavaCompile()
.setTarget(buildDir)
.addSource(srcDir)
.compile();
}
@Default
@Depends("compile")
public void jar() throws FileNotFoundException, IOException {
new JarBuilder()
.setFileName("builder.jar")
.addSource(buildDir)
.addInclude("**/*.class")
.addSource(".")
.addInclude("LICENSE")
.setMainClass("com.fourspaces.builder.Builder")
.create();
FileSupport.copyFile("builder.jar","builder/lib/builder.jar");
}
public void init() {
mkdirs(buildDir);
mkdirs(srcDir);
}
public void clean() {
deleteDir(buildDir);
}
public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, FileNotFoundException, IOException {
Builder.invokeBuilderTasks(new BuilderBuilder(),args);
}
}
So, all in all, not bad. I'm curious to see if there is any interest in this builder. If there is, I'm interested in polishing things up a bit and making this project a bit bigger (and give it a name). I'm planning migrating my own projects over to this type of builder, so the built-in tasks will be pretty much centered around what I need. Specifically, the tasks that are on the list are: SVN update/commit, Javadoc generation, creating a WAR file, starting a Jetty container (using said WAR file), and JUnit testing. One other things that I'd like to add is integration with Maven/Ivy jar repositories. If you're interested in checking it out, you can check it out from SVN at: http://svn.fourspaces.com/public/javabuilder/trunk/
Note: it should be mentioned that this requires using Java 6, since it relies on the Java Compiler API. When running, you should make sure that you are using the "java" executable from the JDK and not the JRE (a common problem)
