MSBuild

So you have a game, and you want to get regular builds for people to test. Now you can do a build yourself , but you’re not a philistine are you? You want to use Continuous Integration (CI).

What is Continuous Integration

If you don’t know, CI is where a server somewhere builds your projects for you when you commit changes. The idea being you script up the steps you need doing and let the server do all the hard work. I’ve used this on many occasions. Any time I find myself doing the same job more that twice, I’ll think about scripting it or getting a machine to do the job.

Now there are allot of CI systems out there, Team City, Jenkins, TFS to name a few. However this post is going to concentrate on the free offering of Travis CI. The reasons for this are, firstly its free, secondly it offers Mac hosts. Having a Mac host in the cloud for a CI system is quite a unusual thing.

Setup Travis.

Signing up for travis is quite simple. Head over to https://travis-ci.org and Signup with your Git hub account. It will ask you which repositories it needs access to. The process is really simple. Note that https://travis-ci.org is the open source free offering for projects which are..open source. Travis CI does offer paid for services for close source projects but the setup is pretty much the same.

Scripting your .travis.yaml

The core of Travis CI is that is looks for a very particular build script, `.travis.yaml`. If you have this file in the root of your repo, Travis CI will use it and run the build. It can work without the script, but I prefer to script things since it gives you more control.


language: csharp
solution: VectorRumble.Shared.sln
os:
- osx
env:
global:
- Configuration=Release

This snippet tells Travis CI that we are a C# project (this is MonoGame right 😉 ). We also define the name of the Solution we are going to build. The next step is to define the operating systems we are running on. In this case osx. We also setup some global variables which can be used later.
Note they also support linux, and docker based images. So if they don’t have the exact system you want, a docker image might work.

The great thing is that Travis CI will automatically realise that we need Mono installed. Because we are using C# on mac, so that will be taken care off. It will grab the latest stable releases from Xamarin. By default Xcode is installed as well (on osx), so you can use this to build iOS apps too. But we need MonoGame..

So how do we install that? Well we add a new section called “install”


install:
- wget -O MonoGame.pkg "http://teamcity.monogame.net/guestAuth/repository/download/MonoGame_PackageMacAndLinux/.lastSuccessful/MonoGame.pkg?branch_MonoGame=%3Cdefault%3E"
- sudo installer -pkg "MonoGame.pkg" -target /

what this code does is download the latest development branch from the MonoGame CI system. And then install it! Very cool.
The end result will be your Mac bot will have all the Pipeline tools an assemblies installed and ready to use.
Now if you want to just get the latest stable release you can use the following url.


http://teamcity.monogame.net/guestAuth/repository/download/MonoGame_PackageMacAndLinux/.lastSuccessful/MonoGame.pkg?branch_MonoGame=%3Cmaster%3E

Note the “%3E” characters are required. The next step is to build the project. So we add the following.


script:
- nuget restore VectorRumble.Shared.sln
- msbuild VectorRumble.Desktop/VectorRumble.Desktop.csproj /p:Configuration=Release /v:d /t:Build

First thing is to do a Nuget restore. For this project I am using the Remote Effect Pipeline extension from my previous post, so I need to make sure that package is in place. But if you are using MonoGame Nuget package you will need to do this as well.
Next step is to build the app, as normal. Rather than building the entire solution I am building a specific project. But if you want to build both iOS, DesktopGL etc you can setup a configuration in the Solution which just builds those projects.

Thats it!

Not much too it is there. If you have an open source project (or a closed source one) there is absolutely no reason not to setup some sort of Continuous Integration.

In the past it might seem that Windows users of MonoGame get all the cool stuff, and Mac / Linux users are left out in the cold. To be honest for a while that was true, but the great news is that more recently that has begun to change. On going community efforts have resulted in both MacOS and Linux installers which will download and install templates into the Xamarin Studio and MonoDevelop. They also install the Pipeline tool which is a GUI you can use to build your content for your game.

All that was great, but again Windows has something that Mac and Linux developers just didn’t have access to. Automatic Content Building, this is where you just include the .mgcb file in your project, set its Build Action to “MonoGameContentReference” and providing you created the project via one of the templates it would “just work”. Your .xnb files would appear as if by magic in your Output Directory without all that messy manual linking of .xnbs.

So how does it work.. Well to fully understand we need to dig into MSBuild a bit 🙂 I know recently I’ve been talking about MSBuild allot but thats because in my day job (@Xamarin) I’m dealing with it ALLOT! So its topical from my point of view 😉

So if you dig into your csproj which was created in Visual Studio via one of the MonoGame templates  you will see a number of things. The first is a <MonoGamePlatform> element. This element is used later to tell the MGCB (MonoGame Content Builder) which platform it needs to build for.  Next up is the <MonoGameContentReference> element which will contain a link to the .mgcb file.. This again is used later to tell MGCB which files to build. Note that you are not just limited to one of these. If you have multiple assets and different resolutions (e.g @2x stuff for iOS) you can have a separate .mgcb file for those and include that in your project. The system will collect ALL the outputs (just make sure they build into different intermediate directories).

The last piece of this system is the “MonoGame.Content.Builder.targets” file. This is the core of the system and you should be able to see the Import near the bottom of your .csproj. This .targets file is responsible for going through ALL the MonoGameContentReference Items in the csproj and calling MGCB.exe for each of them to build the content, it will also pass

/platform:$(MonoGamePlatform)

to the .exe to that it will build the assets for the correct platform. This is all done in the BeforeBuild msbuild event, so it will happen before the code is even built, just like the old XNA content references used to do. But this time you don’t need to do any fiddling to get this to work on a command line, it will just work. Now calling an .exe on a build in a .targets isn’t exactly magic, the magic bit is right here


<Target Name="BuildContent" DependsOnTargets="Prepare;RunContentBuilder"
Outputs="%(ExtraContent.RecursiveDir)%(ExtraContent.Filename)%(ExtraContent.Extension)">
<CreateItem Include="$(ParentOutputDir)\%(ExtraContent.RecursiveDir)%(ExtraContent.Filename)%(ExtraContent.Extension)"
AdditionalMetadata="Link=$(PlatformResourcePrefix)$(ContentRootDirectory)\%(ExtraContent.RecursiveDir)%(ExtraContent.Filename)%(ExtraContent.Extension);CopyToOutputDirectory=PreserveNewest"
Condition="'%(ExtraContent.Filename)' != ''">
<Output TaskParameter="Include" ItemName="Content" Condition="'$(MonoGamePlatform)' != 'Android' And '$(MonoGamePlatform)' != 'iOS' And '$(MonoGamePlatform)' != 'MacOSX'" />
<Output TaskParameter="Include" ItemName="BundleResource" Condition="'$(MonoGamePlatform)' == 'MacOSX' Or '$(MonoGamePlatform)' == 'iOS'" />
<Output TaskParameter="Include" ItemName="AndroidAsset" Condition="'$(MonoGamePlatform)' == 'Android'" />
</CreateItem>
</Target>

This part is responsible for adding the resulting .xnb files to the appropriate ItemGroup for the platform that we are targeting. So in the case of a Desktop build like Windows, Linix we use Content. For iOS and Mac we use BundleResource and for Android we use AndroidAsset. Because we are doing this just before the Build process, when those target platforms actually build the content later they will pick up the items we added in addition to any other items that the projects themselves included.

Now the really interesting bit is that code above was not how it originally looked.. The problem with the old code was it didn’t work with xbuild, which is what is used on Mac and Linux. So it just wouldn’t work. But now the entire .targets file will run quite happily on Mac and Linux and have intact been included in the latest unstable installers. So if you want to try it out go and download the latest development installers and give it a go.

If you have an existing project and you want to upgrade to use the new content pipeline system you will need to do the following

  1. Open your Application .csproj in an Editor.
  2. In the first <PropertyGroup> section add <MonoGamePlatform>$(Platform)</MonoGamePlatform>
    where $(platform) is the system you are targeting e.g Windows, iOS, Android.
  3. Add the following lines right underneath the <MonoGamePlatform /> element <MonoGameInstallDirectory Condition="'$(OS)' != 'Unix' ">$(MSBuildProgramFiles32)</MonoGameInstallDirectory>
    <MonoGameInstallDirectory Condition="'$(OS)' == 'Unix' ">$(MSBuildExtensionsPath)</MonoGameInstallDirectory>
  4. Find the <Import/> element for the CSharp (or FSharp) targets and underneath add <Import Project="$(MSBuildExtensionsPath)\MonoGame\v3.0\MonoGame.Content.Builder.targets" />

Now providing you have the latest development release this should all work. So if you have an old project go ahead and give it a try, its well worth it 🙂

One of the many requests I see from customers of Xamarin.Android is the ability to “run a script” after the package has been built and signed, or do some process before the build process. Allot of people end up trying to use the CustomCommands which are available in the IDE to do this work, and shy away from getting down and dirty with MSBuild. The thing is, sometimes MSBuild is the only way to get certain things done.

Most people already know about the “SignAndroidPackage” target which is available on Xamarin.Android projects. If not basically you can use this target to create a Signed package, most people would use this on a Continuous Integration (CI) server. You can use it like so

  msbuild MyProject.csproj /t:SignAndroidPackage

Note that this target is ONLY available on the .csproj NOT the .sln but that is ok because MSBuild does its just of resolving projects etc and it should build fine. You can also pass in addition parameters to define your KeyStore etc, more information can be found in the excellent documentation on the subject.

Now we want to run a script after this has been done, Custom Commands can’t be used as they run at the wrong time in the build process, so what do we do? This is where MSBuild targets come in. What we need to do is add a custom MSBuild target which hooks into the build process After the SignAndroidPackage has run.

First thing to do is open the .csproj of your application in your favourite editor and find the last </Project> element right at the bottom of the file. Next we define a new target just above the </Project> element like so

<Target Name="AfterAndroidSignPackage" AfterTargets="SignAndroidPackage">
   <Message Text="AfterSignAndroidPackage Target Ran" />
</Target>

Save the .csproj and then run the command

  msbuild MyProject.csproj /t:SignAndroidPackage

and you should see the text “AfterSignAndroidPackage Target Ran” in the build output.  If not add a /v:d argument to the command line, which switches the MSBuild output to verbose mode. The key thing here is the Target defines the “AfterTargets” attribute which tells MSBuild when to run this script. In this case after the SignAndroidPackage, there is also a “BeforeTargets” attribute which will run a target.. you guessed it.. before the target(s) listed in the attribute.

Now we have a target that will run after the package has been signed. Next this to do is to get it to run our script, for this we can use the MSBuild Exec task. One thing to note is that its more than likely that some of your developers are running on Mac as well as Windows so we need to make sure that any command we do run will work on both systems.

Fortunately we have a way of conditionally executing targets using the Condition attribute. This means we can do something like

<Exec Condition=" '$(OS)' != 'Unix' " Command="dir" />
<Exec Condition=" '$(OS)' == 'Unix' " Command="ls" />

this allows us to run separate commands based on the OS we are running (in this case a directory listing). As you can see we can easily extend this to run a batch file or shell script. So the final target would be

<Target Name="AfterAndroidSignPackage" AfterTargets="SignAndroidPackage">
  <Exec Condition=" '$(OS)' != 'Unix' " Command="dir" />
  <Exec Condition=" '$(OS)' == 'Unix' " Command="ls" />
</Target>

MSBuild targets can also define Inputs and Outputs which will allow the build engine to decide if it needs to run the target at all. That is something that you can read up on if you are interested in learning more, but for now if you just want to “run a script after the apk is built” this is all you need.