Continuous integration (CI) is a proven method for improving software quality and reducing time and cost of software projects. Jenkins, the leading open source CI server, is a popular choice to achieve a continuous build of many different kinds of projects.
Equally important for streamlined development process is getting customer feedback early and often. This is a requirement for many modern software development paradigms (e.g. Scrum).
With Jenkins and Wireless App Distribution (a feature that has been available since iOS 4) it is possible to automatically publish the results of a continuous build as a customer-accessible artifact. The app is easily downloaded via the test device’s Safari web browser. We employed the Hockey framework to render the XML needed for the wireless app distribution, but you can also do this manually if you like.
This blog post will show the steps required to set up such a process, but assumes familiarity
with building, signing and viagra iOS applications.
Setting up Jenkins
We use Jenkins in master/slave mode and a Mac Mini as build slave. Setting up a Mac OS X build slave is no different than setting up a normal UNIX build slave (integration via SSH and public key authentication) and is not described here. If you are running Jenkins in non-distributed mode (master node only) on an OS X machine you obviously don’t need to set up a slave.
You will also need to install an up-to-date version of XCode on the slave, preferably the same version as your developers use.
I recommend creating a dedicated ‘jenkins’ user, this allows to keep Jenkins data and key material cleanly separated from the rest of the system.
Setting up Code Signing / Provisioning
If we want to create signed binaries that will actually run on a real device, we need to set up the keys and certificates for code signing. This process should be familiar to anyone with iOS development experience, you can read all about it on Apple’s iOS developer portal.
I recommend creating a separate developer profile (i.e. Apple ID) in the portal strictly for usage with Jenkins, but it’s optional.
You can either set up the developer key and certificate directly on the slave, or you can do it manually. I’ll show you how to do the latter step, which will allow to set up multiple slaves identically and in a reproducible fashion.
You will need the following:
- Developer key and certificate
- Distribution key and certificate
The way I usually do this is I export them from Keychain Access as PKCS 12 containers (select key and certificate and choose “Export 2 items” from the context menu).
Now we have to add these to the user’s keychain. Login as user ‘jenkins’ and enter the following in the terminal.
security create-keychain -p jenkins appledev security default-keychain appledev security import developer_cert_and_key.p12 -k appledev -f pkcs12 -A -Psecurity import distribution_cert_and_key.p12 -k appledev -f pkcs12 -A -P
This creates a keychain named ‘appledev’ which contains only the keys and certificates used for code signing and provisioning iOS applications.
The other thing you need to do is copy the developer provisioning file into the /Users/jenkins/Library/MobileDevice/Provisioning Profiles, otherwise xcodebuild will fail with the following error message:
=== BUILD NATIVE TARGET Example OF PROJECT Example WITH CONFIGURATION Release === Check dependencies [BEROR]Code Sign error: a valid provisioning profile matching the application's Identifier 'com.noser.ios.Example' could not be found ** BUILD FAILED **
What I usually do is check in the provisioning profile and copy it as part of the build shell script. Again, read up on provisioning profiles in the developer portal, if you are unsure of what they represent.
Checking out and building the project
Follow the Jenkins manual for checking out your project (the directory containing the .xcodeproject folder). Then use xcodebuild to build the project from the command-line. Running xcodebuild in the directory should return something like this:
imac001:MyProject ahs$>xcodebuild -list Information about project "Example": Targets: Example ExampleTests Build Configurations: Debug Release If no build configuration is specified "Release" is used.
This is the output you get when creating an iOS application with a test project using the wizard in XCode 4. Now actually build the project in Release configuration (this is usually what you want to distribute to customers).
xcodebuild -target Example -configuration Release -sdk iphoneos
You will see a lot of output from the build, and hopefully a ** BUILD SUCCEEDED ** message at the end. If your build fails, check if you can build it in XCode, otherwise scan the output for possible error sources (take your time, the output is quite cluttered).
If everything works, include this line in the “Execute shell” portion of your project’s build configuration in Jenkins.
Creating a distributable app
To distribute your app to customers, you need to build an IPA file (iPhone application archive). You can also do this from the command-line, with the following “magic” line.
xcrun -verbose -log -sdk iphoneos PackageApplication "$WORKSPACE/Example/build/Release-iphoneos/Example.app" -o "$WORKSPACE/Example.ipa" --sign "iPhone Distribution: My Company" --embed "$WORKSPACE/provisioning/Example_AdHoc_Profile.mobileprovision"
Note that we are also embedding the AdHoc provisioning profile for the app. This file is managed through the developer portal and should be familiar to you. It contains a list of iPhone/iPod UUIDs (the customer’s test devices) and specifies that those devices may run your app. For this project, I included the provisioning profile along with the source code in the repository. You will also have to adapt the –sign “iPhone Distribution: My Company” to the common name of the distribution certificated issued to you by Apple.
Putting it all together, your Jenkins build instructions should be as follows:
security unlock-keychain -p jenkins /Users/jenkins/Library/Keychains/appledev xcodebuild -target Example -configuration Release -sdk iphoneos xcrun -verbose -log -sdk iphoneos PackageApplication "$WORKSPACE/Example/build/Release-iphoneos/Example.app" -o "$WORKSPACE/Example.ipa" --sign "iPhone Distribution: My Company" --embed "$WORKSPACE/provisioning/Example_AdHoc_Profile.mobileprovision"
You can test if the IPA file works by importing it into iTunes the old-fashioned way.
Setting up App Publishing
Once you have your IPA file, you need to add it as an artifact in Jenkins. Check the “Archive the artifacts” checkbox and specify **/*.ipa as wildcard expression.
The IPA file should now show up as a build artifact for successful builds.
In our setup, we have a separate, externally accessible server running the Hockey framework, so we have to get the IPA over there. We use the SCP publish plugin to achieve this.
Check the Hockey documentation about how to setup Hockey. Once you have it up and running with an IPA file and provisioning profile, just overwrite the IPA file with the one built by Jenkins.
Voila, your customer now has easy access to your bleeding-edge development tree.
Is there a way to specify user’s Library/Preference directory.
I am running Jenkins with user jenkins and when the build script is run, the follow error occcurs:
security default-keychain -s /Users/Shared/Jenkins/Home/Library/Keychains/appledev
error:
Will not set default: UID=300 does not own directory /Library/Preferences
security: SecKeychainSetDefault: Could not write to the file. It may have been opened with insufficient access privileges.
I can run the build script fine from the command line. This only occurs when running from the jenkins server. Why is it security default-keychain not looking in the jenkins user’s default ~/Library/Preferences
Excellent post. I am dealing with some of these issues as
well..