Monday, April 4, 2016

Teamcity and HockeyApp; Delivering your iOS app

TeamCity and Hockey App are awesome tools for creating a daily (or continuous) build and distribution of your iOS app. Most developers will use Xcode to create an ad hoc distribution but the Xcode command line tools are more convenient for this purpose.

For a recent project I have been using Bitbucket, TeamCity and HockeyApp to create a canary build. In addition it is using CocoaPods for dependency management. This approach works well but it takes some time to figure all things out. If something goes wrong it is difficult to find out what went wrong although in most cases somewhere in the large logfiles a clue can be found.

Building an IPA file successfully does not guarantee the signing was successful as well. So the logfile says yes but your testers or early adopters say No. For this reason it is always smart to verify if the app can be installed on a device using Hockey app.

I will tell you what I did to make the four of them cooperate and how you can avoid some of the mistakes that I have made. It goes beyond the purpose of this blog to tell you everything about TeamCity or HockeyApp, so for now I assume you already have installed TeamCity and that you do have a HockeyApp account.

Prerequisites

Obtain a distribution certificate

Use the machine (your own MacBook or a dedicated build server) where TeamCity is running to create a distribution certificate in Apples developer portal.

Create app ID, add devices and create an ad hoc provision profile

Unless you have already done so you need to create a new app ID, add some devices and create an ad hoc provision profile, just like you are used to do when creating an ad hoc distribution on your development machine.

Let's create a little script that takes care of dependencies in the Pod file, building an archive and creating an IPA file, using a provision profile file. At the end of the script we will upload the app to Hockey app and clean the archive and IPA file. It is that easy. Well, once you know what is going on, things are easy...

Pod install

If you are using CocoaPods to manage the dependencies in your project (and well, of course you do !) then you need to update them on your build server as well. That makes sense. So the first of the script goes like this:

pod install

Note: If CocoaPods is not installed on your build server then you need to install the gem first.

sudo gem install cocoapods

Build the archive

The command below is what you need for building the archive for a workspace, as is the case if you are using CocoaPods.

Here we will build the sample workspace using the sample scheme. It will create a sample_ad_hoc XCode archive file in the work folder. It is the same thing as when you choose Archive from the menu in the Xcode IDE.

xcodebuild -workspace sample.xcworkspace  
-scheme sample clean archive 
-archivePath /path to your TeamCity work folder/sample_ad_hoc.xcarchive

If you have no clue about schemes (or targets) in your project or workspace. you could use the list command to find out. Execute this command in the directory where your workspace or project reside.

xcodebuild -list

- or -

xcodebuild -workspace sample.xcworkspace -list

The output wil be something like this
Information about project "sample":
    Targets:
        sample
        sampleTests
        sampleUITests

    Build Configurations:
        Debug
        Release

    If no build configuration is specified and -scheme is not 
    passed then "Release" is used.

    Schemes:
        sample
        sample-cal

Export the archive

If everything went well an archive file has been created. The next step is to create a distributable IPA file from it. For this we need to have a valid provision profile file that you have created in the developer portal previously.

xcrun xcodebuild -exportArchive -exportPath build/ 
 -archivePath "path to work folder/sample_ad_hoc.xcarchive" 
 exportOptionsPlist exportOptions.plist  
 -exportProvisioningProfile "name of the provisioning profile"

Here you need the provision profile file that you have download from the Apple developer portal. It probably will be convenient to commit the provision profile file to the repository as well.You can also download the provision profile in a different directory on the build server and include the path to it.

Note: The name of the provision profile is the name as you have typed it at the Apple developer portal when creating it (or as it appears in Xcode -if you would have downloaded and installed the provision profile by double clicking on it-). Other than you might have expected it is not the name of the file.

Distribute the IPA file

Verify if a build.ipa file exists in the work folder. If it does you can distribute it to HockeyApp. It requires an app token from HockeyApp and a Hockey app Id. Make sure the app Id is configured for uploading purposes.

Using the curl command you can easily upload the IPA file you have just created. If this is for a daily (or continuous) build you probably do not want to notify all users each time a new version is available. You can use notify=0 for that.

HockeyApp obtains the version Id from the IPA file so you might want to create an auto incremental script later.

curl -F "status=2" -F "notify=0" -F 
 "ipa=@/build.ipa" -H 
 "X-HockeyAppToken:" 
 https://rink.hockeyapp.net/api/2/apps//app_versions/upload

Clean up

Very important but easy to forget is to clean up things, just to make sure that Hockey App fails if the build fails as well. Include this in your script to remove both the IPA and the archive:

rm build.ipa
rm -rf //sample_ad_hoc.xcarchive

Conclusion

Finally your script looks more or less like this:

pod install

xcodebuild -workspace sample.xcworkspace  
 -scheme sample clean archive 
 -archivePath /path to your TeamCity work folder/sample_ad_hoc.xcarchive

xcrun xcodebuild -exportArchive -exportPath build/ 
 -archivePath "path to work folder/sample_ad_hoc.xcarchive" 
 exportOptionsPlist exportOptions.plist 
 -exportProvisioningProfile "name of the provisioning profile"

curl -F "status=2" -F "notify=0" -F 
 "ipa=@/build.ipa" 
 -H "X-HockeyAppToken:your hockey app app token" 
 https://rink.hockeyapp.net/api/2/apps/
  your hockey app app id/app_versions/upload

rm build.ipa

rm -rf //sample_ad_hoc.xcarchive

You can store the script in a file and call it in a single build step or you can create multiple build steps. With some modifications you can use also the script for Jenkins instead of Teamcity.

These are just the basics for a CI/CD flow and there many things that you could include with a script or additional build steps. What about automated unit testing? Or running Cucumber tests on your build server? That would be fun too!

Further reading