Automated Ad-Hoc Beta iPhone App Publishing

A while back, I came across Jeffrey Sambells post about iOS Wireless App Distribution, thought “that’s cool, I could automate most of that” and then promptly forgot about it. A little after this, Hunter Hillegas introduced iOS Beta Builder which I also thought “cool, I could extend that to do some of the automation I thought about” and then forgot about it again.

BetaBuilder-TDO App showing ABC of the Sea metadata

BetaBuilder-TFO App


Ad Hoc app distribution

I already had a small shell script I run to make my .ipa files which saves me from the tediousness of “Build and Archive” and then saving the archived file – if I have to click something, it loses my attention. I already build iOS projects with an extra build configuration called “Ad Hoc” that signs with the current Ad-Hoc distribution profile. Beta Builder nicely took the .ipa (which is really just a zip file), discovered the contents and produced a handful of useful things:

  • The manifest file, that iOS 4.0 and newer can use to find the .ipa file.
  • A zip file containing the .ipa file and a copy of the distribution profile, for users that need to use iTunes to load Ad-Hoc builds.
  • A nice index.html to link to all of the above with brief instructions.

Initially I setup Beta Builder to deliver the files it built to a local directory and simply rsync‘ed the structure to my origin HTTP server. That was nice, but it involved too much clicking for my liking. Hunter had hinted that the source would appear on GitHub sometime… but again, I forgot all about it until he posted version 1.0.1 and I happened to want to distribute another build of ABC of the Sea. I was motivated. What I wanted to achieve was to be able to build an Ad-Hoc target in Xcode and then run one script to publish the package. I love this stuff. To get there, I decided I needed:

BetaBuilder HTML page for ABC of the Sea beta release

BetaBuilder generated HTML

  • My existing script that builds an .ipa from the build directory.
  • A non-interactive version of Beta Builder, driven from the command line.
  • For Beta Builder to read more keys from the projects Info.plist file, in particular, to differentiate between the application version and build number.
  • To link to the README.txt file on the HTML page that Beta Builder generates.
  • To have a more useful name for the legacy zip file it generates.
  • And then rsync the resulting files to my origin server.

I made a copy of Beta Builders source tree in a local SVN repository and hacked away (sometime I’ll create a branch and commit it back to GitHub). I won’t belabor over what I changed (diffs and such are attached) but it suffices to say that I can publish my beta releases with one command, sweet.

The scripts

Aside from my hacked version of Beta Builder, there are three shell scripts that run together.

  • <PROJECT_DIRECTORY>/Scripts/package-app.sh which knows the project and target names to use for a given project.
  • $HOME/bin/package-app that does most of the grunt work.
  • $HOME/bin/sync-iosbeta that copies my local iosbeta directory tree to the origin HTTP server.

The first of these is very simple. Its responsibility is to validate the only parameter it receives (the target configuration name, Debug, Release, Ad Hoc), ensure the build is uptodate (call xcodebuild) and then package it all up.

<PROJECT_DIRECTORY>/Scripts/package-app.sh
1
2
3
4
5
6
7
8
9
10
11
#!/bin/sh
# $Id: package-app.sh 335 2010-09-03 00:07:00Z chrisy $
conf="$1"
[ "$conf" = "AdHoc" ] && conf="Ad Hoc"
if [ "$conf" != "Debug" -a "$conf" != "Release" -a "$conf" != "Ad Hoc" ]; then
        echo "ERROR: Invalid configuration. Use Debug, Release or Ad Hoc."
        exit 1
fi
cd $(dirname $0)/../ || exit 1
xcodebuild -configuration "$conf" || exit 1
exec $HOME/bin/package-app 'ABC of the Sea' 'ABC of the Sea' "$conf"

The real work is done in package-app. The primary function is to build a ZIP file crafted in the correct manner. This is simply a copy of the targets build directory but prefixed with Payload/ in the path. The resulting file is often suffixed with .ipa, but .zip works just as well with all the tools I have tried it with. I since tweaked the script to call my modified Beta Builder and distribute the results, and my tweaks to Beta Builder include steps to name the package as a .ipa, just in case.

This is not the cleanest of shell scripts, but it is functional:

$HOME/bin/package-app
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
#!/bin/sh

project=$1
app=$2
type=Release
extra=

if [ -z "${project}" -o -z "${app}" ]; then
        echo "Usage:   $(basename $0) <project name> <target name> [configuration]"
        echo "example: $(basename $0) FivePeeEmm 5somewhere"
        echo "configuration defaults to 'Release'"
        exit 1
fi

if [ ! -z "$3" ]; then
        type=$3
        extra="-$3"
fi

base="${HOME}/Documents/iPhone Projects/${project}"

if [ ! -d "${base}" ]; then
        echo "ERROR: Can't find project \"${project}\" at \"${base}\""
        exit 1
fi

appdir="${base}/build/${type}-iphoneos"

if [ ! -d "${appdir}/${app}.app" ]; then
        echo "ERROR: Can't find app \"${app}\" at \"${appdir}/${app}.app\""
        exit 1
fi

mkdir -p $HOME/Applications || exit 1

# Make a copy of the build into the Payload directory
payloaddir="${base}/build/Payload"
payload="Payload"
rm -rf "${payloaddir}"
mkdir -p "${payloaddir}"
cp -rp "${appdir}/" "${payloaddir}/"

cd "${payloaddir}/.."
bindir="${payload}/${app}.app"

# Get build and release versions
bundle_id=$(plutil -convert xml1 -o - - < "${bindir}/Info.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 - - < "${bindir}/Info.plist"  |
        grep -A 1 CFBundleShortVersionString |
        sed -e 's/<[^>]*>//g' -e 's/[      ]//g'| tail -1)
build_ver=$(plutil -convert xml1 -o - - < "${bindir}/Info.plist"  |
        grep -A 1 CFBundleVersion |
        sed -e 's/<[^>]*>//g' -e 's/[   ]//g'| tail -1)

echo "Packaging project \"${project}\" target \"${app}\""
echo "  Bundle ID \"${bundle_id}\" (${bundle_short}) version ${release_ver} build ${build_ver}..."

zip="$HOME/Applications/${app}-${release_ver}-${build_ver}${extra}.zip"
zip=$(echo "${zip}" | sed -e 's/  */_/g')

year=$(date +%Y)
rm -f "${zip}"
zip -Xoyrz "${zip}" "${bindir}" << EOT | grep -v 'enter new zip file comment'
Project: ${project}
App: ${app}
Release: ${release_ver}
Build: ${build_ver}
(c) ${year} Chris Luke

.
EOT

zip -T "${zip}" || exit 1
unzip -tq "${zip}" || exit 1
echo "Done. Package is ${zip}"

if [ "${type}" = "Ad Hoc" ]; then
        # One more step - package it up for iosbeta distribution!
        odir="$HOME/Documents/iosbeta/${bundle_short}-${build_ver}"
        ourl="http://example.com/${bundle_short}-${build_ver}"
        echo "Distributing Ad Hoc release into ${odir}..."
        mkdir -p "${odir}" || exit 1
        if open "$HOME/Documents/MacOSX Projects/BetaBuilder TFO/build/Debug/BetaBuilder.app" --args \
                -i "${zip}" -o "${odir}" -u "${ourl}" -r "${base}/README.txt" ; then
                $HOME/bin/sync-iosbeta
        else
                echo "BetaBuilder encountered an error. Check system logs."
                exit 1
        fi
fi

BetaBuilder TFO

My hack to add command-line driven options to BetaBuilder simply uses getopt(3) on the argc/argv parameters in main() and if we recognise any parameters then it never calls UIApplication to fire-up the GUI. For example, output from -h:

1
2
3
4
5
6
7
8
Usage: /Users/chrisy/Documents/MacOSX Projects/BetaBuilder TFO/build/Debug/BetaBuilder.app/Contents/MacOS/BetaBuilder [options]
If no options are specified, the app runs in interactive mode.

 -h            This message
 -i <file>     The input file (.zip or .ipa) (required)
 -o <dir>      The output directory (required)
 -u <url>      The URL of the output directory (required)
 -r <file>     The README.txt file to include (optional)

However, running the binary directly like this doesn’t give the app access to its bundle, and that’s where the template index.html lives. That is why package-app uses open to run the program (however, this causes NSLog() output to all go to the system logs, not stderr).

Running it

And, for kicks, here’s a snippet of sample output:

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
bean:~ chrisy$ cd Documents/iPhone\ Projects/ABC\ of\ the\ Sea/
bean:ABC of the Sea chrisy$ Scripts/package-app.sh AdHoc

=== BUILD NATIVE TARGET ABC of the Sea OF PROJECT ABC of the Sea WITH CONFIGURATION Ad Hoc ===
Check dependencies
CodeSign "build/Ad Hoc-iphoneos/ABC of the Sea.app"
    cd "/Users/chrisy/Documents/iPhone Projects/ABC of the Sea"
    setenv PATH "/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin:/Developer/usr/bin:/Users/chrisy/bin:/opt/subversion/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin:/Developer/usr/bin:/Developer/usr/sbin"
    setenv _CODESIGN_ALLOCATE_ /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/codesign_allocate
    /usr/bin/codesign -f -s "iPhone Distribution: Chris Luke" "--resource-rules=/Users/chrisy/Documents/iPhone Projects/ABC of the Sea/build/Ad Hoc-iphoneos/ABC of the Sea.app/ResourceRules.plist" --entitlements "/Users/chrisy/Documents/iPhone Projects/ABC of the Sea/build/ABC of the Sea.build/Ad Hoc-iphoneos/ABC of the Sea.build/ABC of the Sea.xcent" "/Users/chrisy/Documents/iPhone Projects/ABC of the Sea/build/Ad Hoc-iphoneos/ABC of the Sea.app"

/Users/chrisy/Documents/iPhone Projects/ABC of the Sea/build/Ad Hoc-iphoneos/ABC of the Sea.app: replacing existing signature
Validate "build/Ad Hoc-iphoneos/ABC of the Sea.app"
    cd "/Users/chrisy/Documents/iPhone Projects/ABC of the Sea"
    setenv PATH "/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin:/Developer/usr/bin:/Users/chrisy/bin:/opt/subversion/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin:/Developer/usr/bin:/Developer/usr/sbin"
    setenv PRODUCT_TYPE com.apple.product-type.application
    /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/Validation "/Users/chrisy/Documents/iPhone Projects/ABC of the Sea/build/Ad Hoc-iphoneos/ABC of the Sea.app"

** BUILD SUCCEEDED **

Packaging project "ABC of the Sea" target "ABC of the Sea"
  Bundle ID "org.flirble.abcsea" (abcsea) version 1.0.0 build 12...
  adding: Payload/ABC of the Sea.app/ (stored 0%)
  adding: Payload/ABC of the Sea.app/_CodeSignature/ (stored 0%)
  adding: Payload/ABC of the Sea.app/_CodeSignature/CodeResources (deflated 64%)
  adding: Payload/ABC of the Sea.app/a-ipad.png (stored 0%)
...
  adding: Payload/ABC of the Sea.app/z-iphone.png (stored 0%)
  adding: Payload/ABC of the Sea.app/zooplankton.html (deflated 51%)
test of /Users/chrisy/Applications/ABC_of_the_Sea-1.0.0-12-Ad_Hoc.zip OK
No errors detected in compressed data of /Users/chrisy/Applications/ABC_of_the_Sea-1.0.0-12-Ad_Hoc.zip.
Done. Package is /Users/chrisy/Applications/ABC_of_the_Sea-1.0.0-12-Ad_Hoc.zip
Distributing Ad Hoc release into /Users/chrisy/Documents/iosbeta/abcsea-12...
Syncing iOS beta distribution...
building file list ...
13 files to consider
abcsea-12/
abcsea-12/ABC_of_the_Sea-1.0.0-12-Ad_Hoc.ipa
      32.88M 100%  119.64MB/s    0:00:00 (xfer#1, to-check=4/13)
abcsea-12/ABC_of_the_Sea-1.0.0-12-Ad_Hoc.zip
      32.62M 100%   59.26MB/s    0:00:00 (xfer#2, to-check=3/13)
abcsea-12/README.txt
       7.63K 100%   14.20kB/s    0:00:00 (xfer#3, to-check=2/13)
abcsea-12/index.html
       2.01K 100%    3.75kB/s    0:00:00 (xfer#4, to-check=1/13)
abcsea-12/manifest.plist
         763 100%    1.42kB/s    0:00:00 (xfer#5, to-check=0/13)

sent 105.70K bytes  received 68.99K bytes  116.46K bytes/sec
total size is 131.00M  speedup is 749.90
bean:ABC of the Sea chrisy$

Attachments

All I need do now is email the mailing-list I have of beta testers to let them know build 12 is up and where to find it – and I’ll probably automate that, too!

Did you like this? Share it:

Tags: automation, BetaBuilder, Hunter Hillegas, iOS, iPad, iPhone, Jeffrey Sambells, MacOS X, Programming, shell script, Xcode
Posted in: iOS, Technology 2 Comments

The anatomy of a page curl

As I was developing the ABC of the Sea App, which is an interactive picture-book based on a proof-of-concept Tara (my wife) made many years ago and did not publish, one of the early and obvious items on the checklist of cool things it should do is have a nice curling transition when moving between pages. Initially I expected this to be easy – Apples Apps do this, iBooks on the iPad especially, so it’s built in, right?

Wrong. Well, mostly wrong. I quickly discovered that the only documented curling transition for a UIView goes up/down, not left/right. It’s the one that Maps uses. Some Googling then revealed that an undocumented transition did perform left/right curling. However, being undocumented means it’s not viable for an App Store submission. It may later acquire an official constant and become kosher, but not yet.

Continue reading “The anatomy of a page curl” »

Did you like this? Share it:

Tags: ABC of the Sea, cone, curl, cylinder, iOS, iPad, iPhone, Objective C, OpenGL, page, page curl, page turn, Programming, Trigonometry
Posted in: iOS, Technology Comments Off

Code indenting in WordPress

A distinct limitation in WordPress when posting code snippets is that it is very easy to lose any embedded leading whitespace on lines, tabs especially. This means you lose your indentation. And that is bad.

I’ve been using CodeColorer and am moderately pleased with the results but none of the available syntax highlighters, or other plugins, seemed to have any support to (re)indent text. So I decided to see if I could bolt such functionality onto CodeColorer.
Continue reading “Code indenting in WordPress” »

Did you like this? Share it:

Tags: code indentation, CodeColorer, PHP, Programming, syntax highlighting, uncrustify, WordPress
Posted in: Technology, WordPress 1 Comment

How to find the moon

Moon during Lunar Eclipse

The Moon

Intriguing title for a first web log post? I thought so! It’s the working title for something that, like so much that I am doing now, exists only in my head at the moment. It will be a childrens story based on a series of dialogue with my daughter on the way to school that began with:

Her: “Where are we going?”

Me: “The moon!”

Her: “Really? How do we get there?”

Continue reading “How to find the moon” »

Did you like this? Share it:

Tags: Apple, Family, iOS, Network Engineering
Posted in: Apple, iOS, Personal 1 Comment

This website uses a Hackadelic PlugIn, Hackadelic Sliding Notes 1.6.5.
This website uses a Hackadelic PlugIn, Hackadelic SEO Table Of Contents 1.7.3.