Home

Hudson's real top page lives in https://hudson.dev.java.net/ and link to three pages in the Wiki

News

Maven not downloading latest snapshots or releases
Ever had issues with maven not downloading the latest snapshots when you know for a fact new snapshots are available? Or your CI environment just deployed a new release (2.0), but when another Hudson job builds, maven does not download the latest 2.0 release artifact. Want an automated solution so you don't have to manually delete the artifacts from your local repository just so maven will download the latest?

Force maven to download latest snapshots
Our company uses Hudson for our automated CI environments. Our project basically has two jobs. The first job checks out and builds HEAD when modified and deploys SNAPSHOT WARs to our companies maven2 repository (artifactory). The second job, which builds nightly, uses maven to download the SNAPSHOT WARs from artifactory, creates an EAR, deploys it to JBoss, and runs integration tests. By default, maven will check once a day for changes to snapshots, so when our second job was triggered, maven inside hudson was not downloading the latest SNAPSHOT WARs.

The solution was to append the -U in the maven goals (run mvn -help). It stands for update-snapshots and tells maven to update all snapshots no matter what.


Force maven to download latest releases
Our next problem was when we created a branch and started creating release artifacts such as 2.0. Unfortunately the description given by maven for the -U option is incorrect (or at least in v 2.0.9), "Forces a check for updated releases and snapshots on remote repositories". As much as I tried, the -U option wouldn't work in our hudson job to force maven to download the latest non-snapshot releases.

The only current solution I know of is to use the maven-dependency-plugin and its goal purge-local-repository. So in your maven goals at some point execute mvn dependency:purge-local-repository and maven will physically delete your projects artifacts from the local repository (/home/user/.m2/repistory) and its transitive dependencies (I think). I tried setting the actTransitively to false and it didn't work for us so I just removed it. I also set verbose to true so I could see what maven deleted in Hudson's console output.



The pipes are used to separate out different goals to isolate its classpath or properties. That way we can skip tests in one run, and then run them in the next all in the same goals section.
DVCS? I'm sold
As a casual reader of DVCS advantages like git and mercurial, I think I am ready. Motivated by a great article shared by Kit Plummer, I am sold and ready to start using it. Unfortunately, it's probably not going to happen any time soon at work until a) IT supports a mercurial server like they do svn or b) I start a new project were new decisions like language (ruby/groovy) and source control can be made and I'm not hindered by "we are already using svn, why switch" or "I just got comfortable with svn" or "every team member would have to learn mercurial". Reminds me of my favoriate Zed Shaw quote from this article:
"This folks is the classic problem with programmers today. They absolutely refuse to learn anything new unless they can see that learning the new hotness will give them an immediate 200% boost in salary or get them hot honeys at the next conference."
Now that I see that Hudson has a mercurial plugin and Jira does as well, then technically speaking, I can't think of any other reason not to switch.
Automating Your ASP.NET Build and Deploy Process with Hudson
Writing a Hudson plugin (Part 7 - Putting it all together)

Life gets in the way... but we're back with our final installment! So where to start, let's start with a publisher for freestyle builds, then we'll add a publisher for Maven 2 builds... These will both require some reports to display results, and then finally we'll need the plugin entry point. But before we get into all that, perhaps I should briefly explain structured form submission support

DataBoundConstructors

Hudson uses Stapler as it's web framework. One of the things that Stapler provides is support for constructing objects from a JSON data model. Basically, if you have a class with a public constructor annotated with @DataBoundConstructor, Stapler will bind fields from a JSON object by matching the field name to the constructor parameter name. If a parameter also has a @DataBoundConstructor, then Stapler will recurse to construct this child object from the child JSON object.

Note: The only hole in this (at the moment) is if you want to inject a variable class, i.e. it does not support the case where there are three ChildImpl classes all implementing Child, and all with @DataBoundConstructor and Parent's constructor has a parameter which takes Child... However, plans are afoot to fix this!

JavaNCSSPublisher

Publishers in Hudson must have a Descriptor, this will be registered with Hudson and allows Hudson to create Publisher instances which have the details for the project they are publishing. Descriptors are normally implemented as an inner class called DescriptorImpl and there is normally a static field of the publisher DESCRIPTOR that holds the Descriptor singleton. 99.995% of the time, you will want your publisher to have a @DataBoundConstructor, so without further delay, here is the publisher:

package hudson.plugins.javancss;

import hudson.maven.MavenModule;
import hudson.maven.MavenModuleSet;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Descriptor;
import hudson.plugins.helpers.AbstractPublisherImpl;
import hudson.plugins.helpers.Ghostwriter;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Publisher;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;

public class JavaNCSSPublisher extends AbstractPublisherImpl {

private String reportFilenamePattern;

@DataBoundConstructor
public JavaNCSSPublisher(String reportFilenamePattern) {
reportFilenamePattern.getClass();
this.reportFilenamePattern = reportFilenamePattern;
}

public String getReportFilenamePattern() {
return reportFilenamePattern;
}

public boolean needsToRunAfterFinalized() {
return false;
}

public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();

public Descriptor<Publisher> getDescriptor() {
return DESCRIPTOR;
}

public Action getProjectAction(AbstractProject<?, ?> project) {
return new JavaNCSSProjectIndividualReport(project);
}

protected Ghostwriter newGhostwriter() {
return new JavaNCSSGhostwriter(reportFilenamePattern);
}

public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> {

private DescriptorImpl() {
super(JavaNCSSPublisher.class);
}

public String getDisplayName() {
return "Publish " + PluginImpl.DISPLAY_NAME;
}

public boolean isApplicable(Class<? extends AbstractProject> aClass) {
return !MavenModuleSet.class.isAssignableFrom(aClass)
&& !MavenModule.class.isAssignableFrom(aClass);
}

}

}

By inheriting from AbstractPublisherImpl we get a lot of the work done for us, all we really need to do is provide a Ghostwriter and the project level report (JavaNCSSProjectIndividualReport which we will see later

We need hudson/plugins/javancss/JavaNCSSPublisher/config.jelly to allow the user to specify the report file name pattern... not much to this, so here it is:

<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:bh="/lib/health">
<f:entry title="JavaNCSS xml report pattern"
description="
This is a file name pattern that can be used to locate the JavaNCSS xml report files
(for example with Maven2 use &lt;b&gt;**/target/javancss-raw-report.xml&lt;/b&gt;).
The path is relative to &lt;a href='ws/'&gt;the module root&lt;/a&gt; unless
you are using Subversion as SCM and have configured multiple modules, in which case it is
relative to the workspace root.&lt;br/&gt;
JavaNCSS must be configured to generate XML reports for this plugin to function.
">
<f:textbox name="javancss.reportFilenamePattern" value="${instance.reportFilenamePattern}"/>
</f:entry>
</j:jelly>

JavaNCSSMavenPublisher

This is fairly similar to the freestyle publisher, except that we do not need the user to configure everything for us, as we can grab some of the stuff from the pom.xml.

We could, if necessary, tweak the pom.xml to ensure that the report we are looking for is generated... an example of this is the cobertura maven plugin which does not generate an XML report by default. A Hudson plugin can modify the cobertura plugin's configuration before it executes in order to ensure that the xml report is generated. Note: some people regard this kind of thing as evil, as the pom.xml is no longer behaving the same as when run from the command line.

Ok, so here is the Maven publisher...

package hudson.plugins.javancss;

import hudson.maven.*;
import hudson.model.Action;
import hudson.plugins.helpers.AbstractMavenReporterImpl;
import hudson.plugins.helpers.Ghostwriter;
import net.sf.json.JSONObject;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;

import java.io.File;

public class JavaNCSSMavenPublisher extends AbstractMavenReporterImpl {

@DataBoundConstructor
public JavaNCSSMavenPublisher() {
}

private static final String PLUGIN_GROUP_ID = "org.codehaus.mojo";
private static final String PLUGIN_ARTIFACT_ID = "javancss-maven-plugin";
private static final String PLUGIN_EXECUTE_GOAL = "report";

protected boolean isExecutingMojo(MojoInfo mojo) {
return mojo.pluginName.matches(PLUGIN_GROUP_ID, PLUGIN_ARTIFACT_ID)
&& PLUGIN_EXECUTE_GOAL.equals(mojo.getGoal());
}

protected Ghostwriter newGhostwriter(MavenProject pom, MojoInfo mojo) {
// get the name of the xml report

String tempFileName;
try {
tempFileName = mojo.getConfigurationValue("tempFileName", String.class);
} catch (ComponentConfigurationException e) {
tempFileName = null;
}
if (tempFileName == null) {
// the name was not overridden in the pom, so use the default
tempFileName = "javancss-raw-report.xml";
}

// get the xml output directory

File baseDir = pom.getBasedir().getAbsoluteFile();
File xmlOutputDirectory;
try {
xmlOutputDirectory = mojo.getConfigurationValue("xmlOutputDirector", File.class);
} catch (ComponentConfigurationException e) {
xmlOutputDirectory = null;
}
if (xmlOutputDirectory == null) {
// the directory was not overridden in the pom, so use the default
xmlOutputDirectory = new File(pom.getBuild().getDirectory());
}

String searchPath;
String targetPath = makeDirEndWithFileSeparator(fixFilePathSeparator(xmlOutputDirectory.getAbsolutePath()));
String baseDirPath = makeDirEndWithFileSeparator(fixFilePathSeparator(baseDir.getAbsolutePath()));

if (targetPath.startsWith(baseDirPath)) {
searchPath = targetPath.substring(baseDirPath.length()) + tempFileName;
} else {
// we have no clue where this is, so default to anywhere
searchPath = "**/" + tempFileName;
}

return new JavaNCSSGhostwriter(searchPath, targets);
}

private String makeDirEndWithFileSeparator(String baseDirPath) {
if (!baseDirPath.endsWith(File.separator)) {
baseDirPath += File.separator;
}
return baseDirPath;
}

private String fixFilePathSeparator(String path) {
return path.replace(File.separatorChar == '/' ? '\\' : '/', File.separatorChar);
}

public Action getProjectAction(MavenModule module) {
for (MavenBuild build : module.getBuilds()) {
if (build.getAction(JavaNCSSBuildIndividualReport.class) != null) {
return new JavaNCSSProjectIndividualReport(module);
}
}
return null;
}

public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();

/**
* {@inheritDoc}
*/
public MavenReporterDescriptor getDescriptor() {
return DESCRIPTOR; //To change body of implemented methods use File | Settings | File Templates.
}

public static final class DescriptorImpl extends MavenReporterDescriptor {

/**
* Do not instantiate DescriptorImpl.
*/
private DescriptorImpl() {
super(JavaNCSSMavenPublisher.class);
}

/**
* {@inheritDoc}
*/
public String getDisplayName() {
return "Publish " + PluginImpl.DISPLAY_NAME;
}

public MavenReporter newInstance(StaplerRequest req, JSONObject formData) throws FormException {
return req.bindJSON(JavaNCSSMavenPublisher.class, formData);
}

}

}

The only complexity is in the newGhostwriter method, where we have to find out what the configuration for the maven plugin is in order to find the xml report.

We will need a hudson/plugins/javancss/JavaNCSSMavenPublisher/config.jelly file for this publisher... not much to this one as we get what we need from the pom.xml

<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:bh="/lib/health">
</j:jelly>

The reports

We have a total of four reports to generate:
  • Individual build report - used for freestyle and maven 2 modules
  • Individual project report - used for freestyle and maven 2 modules
  • Aggregated build report - used for maven 2 projects
  • Aggregated project report - used for maven 2 projects

To keep to our DRY principals, we'll use some abstract classes to pull together the common code. First, AbstractBuildReport which will form the basis of our build reports:

package hudson.plugins.javancss;

import hudson.model.AbstractBuild;
import hudson.plugins.helpers.AbstractBuildAction;
import hudson.plugins.javancss.parser.Statistic;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

import java.io.IOException;
import java.util.Collection;

public abstract class AbstractBuildReport<T extends AbstractBuild<?, ?>> extends AbstractBuildAction<T> {
private final Collection<Statistic> results;
private final Statistic totals;

public AbstractBuildReport(Collection<Statistic> results) {
this.results = results;
this.totals = Statistic.total(results);
}

public Collection<Statistic> getResults() {
return results;
}

public Statistic getTotals() {
return totals;
}

public String getSummary() {
AbstractBuild<?, ?> prevBuild = getBuild().getPreviousBuild();
while (prevBuild != null && prevBuild.getAction(getClass()) == null) {
prevBuild = prevBuild.getPreviousBuild();
}
if (prevBuild == null) {
return totals.toSummary();
} else {
AbstractBuildReport action = prevBuild.getAction(getClass());
return totals.toSummary(action.getTotals());
}
}

public String getIconFileName() {
return PluginImpl.ICON_FILE_NAME;
}

public String getDisplayName() {
return PluginImpl.DISPLAY_NAME;
}

public String getUrlName() {
return PluginImpl.URL;
}

public boolean isGraphActive() {
return false;
}

}

Similarly, we have AbstractProjectReport which will be used for project reports:

package hudson.plugins.javancss;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;

import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.ProminentProjectAction;
import hudson.plugins.helpers.AbstractProjectAction;
import hudson.plugins.javancss.parser.Statistic;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

public abstract class AbstractProjectReport<T extends AbstractProject<?, ?>>
extends AbstractProjectAction<T>
implements ProminentProjectAction {

public AbstractProjectReport(T project) {
super(project);
}

public String getIconFileName() {
for (AbstractBuild<?, ?> build = getProject().getLastBuild();
build != null; build = build.getPreviousBuild()) {

final AbstractBuildReport action = build.getAction(getBuildActionClass());
if (action != null) {
return PluginImpl.ICON_FILE_NAME;
}
}
return null;
}

public String getDisplayName() {
for (AbstractBuild<?, ?> build = getProject().getLastBuild();
build != null; build = build.getPreviousBuild()) {
final AbstractBuildReport action = build.getAction(getBuildActionClass());
if (action != null) {
return PluginImpl.DISPLAY_NAME;
}
}
return null;
}

public String getUrlName() {
for (AbstractBuild<?, ?> build = getProject().getLastBuild(); build != null; build = build.getPreviousBuild()) {
final AbstractBuildReport action = build.getAction(getBuildActionClass());
if (action != null) {
return PluginImpl.URL;
}
}
return null;
}

public String getSearchUrl() {
return PluginImpl.URL;
}

public boolean isGraphActive() {
return false;
}

public Collection<Statistic> getResults() {
for (AbstractBuild<?, ?> build = getProject().getLastBuild();
build != null; build = build.getPreviousBuild()) {
final AbstractBuildReport action = build.getAction(getBuildActionClass());
if (action != null) {
return action.getResults();
}
}
return Collections.emptySet();
}

public Statistic getTotals() {
for (AbstractBuild<?, ?> build = getProject().getLastBuild();
build != null; build = build.getPreviousBuild()) {
final AbstractBuildReport action = build.getAction(getBuildActionClass());
if (action != null) {
return action.getTotals();
}
}
return null;
}

protected abstract Class<? extends AbstractBuildReport> getBuildActionClass();

}

Now that we have these abstract classes, we can roll out our concrete reports. First the individual build report:

package hudson.plugins.javancss;

import java.util.Collection;
import java.util.List;
import java.util.Map;

import hudson.maven.AggregatableAction;
import hudson.maven.MavenAggregatedReport;
import hudson.maven.MavenBuild;
import hudson.maven.MavenModule;
import hudson.maven.MavenModuleSetBuild;
import hudson.model.AbstractBuild;
import hudson.plugins.javancss.parser.Statistic;

public class JavaNCSSBuildIndividualReport extends AbstractBuildReport<AbstractBuild<?, ?>>
implements AggregatableAction {

public JavaNCSSBuildIndividualReport(Collection<Statistic> results) {
super(results);
}

@Override
public synchronized void setBuild(AbstractBuild<?, ?> build) {
super.setBuild(build);
if (this.getBuild() != null) {
for (Statistic r : getResults()) {
r.setOwner(this.getBuild());
}
}
}

public MavenAggregatedReport createAggregatedAction(MavenModuleSetBuild build,
Map<MavenModule,
List<MavenBuild>> moduleBuilds) {
return new JavaNCSSBuildAggregatedReport(build, moduleBuilds);
}

}

That was fairly painless... Note that we interfaces for both the freestyle and maven2 project types, this is OK as the freestyle projects will ignore the Maven2 stuff and vice-versa while the common code is shared by both. Next we need the aggregated build report:

package hudson.plugins.javancss;

import hudson.maven.*;
import hudson.model.Action;
import hudson.plugins.javancss.parser.Statistic;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

public class JavaNCSSBuildAggregatedReport
extends AbstractBuildReport<MavenModuleSetBuild>
implements MavenAggregatedReport {

public JavaNCSSBuildAggregatedReport(MavenModuleSetBuild build,
Map<MavenModule, List<MavenBuild>> moduleBuilds) {
super(new ArrayList<Statistic>());
setBuild(build);
}

public synchronized void update(Map<MavenModule, List<MavenBuild>> moduleBuilds,
MavenBuild newBuild) {
JavaNCSSBuildIndividualReport report =
newBuild.getAction(JavaNCSSBuildIndividualReport.class);

if (report != null) {
Collection<Statistic> u = Statistic.merge(report.getResults(), getResults());
getResults().clear();
getResults().addAll(u);
getTotals().add(report.getTotals());
}
}

public Class<? extends AggregatableAction> getIndividualActionType() {
return JavaNCSSBuildIndividualReport.class;
}

public Action getProjectAction(MavenModuleSet moduleSet) {
for (MavenModuleSetBuild build : moduleSet.getBuilds()) {
if (build.getAction(JavaNCSSBuildAggregatedReport.class) != null) {
return new JavaNCSSProjectAggregatedReport(moduleSet);
}
}
return null;
}

}

This report is only used for the Maven2 project types. The two key methods are update which is called as each module completes, and getProjectAction which should return the project level aggregated report if there is a report to show. At this point we're ready for the individual project report:

package hudson.plugins.javancss;

import hudson.model.AbstractProject;
import hudson.model.Actionable;
import hudson.model.ProminentProjectAction;
import hudson.model.AbstractBuild;
import hudson.util.ChartUtil;
import hudson.util.DataSetBuilder;
import hudson.plugins.javancss.parser.Statistic;

import java.util.Collection;

public class JavaNCSSProjectIndividualReport
extends AbstractProjectReport<AbstractProject<?, ?>>
implements ProminentProjectAction {

public JavaNCSSProjectIndividualReport(AbstractProject<?, ?> project) {
super(project);
}

protected Class<? extends AbstractBuildReport> getBuildActionClass() {
return JavaNCSSBuildIndividualReport.class;
}
}

Don't repeat ourselves comes in handy here as essentially all the work has been done for us!. The project aggregated report:

package hudson.plugins.javancss;

import hudson.model.Actionable;
import hudson.model.ProminentProjectAction;
import hudson.model.AbstractBuild;
import hudson.model.Action;
import hudson.maven.MavenModuleSet;
import hudson.maven.MavenModuleSetBuild;
import hudson.plugins.javancss.parser.Statistic;

public class JavaNCSSProjectAggregatedReport
extends AbstractProjectReport<MavenModuleSet>
implements ProminentProjectAction {

public JavaNCSSProjectAggregatedReport(MavenModuleSet project) {
super(project);
}

protected Class<? extends AbstractBuildReport> getBuildActionClass() {
return JavaNCSSBuildAggregatedReport.class;
}
}

Again DRY to the rescue... At this point all that remains is to present the reports from these backing objects... so on with the jelly views. The helper classes and our inheritance makes this easy... all we need is two jelly files: hudson/plugins/javancss/AbstractBuildReport/reportDetail.jelly and hudson/plugins/javancss/AbstractProjectReport/reportDetail.jelly. Here they are:

<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<h1>Results</h1>

<table border="1px" class="pane sortable">
<thead>
<tr>
<th>Package</th>
<th title="Class count">Classes</th>
<th title="Function count">Functions</th>
<th title="Javadoc count">Javadocs</th>
<th title="Non-commenting Source Statements">NCSS</th>
<th title="Javadoc line count">JLC</th>
<th title="Single-line comment line count">SLCLC</th>
<th title="Multi-line comment line count">MLCLC</th>
</tr>
</thead>
<tfoot>
<tr>
<th align="left">Totals</th>
<th align="right">${it.totals.classes}</th>
<th align="right">${it.totals.functions}</th>
<th align="right">${it.totals.javadocs}</th>
<th align="right">${it.totals.ncss}</th>
<th align="right">${it.totals.javadocLines}</th>
<th align="right">${it.totals.singleCommentLines}</th>
<th align="right">${it.totals.multiCommentLines}</th>
</tr>
</tfoot>
<tbody>
<j:forEach var="r" items="${it.results}">
<tr>
<th align="left">${r.name}</th>
<td align="right">${r.classes}</td>
<td align="right">${r.functions}</td>
<td align="right">${r.javadocs}</td>
<td align="right">${r.ncss}</td>
<td align="right">${r.javadocLines}</td>
<td align="right">${r.singleCommentLines}</td>
<td align="right">${r.multiCommentLines}</td>
</tr>
</j:forEach>
</tbody>
</table>
</j:jelly>

Yep, the two files are identical! Other plugins may not be quite so lucky... but in general the project level report should be the same as the report for the latest build

Making a plugin

Now we are ready to make our plugin.... for this we need a class that extends hudson.Plugin and registers our publisher's descriptors with the appropriate lists... here it is:

package hudson.plugins.javancss;

import hudson.Plugin;
import hudson.maven.MavenReporters;
import hudson.tasks.BuildStep;

public class PluginImpl extends Plugin {
public void start() throws Exception {
BuildStep.PUBLISHERS.add(JavaNCSSPublisher.DESCRIPTOR);
MavenReporters.LIST.add(JavaNCSSMavenPublisher.DESCRIPTOR);
}

public static String DISPLAY_NAME = "Java NCSS Report";
public static String GRAPH_NAME = "Java NCSS Trend";
public static String URL = "javancss";
public static String ICON_FILE_NAME = "graph.gif";
}

And that's pretty much it... we should have a working plugin

Finishing touches

OK, so the plugin does not have health reports (i.e. the weather icons) and it does not show a trend graph... I think I'm going to need a part 8 :-(

Cobertura with Maven and Hudson
Cobertura and Maven: There are TWO important things to do. Then, we’ll see about integrating Cobertura and Hudson. First, set Cobertura as one of the reports in pom.xml: <reporting> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <version>2.2</version> <!-- use last version [...]
Top 10 features of Hudson

Kohsuke (aka Mr. Hudson) and I attended The Server Side Java Symposium, Las Vegas in Mar 2008. In one of the evenings we spent togehter, I decided to pick Kohsuke's brain on top 10 features of Hudson. My notes were lost but luckily I found them so this article is slightly dated but most of the content is still very much valid. Newer features have been added since then anyway.

Here you go ...
  1. Ease of installation & use - Hudson is downloaded as a single WAR and run just using "java -jar hudson.war" - no additional configuration or container. It can also be started by clicking on this link. There has been emphasis on a single web app with no need to run a Continuous Integration (CI) server or client. If started using "java -jar hudson.war", then the default page looks like:



    More details about ease of installation & use are available in Hudson Docs.
  2. Fairly extensive ecosystem of plugins - You can easily write plugins to support tools/processes in your team. And after you contribute, they can be used by others as well.
    1. Update Center - for finding and installing plugins
    2. Source Code Control Plugins: Visual Source Safe, Git, Perforce and many others.
    3. Most of the plugins are small and can be created in spare time.
    4. Maven support for generating plugin templates. Read more about developing plugins in Extend Hudson.
  3. Distributed build support - This lets you use those empty sitting machines in a master/slave configuration and churn out the builds. Hudson "baby sits" the slave and performs some non-trivial work to monitor it such as clock synchronization, disk space monitoring and restarting the slave if it gets disconnected.
  4. Inter team support - Multiple teams working together with an inter-dependency require downstream projects to be automatically built. A complete chain of projects with upstream/downstream can be easily configured. Such projects also need to keep track of which version of this is used by which version of that. Hudson uses Finger printing to simplify this.
  5. Open source - Hudson is fully open-sourced under the MIT License.
  6. Maturity - 226th release was released on 6/17 and never lost data compatbility even once. The migration from older to newer version is seamless, it's basically just redeploying the WAR and there is no extra configuration required.
  7. Extensive tools outside Hudson - This is possible because of programmable control interface. Some of the examples are:
    1. Hudson Tracker & Tray Application - Small application that sits in your task tray and monitor Hudson builds
    2. Trac plugin - Creates links from Hudson projects to Trac instances.
    3. Firefox Build Monitor plugin - Displays Hudson orbs on Firefox status bar panel to indicate the build status
  8. Permalink support - Hudson provides easily readable URLs for most of the pages such as "last successful build", "promoted build". These URLs can be used linked from anywhere.
  9. Localization - Localization is available in English, Japanese, Gemany, French, Turkish, Brazilian, Portugese, Russian. You can easily create your own localization bundle by following these instructions. There is even an IntelliJ plugin to internationalize existing code.
  10. Building blocks - Hudson builds on general-purpose building blocks which can be used for other projects as well:
    1. Stapler
      1. URL binding of domain objects
      2. JSON & XML generation
      3. Much of the implementation of remoting is in Stapler - take POJO and convert into XML
      4. IntelliJ plugin for Stapler
      5. View tier is pluggable - Hudson uses Jelly.
    2. Remote Access API
    3. Localizer - small runtime library to choose the locale with IDE plugin for i18n

So you still think you need another Continuous Integration tool ?

Download Hudson now!

Technorati: hudson top10 glassfish
Contribute to Hudson!
Readers to this blog know that I take every opportunity to talk about Hudson, one of the very best Continuous Integration tools. Well, I have another very good reason: you can contribute to it and make money, at least if you let yourself known before the end of June. Sun has started what they call the [...]
Plugin download stats are in after Update Center
Update center in Hudson had a very positive impact on plugin downloads
How to FTP artifacts in maven2
If there is one word a team should keep in mind when building a CI (Continuous Integration) environment it's Automate, Automate, and Automate (see Production-ready software, on-demand). Most teams, including mine currently, performs their releases manually; creating the branch, incrementing the POMs, uploading the artifacts to a repository, etc. This is one reason I wrote about using the maven release plugin to automate this process. I will say that having a CI server such as Hudson only helps in making automation easier.

My team also creates an official release about once every two months and supports that release anywhere between 1-2 weeks to months. It's also pretty intense during those 1-2 weeks when we have co-workers on-site installing and supporting our software in a completely different timezone and with limited access to the phone and internet.

Over the weekend I wrote a simple maven2 pom to help automate FTP'ing the release artifacts to an FTP server. Last week we created a branch and a job in hudson that builds that branch. After it successfully builds we use the assembly plugin to package our EAR, documentation, SQL files, and everything else into a single folder. In the past, a guy would then manually copy these files to the FTP server, which was used by the on-site team to download the latest artifacts containing improvements and bug fixes. This release we wanted to automate this step to increase our response time for the on-site team.

First Try using GMaven Plugin
I knew that Ant had an FTP task and I love doing Ant in Groovy because its so much easier so I decided to first try the GMaven plugin (maven groovy plugin). It was a short trip since the FTP task is an optional library in Ant and you have to include the jar in your POM, but I could never get the gmaven plugin to recognize the dependency (see MGROOVY-152). Look at the Jira issue attachments if you want to reference my POM as an example. It's too bad I couldn't get this to work because it would have been sweet:
log.info('Entering in ftp script')

def config = [server: 'localhost',
remotedir: '/home/jlorenzen/ftptest',
user: 'ftp',
password: 'ftp'
];

ant.ftp(config) {
fileset(dir: '
/home/jlorenzen/Documents')
}

Second Try Maven AntRun Plugin
I was finally able to get this to work using the maven-antrun-plugin. Here is my POM
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>ftp</id>
<phase>install</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<ftp server="localhost"
remotedir="/home/jlorenzen/ftptest"
userid="ftp"
password="ftp">
<fileset dir="/home/jlorenzen/Documents"/>
</ftp>
</tasks>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>ant</groupId>
<artifactId>ant-commons-net</artifactId>
<version>1.6.5</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>1.4.1</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
Debian packages of Hudson
Producing debian packages is now a part of the Hudson release process.
Hudson 1.222, update center, and community updates
I just posted Hudson 1.222. This version contains the update center for simplifying plugin installations
BuildMon v0.1 - Bringing Hudson Orbs To Firefox
About the French translation in Hudson
The French translation of Hudson is a contribution I have made to the project. The work is complete for the core part of Hudson, and I consider it stable, though many bits are not internationalized, hence appear in English. What can you do if you want to help? visit the wiki page for the French translation: it [...]
JAXB

Well, you might have already read Kohsuke's blog about JAXB 2.2 maintenance release. Hudson is his baby now, and he's moving away from JAXB duties.
The reason I mention it on my blog is that I'm going to take over the JAXB work going forward.
I'll leave it with that note for now, maybe I'll write more later on.


New features in Metro 1.3 and NetBeans


Recently, there've been couple of blogs about new features in Metro 1.2 release, and about some additional things that will be released with Metro 1.3.

I've been working on updating the NetBeans Metro plugin to make sure these features are easily configurable from our tools as well.

Today, I'd like to announce availability of NetBeans 6.5 builds that contain support for both Metro 1.2 and Metro 1.3 features. Click here to download your NetBeans 6.5 copy from Hudson continuous builds, or grab a daily builds. You shall get either JavaEE or Full bundle.
Note that in order to leverage the new features, you need to have Metro 1.2 or Metro 1.3 installed on top of your GlassFish installation.


The biggest changes you find in the UI are shown in the following screens:


  • Namespace Version Chooser - Metro 1.3 supports .NET 3.5 release, which has policy assertions in a different (standard based) namespaces than .NET3.0. Thus, you need to have a choice which version your service shall be developed with.



  • Kerberos - additional security profile available for 1.3 version with Kerberos support. Read more about Metro Kerberos support on Ashutosh's blog, and if you are not familiar with Kerberos itself, read more on Wikipedia



  • STS Issued Supporting Token Profile - new security profile based on Issued tokens.



  • Hash passwords - support for Hash Passwords in the Username Authentication with Symmetric Keys security profile. Read more about Hash Password support on Ashutosh's blog, and if you are not familiar with Digest Authentication itself, read more on Wikipedia



  • Encrypted Supporting Tokens - ability to encrypt supporting tokens.




There are some other things which I might forgot about, so let me know if you miss anything. Also, let me know if you find any errors, meet any exceptions, anything like that. Currently these are development builds, and release quality shall be met with NetBeans 6.5 release. However, I think the metro modules shall be stable enough already. Let me know ;O)

I'll be spending more time on Hudson going forward
I'm happy to report that Hudson just became my main responsibility.
Hudson adds support for StyleCop
Last week Microsoft released StyleCop (aka Microsoft Source Analysis for C#), a tool that analyzes C# code and enforces a certain code style (similar to Checkstyle). The tool will go through all C# source files and mark the code style violations. Now Hudson (a Continuous Integration server) together with the Violations plugin has added support for parsing the StyleCop XML report and display the results for every build.

The Violations plugin displays a trend graph over builds so it is real easy to see that your project is progressing in the right direction (decreasing the number of violations). As you can see the plugin also supports FxCop reports.


Each build will display a listing of the files that are violating the code style, and it is also possible to see the number of fixed (or introduced) violations for every build.


Clicking on a file will display excerpts from the C# code showing the violations.


Setup
Follow the guideline here after downloading StyleCop. Add the <Import Project="$(ProgramFiles)\MSBuild\Microsoft\SourceAnalysis\v4.2\Microsoft.SourceAnalysis.targets" /> to every csproj file that you are building within Hudson. When the VS project is built, StyleCop will store a file named SourceAnalysisViolations.xml in the project folder. As of now, there is no way to store it elsewhere. The XML file contains all violations that were found in the Visual Studio project.

Hudson Configuration
To configure the violations plugin, enable the "Report violations" check box. If there are several VS projects in the build, you should use **/*/SourceAnalysisViolations.xml as it will find XML files in all sub folders.


After configuration, start a new build to analyze and display the StyleCop results in Hudson. To see a trend graph you need at least two successful builds.

Tips
  • Do not enable all rules, choose those that are valid for your organization and make sense for you.
  • Do not activate too many rules at first, as it probably will generate too many violations. Too many violations may be ignored by your co-workers, as it seems to be too much work to fix them all. When the most critical code style violations has been fixed, increase the number of rules.
Metro with GlassFish v3 TP2 and NetBeans

I just finalized NetBeans support for Metro on top of GlassFish v3 TP2. What do you need to do to start using it? Here are the steps:



  1. First, download and install GlassFish V3 TP2 with Metro support from it's update center. Bhakti has a nice blog post with instructions how to do it: http://weblogs.java.net/blog/bhaktimehta/archive/2008/05/working_with_me.html

  2. Next, download and install latest NetBeans 6.5 development build e.g. from here (get the full or javaee bundle): http://deadlock.netbeans.org/hudson/job/trunk/lastSuccessfulBuild/artifact/nbbuild/dist/zip/

  3. Start NetBeans, go to Tools --> Plugins menu, and select "Available Plugins" tab

  4. Check checkbox next to "GlassFish V3 JavaEE Integration" and click "Install"

  5. Add the server to IDE. Go to "Services" tab, right-click "Servers" node, select "Add Server..." and follow wizard instructions to register your GlassFish v3 instance to NetBeans

Now you can use GlassFish V3 TP2 as a target server for your Web Projects and create web services/clients inside the same way you would do with GlassFish v2. The only difference is that JSR 109 integration with GlassFish is not ready yet, so you can't enjoy features like Web Service Tester, or deploying services/clients without generating necessary artefacts. Work on JSR 109 is in progress, and shall be ready soon. Keep an eye on Bhakti's blog for this.


 

Using Hudson as Rails CI server

For some time, Hudson has become my favorite continuous integration server. It's easy to configure and provides a handful of really interesting plug-ins. The only thing that I missed was the possibility of use Rake as a project build tool and thus I'll be able to take my java, ruby or rails projects into the same CI server.

Well, past weekend I had too much spare time so I decided to develop my first Hudson plugin and this morning I've released the first version of the Hudson Rake plugin.

Once you have installed Hudson you just need to donwload the plug-in and upload it from the Manage Hudson section:

When the plugin is avalable it detects your ruby instances installed from your PATH but it allows you to add other ruby or jruby paths:

Finally you just need to select the Invoke Rake option into the project configuration and select the tasks that you want to Hudson executes:

That's it, you are ready to go with Rake, Hudson and the Continuous Integration Game.

Accessing Hudson Variables with a Free-Style Project
Here is a great article on creating a manifest file containing hudson variables so that you can know exactly what build number and svn number built your artifact. However, in my case I wanted to create a properties file under my maven2 project under src/main/resources and let maven filtering take care of the rest. But for some reason it wouldn't work. After some further research, I eventually found out that free-style projects appear to not work the same as a maven2 project in hudson when trying to access hudson variables.

In summary, hudson variables work as expected when you create a maven2 project in hudson. With a free-style project you have to perform an extra step. Why, I don't know; I am sure there is a good reason. It's just too late to figure it out.

Here are the extra steps you can follow to create a properties file containing hudson values.

Update POM
In your projects POM or parent POM add the following properties at the bottom
<project>
....
<properties>
<build.number>${BUILD_NUMBER}</build.number>
<build.id>${BUILD_ID}</build.id>
<job.name>${JOB_NAME}</job.name>
<build.tag>${BUILD_TAG}</build.tag>
<executor.number>${EXECUTOR_NUMBER}</executor.number>
<workspace>${WORKSPACE}</workspace>
<hudson.url>${HUDSON_URL}</hudson.url>
<svn.revision>${SVN_REVISION}</svn.revision>
</properties>
</project>
Create a properties file
Create a application.properties file under your maven2 projects src/main/resources directory.

Now add this to it

build.number=${build.number}
build.id=${build.id}
job.name=${job.name}
build.tag=${build.tag}
executor.number=${executor.number}
workspace=${workspace}
hudson.url=${hudson.url}
svn.revision=${svn.revision}
Guide to building .NET projects using Hudson
In this guide I'm going to show how to set up a C# project on the Continuous integration server Hudson. I've been using Hudson on .NET projects since september and it works really well. I'm going to use Media Portal as the example project.

The below goals will be solved in this guide:

  • Get the source code from the Subversion repository
    • Link change logs to the repository browser using ViewVC
  • Build the project using MBuild

  • Run the tests using NUnit and display the results together with a trend graph

  • Publish artifacts from the build (nightly builds)

  • Run FxCop on an assembly and display warnings (linked with source code) and a trend graph

  • Search the source code for TODO, FIXME comments and display the open tasks with links to the source code

Initial downloads
The following files are needed besides Java (at least 1.5). Get the latest version of all files and notice that the Hudson file has the extension .war and plugins .hpi. This guide assumes that MSBuild, NUnit and FxCop are already installed and working.

Installation steps
I'm going to install Hudson into c:\Program Files\Hudson.

  1. Copy the hudson.war file to c:\Program Files\Hudson

  2. Start Hudson through "java -DHUDSON_HOME=data -jar hudson.war". Verify that you can access Hudson through http://localhost:8080

  3. Copy the plugins to c:\Program Files\Hudson\data\plugins

  4. Stop Hudson by pressing Ctrl+C in the command prompt where you started Hudson.

  5. Start Hudson again and you should be set to go.

Hudson system configuration
Follow the following steps to configure the tools that Hudson will use in building MediaPortal.

  1. Go to the System configuration at http://localhost:8080/configure.

  2. MSBuild Builder - Set the path to the MSBuild tool to C:\Windows\Microsoft.NET\Framework\v2.0.50727\msbuild.exe


MediaPortal job configuration

  1. Click the "New job" link at the home page.

  2. Enter the name "MediaPortal", check the "Build a free-style software project" and press OK.


Source code management
MediaPortal uses a Subversion SCM. Hudson supports CVS and SVN out of the box, but there are many plugins for other SCMs. After checking out the files from the repository Hudson will show the new change sets since the previous build in the Build page. A detailed view of change sets can be seen in the Changes page as the name of the developer, files that were changed and the comment for the change. Each change set is linked to the MediaPortal subversion repository browser, so it is easy to browse the actual file that changed.

  1. Press the Subversion radio button to configure the SCM.
  2. Repository URL=https://mediaportal.svn.sourceforge.net/svnroot/mediaportal/trunk/mediaportal

  3. Local module directory=.

  4. Press the Advanced button

  5. Repository Browser=ViewSVN, URL=http://mediaportal.svn.sourceforge.net/viewvc/mediaportal/

To test the configuration, press Save and then Build. The source code will be downloaded from the repository and put into the Workspace. If there were any changes in the SCM repository they can be viewed in the Changes page.


When the build is completed verify that it has checked out the code by going to the Workspace page. Using the Workspace page you can browse and view the files that has been checked out and it doesn't matter if the files are on the master or on a distributed slave!

Building the project
Im going to build a Release version of MediaPortal using MSBuild and the mediaportal.sln solution file. Hudson also supports NAnt and many other build tools through plugins.

  1. Click the "Add build step" and select "Build a Visual Studio project or solution using MSBuild."

  2. MsBuild Build File=mediaportal.sln

  3. Command Line Arguments=/p:Configuration=Release

To test the configuration, press Save and then Build. Now the source code should be updated if there any changes and then built using MSBuild. While the build is running you can check the Console log that is updated as the build continues.


Running unit tests and showing the result
Hudson can read NUnit XML reports and display the results of them for each build. If a test fails, then it will be displayed in the Status page. Hudson will also show how many builds ago a test failed (it's age); that way it is simple to see if a test failed in the current build. If a build has at least one failing unit tests then the build will become Unstable (yellow); otherwise it is marked as Successful (blue). After two builds you will get a trend graph showing the test results over time. To run the NUnit tests, I'm running nunit-console from Hudson after the MSBuild has compiled all files.

  1. Click the "Add build step" and select "Execute Windows batch command"

  2. Command=
    "c:\program files\nunit\bin\nunit-console.exe" MediaPortal.Tests\bin\RELEASE\MediaPortal.Tests.dll /xml=nunit-result.xml /config=test.config
    exit 0

  3. Check the "Publish NUnit test result report" and enter "nunit-result.xml"

Start a new build, now the unit tests will be run and their results are collected at the end of the build. When the build is completed, you can see how many tests there were, and how many failed, in the build in the Status page.

More information on namespace detail can be shown in the Test result page.

The test results are then displayed as a trend report. (This trend graph is copied from my python project, as it shows a variation over time)


Archiving build artifacts
It is good to archive the outputs (artifacts) from the build, so they can be retrieved later. This way it is possible to get a version of MediaPortal that was built a week ago, and compare the functionality to one built today. Hudson can be configured to store any number of files for each build, it can also be configured so only the latest successful build's artifacts are stored. As I'm not really sure what the output from MediaPortal is, I'm going to show how to archive the files for the MPInstaller.

  1. Click the "Archive the artifacts".

  2. Files to archive = MPInstaller\bin\Release\*

In the next build, Hudson will store all files in the MPInstaller release folder so it can be retrieved later even if there are newer builds available.


Analyzing code with FxCop
Hudson can collect output from several quality metric tools and show them for each build. Similar to the unit tests, they will be displayed in a trend graph. For .NET projects, it is good to use FxCop. FxCop analyzes managed code assemblies and can be very good to use but it may also generate too many warnings at start, in this example I will only use the security rules and analyze the Core.dll assembly.

  1. Click the "Add build step" and select "Execute Windows batch command"

  2. Command =

    "c:\Program Files\Microsoft FxCop 1.35\fxcopcmd.exe" /file:Core\bin\Release\Core.dll /rule:SecurityRules.dll /out:fxcop-result.xml
    exit 0

  3. Check the "Violations" check box, and set fxcop=fxcop-result.xml

Next build will take considerate longer time, as FxCop goes through the assembly and analyzes it to find potential problems. When the build finishes, you will see a nice summary on how many violations there are in the current build.

Finding source code comments
A nice plugin is the Tasks plugin as it will go through the sources, and find specific comments that should be watched. In this example I'm going to search for TODO or FIXME comments,

  1. Click the "Scan workspace for open tasks".

  2. Files to scan = **/*.cs

  3. High priority=FIXME

  4. Normal priority=TODO

If the plugin finds any comments in the source they will be displayed in the build page.

There will also be a trend graph so it is possible to monitor that the tasks are decreasing over time. Together with the summary on what comments were found, the full source code can be viewed.





Summary
I hope this was a useful guide to start building .NET projects with Hudson, one of the many CI servers. As shown above it does not take much configuration to start building a project using Hudson. The best way to start using continuous integration is to start building the project. Then focus on running unit tests and other quality metrics so you can monitor the quality progress on your project.

Currently there are some plugins in the pipeline that could be interesting for .NET developers:
  • A SCM implementation for Microsofts Team System

  • Support for CodePlex issues and Wiki words in change set comment. And a very simple configuration to check out code from a CodePlex project

  • Support for displaying coverage stats using NCover and MS own coverage tool
Finally OpenSolaris Installed
Finally, after 4 attempts, I got a half way decent install of Solaris. My previous attempts included installing Solaris Express Developer Edition on VMware Server 1.0.4 and VirtualBox 1.5. Both had major issues but I had the best luck with VMware Server, it just took like 15 minutes to boot and I couldn't install vmware tools. So at JavaOne 08 they announced http://www.opensolaris.com with the version 2008.05. So I thought I would give it a try and their install documentation seemed very thorough (see how to install on virtualbox). Unfortunately, installing this in virtualbox did not work (would't boot) and so I decided to give it one more shot on VMware Server and I guess I got lucky because it actually worked to my surprise. And it actually boots rather fast compared to my previous experience with solaris.

Since it uses GNOME it reminded me a lot of my ubuntu system. I was even able to successfully install vmware-tools which I got really excited about because I would have bet against this previously. Compiz is installed by default but I was unable to change the settings yet (it kept rebooting for some reason). Maybe it's because I haven't enable the nvida drivers yet; which was also a surprise that the nvida drivers where already installed.

Here are some features compared to Linux. I am not a file system guru but ZFS sounds interesting.

Anyways so far so good. I do miss sudo. I am now able to start working efficiently on providing hudson as a package in solaris. I am assuming that once I get done the below search will actually return a result (me <-- crossing my fingers).

Hudson won a Duke's Choice Award
Hudson won Duke's Choice Award this year in JavaOne
Teresa's DocBook vs. DITA article
Scott Hudson points to DocBook versus DITA: Will the Real Standard Please Stand Up? (slides here at dev.day.com) wriiten by my colleague Teresa Mulvihill. Scott points out that DocBook can be used for single-sourcing out of the box, thanks to support for XInclude and I would like to add that we at Day are currently in the process of translating our documentation to DocBook (not DITA) and first results are looking very promising. We are using Wilfried Springer's DocBook Maven Plugin as the driver of the processing toolchain, which is powerful, because we can integrate documentation development tightly with software development, sharing the same repository, using the same branches and tags and using the same build process.
Using Maven War Overlays to extend Hudson
I just recently found out about one of the neatest features of the maven-war-plugin called WAR Overlays. Basically it provides a very simple way to merge multiple WARs together to create an Uber WAR. You simply add a WAR as a dependency in your POM verses adding a JAR, and the maven-war-plugin will take care of the rest. My team uses this ability to extend the JSPWiki WAR to add in our wiki pages. The result is an Uber WAR including the JSPWiki stuff and our wiki pages. Then when a new JSPWiki WAR is available we just update the dependency version in our POM.

So for an example, I am going to demonstrate extending my favorite CI tool Hudson since it's freaking awesome and is downloaded as a WAR. Don't try this at home since hudson already provides the ability to extend it using plugins (which also rocks by the way).

Create a Simple WAR Project
First, create a war project using maven-archetypes. Execute mvn archetype:generate and select #18. Run mvn clean install to ensure it builds correctly (I am using maven v2.0.9).

Install hudson into local repository
Next we need to be able to consume the hudson war in our pom and since I am unware of the hudson war being available on any external repository we are going to just install it manually into our local m2 repository.
  • Download the latest hudson war
  • Install hudson.war into your local repository using the mvn install plugin by running: mvn install:install-file -Dfile=hudson.war -DgroupId=hudson -DartifactId=hudson -Dversion=1.0 -Dpackaging=war
Consume the hudson.war in your pom
Open up your war's parent pom and add the hudson war as a dependency.
<dependency>
<groupId>hudson</groupId>
<artifactId>hudson</artifactId>
<version>1.0</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
Next, since we want to run hudson in embedded mode without a container we need to add to the generated manifest file the Main class since I am too lazy to figure out how to include the original hudson manifest file (even though I am sure its possible). Include the following in your build section:
<build>
<finalName>mywar</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Main-Class>Main</Main-Class>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
Build and Run it
Now build the war again: mvn clean install. And your WAR should be merged with the hudson war. In fact for verification, your simple war contained an index.jsp under src/main/webapp and if you extract the war under target you will see your index.jsp.

Run: java -jar target/mywar.war
and go to: http://localhost:8080/index.jsp

If you are doing this on a serious level, you might want to look into the maven-cargo-plugins ability to create Uber wars.
More praise for Hudson

Over on the Maven2 Users list a recent poster asked what CI server was best, and Hudson was the only answer.

Then Jason van Zyl posted this praise for Hudson:

I know from my vantage point Hudson is the only system I will provide commercial support for at Sonatype because the battle is over. Hudson won by making developers lives' easier. Kohsuke will go to no end to make things easier for users.

...

At any rate I guarantee you that inside 3 months Hudson will have the best Maven integration of any CI/Build Server there is.

I'm personally biased towards Hudson, but I have to agree that it has won the war of the CI servers. I don't see anything coming close in ease of use or speed of startup.

Writing a Hudson plugin (Part 6 - Parsing the results)

In some ways parsing the JavaNCSS results is the least interesting part of developing a Hudson plugin, as once I have implemented the parser, it is available for everyone. For that reason I will focus more on:

  • best practice techniques for parsing results

  • common gotchas

  • designing for extension

Getting started

First off, we need to analyse the results file format. In the case of JavaNCSS there are multiple ways that the results file can be generated: from the JavaNCSS program directly, from ANT or from Maven. This leads us onto gotcha #1

Gotcha #1:
Never assume that a build tool generates the same format of output when run from the command line, ANT or Maven.

A case in point for Gotcha #1 is Findbugs which generates one XML format from the command line and ANT, and generates a different format that appears similar at first glance when run from Maven (mail thread). In this case it turns out that Maven 1 used the different format output, and it is feared that some people came to depend on this Maven 1 format, so when the plugin for Maven 2 was developed, they kept the Maven 1 format. In any case, the moral is don’t assume, check!

So we use the sample projects from Part 1 and generate an ANT and a Maven 2 XML report. First off, here is the report from ANT:

<?xml version="1.0"?>
<javancss>
<date>2008-04-12</date>
<time>11:22:30</time>
<packages>
<package>
<name>com.onedash.common</name>
<classes>1</classes>
<functions>3</functions>
<ncss>10</ncss>
<javadocs>3</javadocs>
<javadoc_lines>12</javadoc_lines>
<single_comment_lines>0</single_comment_lines>
<multi_comment_lines>0</multi_comment_lines>
</package>
<package>
...
</package>
...
<total>
<classes>5</classes>
<functions>8</functions>
<ncss>46</ncss>
<javadocs>9</javadocs>
<javadoc_lines>37</javadoc_lines>
<single_comment_lines>0</single_comment_lines>
<multi_comment_lines>0</multi_comment_lines>
</total>
<table>
<tr><td>Packages</td><td>Classes</td><td>Functions</td><td>NCSS</td><td>Javadocs</td><td>per</td></tr>
<tr><td>4.00</td><td>5.00</td><td>8.00</td><td>46.00</td><td>9.00</td><td>Project</td></tr>
<tr><td></td><td>1.25</td><td>2.00</td><td>11.50</td><td>2.25</td><td>Package</td></tr>
<tr><td></td><td></td><td>1.60</td><td>9.20</td><td>1.80</td><td>Class</td></tr>
<tr><td></td><td></td><td></td><td>5.75</td><td>1.13</td><td>Function</td></tr>
</table>
</packages>

<objects>
<object>
<name>com.onedash.common.Factory</name>
<ncss>7</ncss>
<functions>3</functions>
<classes>0</classes>
<javadocs>3</javadocs>
</object>
<object>
...
</object>
...
<averages>
<ncss>6.60</ncss>
<functions>1.60</functions>
<classes>0.00</classes>
<javadocs>1.80</javadocs>
</averages>
<ncss>46.00</ncss>
</objects>

<functions>
<function>
<name>com.onedash.common.Factory.Factory()</name>
<ncss>1</ncss>
<ccn>1</ccn>
<javadocs>1</javadocs>
</function>
<function>
...
</function>
...
<ncss>46.00</ncss>
</functions>
</javancss>

OK, first off, for those following the tutorial exactly, I have cheated a little. I added some more source files into the project to make sure that I have multiple classes is different packages. You can see the source code I built from here. Additionally, I have trimmed the output somewhat to highlight the interesting bits, removing the duplicate entries.

From this report file we can see a basic XML structure:

  • The root element is <javancss> and has child elements: <date>, <time>, <packages>, <objects>, and <functions>

  • The <date> and <time> elements are the timestamp when the report was generated with the date in YYYY-MM-DD format and the time in HH:MM:SS format

  • The <packages> element has child elements: <package>, <total>, and <table>. There are multiple <package>; elements, but only one <total> and <table> element.

    • The <package> elements have child elements: <name>, <classes>, <functions>, <ncss>, <javadocs>, <javadoc_lines>, <single_comment_lines> and <multi_comment_lines>. The <name> element contains the name of the package as a String and the other elements contain totals as Integers.

    • The