Running Java builds using Team Foundation Build

The customer I’m working at, decided a while back to use Team Foundation Server as there standard ALM tool for both .NET and Java based projects.This also means that all teams are required to run their builds using Team Foundation Build.
For the Java based projects, the customer had some requirement to which the Team Foundation Build process should adhere to:
– Java builds should run on Windows as well as Linux machines
– (JUnit) test result should be imported into TFS and a link should show up in the build report
– The build report should have a link to the build process (Ant, Maven or Gradle) log

Since there is no build agent for Linux machines, we need to use a the default (Windows) build agent to call a remote script on the Linux machines, using SSH for example.
After doing some research on the internet, I came to the conclusion that there are no build templates out there that suited my needs. So I decided to create my own.

Since the default build template provides some functionality I want to use, like associating change sets and work items at the end of the build and setting a label in Version Control, I used the default template as a starting point.
I tried to stay clear of building my own custom assemblies. Instead I used the Community TFS Build Extensions and the Microsoft Visual Studio Team Foundation Server Build Extensions that contain a lot of activities that can help out setting up custom build templates.

[Running the build on a Linux machine]
I pretty much stripped everything that is in the ‘Try Compile and Test’ part of the default template and added my own logic. Getting the logic in the template to run a Java build wasn’t hard. Just use the InvokeProcess activity to call an Ant, Maven or Gradle script and you’re done.
Setting up the build to run on a Linux machine is quite easy as well. The community TFS Build extensions provide an activity to invoke a script on a remote machine. This activity is using PuTTy to execute the remote script. (You can read more on the usage of this activity here.)

To actually run a build script on the remote machine, the sources need to be on that machine as well!Since we are using the default template, the ‘get’ of the sources occurs on the Windows machine and not on the Linux machine. Of course I could copy them over, but that takes additional time. Instead I decided to install the Team Explorer Everywhere on the Linux machine. By calling TF on the Linux machine I was able to create a workspace, add the mappings and perform a ‘get’ on the Linux machine.
This works fine, however; I’m getting the sources twice now. Once on the Windows machine and once on the Linux machine. So I disabled the ‘get’ on the Windows machine, and things started to go wrong.
By disabling the ‘get’ on the Windows machine, the build can’t create a label anymore. The default activity that Microsoft provides creates the label based on the workspace. But since we don’t perform a ‘get’ anymore there is nothing in the workspace. This can off course be fixed easily by setting the label from the Linux machine using TF. However, it didn’t stop there.
The next thing I noticed, is that no change sets and work items were associated to my build. It turns out that the default activity that associates the change sets and work items uses the workspace as well.
Since I didn’t want to create my own custom activities or build an Ant, Maven or Gradle task to associate the change sets and work items, I decided to leave the ‘get’ on the Windows machine in the template and just add the ‘get’ on the Linux machine in parallel as well. This way I should not loose a lot of time because I’m getting the sources twice.

[Importing JUnit test results]
The next thing I needed to fix, was importing the JUnit test result into TFS so that they would show up as part of the build report.

image
Because the tests are executed on a Linux machine, I needed to copy those result files back to the build agent. The community TFS Build extensions provides an activity to easily copy files from and to a Linux machine. Having the result files on the build agent, then allows me to use an activity that Microsoft released with their build extensions, to translate those JUnit test result files into a TRX file and upload the result to TFS.
So just add the activity to build template and you’re good to go, I thought. It turns out, your not!
Two things need to be fixed:
Since your not building any Visual Studio solutions or running any tests using the Microsoft infrastructure, the compilation state and test state are not set for your build. You need to do this yourself. Using the SetBuildProperties activity I set both the compilation and test status to succeeded if the remote process executed without errors.
image

The other thing I needed to do, is add a build node to the build report, for the compilation step. Again; since we are not compiling Visual Studio solutions, there is no build node added to the build report for our compilation results. We need to do this ourselves. (If I don’t add this ‘root’ node to the build report, the test result will not show up.)
As I didn’t want to create custom activities, I just used the InvokeMethod activity to add the build node to the report:

image

The first call is to create the build node. This can be done by calling the static AddProjectBuildNode method on the InformationNodeConverters class. The second call is to save the build node.
Important to note is, that you need to provide a platform and flavor to register the build node. the values should be the same values as you use to upload the TRX file. If you use different values, the test result will still not show up in the build report.

[Add link to the process log file]
When building a Visual Studio solution in your build, a link to the MSBuild log file is added to the report automatically. Typically this links to a log file on the drop location.

image
However, since we are running an Ant, Maven or Gradle script (on a remote) machine, this file is not automatically added to the build log.
To make this happen we need to do 2 things. First you need to make sure, that a build node was added to the build log for the compilation results. We did this already, to ensure that the test result show up.
The next thing, is to link the build log to the report. The way this works is that the build report will look at the (compilation) build node and looks for the first external link child node. This link is used to create the hyperlink in the build report.
I used the InvokeMethod activity again to create the external link node and add it to the previously created compilation build node.

image

The first call is to create the external link node by invoking the static AddExternalLink method on the InformationNodeConverters class. The second step is to save the new node.

[Results]
The end result is, that for the Java builds running on a Linux machine the build report looks like this:

image

As you can see, it has all the ‘normal’ information, such as a link to the build log file, the test results, associated change sets and associated work items are in the build report, just as requested.