Automated packaging, building, and deployment script for iOS/Android Sencha Touch + Phonegap apps

If you use Sencha Touch + Phonegap to develop apps for multiple platforms, you’ve likely run into the issue of how to manage OS-specific files and build components. For example, I use very different SASS stylesheets for iOS compared to Android, and certainly don’t wish to include unnecessary elements in the final Phonegap packaging for either platform. Of course there’s also the matter of separate OS-specific cordova.js files. Additionally, the steps of packaging the app with Sencha Cmd, then copying the output twice to both my Android and iOS native app’s www/ folder proved quite tedious. Lastly, If you’re using the “freemium” business model and maintain both free and paid versions of your app, the previous step is doubled. As such, I created a quick-and-dirty Python script to automate the build task in it’s entirety, which I’ve copied below.

I keep a common app.json & a _base.scss with shared elements in my Sencha Touch app directory. During each build executed by the script, the appropriate files for each OS are combined with the base files and output to the final native project directory (in a separate project location). After completion, a .SUCCESS or .ERROR file is created to indicate build success or any errors encountered in the event of failure.

If you use this script, be sure to change the directories to reflect that of your own project setups. Save the script as whatever_you_want.py in your Sencha app directory, and simply execute the script. The script is currently configured for iOS and Android, but can easily be extended to incorporate any Sencha/Phonegap supported OS.

custom_js_packager.py

import os, shutil, datetime
from subprocess import call

baseDir =  r"C:\Users\Brandon\Development\git"
senchaDir = baseDir + r"\caffeine\src\main\webapp\mobile"
senchaPackageDir = senchaDir + r"\build\mobile\package"
senchaSASSDir = senchaDir + r"\resources\sass" 

androidAppJSON = senchaDir + r"\app-android.json"
iosAppJSON = senchaDir + r"\app-ios.json"
appJSON = senchaDir + r"\app.json"

androidSCSS = senchaSASSDir + r"\_android.scss"
iosSCSS = senchaSASSDir + r"\_ios.scss"
appSCSS = senchaSASSDir + r"\app.scss"

androidProjectBaseDir = baseDir + r"\caffeine-android"
androidCaffeineDir = androidProjectBaseDir + r"\com.hawkinbj.CaffeinePro.Caffeine"
androidCaffeineProDir = androidProjectBaseDir + r"\com.hawkinbj.Caffeine.Caffeine"
androidWWWDir = r"\assets\www"

iosProjectBaseDir = baseDir + r"\caffeine-ios"
iosCaffeineDir = iosProjectBaseDir + r"\caffeine-ios"
iosCaffeineProDir = iosProjectBaseDir + r"\caffeine-ios-pro"
iosWWWDir = r"\www"

SUCCESS_EXT = ".SUCCESS"
ERROR_FILENAME = "error.ERROR"

def run():
    do_android()
    do_ios()

    print "done!"

def base_do(jsonFileName, scssFileName):
    remove_previous_info_files(jsonFileName)
    clean_package_dir()
    prepare_json(jsonFileName)
    prepare_scss(scssFileName)
    sencha_package()
    create_success_file(jsonFileName)

def do_android():
    print "doing android"
    base_do(androidAppJSON, androidSCSS)
    update_android_www()
    print "finished android"

def do_ios():
    print "doing ios"
    base_do(iosAppJSON, iosSCSS)
    update_ios_www()
    print "finished ios"

def remove_previous_info_files(osName):
	delete_success_file(osName)
	delete_error_file()

def delete_success_file(osName):
    try:
        os.remove(osName + SUCCESS_EXT)
    except:
        pass

def delete_error_file():
    try:
        os.remove(ERROR_FILENAME)
    except:
        pass

def create_success_file(fileName):
    create_and_write_file(fileName + SUCCESS_EXT, "success")

def create_error_file(errorMsg):
    create_and_write_file(ERROR_FILENAME, errorMsg)

def create_and_write_file(fileName, text):
    file = open(os.path.join(senchaDir, fileName), 'w+')
    if text:	
        file.write(str(datetime.datetime.now()) + " " + text)
    file.close()

def prepare_json(fileName):
    errorMsg = "OS-specific app.json files are missing!"

    if os.path.exists(fileName):
        copy_file(fileName, appJSON)
    else:
        create_error_file(errorMsg)
        raise Exception(errorMsg)

def prepare_scss(fileName):
    errorMsg = "OS-specific app.scss files are missing!"

    if os.path.exists(fileName):
        copy_file(fileName, appSCSS)
    else:
        create_error_file(errorMsg)
        raise Exception(errorMsg)

def copy_file(src, dest):
    shutil.copyfile(src, dest)

def copy_tree(src, dest):
    try:
        shutil.copytree(src, dest, symlinks=False)
    except:
        pass

def remove_tree(path):
    shutil.rmtree(path, ignore_errors=True)

def sencha_package():
    call("sencha app build package", cwd=senchaDir)

def update_android_www():
    proAssets = androidCaffeineProDir + androidWWWDir
    regAssets = androidCaffeineDir + androidWWWDir

    base_update_www(proAssets)
    base_update_www(regAssets)

def base_update_www(path):
    remove_tree(path)
    copy_tree(senchaPackageDir, path) 

def update_ios_www():
    proAssets = iosCaffeineProDir + iosWWWDir
    regAssets = iosCaffeineDir + iosWWWDir

    base_update_www(proAssets)
    base_update_www(regAssets)

def clean_package_dir():
    remove_tree(senchaPackageDir)

run()

if you have any questions, leave a comment and I’ll get back to you. Hopefully others find this useful or are inspired to roll their own solution. Cheers!

Requirements are Sencha Cmd (environment variable must be set) and Python.