How to configure a Jenkins slave to build Xamarin.iOS projects

Adding a continuous integration workflow to your Xamarin project is extremely valuable in saving your team time while improving the overall quality of your app. This post details the step by step process I recently undertook to set up an automated build job for a Xamarin.iOS project using a Jenkins slave on Mac OS X. Admittedly, I'm not a Mac guy so if any of these steps can be improved upon please let me know in the comments.

Environment

The Jenkins master is a VM running Windows Server 2012 R2 with Jenkins server ver. 2.19.4

The slave agent runs on a Mac mini. The mini is a cost-effective solution for running a dedicated mac build host. There are also cloud options like MacinCloud if you want to go that route. No matter what you're running for a Mac the steps in this guide should apply all the same.

Mac mini (Late 2014) specs:

macOS Sierra v10.12.2

CPU: 2.6 GHz Intel Core i5

Ram: 16 GB DDR3

Initial Mac slave setup

There's a couple of things we need in place on our Mac to begin. Let's start by making a user account for our build to run under.

Create a new user and make it an Admin. I've called mine "macbuild". Set this user to automatically login. Guide here

Next, make sure the java SDK is installed. If it isn't go here to download the latest version (8u112 for me), install and restart your Mac before proceeding.

Configuring SSH

The windows box running our Jenkins master needs to talk to the Mac. Here's how I enabled SSH between the two to make that happen.

On the Mac, goto System Preferences -> Sharing and check "Remote Login". Leave "Administrators" selected under "Allow Access for".

On the Windows server, let's generate an SSH key for the account that Jenkins will run under.

Before we can do that we must ensure SSH is setup for Windows. I'm using Git so I downloaded and installed the Git client for Windows using this guide as a reference (be sure to include "Unix tools"). This package also installs ssh. Depending on your setup, this step may or may not apply.

Once we have git/ssh installed verify the SSH client is available. From a Git Bash window run:

$ ssh -v

If you see something like:

usage: ssh [-1246AaCfGgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec] [-D [bind_address:]port] [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11] [-i identity_file] [-J [user@]host[:port]] [-L address] [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port] [-Q query_option] [-R address] [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]] [user@]hostname [command]

You're good to go, otherwise, you'll need to repeat the steps above to install the git client/ssh or install directly using your favorite package manager.

To generate the ssh key for your jenkins user run:

$ ssh-keygen -t rsa -b 4096 -C "jenkinsbuildmaster"

You'll be prompted to specify a file location to save the key - I simply hit enter and left this blank to accept the default of user home directly/.ssh/id_rsa

Next, I hit enter again twice to leave the passphrase empty.

After that, the command completed successfully and notified me of the location where it saved my id and key files. Your identification has been saved in /c/Users/jenkinsbuildmaster/.ssh/id_rsa. Your public key has been saved in /c/Users/jenkinsbuildmaster/.ssh/id_rsa.pub.

At this point, I wanted to allow my ssh connection to use password-less login by creating an authorized_keys file on the slave.

To do that:

Within my Windows bash prompt, I ssh'd to the slave by specifying the remote admin account we created earlier and IP: $ ssh macbuild@192.168.10.112

You will know the ssh plumbing is successfully in place between the two if you are prompted for a password.

Once logged in, create a folder named ssh. macbuild-mini:~ macbuild$ mkdir /ssh

Make sure the new folder is accessible to this account only: macbuild-mini:~ macbuild$ chmod 700 ./.ssh

Leave that session right where it is and go open a new one.

In the new bash session window, navigate to the .ssh directory: cd /c/users/jenkinsbuildmaster/.ssh

Secure copy your id_rsa.pub key file to the remote server, providing the remote server's user account password when prompted. $ scp ./id_rsa.pub macbuild@10.30.10.112:/users/macbuild/.ssh

key file to the remote server, providing the remote server's user account password when prompted. When the copy is complete, the second session can be closed.

Back on the first session, drop into the .ssh directory: macbuild-mini:~ macbuild$ cd /users/macbuild/.ssh

List the files and we'll see the id_rsa.pub file we just created.

Next, add its contents to a file name authorized_keys: macbuild-mini:.ssh macbuild$ cat id_rsa.pub >> authorized_keys

Now logout of the ssh session but leave your terminal window open. macbuild-mini:.ssh macbuild$ logout

Try to connect again, this time we should not be prompted for a password - cool! $ ssh macbuild@192.168.10.112

At this point, the ssh link between our windows master and mac slave has been established.

Add the slave node

With SSH connectivity enabled, we can define our slave node on the master.

Ensure you're logged into your jenkins instance with an admin account then navigate to Manage Jenkins -> Manage Nodes and click New Node.

Give the node a name and select the Permanent Agent option.

Click Ok

The next screen is where we specify the vital details about our new node

The important values here are: # of executors - I set this to 4 to match the number of logical cpu cores in the mac. Remote root directory - I set this to /users/macbuild/home - ensure this folder location exists on your mac Usage Use this node as much as possible Launch method = Launch slave agents via SSH Selecting the SSH option will present a couple of additional options for Host and Credentials. Enter your mac's ip or hostname in the Host field. For Credentials we'll add some to use the ssh key we already generated. Click Add and choose the Jenkins credentials provider. Domain = Global Credentials Kind = SSH Username with private key Scope = System Username is the Mac user account we created earlier, in my case macbuild Finally, Private key = **From the Jenkins master ~/.ssh ** Leave everything as is and add the credentials.

Back on the main node details form, select our newly created credentials

That's it! Click Save to apply our changes.

At this point jenkins will try to contact our Mac and initialize the agent. To check the progress of that we can look at the log of our new node to ensure everything looks good.

If everything is set up properly we will see in the log that Jenkins was able to connect and properly initialize the slave.

[01/06/17 10:15:01] [SSH] Opening SSH connection to 192.168.10.112:22. [01/06/17 10:15:02] [SSH] Authentication successful. [01/06/17 10:15:02] [SSH] The remote users environment is: BASH=/bin/bash ... <===[JENKINS REMOTING CAPACITY]===>channel started Slave.jar version: 2.62.3 This is a Unix agent Evacuated stdout Agent successfully connected and online

Additional Mac slave setup

Now that we have our Mac configured successfully as a Jenkins node we're almost ready to put it to work. But before we can do that we must install all the required tools and SDKs required to build a Xamarin iOS project.

Here's the list of things I needed to install on my clean Mac Mini

jdk 8

Xcode (8.2.1 at present)

Xamarin Studio. Note if you're planning on building for Android on your mac you'll need to install Xamarin.Android.

Plugins

The lifeblood of Jenkins is its plugins. For this tutorial, we'll finish by building a simple test project on our slave without the need for any additional plugins. In a future post, we'll expand on this by building a more real-world project which will use several plugins to make life easier.

Creating a job

We're now ready to define the job on our master and get a project building. This will just be a simple smoke test we'll use to verify that everything is working properly on the Mac slave.

To create a job, in Jenkins:

Click New Item

Give your project a name, I called mine iOS Smoke Test

Select Freestyle project as the project type and hit OK

On the project configuration screen: Check Restrict where this project can be run and enter the name of your slave in Label Expression - macbuild-mini for me In Source Code Management enter your SCM settings. I'm using a git repo on BitBucket so I entered the url of my remote repository and immediately Jenkins complained with a "Failed to connect to repository" error because it couldn't connect. In my case this is fine because Jenkins is trying to connect to the repo under the context of the id it is running under on our master and it can't because that user account does not have ssh access to the repo. This may or may not apply to you but wanted to call it out as it can be ignored. We can ignore Build Triggers for now, we'll run our test job manually. Under Build, I added two build steps to actually build our project from the command line. These are both shell commands, first to restore required nuget packages for our solution: /Library/Frameworks/Mono.framework/Commands/nuget restore Mobile/XamarinForms_Test.sln Secondly, the command to actually compile and build our code: /Library/Frameworks/Mono.framework/Commands/xbuild Mobile/XamarinForms_Test/XamarinForms_Test.iOS/XamarinForms_Test.iOS.csproj After that, I clicked Save to create the job - remember we just want to create a very simple job to test at this point. In the next post, we will build a real xamarin ios project with proper triggers, signing and a deployment workflow.



Another moment of truth, we're ready to build the new job and see what happens.

I clicked Build Now and instantly watched as my job failed, such is life with Jenkins :)

Examining the console output I found an error indicating that Jenkins could not locate git on my Mac.

Caused by: java.io.IOException: Cannot run program "git.exe" (in directory "/users/macbuild/home/workspace/iOS Smoke Test"): error=2, No such file or directory

Google immediately lead me to this answer on stackoverflow.

I followed the answer below the accepted answer and needed to specify the git location on my Mac mini.

With that complete, I tried again and voila - my project built successfully!

Wrapping up

If you've made this it this far, congrats! There's certainly a lot of steps here and therefore many potential points of failure/frustration. But I believe the long-term investment in any CI/CD process is a worthwhile one.

In a future post, we'll expand on what we've set up today by adding some plugins, triggers and automated tests to build and deploy our Xamarin iOS project in a more real-world fashion.

Until then, happy coding!