We will have a look at a TestProject
to get an idea of how jenkins can be used to build, sign and deploy iOS projects. The whole process consists of 4 steps. Note that to deploy an app in this manner, an enterprise distribution certificate is required for each app.
This guide is how I have set up my enterprise builds for my apps that use cocoaPods
for dependencies, but most of the same principles apply for any iOS apps.
Because the project uses cocoaPods
, it means we have to build a workspace and have a build scheme set up.
Note: Apple Enterprise licenses can ONLY be used for builds internal to your company. I am not responsible for anything that may happen if you try to distribute apps outside your company using an enterprise license.
This guide may be a bit brief, don’t hesitate to ask me any questions. This is mostly for my own reference.
Step 1 - Poll SCM
- Dev builds poll the SCM looking for changes to the
master
branch at midnight every night. - If no changes have occurred, then the project is not built.
- If modifications have been made, the next step is executed.
Step 2 - Xcode build
Below is a screenshot from jenkins showing the fields used for the xcode plugin.
Clean before build
- we don’t want any cached compiled objects hanging around.Xcode Schema File
-TestProjectDev
- Because of the way we have our project set up, our top level item is a workspace (
TestProject.xcworkspace
), we have to build based on Schemes as Xcode does not support building a target from a workspace. TestProjectDev
specifies the scheme we want to build with.
- Because of the way we have our project set up, our top level item is a workspace (
Configuration
-Release
- We want the release version. There are several subtle differences between
Debug
andRelease
versions. The most notable is thatDebug
builds are usually built for just one architecture, whereRelease
builds are built for all supported architectures, usually with compile time optimisations as well.
- We want the release version. There are several subtle differences between
Workspace File
- This is our top level workspace file (TestProject.xcworkspace
) that we build from. If this option wasn’t specified, by default the xcode build would look for the first*.xcodeproj
file it can find. This would cause our build to fail.Unlock Keychain
- This allows us to unlock the keychain which is required for signing or resigning
.ipa
files. - The location of the keychain is the default OSX location
- This allows us to unlock the keychain which is required for signing or resigning
Step 3 - Resigning
- This step only occurs if previous steps were successful
- This step only occurs if the text
BUILD
is present in the log text (basically every build) - The result of this build will be escalated to the job status. If this step fails and other steps before it were successful, the job will still fail.
Below is the script used for resigning:
#!/bin/sh
PROJECT_BUILDDIR="${WORKSPACE}/build/TestProject/Build/Products/Release-iphoneos"
APPLICATION_NAME="TestProjectDev"
BUILD_HISTORY_DIR="/Users/administrator/Provisioning/builds"
DEVELOPER_NAME="iPhone Distribution: Test Company LTD"
PROVISIONING_PROFILE="/Users/administrator/Provisioning/TestProjectDevDistributionProfile.mobileprovision"
HOST_LOCATION="/Library/WebServer/Documents/apps"
#Sign The .app file and create .ipa file
/usr/bin/xcrun -sdk iphoneos PackageApplication -v "${PROJECT_BUILDDIR}/${APPLICATION_NAME}.app" -o "${BUILD_HISTORY_DIR}/${APPLICATION_NAME}.ipa" --sign ${DEVELOPER_NAME} --embed ${PROVISIONING_PROFILE}
#Get the version from the Info.plist file
APP_PATH="${PROJECT_BUILDDIR}/${APPLICATION_NAME}.app"
VERSION=`defaults read ${APP_PATH}/Info CFBundleShortVersionString`
BUNDLE_ID=`defaults read ${APP_PATH}/Info CFBundleIdentifier`
# Create plist
cat ${HOST_LOCATION}/template.plist | sed -e "s/\${APP_NAME}/$APPLICATION_NAME/" -e "s/\${BUNDLE_ID}/$BUNDLE_ID/" -e "s/\${BUNDLE_VERSION}/$VERSION/" > ${HOST_LOCATION}/${APPLICATION_NAME}.plist
- The first part of this script sets up the locations and script variables
PROJECT_BUILDDIR
- The folder that the built.ipa
file resides from the previous build step.APPLICATION_NAME
- In this case it isTestProjectDev
BUILD_HISTORY_DIR
- The folder that the re-signed app will be output toDEVELOPER_NAME
- Has to match the.mobileprovision
file used for signing.PROVISIONING_PROFILE
- The location of the.mobileprovision
file used for signing. These must be downloaded from the Apple developer website or shown in Finder from the Xcode organizer.HOST_LOCATION
- Where the final signed app will exist
- The next stage of the build process is actually re-signing the app using the Xcode command line tool called
xcrun
. Note that the version in/usr/bin/
is a symlink to the Xcode version that has been selected by thexcodeselect
command line tool. This is important if more than one version of xcode is installed. Each version of Xcode stores command line tools inside the.app
file in applications. - After re-signing, we grab the version, and bundle-id. These are used in the next part
APP_PATH
- The full path including the.app
extension of the re-signed appVERSION
- This is theCFBundleShortVersionString
. This value is set when you change the version in Xcode for each of the build schemes.BUNDLE_ID
- TheCFBundleIdentifier
for the built app. ForTestProject
, this will beau.com.wordpress.danielbeard.TestProject
. These can be set by modifying the build Schemes, but shouldn’t be changed for existing applications. Production has a different bundleId, which is what allows for both the dev and prod versions to exist on a single iPad at the same time.
- We then auto generate the enterprise install PLIST file. This is needed to install apps from a URL location and acts as a “description” of the app that is about to be installed.
- The actual creation of this file is quite simple, we use
cat
to copy thetemplate.plist
file to the new location and then usingsed
we replace theBUNDLE_ID
andBUNDLE_VERSION
values. These values are read from the Apps Info.plist from inside the .app folder.
- The actual creation of this file is quite simple, we use
Below is an example template.plist file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>http://hostlocation/apps/${APP_NAME}.ipa</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>${BUNDLE_ID}</string>
<key>bundle-version</key>
<string>${BUNDLE_VERSION}</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>${APP_NAME}</string>
</dict>
</dict>
</array>
</dict>
</plist>
Step 4 - Copying to host location
- This step simple grabs the signed
.ipa
file and puts it in the host location so that the macmini website can serve it over a URL. The host location is/Library/Webserver/Documents/apps
Script looks like this:
#!/bin/sh
APPLICATION_NAME="TestProjectDev.ipa"
BUILD_HISTORY_DIR="/Users/administrator/Provisioning/builds"
HOST_LOCATION="/Library/WebServer/Documents/apps/"
cp "${BUILD_HISTORY_DIR}/${APPLICATION_NAME}" "${HOST_LOCATION}${APPLICATION_NAME}"
And that’s it! The output is a .ipa file and a .plist file. These are both required to install an enterprise app.