RubyMotion Workflow Customizations

    Thom Parkin
    Share

    rmyourway

    RubyMotion is a set of tools that simplifies the development of iOS applications, compiling source code to native iOS machine language. It shares its heritage with MacRuby, making it a familiar option for experienced Rubyists to develop applications for iPhone/iPod/iPad.
    RubyMotion provides direct access to iOS Classes and Libraries, RubyGems and supports the mixing of Ruby and Objective-C style calling conventions.
    The structure of an iOS application observes MVC (like Rails) and RubyMotion’s Command-Line workflow is driven by a Rakefile; just like Rails.

    Your standard workflow in RubyMotion will probably include Bundler and RVM { As of this writing, RubyMotion requires a minimum of Ruby 1.9 }

    But these similarities to Ruby on Rails are, in many cases, only superficial.

    In this article I want to focus on the Rakefile that is used in RubyMotion applications. And I hope to provide some help in keeping your work a bit more organized (and DRY) once you have several RubyMotion applications in development.

    The techniques and ideas I present here are my own – carry no particular warranty – and are presented as guidance. You are encouraged to take what is presented here and expand it or adjust it to suit your needs.

    If you create something particularly interesting or unique, I hope you share it with the community (in a Gist or repository on Github) and comment to this post.

    The RubyMotion Project Structure

    To instantiate a new application (project) the command is simply motion create applicationName

    This builds the structure of a RubyMotion application like this:

    applicationName
        
          Rakefile
        
         resources
         app
             
              app_delegate.rb
        
         .gitignore
        
         spec
             
              main_spec.rb

    If you are familiar with iOS development (perhaps you have worked in Objective-C with XCode), the appdelegate.rb may be recognizable.

    • The resources directory is where all images, icons and other resources should be maintained.
    • The .gitignore has many sensible defaults already defined for you. Although you will still need to git init a repository for your project.
    • The spec directory is for the tests (you ARE writing tests for your Ruby, are you not?). RubyMotion uses Bacon (an RSpec clone). And a sample spec is created by default.This build system that generates these files is Open Source on Github at HipByte/RubyMotion.The part of this system that will be the focus of this article is the Rakefile.

    The RubyMotion Rakefile is the “Heart and Soul” of your CLI workflow. Very much like Rails, most tasks you need are just a rake command away. You can create customized tasks by editing it and get a list of current tasks with a call to rake -T.

    Here is the list of rake commands for RubyMotion 1.32

    rake archive               # Create an .ipa archive
    rake archive:distribution  # Create an .ipa archive for distribution (AppStore)
    rake build                 # Build everything
    rake build:device          # Build the device version
    rake build:simulator       # Build the simulator version
    rake clean                 # Clear build objects
    rake config                # Show project config
    rake ctags                 # Generate ctags
    rake default               # Build the project, then run the simulator
    rake device                # Deploy on the device
    rake simulator             # Run the simulator
    rake spec                  # Same as 'spec:simulator'
    rake spec:device           # Run the test/spec suite on the device
    rake spec:simulator        # Run the test/spec suite on the simulator
    rake static                # Create a .a static library

    Rake Holds Configuration Details

    Unlike Rails, where you rarely need to modify the Rakefile unless you are developing customized commands, in RubyMotion much of the configuration details that are specific to your application will be in this file. In other words, each application in RubyMotion will have its own – unique – version of the Rakefile.

    Most libraries and gems that you wish to require (due to the static compilation, RubyMotion cannot support ‘include’) will be referenced in the Rakefile. The specific icons used in your application and the Codesign credentials must be added to the Rakefile.

    Also, any credentials for APIs you may be using are read from the Rakefile.

    {You can always check on the current configuration with the commmand rake config (see above)}

    The Dilema

    I have always been quite hesitant to include the Rakefile in my Git repository; particularly on projects I may offer publicly. But since it is an essential part of the whole process it cannot simply be excluded.

    How to obscure any ‘proprietary’ credentials while providing the necessary configuration details has been a point of much discussion in the RubyMotion community. One obvious solution (and a very popular one) is to define “environment variables” for those sensitive pieces of data.

    Although that works well, I want more flexibility and prefer something less tightly coupled to any particular device (I work on multiple computers).

    My Solution

    In my RubyMotion workflow I have chosen to define the Rakefile as a symbolic link in every project. My Rakefile symlink points to a single Rakefile that lives in a parent directory. This is a very DRY approach to managing the Rakefile, all “proprietary” credentials, and any customization (as far as additional Rake Tasks). In order to provide the necessary flexibility, I have modified that base Rakefile to load from a app_properties.rb that lives in every application’s home directory.

    Here is an example of a generic Rakefile. This assumes the Rakefile exists just one level up from any RubyMotion project. {This file has been anonymized for publication}

    #-*- coding: utf-8 -*-
    
    $:.unshift("/Library/RubyMotion/lib")
    require 'motion/project'
    
    # custom rake tasks can go here
    
    desc "Open latest crash log"
    task :log do
      app = Motion::Project::App.config
      exec "less '#{Dir[File.join(ENV['HOME'], "/Library/Logs/DiagnosticReports/#{app.name}*")].last}'"
    end
    
    require './app_properties'  #this is the call to a file that exists in every project
    props = AppProperties.new
    props.requires.each { |r| require r } if props.requires.size > 0
    
    Motion::Project::App.setup do |app|
      app.name = props.name
      app.identifier = props.identifier
      app.delegate_class = props.delegate
      app.version = props.version.scan(/d+/).flatten.first
      app.short_version = props.version.scan(/d+/).first #required to be incremented for AppStore (http://iconoclastlabs.com/cms/blog/posts/updating-a-rubymotion-app-store-submission)
      app.icons = props.icons
      app.device_family = props.devices
      app.interface_orientations = props.orientations
      app.provisioning_profile = props.provisioning
      app.codesign_certificate = props.developer_certificate
      if props.testflight?
            require 'motion-testflight'
        app.testflight.sdk = 'vendor/TestFlight'
        app.testflight.api_token = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
        app.testflight.team_token = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
        app.testflight.distribution_lists = ['Developers', 'Testers']
      end
      if props.pixate?
          require 'rubygems'
          require 'motion-pixate'
        app.pixate.user = 'USER ID'
        app.pixate.key  = 'XXXX-XXXX-XXXX-XXXX-XXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXX-XX'
        app.pixate.framework = 'vendor/PXEngine.framework'
      end
    end

    And a sample of the app_properties.rb file that would be in the root of every RubyMotion project.

    class AppProperties
      VERSION = '0.9'
      APP_DELEGATE = 'AppDelegate' #default
      COMPANY_NAME = 'com.websembly.'
    
      def name
         'App Name Here'
      end
    
      def version
       VERSION
      end
    
      def requires  #add libraries to listed in Rakefile as 'require'
        ["sugarcube"]
      end
    
      def frameworks
        []
      end
    
      def contributors
        ["Thom Parkin" => "https://github.com/ParkinT/"]
      end
    
      def developer_certificate
        'iPhone Developer: Thom Parkin (1337THX1138)'
      end
    
      def provisioning
        './provisioning' #symlink
      end
    
      def testflight?
        false
      end
    
      def pixate?
        true
      end
    
      def delegate
        APP_DELEGATE
      end
    
      def icons
        icn = ["#{self.name}.png", "#{self.name}-72.png", "#{self.name}@2x.png"]
      end
    
      def devices
        [:iphone, :ipad]
      end
    
      def orientations
        [:portrait, :landscape_left, :landscape_right] #:portrait_upside_down
      end
    
      def identifier
        COMPANY_NAME + APP_DELEGATE
      end
    
    end

    You may notice that the provisioning profile is also a symlink.


    And so, in each of my RubyMotion projects the Rakefile that is generated by the RubyMotion create command gets replaced by a personalized app_properties.rb file that contains the generic instructions plus those things unique to that application (like the application name).

    The flexibiliy and value of this continues to grow as I expand my use of libraries and additional helpers in my RubyMotion workflow. For example, I recently started using Pixate in my RubyMotion projects. In order to add this capability to all my RubyMotion projects, I simply updated the master Rakefile and can include the necessary references in each app_properties.rb file where it applies.

    As I said before, this is ONE WAY to accomplish this effect. It may not be the best way and I invite you to enhance it. If you do, please let me know.