public
Last active

Command line iOS project builds and over-the-air distribution

  • Download Gist
build.sh
Shell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
#!/bin/bash
 
# https://gist.github.com/949831
# http://blog.carbonfive.com/2011/05/04/automated-ad-hoc-builds-using-xcode-4/
 
# command line OTA distribution references and examples
# http://nachbaur.com/blog/how-to-automate-your-iphone-app-builds-with-hudson
# http://nachbaur.com/blog/building-ios-apps-for-over-the-air-adhoc-distribution
# http://blog.octo.com/en/automating-over-the-air-deployment-for-iphone/
# http://www.neat.io/posts/2010/10/27/automated-ota-ios-app-distribution.html
 
project_dir=`pwd`
 
# Configuration
environment_name="staging"
keychain="ci_keys"
keychain_password="super secret"
workspace="MyApp.xcworkspace"
scheme="Ad Hoc"
info_plist="$project_dir/MyApp-Info.plist"
environment_plist="$environment_name.plist"
environment_info_plist="$environment_name-Info.plist"
product_name="My App $environment_name"
mobileprovision="$project_dir/ad_hoc/MyAppStaging.mobileprovision"
provisioning_profile="iPhone Distribution: My Company, LLC"
build_number="%env.BUILD_NUMBER%"
artifacts_url="http://my_ci_server.example/artifacts/$build_number"
display_image_name="Icon-57.png"
full_size_image_name="Icon-512.png"
 
function failed()
{
local error=${1:-Undefined error}
echo "Failed: $error" >&2
exit 1
}
 
function validate_keychain()
{
# unlock the keychain containing the provisioning profile's private key and set it as the default keychain
security unlock-keychain -p "$keychain_password" "$HOME/Library/Keychains/$keychain.keychain"
security default-keychain -s "$HOME/Library/Keychains/$keychain.keychain"
#describe the available provisioning profiles
echo "Available provisioning profiles"
security find-identity -p codesigning -v
 
#verify that the requested provisioning profile can be found
(security find-certificate -a -c "$provisioning_profile" -Z | grep ^SHA-1) || failed provisioning_profile
}
 
function describe_sdks()
{
#list the installed sdks
echo "Available SDKs"
xcodebuild -showsdks
}
 
function describe_workspace()
{
#describe the project workspace
echo "Available schemes"
xcodebuild -list -workspace $workspace
}
 
function increment_version()
{
cd "MyApp"
agvtool -noscm new-version -all $build_number
cd ..
}
 
function set_environment()
{
#copy the info plist for the selected environment into place
cp -v "MyApp/$environment_info_plist" $info_plist || failed environment_plist
#copy the environment settings plist into place
cp -v "MyApp/$environment_plist" "MyApp/environment.plist" || failed environment
 
#extract settings from the Info.plist file
info_plist_domain=$(ls $info_plist | sed -e 's/\.plist//')
short_version_string=$(defaults read "$info_plist_domain" CFBundleShortVersionString)
bundle_identifier=$(defaults read "$info_plist_domain" CFBundleIdentifier)
echo "Environment set to $bundle_identifier at version $short_version_string"
}
 
function build_app()
{
local devired_data_path="$HOME/Library/Developer/Xcode/DerivedData"
 
#get the name of the workspace to be build, used as the prefix of the DerivedData directory for this build
local workspace_name=$(echo "$workspace" | sed -n 's/\([^\.]\{1,\}\)\.xcworkspace/\1/p')
#build the app
echo "Running xcodebuild > xcodebuild_output ..."
 
# disabled overriding PRODUCT_NAME, setting applies to all built targets in Xcode 4 which renames static library target dependencies and breaks linking
# xcodebuild -verbose -workspace "$workspace" -scheme "$scheme" -sdk iphoneos -configuration Release clean build PRODUCT_NAME="$product_name" >| xcodebuild_output
xcodebuild -verbose -workspace "$workspace" -scheme "$scheme" -sdk iphoneos -configuration Release clean build >| xcodebuild_output
 
if [ $? -ne 0 ]
then
tail -n20 xcodebuild_output
failed xcodebuild
fi
#locate this project's DerivedData directory
local project_derived_data_directory=$(grep -oE "$workspace_name-([a-zA-Z0-9]+)[/]" xcodebuild_output | sed -n "s/\($workspace_name-[a-z]\{1,\}\)\//\1/p" | head -n1)
local project_derived_data_path="$devired_data_path/$project_derived_data_directory"
#locate the .app file
 
# infer app name since it cannot currently be set using the product name, see comment above
# project_app="$product_name.app"
project_app=$(ls -1 "$project_derived_data_path/Build/Products/Release-iphoneos/" | grep ".*\.app$" | head -n1)
# if [ $(ls -1 "$project_derived_data_path/Build/Products/Release-iphoneos/$project_app" | wc -l) -ne 1 ]
if [ $(ls -1 "$project_derived_data_path/Build/Products/Release-iphoneos/" | grep ".*\.app$" | wc -l) -ne 1 ]
then
echo "Failed to find a single .app build product."
# echo "Failed to locate $project_derived_data_path/Build/Products/Release-iphoneos/$project_app"
failed locate_built_product
fi
echo "Built $project_app in $project_derived_data_path"
 
#copy app and dSYM files to the working directory
cp -Rf "$project_derived_data_path/Build/Products/Release-iphoneos/$project_app" $project_dir
cp -Rf "$project_derived_data_path/Build/Products/Release-iphoneos/$project_app.dSYM" $project_dir
#rename app and dSYM so that multiple environments with the same product name are identifiable
echo "Retrieving build products..."
rm -rf $project_dir/$bundle_identifier.app
rm -rf $project_dir/$bundle_identifier.app.dSYM
mv -f "$project_dir/$project_app" "$project_dir/$bundle_identifier.app"
echo "$project_dir/$bundle_identifier.app"
mv -f "$project_dir/$project_app.dSYM" "$project_dir/$bundle_identifier.app.dSYM"
echo "$project_dir/$bundle_identifier.app.dSYM"
project_app=$bundle_identifier.app
#relink CodeResources, xcodebuild does not reliably construct the appropriate symlink
rm "$project_app/CodeResources"
ln -s "$project_app/_CodeSignature/CodeResources" "$project_app/CodeResources"
}
 
function sign_app()
{
echo "Codesign as \"$provisioning_profile\", embedding provisioning profile $mobileprovision"
#sign build for distribution and package as a .ipa
xcrun -sdk iphoneos PackageApplication "$project_dir/$project_app" -o "$project_dir/$project_app.ipa" --sign "$provisioning_profile" --embed "$mobileprovision" || failed codesign
}
 
function verify_app()
{
#verify the resulting app
codesign -d -vvv --file-list - "$project_dir/$project_app" || failed verification
}
 
function build_ota_plist()
{
echo "Generating $project_app.plist"
cat << EOF > $project_app.plist
<?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>$artifacts_url/$project_app.ipa</string>
</dict>
<dict>
<key>kind</key>
<string>full-size-image</string>
<key>needs-shine</key>
<true/>
<key>url</key>
<string>$artifacts_url/$full_size_image_name</string>
</dict>
<dict>
<key>kind</key>
<string>display-image</string>
<key>needs-shine</key>
<true/>
<key>url</key>
<string>$artifacts_url/$display_image_name</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>$bundle_identifier</string>
<key>bundle-version</key>
<string>$short_version_string $build_number</string>
<key>kind</key>
<string>software</string>
<key>subtitle</key>
<string>$environment_name</string>
<key>title</key>
<string>$project_app</string>
</dict>
</dict>
</array>
</dict>
</plist>
EOF
}
 
echo "**** Validate Keychain"
validate_keychain
echo
echo "**** Describe SDKs"
describe_sdks
echo
echo "**** Describe Workspace"
describe_workspace
echo
echo "**** Set Environment"
set_environment
echo
echo "**** Increment Bundle Version"
increment_version
echo
echo "**** Build"
build_app
echo
echo "**** Package Application"
sign_app
echo
echo "**** Verify"
verify_app
echo
echo "**** Prepare OTA Distribution"
build_ota_plist
echo
echo "**** Complete!"
example_output.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
**** Validate Keychain
Available provisioning profiles
1) 0b1a9aaa619c6022e2f0e91c46af5209 "iPhone Distribution: MyCompany, LLC"
1 valid identities found
SHA-1 hash: 0b1a9aaa619c6022e2f0e91c46af5209
**** Describe SDKs
Available SDKs
Mac OS X SDKs:
Mac OS X 10.6 -sdk macosx10.6
iOS SDKs:
iOS 4.3 -sdk iphoneos4.3
iOS Simulator SDKs:
Simulator - iOS 3.2 -sdk iphonesimulator3.2
Simulator - iOS 4.0 -sdk iphonesimulator4.0
Simulator - iOS 4.1 -sdk iphonesimulator4.1
Simulator - iOS 4.2 -sdk iphonesimulator4.2
Simulator - iOS 4.3 -sdk iphonesimulator4.3
**** Describe Workspace
Available schemes
Information about workspace "My_App":
Schemes:
Ad Hoc
Development
**** Set Environment
My_App/My_App/staging-Info.plist -> /Users/tester/TeamCity/build-agent-1/work/My_App_staging/My_App/My_App/My_App-Info.plist
My_App/My_App/staging.plist -> My_App/My_App/environment.plist
Environment set to com.MyCompany.My_App.staging at version 1.0
**** Increment Bundle Version
Setting version of project My_App to:
24.
Also setting CFBundleVersion key (assuming it exists)
Updating CFBundleVersion in Info.plist(s)...
Updated CFBundleVersion in "My_App.xcodeproj/../My_App/My_App-Info.plist" to 24
Updated CFBundleVersion in "My_App.xcodeproj/../My_AppTests/My_AppTests-Info.plist" to 24
**** Build
Running xcodebuild > xcodebuild_output ...
Built My_App staging.app in /Users/tester/Library/Developer/Xcode/DerivedData/My_App-dnzbsdgcbuvddhcenlrdqhdfjjmh
Retrieving build products...
/Users/tester/TeamCity/build-agent-1/work/My_App_staging/com.MyCompany.My_App.staging.app
/Users/tester/TeamCity/build-agent-1/work/My_App_staging/com.MyCompany.My_App.staging.app.dSYM
**** Package Application
Codesign as "iPhone Distribution: MyCompany, LLC", embedding provisioning profile /Users/tester/TeamCity/build-agent-1/work/My_App_staging/ad_hoc/My_App_Staging_Ad_Hoc.mobileprovision
**** Verify
Executable=/Users/tester/TeamCity/build-agent-1/work/My_App_staging/com.MyCompany.My_App.staging.app/My_App staging
Identifier=com.MyCompany.My_App.staging
Format=bundle with Mach-O universal (armv6 armv7)
CodeDirectory v=20100 size=3515 flags=0x0(none) hashes=167+5 location=embedded
CDHash=eb62e8fe529a4dbdaef163509ffff137
Signature size=4317
Authority=iPhone Distribution: MyCompany, LLC
Authority=Apple Worldwide Developer Relations Certification Authority
Authority=Apple Root CA
Signed Time=Jun 9, 2011 4:31:52 PM
Info.plist entries=33
/Users/tester/TeamCity/build-agent-1/work/My_App_staging/com.MyCompany.My_App.staging.app/My_App staging
Sealed Resources rules=3 files=97
/Users/tester/TeamCity/build-agent-1/work/My_App_staging/com.MyCompany.My_App.staging.app/_CodeSignature/CodeResources
Internal requirements count=1 size=180
**** Prepare OTA Distribution
Generating com.MyCompany.My_App.staging.app.plist
**** Complete!

Customize the variables in the "Configuration" section to match your app and build machine. You may also need to update the paths in "set_environment".

Once complete visit itms-services://?action=download-manifest&url=http://my_ci_server.example/path/to/project_app.plist to install ad hoc builds over the air. Requires that the installing device be added to your ad hoc provisioning profile and running iOS 4 or later.

As far as I can tell, setting the default keychain only affects which keychain new items are added to. I was having trouble because the keychain with my cert wasn't in the search list, and so wasn't picking up anything on it - despite setting the keychain to be the default.

I now have the following:

function backup_keychain_search_list() {
    KEYCHAIN_SEARCH_LIST=`security list-keychains | cut -f 2 -d"`
}

function use_and_validate_keychain {
    ...
    echo "Changing keychain search path to be ${full_keychain_path}"
    security list-keychain -s "${full_keychain_path}"
    echo "Changing default keychain to ${full_keychain_path}"
    security default-keychain -s "${full_keychain_path}"
    ....
}

function cleanup() {
    echo "Resetting the keychain search list"
    security list-keychains -s ${KEYCHAIN_SEARCH_LIST}

    echo "Resetting the default keychain to login"
    security default-keychain -s login.keychain
}

have you try to change this to suit XCode-runner?

Thanks!

Hi, I am trying to use this script for automating my iOS project. I have one concern here is, I am finding it tough to do it with a workspace based project. Because, I have only worked in project based templates in Xcode and thus finding it tough to find the paths.

Could you please tell me how can I make it work for a project based template. It will be great if I can do that...

Thank you, very useful script!

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.