Automated ad hoc builds using Xcode 4

来源:互联网 发布:淘宝好的手办店 编辑:程序博客网 时间:2024/05/22 15:53


I’ve previously discussed Continuous Integration for iPhone Projects in TeamCity using Xcode 3 and Building Xcode 4 Projects from the Command Line. Now I’ll tie those together and use TeamCity to automatically create ad hoc builds I can install over the air (directly onto a device without using iTunes) every time I check in code.

I created a basic project configuration in TeamCity 6 to checkout my iOS project from git.

I set the artifact paths for this configuration to

project_name.acceptance.app*
ad_hoc/*.png

This will eventually allow TeamCity to collect the icons, project_name.acceptance.app.ipa, and project_name.acceptance.app.plist files needed to perform an over the air install of the ad hoc build.

Finally I created the following custom script build step (available as https://gist.github.com/949831).

#!/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.htmlproject_dir=`pwd`# Configurationenvironment_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_keychainechoecho "**** Describe SDKs"describe_sdksechoecho "**** Describe Workspace"describe_workspaceechoecho "**** Set Environment"set_environmentechoecho "**** Increment Bundle Version"increment_versionechoecho "**** Build"build_appechoecho "**** Package Application"sign_appechoecho "**** Verify"verify_appechoecho "**** Prepare OTA Distribution"build_ota_plistechoecho "**** Complete!"

That is quite a few functions but the bottom of the script steps through them in what is hopefully an understandable sequence.

  1. Unlock the keychain containing the private key and provisioning profile used to sign the ad hoc build. Necessary since the TeamCity user’s login keychain may be locked when this build runs.
  2. List the available sdks on the build machine (unnecessary but I found it helpful when debugging build settings).
  3. List the schemes found in the workspace to be built (again purely for debugging).
  4. Copy a plist containing application settings to “environment.plist” which will be copied into the app bundle when built and used to define application behavior, for example it contains the url of the server this build should communicate with.
  5. Build the app using the specified workspace and scheme. Copy the resulting app and dSYM to the project directory so TeamCity can easily find them as build artifacts.
  6. Sign the app using the specified mobile provisioning profile and create a “.ipa” package.
  7. Verify that the app was successfully signed.
  8. Create a plist to allow over the air installation of the app.

Once run any device which has been added to the mobile provisioning profile used to sign this build can install the app just by visiting (using an appropriate %system.teamcity.buildType.id% for the TeamCity build configuration).

itms-services://?action=download-manifest&url=http://teamcity.example.com:8111/guestAuth/repository/download/%system.teamcity.buildType.id%/.lastSuccessful/project_name.acceptance.app.plist

Possible issues:

  • Access to the keychain will present a security dialog the first time this build runs so it was necessary for me to sign into the TeamCity user’s account using VNC and allow access to that keychain.
  • I found that builds occasionally failed to correctly create the CodeResources symlink so I recreate it manually. When this link was broken the ipa would fail verification.
  • An iOS device won’t be able to install the app if the artifacts require authenticating to the TeamCity server so I enabled guest access. Alternately I could have exposed those artifacts through some other service and created a nice installation guide page which links to them if I didn’t want to allow guest access to my TeamCity server.

For additional resources for building over the air distribution builds take a look at:

Mike Nachbaur’s posts on “How to Automate your iPhone App Builds with Hudson” and “Building iOS Apps for Over the Air Ad Hoc Distribution“.
Vincent Daubry’s “Automating Over The Air Deployment for iPhone“.
Basil Shkara’s “Automated OTA iOS App Distribution“.


From: http://blog.carbonfive.com/2011/05/04/automated-ad-hoc-builds-using-xcode-4/

原创粉丝点击