Hudson is the excellent continuous integration server written by Kohsuke Kawaguchi and others. Integral to Hudson is a plugin mechanism that allows third-parties to extend Hudson in several ways, including reporting test failures, results of automated code inspections, notification of broken builds and publication of build artifacts. This article will introduce the reader to the process of developing and modifying Hudson plugins through an easy-to-follow set of instructions and illustrations. While not a comprehensive treatise on Hudson plugin development or architecture, this article will help get you started with plugin development. This article does assume basic familiarity with Hudson. For a suitable introduction, see Automated Builds Made Easy with Hudson.
The Plugin Tutorial on the Hudson WIKI gives the steps to create the example Hudson Plugin using Maven and how to open up the Plugin using IDE's that support Maven project integration. This article supplements the wiki by providing additional illustrated instructions.
Navigate to http://maven.apache.org and download the latest Maven 2 release. The author used Maven 2.2.1.
This will connect to the central Maven repository and download the necessary Maven information to create the workspace, then create it.
mvn -cpu hpi:create ... "Enter the groupId of your plugin:" com.ociweb.jnb.aug2010 "Enter the artifactId of your plugin:" hpiExample
After answering the prompts as shown, a new workspace will be created inside the directory hpiExample.
The
Java package com.ociweb.jnb.aug2010 will be used.
This will build the plugin.
cd hpiExample mvn package
This will build the file target\hpiExample.hpi.
Before proceeding, we need to download and run Hudson so that we will have somewhere to deploy this new plugin. Navigate to http://hudson-ci.org and download the latest Hudson release. The author used Hudson 1.369.
Create a directory, say "hudson" and copy the downloaded hudson.war to that directory and start it.
This will
start Hudson on the embedded Winstone servlet container.
mkdir hudson cd hudson java -jar hudson.war Running from: C:\hudson\hudson.war [Winstone 2010/07/27 06:34:58] - Beginning extraction from war file hudson home directory: C:\Users\lewisd\.hudson [Winstone 2010/07/27 06:34:59] - HTTP Listener started: port=8080 [Winstone 2010/07/27 06:34:59] - AJP13 Listener started: port=8009 Using one-time self-signed certificate ...
You may now navigate with your web browser to http://localhost:8080.
We can now install the plugin we built to the local Hudson instance we created. First, click to Manage Hudson.
Next click to manage plugins.
Next click Advanced tab.
Next upload the plugin.
Next verify the plugin is installed. Note: You will need to stop and restart Hudson. On Windows, you may press Ctrl-C in the command window running Hudson.
Finally we have the plugin installed.
To exercise the plugin, let's check first for any global configuration options the plugin contributed.
Observe that it does provide the option to say hello in French as well as help available if you click on the question mark icon.
To exercise the plugin further let's first create a new trivial job. From the main "Manage Hudson" page click the "Create New Job" link.
Next, in the job config screen, type a name for the job, choose free-style project, and press OK.
This will create a new Job and take you directly to the Job Configuration page. Scroll down to the Build section and click to add a new build step.
Now we see the Job configuration options for the Hello World Builder, again with help available if you click on the question mark icon.
Now type a name and scroll to the bottom of the job configuration page and click "Save." Note that if your name is less than four characters long, i.e. "Dan", a warning will appear when you navigate away from the name field.
Next, you can run the job.
Next, you can see that the job ran and see the results. Click build #1 in this history.
Now from the build page, you can click to see the console output.
Now we can see that our plugin performed it's build step by printing "Hello, Dan!".
In the previous section we built, installed, verified and exercised the Hello World plugin that is available for download from the central Maven 2 repository. In this section, we will make some minor changes to the plugin and observe the results. For this article, the free and open source IntelliJ IDEA Community Edition will be used. However, Eclipse, Netbeans, or even a simple text editor could be used.
Open IDEA and click File->Open Project... from menu, then navigate to the pom.xml.
IntelliJ IDEA recognizes the pom.xml as a Maven build file. Select "OK".
Click to open the project pane on the left side of the IDE, then navigate to hpiExample/src/main/java/com/ociweb/jnb/aug2010/HelloWorldBuilder
and double
click on HelloWorldBuilder.java.
Now we're ready to make a small change. Because "Dan" is a common name, let's modify the name field validation to
only warn if names are less than 3 characters long.
To do this, we can edit the doCheckName() method.
/**
* Performs on-the-fly validation of the form field 'name'.
*
* @param value
* This parameter receives the value that the user has typed.
* @return
* Indicates the outcome of the validation. This is sent to the browser.
*/
public FormValidation doCheckName(@QueryParameter String value) throws IOException, ServletException {
if(value.length()==0)
return FormValidation.error("Please set a name");
if(value.length()<3)
return FormValidation.warning("Isn't the name too short?");
return FormValidation.ok();
}
Note that if you look for usages of doCheckName() using the IDE's "find usages" feature, you won't find
any. That is because Hudson is using Stapler to bind the validation of the name field to the
doCheckName() method using Java reflection.
At this point you can return to step three to rebuild, upload the updated plugin. However, a shortcut exists. Simply
do mvn hpi:run. This will automatically start the Jetty servlet container with your plugin already
deployed.
Observe that we don't see the "Isn't the name too short?" validation message anymore when the "Name" field loses focus.
We have customized the Hello World plugin and learned the basic techniques for building and installing a plugin. In the next section, we'll modify a more useful plugin that the author has used in a production environment.
One of the strengths of Hudson is the ease with which you can take an existing plugin and modify it to suit your purposes. To illustrate this strength we will modify the Text-Finder Plugin. The Text-Finder Plugin can search the workspace and/or console for files containing a given regular expression and optionally fail the build or mark the build unstable. The author has used this relatively simple plugin in a production environment and found it to be useful. We will enhance the Text-Finder Plugin to add a description indicating why the build failed or was marked unstable. To start, we will check out the Text-Finder Plugin from the source control repository. The wiki page Checking out existing plugins describes the process of checking out an existing plugin. We will utilize this information in the next step.
For this step you will need a command-line svn client. On Windows, the author recommends SlikSVN.
Once you have installed a command-line svn client, you may issue the following command:
svn co https://svn.dev.java.net/svn/hudson/trunk/hudson/plugins/text-finder
We have now checked out the Hudson Text-Finder plugin into the text-finder directory. Now, let's build
it.
cd text-finder mvn package
This will build the file target\text-finder.hpi.
At this time you can install it into your Hudson instance as described above and exercise it inside the job we created in the previous section. This exercise is left to the reader.
Now we can open the Text-Finder pom.xml using IntelliJ IDEA. Navigate to TextFinderPublisher.java and
double-click to open.
Scroll down to the end of the findText() method and modify the code to call the Build.setDescription()
method. This method sets the description of an individual build of a project. The build description
is visible in the Build History pane on the left side of the project page. It is also visible in the build detail
page.
Before:
if (foundText != succeedIfFound)
build.setResult(unstableIfFound ? Result.UNSTABLE : Result.FAILURE);
} catch (AbortException e) {
// no test file found
build.setResult(Result.UNSTABLE);
}
}
After:
if (foundText != succeedIfFound) {
build.setResult(unstableIfFound ? Result.UNSTABLE : Result.FAILURE);
build.setDescription("build result set to " + build.getResult()
+ "; regex [" + regexp + "]" + (foundText ? " was " : " was not ") + "matched");
}
} catch (AbortException e) {
// no test file found
build.setResult(Result.UNSTABLE);
build.setDescription("build result set to " + build.getResult()
+ " because Text-Finder aborted");
}
}
To observe the changes, edit the project configuration page and use "Scarecrow" as the name in Hello-World Plugin and also as the regular expression in the Text-Finder Plugin. Be sure the check the "Also search the console output" checkbox and the "Unstable if found" checkbox as shown in the image below.
Now save the changes and build the project. You should see something like the following image.
Hudson is a mature free and open-source continuous integration solution with a rich set of free third-party plugins available. With the help of this article you created and modified your own "toy" Hudson plugin and modified an existing "real" plugin. For further reading about Hudson Plugin development, please see the Hudson Wiki page Extend Hudson.