Gradle is a relatively new Java build system, which mixes some of the ideas from Maven and Ant: the well-defined structure, multi-module support and dependency management from Maven, but also the customizability from Ant.

I recently switched to using it for one of my projects, which was using Maven, when I needed to implement some rather project-specific behaviour in the build and was fairly impressed with it. Unfortunately, it still lacks some things which are easier to do in other build systems, and this post describes how I added code to my build script to generate aggregated Javadoc with APIviz UML diagrams.

There is an open ticket in the Gradle bug tracker about built-in aggregated Javdoc support, so it might be possible that this will be built into Gradle in the future. However, as I lack a time machine, I decided to base my implementation on the ‘workaround’ code in that ticket, with changes to support APIviz.

To avoid cluttering the classpath of my actual application with APIviz, I created a new configuration for doclet JARs, and then added APIviz as a dependency in this configuration to the root project (you’ll need to have added the Maven central repository to your repositories section for this to work:)

configurations {
    doclet
}

dependencies {
    doclet group: 'org.jboss.apiviz', name: 'apiviz', version: '1.3.2.GA'
}

The aggregateJavadoc task itself inherits from the standard Javadoc task. Mostly it consists of configuration, however, to workaround some APIviz projects I also had to introduce some code which runs before the task - APIviz expects all the -sourceclasspath directories passed to it to actually exist, whereas Gradle does not create these directories unless a file is placed in them.

You can see the workaround in the start of task, which simply ensures these directories actually exist to stop APIviz from complaining. This is implemented by adding the code in the doFirst section, which is executed before Javadoc is.

task aggregateJavadoc(type: Javadoc) {
    group 'Documentation'
    description 'Generates aggregated Javadoc API documentation for the main source code of all projects.'

    doFirst {
        subprojects.each { project ->
            project.sourceSets.main.output.each { dir ->
                dir.mkdirs()
            }
        }
    }

The next piece of code is what I borrowed from the ticket I mentioned earlier. It merges the source and classpaths of all submodules into a single path containing them, and sets these as options in the Javadoc task:

    source subprojects.collect { project ->
        project.sourceSets.main.allJava
    }
    classpath = files(subprojects.collect { project ->
        project.sourceSets.main.compileClasspath
    })

APIviz also expects another option to be passed to it called -sourceclasspath, which is the location where the compiled .class files are placed for your project’s .java files. This is the code I used to set that option:

    options.addStringOption('sourceclasspath', files(subprojects.collect { project ->
        project.sourceSets.main.output
    }).getAsPath())

I chose to make the destination directory match the directory non-aggregated Javadocs are placed in:

    destinationDir file("$project.buildDir/docs/javadoc/")

The remainder of the options are mostly down to your preference. The -nopackagediagram is an APIviz-specific option, which I decided to use as the package diagram tends to look messy when your project has more than a handful of packages. The other options are all fairly standard Javadoc options, which are explained well in the official Javadoc documentation.

    options.showAll()
    options.addBooleanOption('nopackagediagram', true)
    configure(options) {
        windowTitle '<super secret project name here>'
        docTitle '<super secret project name here>'
        links 'http://docs.oracle.com/javase/7/docs/api/'

The final part to mention is the options to set the class name of the APIviz doclet, and the path to it, which can be done like so (notice the use of the configuration created earlier:)

        doclet 'org.jboss.apiviz.APIviz'
        docletpath file(configurations.doclet.asPath)
    }
}

and that’s it! Running gradle aggregateJavadoc should create Javadocs for all of your sub-modules in the build/docs/javadoc directory, including the UML diagrams APIviz draws.