- Added steps to import the app’s Distribution Identity into the Xcode Server keychain
- Fixed problem with Post-Action archive script that would post n-1 versions of the build to TestFlight
This kind of post represents why I started this blog: I spent days banging my head against the wall trying to get this all working, and no one else should have to endure the same.
This post outlines the major steps and gotchas surrounding how to:
- Create a Bot that fetches source from a 3rd party Git repository
- Builds, tests, and archives a project successfully
- Automagically post an fresh build to TestFlight for automatic distribution every day to testers (this is as cool as it sounds)
Here are some prerequisites to consider:
- You have OS X 10.9 with OS X Server installed.
- I used a dedicated Mac Mini; so you may need to translate accordingly if you are using your local machine (though I don’t really recommend it).
- You are familiar enough with provisioning profiles to be dangerous.
- You have a working 3rd part git repository set up for your workspace. I’m using Assembla, but it should all be about the same with github, etc.
- You have a TestFlight account and you know how to use it.
I will be using our Unwired DocLink product, a universal iOS content distribution app, as the model for this post.
As a disclaimer: this is all working for me… I did a lot of head banging and tweaking things to get stuff to work, most if not all of which is described here. Some stuff should have just “worked”, but didn’t at the time of this writing. I would like to keep this article updated if I missed something, so please post your comments below so I can update this article as needed.
And here we go!
Setting up Xcode
First up, you need to do some config with Xcode.
Create a new Build Configuration
Before you create a new scheme, you may want to set up a new Build Configuration. Projects created with Xcode come ready out of the box with Debug and Release builds. I create a duplicate of Release and named it TestFlight. You do this in the Project info screen as shown here:
Create a new build configuration named TestFlight
Next you are going to want to configure your distribution provisioning profile for the build you just made. It is helpful to filter the list down using the search bar at the top. I am using an enterprise distribution certificate, but you can specify an AdHoc one here as well.
Setting the Provisioning Profile
Keep in mind you will need to make sure all of your other build settings are correct as well, including making sure the correct code signing identity is specified in the build settings as well.
Create a new Scheme
You need to create a scheme that your Bot will use. You could technically use your default one, but to keep things a little cleaner, I created a new one. Simply create a duplicate scheme of your working one using the gear in the lower-right of the Manage Schemes window.
Create a Duplicate Scheme
Name the scheme something like “<ProjectName>_CI”. I would avoid white space (unlike my example) because things get a little hairy with paths later and that is one less thing to worry about.
With the newly created Scheme, make sure to check the “Shared” checkbox.
Highlight your newly created scheme and click Edit…
In this window, everything is likely fine by default with the exception of the Archive build configuration. There are a few things to do there, which needs its own section!
Configuring the Archive Scheme Build Process
There are two things you need to do here: configure the proper Build Configuration and specify your TestFlight deployment script.
First, make sure the Archive build configuration is set to TestFlight; the configuration you had just created.
Archive Build Configuration
And now here is where things get fun! Click on “Post-actions”, and if nothing is in there, click the ‘+’ at the bottom to create a “New Run Shell Script” action.
In the resulting window, make sure to use “/bin/bash” as your Shell and select your product for the “Provide build settings from” pull down. From there, you are going to paste in the following script (kudos to Justin Miller at http://developmentseed.org/blog/2011/sep/02/automating-development-uploads-testflight-xcode/) and edit some of the values at the top of the script to match your environment.
Below is the script you should use and substitute your values as needed. An explanation of important aspects of it are explained afterwards.
Whew! There are a lot of little nuggets in there worth explaining:
- UPDATE: My original script used a “/Latest” symbolic link that the Bot would create, however this is created AFTER the integration has completed. Therefore, your builds that were uploaded to TestFlight were version-1. By using the ‘ls’ command, you effectively get the working script. Tangentially, the Archive.xcarchive.zip file does NOT exist at Post-Action time, but the folder does exist. The above script reflects both of these updates.
- Your signing identity is the name of the certificate / key in your Keychain. It should be an EXACT string match, as shown in my Keychain.
Distribution Identity Name
- You’ll notice some hard-coded paths. These are some of the paths used by the Xcode build server that don’t exist on your normal workstation. Some of these paths have been painfully crafted to make work after lots of head-banging.
- The /tmp folder is used as a working directory to do intermediary work. It is cleaned up every time a build is performed.
- This is all working as of the very first GA release of the Xcode Server; later releases may change paths that would require modification to the above script.
Make sure your build is sane
With the newly created scheme selected (next to the run and stop button in Xcode), select iOS Device (or any connected device) and go to the Product menu and select Archive.
Make sure the archive process works, because if it doesn’t work on your development station, it sure as hell isn’t going to work on the Xcode server :). The TestFlight upload process will fail because the specified directories don’t exist on your local machine (unless the Xcode server is on the same machine). However, Xcode should open organizer and a new TestFlight release build should be available as you would expect.
If so, commit your project changes to your git repository. The Xcode server will need all of those settings we just changed in the project file to work correctly.
If you get build errors, Google is your friend. There are simply too many build configuration problems to try explain here. Once you get it working, however, read on!
Prep the Xcode Server
If you haven’t already, make sure you have the Xcode Server running on your Mac OS X server. That set up is pretty straightforward.
Make sure you add your developer team(s), otherwise things won’t work.
Xcode Server Config
There is no need to create any repositories here! That will be handled when you create the bot on your workstation.
However, do make sure you allow HTTPS access to repositories:
Installing Distribution Provisioning Profiles
In theory, at this point you should be done. However, at the time of this writing, essential distribution provisioning profiles are not installed when you configure teams in Xcode server. This means your Xcode server has no way of properly signing your app, resulting in build errors time and time again during the archive phase.
This means you have to manually download the .mobileprovision file(s) and move it/them to the appropriate location on the server. The good news is this is pretty easy.
Using Safari on the OS X server, access your iOS developer portal and download the necessary Ad Hoc or Enterprise .mobileprovision files.
Copy each of them to the appropriate location on your OS X Server so the Xcode server can find them:
# sudo cp <theProfileName>.mobileprovision /Library/Server/Xcode/Data/ProvisioningProfiles/
You have to run the copy using sudo, because of tight permissions on the folder. That said, there is no need to change permissions.
Installing Your Distribution Identity into the Keychain
Another thing the Xcode server doesn’t (can’t?) do is post your app’s iPhone Distribution identity (certificate and key) into the keychain of the Xcode server itself. Remember that this Distribution Certificate (aka Signing Identity) is tied to your Distribution Provisioning Profile. Without this, your app will not compile and you’ll get an error like this:
NO codesigning identities found (i.e. certificate and private key pairs that match your provisioning profile specified)...
With Xcode 5, the Distribution Identity is created through Xcode automatically when you are the agent for the account and a Distribution Certificate doesn’t yet exist. If one already exists, and it isn’t in your keychain already (see below), you may have to either a) manually download the certificate from the iOS Developers portal or b) hunt down the person who has the identity and have them send it to you.
All that said, here are the steps to extract the Distribution Identity from your development machine’s keychain (assuming it was created there), and transported to your Xcode server.
First up, open the Keychain app on your development computer. Select the login keychain and then click My Certificates under categories. In the Search field, type “iPhone Distribution” and you will see your distribution certificates float to the top:
Locating your Distribution Certs
Now a key (pun kinda intended) thing here: make sure you have the little arrow to the left of the name of the certificate. If you do NOT have it, then you DO NOT have the private key for the distribution certificate and things are not going to work (this means you have the certificate, but not the identity). You will need to find the person/computer that DOES have this private key and perform this task from there.
Now, with the arrow opened or closed (I opened it to show you the key, your may be named different) right-click the certificate row and select Export. From there, provide a strong password and save the resulting .p12 file somewhere on your hard drive.
Export the Identity
Note that sometimes the Export option doesn’t show up (at least in OS X prior to 10.9, it may have been fixed). Simply toggle the arrow, click on something else and back, etc. and it should then show up).
At this point, the .p12 should have been saved where you specified. Now transfer it to the Xcode server using your file transfer method of choice: SSH, AirDrop, FTP, GOPHER… whatever floats your boat.
On the Xcode server, locate the transferred .p12 file and drag-n-drop it onto of the Keychain Access app (/Applications/Utilities). You will be presented with a dialog to import the certificate. Select the system keychain then click Add. Provide the strong password you created when exporting the .p12 file, and if all goes well, you will see the identity in System -> My Certificates.
Import the Identity
Guess what, your Xcode Server now has what it needs to build archives for your app!
Creating the Bot
Alright, now this is where things are a little weirder than you would expect them to be.
In my case, I use SSH to access my git repository. However, I cannot for the life of me get Bots configured with SSH to properly pull from the repository. It may be an Assembla thing, but despite my best efforts, I cannot get it working. Instead, I use HTTPS (with username and password) to access the source.
Now here is a weird thing (that may be fixed in a later release). You can only create a Bot using the same git mechanism you have configured for your current workspace! This means that since I am using SSH to access my repo, the only auth form I can use when creating a bot is with SSH (there is no option to switch to HTTPS).
If you can get SSH authentication to work properly on your Xcode server with the specified repo, please share! Otherwise, I am going to assume you will do this HTTPS workaround like I did.
So, how do you use HTTPS? Checkout your project into another folder using HTTPS. You only need to use this for the bot setup, then you can delete this copy from your development machine because the server will now have all it needs to properly authenticate.
In my case, I closed out of my DocLink workspace and basically checked out the same workspace using HTTPS instead of SSH on a different folder on my HD. You can use the Check Out option within Xcode (and create the Repository entry via HTTPS there), or you can checkout via command line. Either way, when you are done you should be looking at an identical copy of your workspace, only using HTTPS with your source control.
Great, now lets create that bot!
The Bot Wizard
Go to the Product Menu and select Create Bot…
In there, you are going to want to make sure to select the new scheme you had made. If it is complaining about not having the shared scheme, make sure you had checked the “Shared” checkbox in the Manage Schemes window and committed your changes to the git repo.
Your Xcode server should appear in the Server list, or you may need to create the server entry first from the menu.
Check the Integrate Immediately so a test run can complete and we can see what is going on.
Initial Bot Config
You may be prompted for your git credentials next (this may be out of order; I’m not going to break my build for the sake of this post, sorry :). When you are, complete the form as appropriate:
Assembla Repo Config and Auth
You can schedule the bot to run whenever you’d like, but I would recommend once a day. The reason being is that you can quickly end up with TONS of TestFlight builds if you have the bot run on every commit, making it somewhat challenging. I run at 3:00am daily, so testers have a new version to work with in the morning.
Bot Schedule Config
Now for another painful lesson that will probably be fixed in a later release: there may be multiple iOS simulators for a give form factor, but not all of them work… This turns into a bit of a guess gaming (you can change these settings after bot creation through the Xcode Server’s website), but rest assured, your tests won’t run if you specify this wrong. So, in short, do not select all iOS Simulators or Devices if you have duplicate entries when selecting Specified Devices. Below is the screenshot of the multiple entries. It seems like the copies lower in the list are the ones that work, though they are shown differently in the Bots Settings via the Xcode server website, so I can’t say for sure.
On the next screen, set up notification options to your preference. Then click Create Bot.
If all goes well, you get a nice big green checkmark! Bot created!
Verifying the Integration
Now we’ll want to make sure things worked alright (if you are lucky, the will!)
Use the Xcode Server website. Usually it is just your machines root URL (e.g. http://ci.unwiredrev.com for our machine). You will see a screen like this (though maybe not with everything because you may not have run successfully before):
Xcode Bots Webpage
It should show “Getting Sources” or “Integrating”.
Clicking on a Bot’s name will reveal details. After some time of things working well, you should see something like this:
Bot Status Details Screen
You can see where things were not working so well…
When an integration is done, it should show completed for the most recent integration, ideally without any errors.
The “Logs” tab is really useful, as it give full logging details for each integration. Our TestFlight script actually writes out to this log during integration, so any errors with the upload show show there. Below is an example of a successful TestFlight upload:
Build Logs with TestFlight Upload Success
I’ve found Sometimes builds will fail for no reason. Rebooting the server has fixed this a few times for me… This was during the beta period, and may not be a problem now.
You also may want to disable Testing and Analyzing while you are figuring this out (in Bot Settings) to save time on the integrations just to make sure the archive is posted to TF correctly.
I think that’s about it! I’ll update this post as I receive feedback, so please comment if this process has worked for you or not. Good luck!