Thursday, February 6, 2014

Automating the Building of Native Android and iOS Apps using Sencha Touch

Overview

Sencha Touch is a technology that can be used to take a Sencha Touch (JavaScript) based application, and generate platform specific binaries used to run that application. This means that you write a single JavaScript based application, which gets turned into an Android APK file and an iOS IPA file or anything else you may need. The issue has been getting this to reliably work for more than one project, on more than one platform, on more than one computer, and without human intervention. This is the story…
Using the following technologies and versions, which will be explained more in detail later:
  • Sencha Command 4.0.1.X
  • Cordova 3.0.0
  • Android (doesn’t matter)
  • Java 7
  • Sencha Touch 2.3
  • Xcode 5.0

Approaches

There are 3 fundamental approaches for dealing with building Sencha Application
  1. Never Cordova - Copy everything that consists of the application into a temporary directory, install Cordova on it, manipulate various configuration files, and then run the Sencha native build.
  2. Always Cordova – This involves keeping the various generated configuration files in your project (and in source control).
  3. Sort of Cordova – This is where you keep the various Cordova generates files in your project but ignore them in SCM and uninstall Cordova before building again.

Wait…what, why?

The way this works is that a lot, and I mean a lot of files and directories get created and modified when you do the native sencha build. The problem comes with knowing what gets generated, what gets modified, and what has to be modified manually by hand and under what conditions.
cordova
  • plugins – This is where any plugins you are using need to go, and you need to put them after installing cordova (sencha cordova init X Y) but prior to adding a native platform (cordova platform add X)
    • com.foo.bar – Example Cordova 3 style plugin
    • android.json – Generated list of plugins in use
  • www – This is a copy of your JavaScript (minified) with a generated HTML file and microloader
    • app.js
    • app.json
    • index.html
    • config.xml – Derived from the config.xml in your root level sencha project
  • platforms – Contains a directory for every native platform
    • android – Actually works as an Eclipse android project, sort of
      • .staging – This is a hidden directory in which anything placed here prior to the native build get copied into assets/www
      • assets
        • www – This is a copy of your minified JavaScript from cordova/www, but with platform specific plugin JS files if needed
          • app.js
          • app.json
          • index.html
          • bar-plugin.js – A custom plugin derived from cordova/plugins/com.foo.bar
      • bin – Generated binaries
      • gen – Generated source code
      • libs
        • cordova-3.0.0.jar
      • res
        • … - lots of generated graphics
        • xml
          • config.xml – The plugins you use get generated declarations in this file as features
      • src
        • com
          • foo
            • bar
              • Bar.java – A custom plugin derived from cordova/plugins/com.foo.bar
            • some
              • app
                • id
                  • MyAppName.java – Wraps the index.html page
      • AndroidManifest.xml – Settings for Android, which gets generated initially
    • ios – Actually works as an iOS Xcode project
      • .staging – This is a hidden directory in which anything placed here prior to the native build get copied into www
      • build – Generated build assets
      • CordovaLib – This is the Cordova Xcode library
        • build
        • Classes
        • CordovaLib.xcodeproj
        • cordova.js
      • MyAppName
        • Classes
        • Plugins – This is where custom plugin native sources get dumped
          • com.foo.bar
            • Bar.m
            • Bar.h
        • Resources
        • config.xml
        • MyAppName-Info.plist
        • MyAppName.xcodeproj
        • www - This is a copy of your minified JavaScript from cordova/www, but with platform specific plugin JS files if needed
          • app.js
          • app.json
          • index.html
          • bar-plugin.js – A custom plugin derived from cordova/plugins/com.foo.bar
Everything under “cordova” gets generated, but only the first time. The difficulty comes with what happens in the following cases:
  • I need to add a plugin
  • I need to remove a plugin
  • I need to make updates to a custom plugin
  • I want a different application ID
  • I want a different application name
  • I want to change the version number
  • I need different Android permissions
  • I want to change application icons
  • I made changes to my Sencha application (the JS files)
“Sort of Cordova”
What you have to do in the above circumstances depends on which of the three Cordova approaches you are using. As it turns out, using the “Sort of Cordova” approach results in the least amount of manual and/or automated work. In particular the “Always Cordova” approach where you try and keep the Cordova configuration results in a nightmare of updating configurations under specific conditions. It took the above directory and file structure research in order to make an attempt at handling configuration updates, which resulted in a lot of complexity.
The resulting project structure looks like the following (when running Sencha Touch as a Dynamic Web Project in Eclipse):
MyProject
  • WebContent
    • cordova - This is the big cordova directory structure described above
    • touch – Sencha Touch source code
      • sencha-touch-all.js
    • app
      • controller
      • model
      • view
      • store
      • app.js
      • app.json
    • index.html
What about “Never Cordova”?
The “Never Cordova” and “Sort of Cordova” approaches share the same fundamental principle of always generating the Cordova configuration from scratch, and the differences where the “cordova” directory is generated. In the “Sort of Cordova” approach the “cordova” directory is kept in the Sencha Project and just uninstallad/ignored, versus copying everything to a temporary location. The problem with the “Never Cordova” approach is that copying all of those files is time consuming, and generally adds 30-60 seconds onto the build process.
Are you sure?
No, but based on the upcoming description of processes the “Sort of Cordova” approach ended up being the least amount of build/scripting code and related complexity. I know this because I implemented all three approaches at one point, and each one has its upsides and downsides. If there is a better way that works to do this I would be eager to hear it.

The Process

Before I launch it to what will be a mind-numbing description of what has to be handled in the build process, it is best to consider what the goals were:
  1. A build that is repeatable, specifically that can be run on a continuous integration server
  2. A build that is stable, specifically something that doesn’t fail some percentage of the time
  3. A build that works for both iOS and Android, and that allows them to be built in parallel
  4. A build that doesn’t require the manual moving or modification of files, meaning I don’t want a human to have to intervene to get a build

1. Input

There are several things that should be used as dynamic inputs to the process, so that a single build process can be used for multiple environments and under multiple conditions:
  • Environment – Specification of the environment that the build is for, so it can be written into stored property files and used to name the resulting binaries.
  • Revision – The SCM Revision number that is going to be checked out and built
  • Build Number – The build number to be used in the downstream AndroidManifest.xml and AppName-Info.plist, as well as being used in the name of the resulting binaries.
  • Application Name – The name of the application used in the various parts of the process that use it as an input. This most notably goes in config.xml as well as AndroidManifest.xml and AppName-Info.plist
  • Application ID – The application ID used (for store submission and general application identification). This also involves AndroidManifest.xml and AppName-Info.plist.
  • Properties File – This refers to a file contain environment specific properties that are going to be embedded in a properties file within the native applications.

2. Cleaning

Before you do a build, delete the directory that is to contain your binary output. With all of the zipping, unzipping, and other activities going on this is done to ensure you are getting an accurate binary.
  • In Ant this is typically the “target” directory, but I would highly suggest using Gradle because the result will be a lot less verbose.
  • In Gradle this is typically the “build” directory.
  • In Maven, you would probably be scratching your head on how to deal with Sencha.
  • For Continuous Integration (like Jenkins), you are always going to want to delete your workspace before doing a build.
    • This is because since even while you are deleting the build directory, we are storing cordova assets within the project structure.
    • Depending on the SCM checkout settings, this can have unpredictable results.
    • The most reliable way is just to start from scratch.

3. The Pre-Compile Check

Checking prior to running the build that the environment has all the appropriate stuff installed will save you and future developers a lot of time. There is nothing more frustrating that chasing down silly errors that were the result of not having the correct version of Cordova installed.
What OS am I using?
It is also a good idea here to establish what OS you are running on, so you know how to interact with the command line. This is because you will be running stuff at the command line a lot.
Windows
Command: cmd
Command Switch: /c
Not Windows
Command: /bin/sh
Command Switch: -c
This matters because this determines how you will call the various sencha and cordova commands, for example:
Windows> cmd /c sencha app refresh
Not Windows> /bin/sh –c sencha app refresh
Sencha Runtime Code Issues
It is possible for things to work in sencha in the browser that do not work once they are on the mobile device. So far the one I have into the build process is checking for is how to navigate through a controller.
Bad: this.getController
Good: this.getApplication().getController
Any .js file that contains the following text, should be a reason for failing the build process: this.getController
Accessing a controller in this manner will not work on iOS or Android.
Cordova Installation
Verify that Cordova 3.0.0 is installed. Why? Anything newer than 3.0.0 doesn’t seem to work, or at least I couldn’t get it to work.
After spending a lot of time trying to chase down why I couldn’t get a sencha native build working, I finally narrowed the issue down to Cordova by looking at all the Cordova commands Sencha was running behind the scenes, and then took Sencha out of the equation by using Cordova 3.3.0, 3.1.0, and 3.0.0 directly:
cordova create HelloWorld com.example.hello "HelloWorld"
cd HelloWorld
cordova platform add android
The adding of the android platform would fail every time for Cordova 3.3.0, 3.2.0, and 3.1.0 with a nodejs.js compile error. I tried this on three different computers (2 Windows machines and a Mac) and got the same result. After losing more time to trying to Google my way out of the issue, I ended up settling on Cordova 3.0.0 since it actually worked in this test, and allowed me to do a native build in sencha.
The result is that a build should run the following command to verify that Cordova 3.0.0 is installed:
cordova -v
If the result doesn’t contain “3.0.0”, abort.
Sencha Command Tool Installation
Sencha Command is the tool that is used to wrap most of the build process, and is used in various forms throughout it. Various versions of Sencha Command exist, but the only ones that will work with the latest Sencha Touch 2.2+ projects is 4.0.1.
For this reason, the following command should be used to verify the version of sencha command in use:
sencha which
If the result does not contain “Sencha Cmd v4.0.1”, abort.
Platform Specific Tools
Depending on what you building, you should verify that the platform specific build tool is available and is of the correct version.
Android – The Android SDK works with every version I have tried, just verify it is installed”
adb version
If the output doesn’t contain “Android Debug Bridge”, abort.
iOS – If you have Xcode5 installed it comes with xcodebuild:
xcodebuild –version
If the output doesn’t contain “Xcode 5.0”, abort.

4. Dealing with the Sencha Touch Framework

The sencha touch framework is a lot of files. Why does this matter? It matters because it takes a lot of time to check-out (and initially check in) these files. This can significantly slow down clean builds from a continuous integration perspective. In order to speed this up I have been zipping the Sencha Touch framework, and extracting it when needed at build time to a location that is ignored by SCM.
The Sencha Touch framework looks like the following:
touch
  • cmd
  • microloader
  • resources
  • src
  • sencha-touch-all-debug.js
  • sencha-touch-all.js
  • sencha-touch.js
Example Process:
  • Does WebContent/touch exist?
  • Yes: Do Nothing
  • No: unzip zip/touch.zip to WebContent/touch

5. Uninstalling Cordova

This is a result of the decision to follow the “Sort of Cordova” process, where we don’t store the various Cordova generated files in SCM. In order for this to work, we have to uninstall Cordova prior to doing a build.
This is done by running the following command from the WebContent directory:
sencha cordova remove
The result is the following:
  • WebContent/cordova is deleted
  • WebContent/.sencha/app/build.properties has cordova properties removed
  • WebContent/.sencha/app/native.properties has codrova properties removed
  • WebContent/build.xml has the cordova import removed from it

6. Installing Cordova

Installing Cordova on a project results in the creation of the WebContent/cordova directory, and all of the various Cordova configuration files and modification to Sencha configuration files.
This is done by running the following command using Application ID and Application name from the WebContent directory:
sencha cordova init some.app.id SomeAppName
The result is the following:
  • WebContent/cordova is created
  • WebContent/.sencha/app/build.properties has cordova properties added
  • WebContent/.sencha/app/native.properties has codrova properties added
  • WebContent/build.xml has the cordova import added to it

7. Generating packager.json

An important note here is that unless you are specifically using the sencha command to build from packager.json, this file and most of its contents are apparently ignored. There is a Sencha command to generate an initial packager.json, however from a build perspective this is something that if you are using that needs to be configured on the fly. Note that in my build process I use sencha app build native instead of the other option to build using package.json. From that perspective, the contents of packager.json look like the following and the items that are used need to be generated with each build:
{
"applicationName":"SomeAppName",
"applicationId":"some.app.id",
"bundleSeedId":"FGSHGFHSD",
"versionString":"1.0",
"versionCode":"1",
"configuration":"Release", // NOT USED
"platform":"Android",
"deviceType":"Universal", // NOT USED
"certificatePath":"android-keystore", // NOT USED
"certificateAlias":"android-alias", // NOT USED
"certificatePassword":"Foobar", // NOT USED
"provisionProfile":"", // NOT USED
"notificationConfiguration":"", // NOT USED
"sdkPath":"C:/adt-bundle/sdk", // NOT USED
"androidAPILevel":"18", // NOT USED
"icon": { // NOT USED
"36":"resources/icons/Icon_Android36.png",
"48":"resources/icons/Icon_Android48.png",
"57":"resources/icons/Icon.png",
"72":"resources/icons/Icon~ipad.png",
"114":"resources/icons/Icon@2x.png",
"144":"resources/icons/Icon~ipad@2x.png"
},
"inputPath":"./", // NOT USED
"outputPath":"../build/", // NOT USED
"permissions":[ // Goes into AndroidManfest.xml, but overridden
"INTERNET",
"ACCESS_NETWORK_STATE",
"CAMERA",
"VIBRATE",
"ACCESS_FINE_LOCATION",
"ACCESS_COARSE_LOCATION",
"CALL_PHONE"
],
"orientations": [ // NOT USED
"portrait",
"landscapeLeft",
"landscapeRight",
"portraitUpsideDown"
]
}
Why aren’t you going the build thing that involves packager.json?
Several reasons:
  1. It doesn’t work, or at least I couldn’t get it to work
  2. I need to build multiple sets of binaries on a single platform:
    1. Android Debug
    2. Android Signed with my Certificate
    3. iOS Ad Hoc
    4. iOS Enterprise
    5. iOS Release

8. Prepping app.json

In order for to be able to use Cordova plugins, WebContent/app.json has to be modified to set remove to false.
As a precaution the build process should just replace any instance of remove=”true” to remote=”false”.
This file is later modified and copied into several other locations involving platform specific www directories.

9. Installing/Updating Cordova Plugins

Why do you have to worry about updating plugins? Because cordova add plugin is broken. Consider this example of me trying to execute it from the WebContent directory (where cordova is installed):
cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-geolocation.git
[RangeError: Maximum call stack size exceeded]
Yes, GIT is installed and a tried to Google my way out of this one as well.
As a workaround I did the following:
  • Downloaded the plugins I wanted to use from https://git-wip-us.apache.org/repos/asf
  • Zipped each one named it after the package name used in plugin.xml for each plugin
  • As part of the build process:
    • if there is a zip file in “plugins” that has the same name as a plugin in WebContent/plugins, do nothing
    • If there is a zip file in “plugins” that does not correspond to the same name in WebContent/plugins, extract it there
    • You then copy all directories from WebContent/plugins into WebContent/cordova/plugins
The result is that when you later run the native builds, the related plugin configurations will get generated and embedded into the downstream native projects.
This also means that if you don’t have a zip file that corresponds to something in WebContent/plugins, you have a plugin that is only for your project that you can update by editing the code that sits in WebContent/plugins. You now have two ways to deal with plugins:
  1. As a zip file that gets extracted into your project when needed
  2. As source code in WebContent/plugins that you just maintain and change there
However if you want to update with a new zip file, you would have to delete the corresponding out of WebContent/plugins so the new source would get extracted there.

10. Configuring Application Properties

Most mobile applications are going to have some embedded properties that do things like specify what URL they are going to need to talk to. In Sencha Touch this can be done by putting a properties file at WebContent/Application.properties, which contains things like the following:
serviceUrl=http://foo/bar/url
appId=SomeAppId
environment=test
At Runtime app.js can read in these properties and store them so they are globally accessible. This avoids the need to have to keep this information in code and/or manually modify them for each environment.
In the project I specify an environment directory that contains property files for each environment, for example:
  • environments/test.properties
  • environments/prod.properties
As a part of the build process, I specify a “properties” parameter that indicates which property file to use, and then use that file to overwrite the contents of WebContent/Application.properties.

11. Replacing Default Icons

There are a lot of icons in various sizes that are used for the application icons on specific platforms. By default there are all Sencha graphics located in WebContent/resouces/icons. I have made a list of the required icons names and sizes for iOS and Android and where they need to go:
  • Icon_Android36.png
    • copy to WebContent/cordova/www/res/icon/android/icon-36-ldpi.png
  • Icon_Android48.png
    • copy to WebContent/cordova/www/res/icon/android/icon-48-mdpi.png
  • Icon.png
    • Copy to WebContent/cordova/www/res/icon/ios/icon-57.png
  • Icon@2x.png
    • copy to WebContent/cordova/www/res/icon/android/icon-72-hdpi.png
    • Copy to WebContent/cordova/www/res/icon/ios/icon-57-2x.png
  • Icon~ipad.png
    • Copy to WebContent/cordova/www/res/icon/ios/icon-72.png
  • Icon~ipad@2x.png
    • Copy to WebContent/cordova/www/res/icon/ios/icon-72-2x.png
  • Icon170x200.png
    • Copy to WebContent/cordova/www/img/logo.png
  • Icon96.png
    • copy to WebContent/cordova/www/res/icon/android/icon-96-xhdpi.png
As it turns out though, none of this is used. I don’t know whether it is some other build settings or because I am not using the packager.json based build. These icons don’t make it into the generated native applications.

12. Manually Adding the Platform

Manually adding the platform refers to the command “cordova platform add X”, where X is the name of your platform like ios or android. The issue is that you should not have to run this command at all, because “sencha app build native” will take care of doing this if needed. Why do I run this command then? Because “sencha app build native” doesn’t always successfully add the required platforms.
The issue I had was with a Windows Server 2012 box where “sencha app build native” would run the “cordova platform add android” command and it was pass, but the WebContent/cordova/platforms/android directory would not get created. If I went into WebContent and ran “cordova platform add android” the directory would get created correctly though. This worked on all of my other machines, looking at the –debug output of sencha did not yield any results, so I went ahead and made the build process add the platform prior to attempting the native build.
Android
cordova platform add android
iOS
cordova platform add ios
The result is that when in the future step that “sencha app build native” is run it will skip the attempt to add the platform.

13. Configuration File Manipulation

The need to manipulate configuration files is the direct result of the “Sort of Cordova” approach. Since we don’t store Cordova configuration files you can’t modify the platform specific configurations files which are:
  • Android: AndroidManifest.xml
  • iOS: AppName-Info.plist
This is the biggest downside to the approach, but as pointed out earlier if you attempt to store the Cordova configuration (even it parts), you end up with a lot of problems when you need to make changes.
The types of changes you need to make depend on the platform.
Android: AndroidManifest.xml
  • If production you need to set android:debuggable="false"
  • Always update the android:versionCode to whatever your build number is
  • Delete all “uses-permissions” nodes, and add them from a list in a build properties file.
    • This is because the plugin permission settings are not always honored when written into AndroidManifest.xml
    • It ended up being easier to just take a list like “android.permission.CAMERA,android.permission.VIBRATE” and write them out in AndroidManifest.xml as a part of the build
Example Result:
<?xml version='1.0' encoding='utf-8'?>
<manifest android:hardwareAccelerated="true" android:versionCode="1" android:versionName="0.0.1" android:windowSoftInputMode="adjustPan" package="some.app.id.SomeAppName" xmlns:android="http://schemas.android.com/apk/res/android">
<supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:resizeable="true" android:smallScreens="true" android:xlargeScreens="true" />
<application android:debuggable="true" android:hardwareAccelerated="true" android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale" android:label="@string/app_name" android:name="AppName" android:theme="@android:style/Theme.Black.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="10" android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.VIBRATE" />
</manifest>
iOS: AppName-Info.plist
  • Append key/string nodes for CFBundleShortVersionString and the marketing version number (like 1.0) to the root dict node. This is what set the displayed version for the application.
  • Append key/array nodes for required external frameworks from a list of names and values
    • This is again because plugin settings are not always honored when derived into native projects
Example Result:
<?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>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>icon.png</string>
<key>CFBundleIcons</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>icon.png</string>
<string>icon@2x.png</string>
<string>icon-72.png</string>
<string>icon-72@2x.png</string>
</array>
<key>UIPrerenderedIcon</key>
<false/>
</dict>
</dict>
<key>CFBundleIdentifier</key>
<string>some.app.id.SomeAppName</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>62</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSMainNibFile</key>
<string></string>
<key>NSMainNibFile~ipad</key>
<string></string>
<key>UISupportedExternalAccessoryProtocols</key>
<array>
<string>some.protocol.name</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
Another reasonable approach would be just to store the AndroidManifest.xml and AppName-Info.plist files, copy them into the appropriate locations prior to native building, and just update the version information.

14. More Icon Moving

If you want to use your own icons (for Android and iOS) applications you are going to have to move them into the derived locations yourself.
  • Icon96.png
    • Copy to WebContent/cordova/platforms/android/res/drawable/icon.png
  • Icon~ipad.png
    • Copy to WebContent/cordova/platforms/android/res/drawable-hdpi/icon.png
    • Copy to WebContent/cordova/platforms/ios/AppName/resources/icons/icon-72.png
  • Icon_Android36.png
    • Copy to WebContent/cordova/platforms/android/res/drawable-ldpi/icon.png
  • Icon_Android48.png
    • Copy to WebContent/cordova/platforms/android/res/drawable-mdpi/icon.png
  • Icon96.png
    • Copy to WebContent/cordova/platforms/android/res/drawable-xhdpi/icon.png
  • Icon.png
    • Copy to WebContent/cordova/platforms/ios/AppName/resources/icons/icon.png
  • Icon@2x.png
    • Copy to WebContent/cordova/platforms/ios/AppName/resources/icons/icon@2x.png
  • Icon~ipad@2x.png
    • Copy to WebContent/cordova/platforms/ios/AppName/resources/icons/icon-72@2x.png

15. Copying Sencha Source Code

For a reason I have not been able to determine the application once native requires Dom.js, which is inside the Sencha source code. Yes, I have tried using both sencha-touch-all.js and sencha-touch.js in app.json. The way I was able to work around this was by copying the Sencha touch source into the www directories in the native projects.
FileNotFoundException: www/src/event/publisher/Dom.js
The reason this is noteworthy is because you can’t directly copy files there, because the Sencha build process will delete them. In order to get WebContent/touch/src into the native www you have to copy it to the .staging directory.
Android:
Copy WebContent/touch/src into WebContent/cordova/platforms/android/.staging/www/src
Results in WebContent/cordova/platforms/android/assets/www/src
iOS:
Copy WebContent/touch/src into WebContent/cordova/platforms/ios/.staging/www/src
Results in WebContent/cordova/platforms/ios/www/src

16. Native Build

There are apparently two ways to produce a native build in Sencha Touch:
sencha app build native
This produces a mostly debug version of whatever platforms you are building. I say “mostly” debug because configurations that can be passed in to do some things like a production release, others require intervention.
sencha app package build <configFile.json>
This is supposed to produce a signed native binary for whatever platforms you are building. I say “supposed to” because I was never able to get it working, while sencha app build native was relatively easy to get to function.
There were several reasons why I didn’t go with this solution:
  1. It doesn’t work, or at least I couldn’t get it to work.
  2. It concerns me when there are two completely different build process, one for production and one for everything else.
  3. I need to build multiple sets of binaries on a single platform, and it is nearly trivial to produce the first binary and then do some modifications to produce other types of signed binaries:
    1. Android Debug
    2. Android Signed with my Certificate
    3. iOS Ad Hoc
    4. iOS Enterprise
    5. iOS Release
The result is I run the following command from the WebContent directory:
sencha app build native

17. Platform Build

This is where you get into things that you have to do because of how you build the native Android binary, and because of what you need to do on iOS to turn an app into an ipa.
Android
Since we produced a debug version of the APK, we have to unsign it, and then sign it again with our key. This is done in the following steps:
  1. Create a copy of the debug APK called signed.zip (An APK file is really a ZIP file)
  2. Unzip signed.zip to the “signed” directory
  3. Delete the directory signed/META-INF
  4. Zip the contents of “signed” into unsigned.apk
  5. Sign the unsigned.apk using the following command:
    • jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore Some.keystore " unsigned.apk" mymobileapplicationgroup
  6. Run the following command to align the signed apk:
    • zipalign 4 unsigned.apk signed.apk
  7. Delete unsigned.apk
The result is that you have two apk files: debug and signed
…but the jarsigned prompts me with a password? I used an Expect script to automate it.
iOS
On iOS you have to the AppName.app file into the signed IPA files that you need, which come in three varieties:
  1. Enterprise – This is used to distribute apps in-house, such as being able to upload them to a tool like TestFlightApp.com (https://developer.apple.com/programs/ios/enterprise/)
  2. Ad Hoc - An ad hoc provisioning profile is a distribution provisioning profile for iOS apps that allows your app to be installed on designated devices and use key technologies and services without the assistance of Xcode (https://developer.apple.com/library/IOs/documentation/IDEs/Conceptual/AppDistributionGuide/TestingYouriOSApp/TestingYouriOSApp.html)
  3. Release – This is the thing you submit to the AppStore
There are enough steps here that I have the build process call a script that is used to do this building.
Prerequisites:
  1. Enterprise Certificate - http://www.maas360.com/maasters/forums/mobile-app-management-mam/show/332/building-ios-apps-for-enterprise-deployment
  2. Ad Hoc Certificate - http://adeem.me/blog/2009/04/24/tutorial-list-guideline-for-building-ad-hoc-application-for-iphone/
  3. Distribution Certificate - https://developer.apple.com/library/mac/Documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html
Steps for building
  1. Unlock the keychain (http://jenkins-ci.361315.n4.nabble.com/Jenkins-with-iOS-development-td4524576.html):
    • security unlock-keychain -p $PASSWORD ~/Library/Keychains/login.keychain
  2. Increment the version number (http://useyourloaf.com/blog/2010/08/18/setting-iphone-application-build-versions.html)
    • agvtool new-version -all SOME_BUILD_NUMBER
    • agvtool new-marketing-version SOME_MARKETING_VERSION
  3. For each certificate do the following, which is to build the app (again) and package it as an IPA but skip the TestFlight uploading:
An issue with xcodebuild however is that is sometimes will fail and not return a failing exit code, the result is that it failed but you don’t know about it. As a workaround I parse the output for “error generated” and if present fail the build.
Other ways of doing it:

18. Finding your Binaries

Now that you have built the various binaries, you know have to find them.
The laziest way to do it is to look for **/*.ipa and **/*.apk files within WebContent while excluding **/*unaligned to ignore some of the Android build step results. On Android you should have a signed APK and a Debug Signed APK, while on iOS you should have an IPA for every certificate you used.
Also note that along with the binaries, you also will have corresponding native projects.
Eclipse Android Project (without a .project and .classpath): WebContent/cordova/platforms/android
iOS Xcode 5.0 Project: WebContent/cordova/platforms/ios
If you want to do native debugging you can use these projects, and you can also generate them without the binaries by stopping after Step 16.

2 comments:

Jan Mardat said...

This is an excellent document that I think everyone who is dabbling with sencha/phonegap wonderland must read and read again. Putting a sencha/phonegap application together is a really painful process. Many kudo's to you for going thru all these steps and documenting them.

John Valentino said...

Thanks for the kind words.

An important part of this process is understanding how Cordova works, since that is what Sencha Command is using behind the scenes to natively package the application. I had a lot of trouble with this until I just made an app using Cordova by itself, and then a Cordova app with a plugin by itself. This gave me a target state to build towards once I worked Sencha back into the process.

This was a result of a lot of trial and error, involved looking through a lot of Sencha Ant build scripting, and involved a lot of research into the details of the native technologies for the various compile and runtime issues that camp up.

About Me

My Photo

I live and work in the Dallas/Fort Worth area as a Principal Consultant for AppFoundation. I have been working with Java since 2000, Flex since it was in beta release, iOS development since 2008, and Sencha and Ext JS 4 since 2012. I have a Bachelor of Music with a double major in Music Performance and Music Composition, and a Bachelor of Science in Computer Information Science from Texas Christian University. I also have a Master of Science in Software Engineering from Southern Methodist University. I specialize in enterprise development, architecture, design, and continuous integration practices.