RubyMotion Workflow Customizations

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.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

No Reader comments

Comments on this post are closed.