#!/bin/sh # Build an IPA package from an Ad Hoc build and then # package it for Ad Hoc distribution. # This version is designed for use as a Shell Script Target # called directly by Xcode. # (c) 2010 Chris Luke. Copying is fine, but please attribute to me! # Useful environment variables Xcode provides: # BUILD_DIR # CONFIGURATION # CONFIGURATION_BUILD_DIR # CONFIGURATION_TEMP_DIR # EFFECTIVE_PLATFORM_NAME # ACTION (build|clean) # CURRENT_PROJECT_VERSION # PROJECT_DIR # PROJECT_NAME # PROJECT_TEMP_DIR # TARGET_TEMP_DIR # User settings we can place in the target settings: # ADHOC_PUBLISHING_URL with replaceables # ADHOC_TARGET_NAME the target we want to copy from # ADHOC_BUNDLE_SOURCE the build dir of the app we want to copy for # distribution- built from TARGET_NAME if not set # ADHOC_TEMPLATE_HTML template for index.html # ADHOC_TEMPLATE_README templatye for readme file # ADHOC_DELIVERY_NAME name of .zip/.ipa with replaceables # ADHOC_DELIVERY_DIRECTORY directory to deliver a .zip archive of app to # ie, releases, archived copies # ADHOC_DELIVER_DSYM If set and not 0 or no then we also copy the .dSYM # ADHOC_PUBLISHING_DIRECTORY directory to deliver the pkg to with replaceables # ADHOC_PUBLISHING_COMMAND command to execute to push materials out with # ADHOC_CONFIG_ADHOC the label used for ad hoc builds # ADHOC_CONFIG_RELEASE the label used for release builds # ADHOC_AUTHOR # Configuration can go into ~/.adhoc-distribution.conf . Defaults have the # form DEFAULT_ and are used only if the variable is not set. # If you name a variable directly in the config, it overwrites any existing # value. # Replaceable values, where supported: # #PROJECT# Project name (spaces removed) # #APP# #TARGET# The app/target name (spaces removed) # #CONFIG# The build configuration name (Release, Ad Hoc) # #VERSION# The version number (from CFBundleShortVersionString) # #BUILD# The build number (from CFBundleVersion) # #BUNDLEID# The bundle ID # #SHORTBUNDLEID# The last component of the bundle ID # #PLATFORM# The targetted platform, iphoneos or iphonesimulator # pfx="Ad-Hoc IPA Packager:" err="$pfx" config_file="$HOME/.adhoc-distribution.conf" # Some generic defaults. These can be overridden in a local config file DEFAULT_BASEDIR= DEFAULT_ADHOC_PUBLISHING_URL="http://localhost/iosbeta/#SHORTBUNDLEID#-#BUILD#" DEFAULT_ADHOC_PUBLISHING_DIRECTORY="$HOME/Documents/iosbeta/#SHORTBUNDLEID#-#BUILD#" DEFAULT_ADHOC_PUBLISHING_COMMAND=$HOME/bin/sync-iosbeta DEFAULT_ADHOC_DELIVERY_NAME="#APP#-#VERSION#-#BUILD#-#CONFIG#.ipa" DEFAULT_ADHOC_DELIVERY_DIRECTORY="$HOME/Documents/iOSArchives" DEFAULT_ADHOC_DELIVER_DSYM=yes DEFAULT_ADHOC_CONFIG_ADHOC="Ad Hoc" DEFAULT_ADHOC_CONFIG_RELEASE="Release" DEFAULT_ADHOC_AUTHOR=$(id -P | cut -d: -f8) DEFAULT_verbose=0 DEFAULT_FORCEPKG=0 default_options="BASEDIR \ ADHOC_PUBLISHING_URL ADHOC_TARGET_NAME ADHOC_BUNDLE_SOURCE \ ADHOC_TEMPLATE_HTML ADHOC_TEMPLATE_README ADHOC_DELIVERY_NAME \ ADHOC_DELIVERY_DIRECTORY ADHOC_PUBLISHING_DIRECTORY \ ADHOC_PUBLISHING_COMMAND ADHOC_CONFIG_ADHOC ADHOC_CONFIG_RELEASE \ ADHOC_DELIVER_DSYM ADHOC_AUTHOR verbose FORCEPKG" mandatory_options="ADHOC_TARGET_NAME ADHOC_DELIVERY_NAME \ ADHOC_DELIVERY_DIRECTORY ADHOC_CONFIG_ADHOC ADHOC_CONFIG_RELEASE \ BUILD_DIR CONFIGURATION CONFIGURATION_BUILD_DIR \ CONFIGURATION_TEMP_DIR ACTION PROJECT_DIR PROJECT_NAME \ TARGET_TEMP_DIR" mandatory_directories="PROJECT_DIR CONFIGURATION_BUILD_DIR \ CONFIGURATION_TEMP_DIR TARGET_TEMP_DIR ADHOC_DELIVERY_DIRECTORY" replacement_options="ADHOC_PUBLISHING_URL ADHOC_DELIVERY_NAME \ ADHOC_TEMPLATE_HTML ADHOC_PUBLISHING_DIRECTORY \ ADHOC_PUBLISHING_COMMAND" # Load any local configuration [ -f "${config_file}" ] && . "${config_file}" # See if any defaults need to be loaded for opt in ${default_options}; do val= eval "val=\"\$$opt\"" if [ -z "$val" ]; then val= eval "val=\"\$DEFAULT_$opt\"" [ ! -z "$val" ] && eval "$opt=\"$val\"" fi done docmd=0 dohelp=0 [ -t 0 -a -z "${ACTION}" ] && dohelp=1 while [ "$1" ]; do opt=$1 dohelp=0 case "$opt" in --project|-p) PROJECT_NAME="$2"; shift docmd=1 shift;; --target|--app|-t|-a) ADHOC_TARGET_NAME="$2"; shift docmd=1 shift;; --config*|-c) CONFIGURATION="$2"; shift shift;; --basedir|-b) if [ -z "${PROJECT_NAME}" ]; then echo "$err Specify project name before base directory." exit 1 fi if [ -z "${ADHOC_TARGET_NAME}" ]; then echo "$err Specify target name before base directory." exit 1 fi BASEDIR="$2"; shift shift;; --force*|-f) FORCEPKG=1 shift;; --verbose|-v) verbose=1 shift;; --help|-h) dohelp=1 shift;; *) dohelp=1 echo "Unknown option \"$opt\"." shift;; esac [ "${dohelp}" = 1 ] && break done if [ "${dohelp}" = 1 ]; then cat << EOT $pfx Usage: $(basename $0) [options] --project|-p Project name --target|--app|-t|-a Target name --config|-c Project configuration name (Debug, Release, Ad Hoc) --basedir|-b Parent directory of projects --force|-f Force packaging, even if up-to-date --help|-h This message --verbose|-v Verbose mode Configuration can also be loaded from "${config_file}" EOT exit 1 fi if [ ! -z "${BASEDIR}" -a "${docmd}" = 1 -a ! -z "${PROJECT_NAME}" -a \ ! -z "${ADHOC_TARGET_NAME}" ]; then if [ ! -d "${BASEDIR}" ]; then echo "$err Basedir \"${BASEDIR}\" is not readable." exit 1 fi [ -z "${CONFIGURATION}" ] && CONFIGURATION="${DEFAULT_ADHOC_CONFIG_ADHOC}" ACTION=build EFFECTIVE_PLATFORM_NAME=-iphoneos PROJECT_DIR="${BASEDIR}/${PROJECT_NAME}" BUILD_DIR="${PROJECT_DIR}/build" PROJECT_TEMP_DIR="${BUILD_DIR}/${PROJECT_NAME}.build" CONFIGURATION_BUILD_DIR="${BUILD_DIR}/${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}" CONFIGURATION_TEMP_DIR="${BUILD_DIR}/${PROJECT_NAME}.build/${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}/${PROJECT_NAME}.build" TARGET_BUILD_DIR="${CONFIGURATION_BUILD_DIR}" TARGET_TEMP_DIR="${BUILD_DIR}/${PROJECT_NAME}.build/${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}/${ADHOC_TARGET_NAME}.build" fi if [ "${ACTION}" != build ]; then if [ "${docmd}" = 1 ]; then echo "$err Incomplete parameters." echo "Need basedir, project name and target name, at minimum." exit 1 fi [ "${verbose}" = 1 ] && echo "$pfx No packaging for action \"${ACTION}\"." exit 0 # we only work when building, not cleaning fi if [ -z "${BUILD_DIR}" -o ! -d "${BUILD_DIR}" ]; then echo "$err No valid BUILD_DIR!" exit 1 fi # Check for mandatory values for opt in ${mandatory_options}; do val= eval "val=\"\$$opt\"" if [ -z "$val" ]; then echo "$err Variable $opt is mandatory, yet empty." exit 1 fi done # Check for mandatory directories for opt in ${mandatory_directories}; do val= eval "val=\"\$$opt\"" if [ ! -d "$val" ]; then echo "$err $opt is a missing mandatory directory \"$val\"." exit 1 fi done if [ -z "${ADHOC_BUNDLE_SOURCE}" ]; then ADHOC_BUNDLE_SOURCE="${CONFIGURATION_BUILD_DIR}/${ADHOC_TARGET_NAME}.app" fi if [ ! -d "${ADHOC_BUNDLE_SOURCE}" ]; then echo "$err Can't find App \"${ADHOC_TARGET_NAME}\" at \"${ADHOC_BUNDLE_SOURCE}\"" exit 1 fi plist="${ADHOC_BUNDLE_SOURCE}/Info.plist" if [ ! -f "${plist}" ]; then echo "$err Can't find \"Info.plist\" at \"${plist}\"" pwd exit 1 fi # Get build and release versions bundle_id=$(plutil -convert xml1 -o - - < "${plist}" | \ grep -A 1 CFBundleIdentifier | \ sed -e 's/<[^>]*>//g' -e 's/[ ]//g'| tail -1) bundle_short=$(echo "${bundle_id}" | sed -Ee 's/^.*\.(.*)$/\1/') release_ver=$(plutil -convert xml1 -o - - < "${plist}" | \ grep -A 1 CFBundleShortVersionString | \ sed -e 's/<[^>]*>//g' -e 's/[ ]//g'| tail -1) build_ver=$(plutil -convert xml1 -o - - < "${plist}" | \ grep -A 1 CFBundleVersion | \ sed -e 's/<[^>]*>//g' -e 's/[ ]//g'| tail -1) config=$(echo "${CONFIGURATION}" | sed -e 's/ *//g') ws_project_name=$(echo "${PROJECT_NAME}" | sed -e 's/ */_/g') ws_app_name=$(echo "${ADHOC_TARGET_NAME}" | sed -e 's/ */_/g') ws_platform=$(echo "${EFFECTIVE_PLATFORM_NAME}" | sed -e 's/^-//') echo "Packaging project \"${PROJECT_NAME}\" target \"${ADHOC_TARGET_NAME}\" config \"${CONFIGURATION}\"" echo "* Bundle ID \"${bundle_id}\" (${bundle_short}) version ${release_ver} build ${build_ver}..." # Do parameter replacement for opt in ${replacement_options}; do val= eval "val=\"\$$opt\"" val=$(echo "$val" | sed -e "s/#PROJECT#/${ws_project_name}/g" \ -e "s/#APP#/${ws_app_name}/g" \ -e "s/#TARGET#/${ws_app_name}/g" \ -e "s/#CONFIG#/${config}/g" \ -e "s/#BUILD#/${build_ver}/g" \ -e "s/#VERSION#/${release_ver}/g" \ -e "s/#BUNDLEID#/${bundle_id}/g" \ -e "s/#SHORTBUNDLEID#/${bundle_short}/g" \ -e "s/#PLATFORM#/${ws_platform}/g" ) eval "$opt=\"$val\"" done mkdir -p "${ADHOC_DELIVERY_DIRECTORY}" || exit 1 [ -z "${ADHOC_DELIVERY_NAME}" ] && \ ADHOC_DELIVERY_NAME="${ADHOC_TARGET_NAME}-${release_ver}-${build_ver}-${config}.zip" zip="${ADHOC_DELIVERY_DIRECTORY}/${ADHOC_DELIVERY_NAME}" zip=$(echo "${zip}" | sed -e 's/ */_/g') if [ "$(echo "${zip}" | grep -Ec '\.(zip|ipa)$')" = 0 ]; then echo "$err ADHOC_DELIVERY_NAME needs to end with .ipa or .zip" echo "The expanded name was: \"$zip\"." exit 1 fi [ "$verbose" = 1 ] && echo "+ zip=$zip" dsymin="${ADHOC_BUNDLE_SOURCE}.dSYM" dsymout=$(echo "${zip}" | sed -Ee 's/\.(zip|ipa)$/.dSYM.zip/') [ "$verbose" = 1 ] && echo "+ dsymin=$dsymin" [ "$verbose" = 1 ] && echo "+ dsym=$dsymout" dozip=yes if [ -f "${zip}" ]; then # See if zip is older than the code signature or binary tf="${ADHOC_BUNDLE_SOURCE}/_CodeSignature/CodeResources" [ -f "${tf}" ] || tf="${ADHOC_BUNDLE_SOURCE}/${ADHOC_TARGET_NAME}" if [ -f "${tf}" -a "${zip}" -nt "${tf}" ]; then dozip=no [ "${verbose}" = 1 ] && echo "$pfx Existing archive is still current, no need to build." fi fi dodsym=no if [ ! -z "${ADHOC_DELIVER_DSYM}" -a -d "${dsymin}" ]; then case "${ADHOC_DELIVER_DSYM}" in [Nn][Oo]|0) dodsym=no;; *) dodsym=yes;; esac if [ "${dsymin}" -ot "${dsymout}" ]; then [ "${verbose}" = 1 ] && echo "$pfx Existing dSYM is still current, no need to copy." dodsym=no fi fi if [ "${dozip}" = yes -o "${FORCEPKG}" = 1 ]; then # Make a copy of the build into the Payload directory tmpdir="${TARGET_TEMP_DIR}/adhoc-pkg" payloaddir="${tmpdir}/Payload/${ADHOC_TARGET_NAME}.app" payload="Payload" rm -rf "${tmpdir}" mkdir -p "${payloaddir}" || exit 1 cp -rp "${ADHOC_BUNDLE_SOURCE}/" "${payloaddir}/" || exit 1 oldpwd=$(pwd) cd "${payloaddir}/../../" || exit 1 bindir="${payload}/${ADHOC_TARGET_NAME}.app" # Now zip it up year=$(date +%Y) rm -f "${zip}" opt_zip= [ "${verbose}" = 0 ] && opt_zip="-q" zip -Xyrz ${opt_zip} "${zip}" "${bindir}" << EOT | grep -v 'enter new zip file comment' Project: ${PROJECT_NAME} App: ${ADHOC_TARGET_NAME} Build configuration: ${CONFIGURATION} Release: ${release_ver} Build: ${build_ver} (c) ${year} ${ADHOC_AUTHOR} . EOT #zip -T "${zip}" || exit 1 unzip -tq "${zip}" || exit 1 echo "Packaged into ${zip}" cd "${oldpwd}" rm -rf "${tmpdir}" fi if [ "${dodsym}" = yes ]; then [ "${verbose}" = 1 ] && echo "$pfx Archiving dSYM..." oldpwd=$(pwd) cd "${dsymin}/../" year=$(date +%Y) rm -f "${dsymout}" opt_zip= [ "${verbose}" = 0 ] && opt_zip="-q" dsymname=$(basename "${dsymin}") zip -Xyrz ${opt_zip} "${dsymout}" "${dsymname}" << EOT | grep -v 'enter new zip file comment' Project: ${PROJECT_NAME} App: ${ADHOC_TARGET_NAME} Build configuration: ${CONFIGURATION} Release: ${release_ver} Build: ${build_ver} (c) ${year} ${ADHOC_AUTHOR} . EOT cd "${oldpwd}" fi # If it's Ad Hoc, the right options are set, it's iphoneos and it has been codesigned, we can publish it. if [ "${CONFIGURATION}" = "${ADHOC_CONFIG_ADHOC}" -a \ "${ADHOC_PUBLISHING_DIRECTORY}" -a "${ADHOC_PUBLISHING_URL}" \ -a "${EFFECTIVE_PLATFORM_NAME}" = "-iphoneos" -a \ -f "${ADHOC_BUNDLE_SOURCE}/_CodeSignature/CodeResources" ]; then # If it exists already, see if it is fresh. dopkg=yes pkg="${ADHOC_PUBLISHING_DIRECTORY}/"$(echo "${ADHOC_DELIVERY_NAME}" | \ sed -e 's/\.ipa$/.zip/') if [ -f "${pkg}" ]; then if [ "${pkg}" -nt "${zip}" ]; then dopkg=no [ "${verbose}" = 1 ] && echo "$pfx Existing package is still current, no need to package and distribute." fi fi if [ "${dopkg}" = yes -o "${FORCEPKG}" = 1 ]; then # One more step - package it up for iosbeta distribution! echo "Distributing Ad Hoc release into ${ADHOC_PUBLISHING_DIRECTORY}..." mkdir -p "${ADHOC_PUBLISHING_DIRECTORY}" || exit 1 opt_readme= [ ! -z "${ADHOC_TEMPLATE_README}" -a \ -f "${ADHOC_TEMPLATE_README}" ] && \ opt_readme="-r \"${ADHOC_TEMPLATE_README}\"" opt_html= [ ! -z "${ADHOC_TEMPLATE_HTML}" -a \ -f "${ADHOC_TEMPLATE_HTML}" ] && \ opt_html="-r \"${ADHOC_TEMPLATE_HTML}\"" cmd="open -n \"$HOME/Documents/MacOSX Projects/BetaBuilder TFO/build/Debug/BetaBuilder.app\" --args \ -i \"${zip}\" \ -o \"${ADHOC_PUBLISHING_DIRECTORY}\" \ -u \"${ADHOC_PUBLISHING_URL}\" \ ${opt_readme} ${opt_html}" if eval "${cmd}"; then if [ ! -z "${ADHOC_PUBLISHING_COMMAND}" -a \ -x "${ADHOC_PUBLISHING_COMMAND}" ]; then echo "Distributing Ad Hoc release to website..." "${ADHOC_PUBLISHING_COMMAND}" fi else echo "BetaBuilder encountered an error. Check system logs." exit 1 fi fi fi exit 0