Showing Wall-E infront of a yellow VW bus with taxi stripes

I have taken quite a liking into Fabulous - a wrapper around Xamarin.Forms allowing you to write functional UIs with F#. When first looking at the project I noticed that is was being built on AppVeyor and Travis. I asked myself: Why use two CI Systems for compiling one project? After some further digging I found out that there are no hosted macOS Agent on AppVeyor. Travis on the other hand did come with agents for Windows and macOS but did not have the Xamarin Toolchain installed on the agents. Installing the Xamarin Toolchain on every run lead to a build time of over 30 minutes. Since Azure DevOps supports building on Windows and macOS I thought I would give it a go and setup a Pipeline to build Fabulous - I mean how hard can it be? Well hard enough to write a blog post to sum up the steps to get over the pitfalls

TLDR: How to run your FAKE scripts on Azure DevOps

Fabulous uses FAKE to execute the build, tests and create the NuGet packages. FAKE is a power full tool for writing build scripts. FAKE is also a .Net Core CLI tool which is designed for being installed and executed from the command line, so it should be a great fit for running on any build server.

Installing FAKE

Azure DevOps build agents do not come with FAKE preinstalled. Since FAKE is a .Net Core CLI tool this is no problem. The following command should solve this issue:

dotnet tool install fake-cli -g

Unfortunately executing FAKE after installation fails. This is because the installation directory on the Azure DevOps build agents differs from the standard installation location of .Net Core - Why? you ask, well the answer given is security. On Windows we can circumvent this fact by installing FAKE into the Workspace directory:

dotnet tool install fake-cli --tool-path .

Under macOS (and Linux) this approach still fails. The suggested solution is to set DOTNET_ROOT. I ended up with the following lines to be executed on the macOS agent:

export DOTNET_ROOT=$HOME/.dotnet/
export PATH=$PATH:$HOME/.dotnet/tools:/Library/Frameworks/Mono.framework/Versions/Current/Commands
dotnet tool install fake-cli -g

On Linux the approach had to adopted again - go figure. I ended up with these lines:

export PATH=$PATH:$HOME/.dotnet/tools:/Library/Frameworks/Mono.framework/Versions/Current/Commands
dotnet tool install fake-cli -g

Now you should be able to run your FAKE script on Azure DevOps

Using NuGetFallbackDirectory

This part is not directly related to FAKE but is something I stumbled over while running on Azure DevOps. One test script was referencing the NuGet packages via the global NuGetFallbackDirectory and was looking for them under the default location. Under macOS the location is in the users home directory, so adopting the path as follows did the trick:

let tfsEnvironment = Environment.GetEnvironmentVariable("TF_BUILD")
if (String.IsNullOrEmpty(tfsEnvironment)) then
    let homepath = Environment.GetEnvironmentVariable("HOME")
    Path.Combine(homepath, ".dotnet/sdk/NuGetFallbackFolder")

Note that the variable TF_BUILD is expected to only be set on TFS/VSTS/Azure DevOps. This will allow the script to fall back to the default location should it be executed on a developers machine.

But why even bother?

What is the motivation of migrating from a working CI to another? Are you doing because you are a Microsoft MVP?

These were questions I got when talking with colleagues about my endeavors to build Fabulous on Azure DevOps. I think AppVeyor and Travis are great tools and they have shown that they are up to the task building and testing Fabulous. Other than because I was curious how hard it could be, there were two aspects why I wanted to try to migrate the build to Azure DevOps:

  1. Merging the builds, having two places doing one thing always comes with overhead.
  2. The other one was seeing how much the build time would be reduced by not having to install Xamarin.

So here is a comparison between the build times before and after:

CI PlatformAgent OSBuildTestTime (minutes)
Azure DevOpsmacOS~13-14
Azure DevOpsWindows~6

Now to keep in mind, the build on macOS and Windows are ran in parallel. So in case of AppVeyor and Travis the resulting build time would be 30-32 minutes. With Azure DevOps this can be brought down to 13-14 minutes.

I would argue that merging two build scripts into one and cutting build time roughly in half are good arguments for why Azure DevOps seems to be a better fit for Fabulous. Then again there was some pain on getting the .Net CLI tools running, which I hope the Azure DevOps team will solve in the future - being products from the same company and all cough

Another aspect was having a build on Linux in the future, since Fabulous supports GTK since 0.30 it would be nice to also compile it on Linux. At the time of writing there were still a few kinks in the build process of Fabulous, but nothing that can't be solved in the future.


Thank you Timothé Larivière and Stuart Lang for all the tips and hints along the way


Title Image showing a factory

Azure DevOps, formerly known as Visual Studio Team Services or VSTS for short, allows you to create automated release pipelines for all different kind of projects. One of the nice things is that you get free build time for opensource projects. So why not give it a spin and look if I can set up the build pipeline for my open source project PureLayout.Net. The PureLayout.Net library is a wrapper of the PureLayout iOS library written in Objective-C which allows you to quickly layout your UI in code. So it differs a bit from your standard Xamarin project as it involves the step of building the project, creating the bindings to C# and then packaging all up in a NuGet package. Since this is an iOS-only project, we will, of course, have to build this on a Mac.

Choosing a build agent

Good thing then that you Azure DevOps (could we all agree on ADO for this in the future? ) provides a Mac build agent hosted on Azure. Now the question left is, will the hosted Mac provide all the tooling that we need? If no, we would have to fall back onto the option of creating our own Mac build agent, i.e. renting it from a third party. For PureLayout.Net we require the following tools to be installed:

Luckily on GitHub the agents OS and tools are all listed. So we see that there is the Xamarin Toolchain and XCode. Unfortunately, objective-sharpie is not installed and while this is a bit of set back what we see installed on the hosted macOS agent is homebrew.


Homebrew is THE package manager for macOS, and it allows opensource projects to provide their tools as packages. A quick search on the interwebs shows there is a keg for objective-sharpie - yes they are going all the way on that brewing analogy. So we could install the tool while before we run the build. So let's go ahead, and set up the build with the hosted macOS agent from ADO.

Hosted vs setting up your Build Agent: There are a few things to consider when choosing between setting up an agent on your own and then register it to ADO or opting for a hosted build agent. While it always depends on your situation. Generally speaking, setting up your build agent brings you more control over the setup and installed tools. You decide when updates happen and can give you have the option of having databases/files and so forth pre-setup and ready for reuse. On the other hand, you must maintain your agent and while this might work okay at first. Consider having to maintain multiple agents. How do you ensure that the agents are all equally setup? MInor differences in the setup could lead to an unstable build infrastructure which is something no one wants. If you do not have any requirement that prevents you from using hosted agents, I would recommend going the easy route and using hosted agents. Hosted agents come with the added benefit of being able to adjust the number of agents according to your workload - if you are a consultancy, this can be a huge bonus since project/build load might vary from time to time.

Configuring the build

The browser is all you need for setting up your build configurations or pipelines as ADO calls them these days. While ADO does offer templates for specific builds such as Xamarin.Android, there is no template for Xamarin.iOS wrapper projects - other than the blank template that is. The first step is to connect the repository which in case of PureLayout.Net is on GitHub. For the first time, one has to link GitHub with the ADO account by following the instructions.

When building PureLayout.Net manually. The the following steps create a new version of PureLayout.Net:

  1. Execute make
  2. Build the solution
  3. Pack the artefact into a NuGet package
  4. Enjoy the new NuGet package

The Makefile creates the native binary (and generate the required wrapping code). To create the wrapper, we require objective sharpie which we can install via Homebrew. To execute all the required commands in ADO a Command Line build step with the following instructions is used:

echo install objective sharpie
brew cask install objectivesharpie
echo Performing make

If you ever want to go down this rabbit hole of creating your wrapper project. Be sure to check out this article by Sam Debruyn - after reading this post of course .

Next up is building the solution of the wrapper project, which can be done with an MSBuild step and defining the path to the csproj file: PureLayout.Binding/PureLayout.Binding/PureLayout.Net.csproj - there is even a handy repo browser. Under Configuration, you can set the build configuration to Release, but instead of hard coding it, consider using the environment variable $(BuildConfiguration). More about environment variables in a bit.


Next up is packing the compiled output into a NuGet package. By adding a NuGet build step, setting the Command to pack, the path to the csproj file and the output folder to the ADO environment variable $(Build.ArtifactStagingDirectory).

The final step is to publish the artefacts, i.e. the NuGet package. There is again a standard build step to use here called Publish Build Artifacts.

Are you still wondering about those environment variables above and how they come together? Environment variables are a great way to reduce duplicate hard written configurations in build steps. There are two kinds of environment variables those that you can define yourself and those that are predefined.

For creation purposes or if you are exploring what ADO has to offer the web UI is a great choice. However, if you talk with the grown-up DevOps engineers, they usually voice some concern over maintainability or the lack of sharing configurable definitions. One of the ways to overcome these issues is to define the entire build steps in a YAML file.

Configuring the build with YAML

YAML Ain't Markup Language or YAML for short is the file format supported by ADO to store the build configuration alongside your code. While versioning also is done by ADO whenever you change the build steps, in the UI. YAML definitions further allow you to create templates for other similar projects - which can be real time savers.

When selecting the build agent, we can view the YAML generated out of the build steps defined via the web UI.


In the projects root folder, we can now create a file, e.g. builddefinition.yaml which we can then check in to Git. Once the Git Repo contains the YAML build configuration, we can create a new pipeline based on the build config. Unfortunately, there is currently no way to use the visual designer and YAML configuration in the same pipeline.

Though not automatically exported the build trigger can also be set in the YAML file. According to the docs - which include some inspiring samples this is the resulting block for PureLayout.Net:

  batch: true
    - master
    - README.md
    - master
    - README.md

The above configuration ensures that all pushes to master are triggering a build. The pr section is in regards to Pull Requests. Note the neat trick you can do with paths which allows you to prevent triggering a build should only the README.md or similar change.

Lesson learnt: Any configuration changes regarding git you still have to do in the visual designer. In the case of PureLayout.Net, it was ensuring that git submodules are checked out.

Is YAML better than the visual ADO web-based editor? Well, it depends. If you are just getting started with setting up automated pipelines, the visual editor allows for faster results. It also provides a better experience when discovering what is available on ADO. However, if you are looking for a way to share configurations, store your build definition alongside your code and know what you want to get done. YAML probably is the better solution for you.

You can see the full YAML configuration for PureLayout.Net on here.


When I initially set out to automate the build for PureLayout.Net, it was because I wanted to reduce the hassle for me to ensure that commits or PRs would not break anything. With the steps above the manual steps left are testing the NuGet package, adjust some metadata if needed and then deploy it to NuGet.org. Since it is a visual library, I feel comfortable manually "testing" the result - which means as much as looking at a screen and checking the layout with my own eyes. The manual release steps are also totally fine with me.

Nevertheless, you could automate a lot more. For instance, push the artefacts automatically to Azure Artifacts or other places, adjust the release metadata pending on various parameters or setting the Version number on the fly. Another possible area to automate would be the testing side and and and... For me I left it here, well not entirely, I still published the build status to GitHub - and this blog :

Build Status