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 togit 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 theRakefile
.
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.
A self-proclaimed ParaHacker and avid Logophile, Thom has been writing software since the days when ALL telephones had wires. Leaving Fortran and COBOL behind and having become bored with Java, Thom is currently a Serial Rails Developer living in The Sunshine State. When not working on Open Source projects, developing articles and tutorials for Learnable, or helping to manage the Sitepoint Forums as a Team Leader, Thom is playing Euro-Games. With a reputation for puns and wordplay, Thom calls himself an avid Logophile and Polyphiloprogenitive Programming Polyglot.