Skip to content

Instantly share code, notes, and snippets.

@jonah-williams
Created April 30, 2011 17:46
Show Gist options
  • Save jonah-williams/949831 to your computer and use it in GitHub Desktop.
Save jonah-williams/949831 to your computer and use it in GitHub Desktop.
Command line iOS project builds and over-the-air distribution
#!/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!"
**** 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!
@jonah-williams
Copy link
Author

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.

@e28eta
Copy link

e28eta commented Oct 28, 2011

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
}

@iAladdin
Copy link

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

Thanks!

@rashmi1988
Copy link

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...

@ryanhanwu
Copy link

Thank you, very useful script!

@nsrinivas
Copy link

Hi jonah , Can You Please Exp-lance how to use this script

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment