Automating Cordova builds and deployment

This post will detail the process I used to fire a single command on my build computer, and then wirelesly install the app on my iOS device.

I use Cordova Cli to manage my project, but rather than execute the cordova commands myself, I let Grunt automate that for me.

Of note, I currently use Cordova 2.9, so while some stuff will have changed, the basic principle will work on newer versions too.

Grunt

First off my Gruntfile.js. Below are the pertinent sections, but the full file can be found at https://gist.github.com/RhinoLance/7778487.

Bumpup

There is nothing worse when debugging, than not being confident that we actually have the new version installed. Bumpup automatically increments my build number with every build, and is read in my app and displayed as the version number. Makes checking bliss.

bumpup: ['package.json', 'www/js/build.json']

Shell

I have three shell commands that I use; build, prepare and buildIpa

shell: {
  options: {
    failOnError: true,
    stdout: false,
    stderr: true
  },
  build: {
    command: 'cordova build ios android'
  },
  prepare: {
    command: 'cordova prepare'
  },
  buildIpa: {
    command: '/usr/bin/xcrun -sdk iphoneos PackageApplication -v "/<projectPath>/platforms/ios/build/Crocpad.app" -o "/<projectPath>/platforms/ios/build/CrocPad.ipa" --sign "iPhone Distribution: Rhino Software Pty Ltd (F86TA6JRXE)" --embed "/<projectPath>/Dev/Keys/iOS/Rhino_Software_AdHoc.mobileprovision"'
  }
}

Build

This simply calls the cordova build command. Note that by default (in Cordova v2.9) the cli only builds for the emulator, so some editing of the command was needed to build for deployment. My edited build command can be found at https://gist.github.com/RhinoLance/7778933.

Prepare

As with the build command, this just calls the Cordova prepare command.

BuildIpa

This command only operates on the iOS build, and does three things.

First it packages the .app, which the cordova build command creates, into an .ipa.

Second it signs the ipa with my signing certificate. Note I don’t need to provide my cert password, as it will automatically retrieve it from the keychain.

Third, it embeds the provisioning profile into the .ipa. As can be seen in this example, I’m embedding an ad-hoc profile for testing, so keeping the app store out of the loop.

Copy

The copy task essentially takes the output .ipa and .apk files and copies them into an output folder. More on this output folder later.

copy: {
  main: {
    files: [
      {
        expand: true, flatten: true, filer: 'isFile',
        src: ['platforms/ios/build/CrocPad.ipa'], 
        dest: '/<projectPath>/Dev/Client/buid/ios/'
      },
      {
        expand: true, flatten: true,
        src: ['platforms/android/bin/CrocPad-debug.apk'], 
        dest: '/<projectPath>/Dev/Client/buid/android/'
      },
      {
        expand: true, flatten: true,
        src: ['package.json'], 
        dest: '/<projectPath>/Dev/Client/buid/'
      }
      ]
   }
}

Tasks

With that out of the way, I then register two tasks of note; one to run the whole process, and the other just to package the .ipa.

// Default task</span>
grunt.registerTask('default', ['bumpup:build', 'jshint', 'shell:build', 'shell:buildIpa', 'copy']);
// Custom tasks
 grunt.registerTask('ipa', ['shell:buildIpa', 'copy']);

 

Web Server

In order for our devices to install our builds, we need to set up a local website.  The virtual directory will need to be the same as we set in our grunt copy output location.  By this point you should see where this is going.

Here is my structure for the virtual directory:

|-index.html
|-qrcode.png
|-android
|   |-CrocPad-debug.apk
|
|-ios
    |-CrocPad.ipa
    |-CrocPad.plist

You’ll notice the QRCode, knowing the URL of the index.html, I used a QR code generator from the web, so that I could load up the page easier on mobile devices using a code reader, rather than having to type it in myself.  You certainly don’t need it.

index.html contains:

<html>
<img src="qrcode.png"/>
<h1><a href="itms-services://?action=download-manifest&url=http://asterix/crocpad/ios/CrocPad.plist">iOS</a></h1>
<h1><a href="android/CrocPad-debug.apk">Android</a></h1>
</html>

plist contains:

<?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>http://asterix/crocpad/ios/CrocPad.ipa</string>
				</dict>
			</array>
			<key>metadata</key>
			<dict>
				<key>bundle-identifier</key>
				<string>com.rhinosw.crocpad</string>
				<key>bundle-version</key>
				<string>0.0.1</string>
				<key>kind</key>
				<string>software</string>
				<key>title</key>
				<string>CrocPad</string>
			</dict>
		</dict>
	</array>
</dict>
</plist>

That’s it!

That’s it.  Now on the mobile device, we simply need the web browser to point to our index.html, and tap the Android or iOS install link, and it will automatically download and install our build.

In practical terms, on my build machine, I simply run my default grunt task, wait about 30 seconds for the build to finish, then hit the link on my device, and in another 10 seconds it’s installed.  Certainly makes debugging easy when the process of edit-build-install-test is so quick.

Leave a Reply

Your email address will not be published. Required fields are marked *