19.2. Java Code Generator Example

In this example, we create a simple Java “executable” (JAR file with a main method) that depends on a simple “library”. The library contains some automatically generated code which is generated by a code generator that we also supply. Our code generator defines a simple ant task to do the generation. To tie this all together, we have several build items, which we will examine from the top down in dependency order. This example can be found in the doc/example/java directory. When building Java code with ant, abuild makes use of a custom logger. This logger suppresses the output of empty ant targets, but does so in a manner that works well with nested targets. If you wish to use the standard ant logger, you can invoke abuild with the --no-abuild-logger command-line option.

We'll start off with the executable build item in the executable directory. If you look at its Abuild.conf file, you will see that it depends on library:

java/executable/Abuild.conf

this: executable
platform-types: java
parent-dir: ..
deps: library

From executable's Abuild-ant.properties file, you can see that this build item is generating the file example-executable.jar, and that it is generating a wrapper script:

java/executable/Abuild-ant.properties

abuild.jar-name = example-executable.jar
abuild.main-class = com.example.executable.Executable
abuild.wrapper-name = example

The src directory under executable contains a java directory and a resources directory. The src/java directory contains the java code, which in this case consists of the single source file Executable.java located in directory structure based on its package, as always. The resources directory contains the file file.text which is to be included in the JAR file. Our example executable reads the contents of this file from the JAR file as an example of how to make use of the resources directory.

Next, we'll turn our attention to the library build item. Our library build item creates a JAR file called example-library.jar as you can see from looking at the value of abuild.jar-name in its Abuild-ant.properties file:

java/library/Abuild-ant.properties

abuild.jar-name = example-library.jar

# Generate a Negator class using code-generator
abuild.hook-build-items = code-generator
code-generator.classname = com.example.library.generated.Negator

In addition, this build item makes use of hooks in the code-generator build item. Specifically, we tell that build item to generate a class called Negator in the com.example.library.generated package. Our library build item contains a single source file in src/java. This source file uses the generated Negator class.

For most users, this is all you really need to know: you will create your own code and resources and build them by setting properties. Sometimes the properties will be standard abuild properties, and sometimes there will be additional properties as required by build items that provide build-related services to your build item. As we continue, we will explore the code-generator build item to see what's going on behind the scenes.

The code-generator build item is in the code-generator directory. This build item creates a JAR file called CodeGenerator.jar. It also sets the abuild property abuild.include-ant-runtime so that the ant runtime library will be included in the compile-time classpath:

java/code-generator/Abuild-ant.properties

abuild.jar-name = CodeGenerator.jar
abuild.include-ant-runtime = yes

This build item also has a file called ant-hooks.xml. This file contains the build hooks that are provided by this build item. The ant-hooks.xml file references the property code-generator.classpath, which is declared in the Abuild.interface file. Here are those files:

java/code-generator/Abuild.interface

# Provide a variable that names our code generator so we can use it in
# taskdef.  There's no reason for most build items to provide this
# information, but it is needed for cases in which there is a reason
# to refer to a specific output product individually.  Since this jar
# just creates an ant task, we don't need to add it to the classpath.

declare code-generator.classpath filename
code-generator.classpath = $(ABUILD_OUTPUT_DIR)/dist/CodeGenerator.jar

java/code-generator/ant-hooks.xml

<?xml version="1.0"?>

<!--  This ant fragment provides the "codegen" task, provided by   -->
<!--  this build item, to generate a class named by the user of    -->
<!--  the build item.                                              -->

<project name="code-generator-hooks">

 <!--  Create the targets required to glue our codegen target      -->
 <!--  into abuild.  We want our codegen task to be called as a    -->
 <!--  code generator as a "generate" hook.  We also provide a     -->
 <!--  properties-help hook.                                       -->

 <!--  Provide the "-generate" target and have it invoke the       -->
 <!--  "codegen" target.                                           -->

 <target name="-generate" depends="codegen"/>

 <!--  Provide properties-help information.                        -->

 <target name="-properties-help">
  <echo>properties-help for code-generator:
Set the following property:
 * code-generator.classname -- the fully qualified classname to
   be generated
</echo>
 </target>

 <!--  Now provide our actual task and target.                     -->

 <taskdef name="codegen"
          classname="com.example.codeGenerator.ExampleTask"
          classpath="${code-generator.classpath}"/>

 <!--  Our code generator will write to the generated-jsrc         -->
 <!--  directory named in the abuild.private.dir.generated-jsrc    -->
 <!--  property as provided by abuild.                             -->

 <target name="codegen">
  <fail message="property code-generator.classname must be defined"
        unless="code-generator.classname"/>
  <codegen sourcedir="${abuild.private.dir.generated-jsrc}"
           classname="${code-generator.classname}"/>
 </target>

</project>

This particular build item provides two hooks: a generate hook, and a properties-help hook. These are implemented by defining targets called -generate and -properties-help. It is proper for all build items that provide hooks to provide a properties-help hook if the hooks expect any additional properties to be set. In this case, we are generating code, so we need to know the name of the class we are to generate. Our -properties-help target echoes this information to ant's output stream. The generate hook is run by abuild before it compiles any code, so the javac step will be able to access the generated source files along with the hand-coded ones. Our -generate target is an empty target that depends on the codegen target which does the actual work.

Notice that there is nothing else required in an ant-hooks.xml file other than creation of appropriately named targets. Abuild's ant call will automatically load and invoke these targets at the right time as required.

The code-generator build item's Abuild.interface does something that most build items don't have to do: it declares a custom variable that holds the name of the JAR file that it generates, and it does not add this JAR file to the classpath:

java/code-generator/Abuild.interface

# Provide a variable that names our code generator so we can use it in
# taskdef.  There's no reason for most build items to provide this
# information, but it is needed for cases in which there is a reason
# to refer to a specific output product individually.  Since this jar
# just creates an ant task, we don't need to add it to the classpath.

declare code-generator.classpath filename
code-generator.classpath = $(ABUILD_OUTPUT_DIR)/dist/CodeGenerator.jar

The reason for this is that code-generator's users don't need to call anything from its JAR file in their code, but its ant-hooks.xml file, which will be invoked with basedir set to the calling build item's output directory, needs to know the location of the JAR file.

The code-generator's src/java directory contains the source code to the ant task, but the ant task's operation is clear just by looking at the ant-hooks.xml file. Notice that it writes its generated code in to the ${abuild.private.dir.generated-jsrc} directory, which is what all code generators should do. (For additional information about writing targets, see Section 27.3, “Guidelines for Ant Target Authors”.) Our generate hook does not have to be concerned about any other details. Abuild will call it automatically at the correct point in the build process.

Next, look at the other-executable build item. This build item actually contains no source code. All it does is define the properties required to generate a wrapper script, but this time, it uses a different main class in the executable JAR file. This build item serves primarily as an example of using the wrapper target, which is a post-package hook, from a build item that doesn't actually package anything. Here are its Abuild.conf and Abuild-ant.properties files:

java/other-executable/Abuild.conf

this: other-executable
platform-types: java
parent-dir: ..
deps: executable

java/other-executable/Abuild-ant.properties

abuild.main-class = com.example.executable.Other
abuild.wrapper-name = other