Using Atlassian Bamboo to build Xamarin apps

I just finished rebuilding/updating my continuous integration environment with the latest version of Atlassian Bamboo, so I thought I'd document the process for other people that may want to do the same as I had to do some research to get everything working.

Setup

Here's a diagram providing a high level overview of how my environment is setup: 

The first machine ("builder") is a Windows 8.1 machine hosting the Atlassian Bamboo web application.   This machine is not used as a build agent.  It's only job is to coordinate the builds and to store the build artifacts.

The second machine ("buildserver01") is a Mac Mini running OS X 10.9 with VMWare Fusion installed on it.  There are two separate OS X 10.9 virtual machines running on it, each with the Bamboo build agent running and a full development stack (Xamarin.iOS, Xamarin.Mac and Xamarin.Android and Xcode).  The virtual machines are used to build all of my Xamarin projects. The reason there are two virtual machines is so that I can either run builds in parallel or do builds against both the Stable and Beta (or Alpha) channel of Xamarin at any time depending on how I change the build agent settings.

The third machine ('buildserver02") is a Windows 8.1 machine with the Bamboo build agent and Visual Studio 2013 installed.  This server is used to build and test TouchDraw related web services which get deployed to a Windows Server 2012 machine.

Additionally, I have an iPad 2 running iOS 7 and a Motorola Xoom connected to one of the virtual machine instances for running automated tests natively on a device.

Bamboo Configuration

Before you can start configuring your build plans, you need define the capabilities that each of the build agents have.  Once your remote agents are up and running (or right away if you're using a local agent), you can define "executables" that are available on that agent:

As shown in the screenshot,  we need to define both the "mdtool" and "xbuild" executables.  The "mdtool" command will be used for building Xamarin.iOS and Xamarin.Mac projects, and the "xbuild" command will be used for building Xamarin.Android projects.

Building an App

In order to build a Xamarin.iOS or a Xamarin.Mac project, you can use the "mdtool" executable that configured earlier.  Essentially, you just need to tell which configuration you want to build (AdHoc|iPhone, AppStore|iPhone, Debug|iPhoneSimulator, etc...) and pass in the path of your solution:

To build a Xamarin.Android project you basically do the same thing, but with the "xbuild" executable: 

Google ChromeScreenSnapz016.png

Unit Testing

Unit Testing a Xamarin.iOS or a Xamarin.Android project is a bit more complicated as you need to do that in the simulator or on a device. This is what took me the longest time to figure out.

The key piece is to use the Touch.Server tool that is mentioned in this blog post.  It will launch unit test project on the target (simulator or real device) and save the test results in a local file.

Once you have that tool installed (I actually store it in the source code repository so it's available to every build agent I configure) you can invoke it like this from a script in your build plan:

mono --debug path/to/Touch.Server.exe \
 --launchsim path/to/project/Debug/TestProject.app \
 -autoexit \
 -logfile=test-reports/TestProject.xml

In the example above it's launching the unit test project on the iPad simulator.  You can use one of the other command line options for Touch.Server.

Bamboo can also parse the results of tests results in the  NUnit 2 XML format to display in the dashboard; however the unit test projects created by the Xamarin tools don't return XML results by default, so you'll need to modify them.

I've added the following to all of the AppDelegates and Activities for my mobile tests:

runner = new TouchRunner (window);
try
{
 runner.Writer = new NUnitOutputTextWriter (runner, new TcpTextWriter("localhost",16384), new NUnitLite.Runner.NUnit2XmlOutputWriter ());
}
catch (Exception)
{
 // Do nothing
}

The snippet above tells the runner to write it's result to a specific TCP port on localhost, and to use the NUnit2 XML format.  By wrapping in a try/catch block, I can run the same unit tests locally on my development machine, since in that case we'd get an exception when it can't connect to Touch.Server.

Lastly, you can't use the version of Touch.Server exactly as it is on GitHub right now.  You'll need to make a small modification to it in order for things to work.  The problem is that the current code adds a header to the results file, which is fine with text based results; however makes XML based tests results un-parseable by Bamboo.  The solution is to comment out lines 87-91 before you build Touch.Server to prevent it from writing a header.

Once you have that configured, you'll be able to see your test results in the Bamboo dashboard after a build:

Google ChromeScreenSnapz018.png