Around Focused Support, the developers who use Macs all use the excellent Dash documentation browser, which provides fast offline search and browsing for many popular development APIs. Using Kapeli's javadocset tool, you can create custom Docsets from your project's javadoc. However, we are firm believers in automated builds, using the Continuous Integration server, Jenkins. We wanted to integrate javadoc and Docset generation in to our CI builds, so that every time someone updates our currently running Sprint branch we would get up-to-date API documentation.

The Problem

Unfortunately, the Kapeli javadocset tool only runs on Mac OS X. The easiest answer would be to set up a Mac OS X Jenkins Slave, and have a specific Jenkins job that runs after our product build to generate the docset. Unfortunately we don't have any Mac servers, and Apple doesn't allow Mac OS X to run as a VM guest except on Apple hardware. I considered running Jenkins slaves on one or more developer laptops, with the idea that the job would just queue up if none of us happened to be online, but that didn't feel like a good answer.

The Solution

Eventually I came across a Go implementation of javadocset. It only implements a library function to generate the docset, so there was a bit of work to do. As a Go newbie, this was a simple and fun introduction to the language. I implemented a very small main program which calls the library function with some arguments from the command line. There is next to no error checking, but works for us - and I welcome patches to make it more robust. It is available on github as go-dash.

Building go-dash

Our jenkins server is running an older LTS version of Ubuntu, so I had to install a local copy of Go - this may not be required on newer operating systems, but the below steps assume a local copy of Go.  

jenkins@ubuntu:~$ mkdir go-dash
jenkins@ubuntu:~$ cd go-dash
jenkins@ubuntu:~/go-dash$ wget https://storage.googleapis.com/golang/go1.5.3.linux-amd64.tar.gz
--2016-01-22 13:04:56--  https://storage.googleapis.com/golang/go1.5.3.linux-amd64.tar.gz
Resolving storage.googleapis.com (storage.googleapis.com)... 74.125.22.128, 2607:f8b0:400d:c02::80
Connecting to storage.googleapis.com (storage.googleapis.com)|74.125.22.128|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 80147269 (76M) [application/octet-stream]
Saving to: ‘go1.5.3.linux-amd64.tar.gz’
go1.5.3.linux-amd64 100%[=====================>]  76.43M  9.73MB/s   in 7.8s   
2016-01-22 13:05:04 (9.85 MB/s) - ‘go1.5.3.linux-amd64.tar.gz’ saved [80147269/80147269]
jenkins@ubuntu:~/go-dash$ tar zxf go1.5.3.linux-amd64.tar.gz 
jenkins@ubuntu:~/go-dash$ ls
go  go1.5.3.linux-amd64.tar.gz
jenkins@ubuntu:~/go-dash$

We also will require git and mercurial to be installed, which I did with sudo apt-get install git mercurial

Then, we clone go-dash, and use go to fetch the dependencies and build the tool:

jenkins@ubuntu:~/go-dash$ mkdir -p src/github.com/FocusedSupport
jenkins@ubuntu:~/go-dash$ cd src/github.com/FocusedSupport/
jenkins@ubuntu:~/go-dash/src/github.com/FocusedSupport$ git clone https://github.com/FocusedSupport/go-dash.git
Cloning into 'go-dash'...
remote: Counting objects: 6, done.
remote: Total 6 (delta 0), reused 0 (delta 0), pack-reused 6
Unpacking objects: 100% (6/6), done.
Checking connectivity... done.
jenkins@ubuntu:~/go-dash/src/github.com/FocusedSupport$ cd ../../../
jenkins@ubuntu:~/go-dash$ export GOPATH=$HOME/go-dash
jenkins@ubuntu:~/go-dash$ export GOROOT=$GOPATH/go
jenkins@ubuntu:~/go-dash$ export PATH=$GOROOT/bin:$PATH
jenkins@ubuntu:~/go-dash/src/github.com/FocusedSupport/go-dash$ go get
warning: code.google.com is shutting down; import path code.google.com/p/go-html-transform/css/selector will stop working
warning: code.google.com is shutting down; import path code.google.com/p/go-html-transform/h5 will stop working
jenkins@ubuntu:~/go-dash/src/github.com/FocusedSupport/go-dash$ go install
jenkins@ubuntu:~/go-dash/src/github.com/FocusedSupport/go-dash$ cd ../../../../
jenkins@ubuntu:~/go-dash$ ls -l bin/go-dash 
-rwxr-xr-x 1 jenkins jenkins 8520408 Jan 22 13:14 bin/go-dash
jenkins@ubuntu:~/go-dash$ bin/go-dash 
Usage: go-dash DocSetName javadocDir
jenkins@ubuntu:~/go-dash$ 

Putting it together

We use ant for our builds, so I added a javadoc target which writes its output to a javadoc directory in the top level of the repository:

<target name="javadoc" depends="init" description="generates javadoc" >
       <javadoc sourcepath="src" destdir="javadoc" classpathref="classpath" author="true" use="true" windowtitle="Our Product API"/>
</target></pre>

I then checked in a short shell script which runs the go-dash tool, archives the generated docset in a .tgz file, and generates a Docset Feed XML file. These two files are left in the dist directory in our repository. Jenkins is configured to archive the files in dist as build artifacts, which means that they are available to download from the Jenkins job page.   

#!/bin/bash

export DOCSET_NAME="OurProduct"

STARTDIR=`pwd`
WORKDIR=`pwd`/docset.$$

export GODASH=${HOME}/go-dash/bin/go-dash

mkdir -p ${WORKDIR}
cd ${WORKDIR}

${GODASH} "${DOCSET_NAME}" ../javadoc

tar -cpzf ${DOCSET_NAME}.tgz "${DOCSET_NAME}.docset"
mv ${DOCSET_NAME}.tgz ../dist/

FEEDXML="../dist/${DOCSET_NAME}.xml"

echo "<entry>" > ${FEEDXML}
echo "<version>${BUILD_NUMBER}</version>" >> ${FEEDXML}
echo "<url>http://jenkins/builds/javadocs/our-product-docset/${DOCSET_NAME}.tgz</url>" >> ${FEEDXML}
echo "</entry>" >> ${FEEDXML}

cd ${STARTDIR}
rm -rf ${WORKDIR}</pre>

The Feed URL is pointing to a directory which is on our nginx web server running on the same machine as jenkins - we have configured it to follow symbolic links, and I've placed a symbolic link in that directory pointing in to the Jenkins job "lastSuccessful" link. This redirection is important because the docset can then be downloaded without being logged in to our Jenkins instance:

jenkins@ci-server:/usr/share/nginx/www/builds/javadocs/our-product-docset$ ls -l
total 8
lrwxrwxrwx 1 jenkins nogroup 82 Dec 28 15:16 OurProduct.tgz -> /var/lib/jenkins/jobs/our-product.active-sprint/lastSuccessful/archive/dist/OurProduct.tgz
lrwxrwxrwx 1 jenkins nogroup 82 Dec 28 15:16 OurProduct.xml -> /var/lib/jenkins/jobs/our-product.active-sprint/lastSuccessful/archive/dist/OurProduct.xml

Jenkins Configuration

Once all this is in place, configuring jenkins is just a matter of adding a couple of build steps:

And setting a post build step to archive the artifacts:

Configuring Dash

To add the Docset to Dash, go in to Dash Preferences, in the Downloads tab:

And select the "+" button, and enter the URL for the Docset feed XML file:

Click Add, then Download, and your new docset should be installed and available for search. Whenever your jenkins jobs runs it will update the feed XML - it appears that Dash checks for updates in its feeds about once a day, so it should download the latest docset relatively quickly.

Cross Platform Note

There are several other documentation browsing tools that appear to be inspired by Dash - I haven't tried either of these, but they both claim Docset and Docset feed compatibility, so everything in this tutorial should work with them as well. For Windows, there is Velocity, available as a free trial and a $20 license. For Windows and Linux, there is the Open Source Zeal Please let me know how they work for you.



Scott Lipcon