Rake your Objective-C Life Easier

Rake, or Ruby Make, is a really awesome little tool that you should use to simplify your Objective-C workflows, and I’m going to show you why. You heard me right, fool, show you. Rake can help you build sketches. A sketch is what I call a small program that just proves a point or is just an isolated place that you play with your code. It isn’t big enough to warrant its own Xcode project, but isn’t a single-file that you can easily build by typing compiler commands in all day. More often than not, my sketches involve source files from existing projects. Rather than make a new product in the Xcode project or a new Xcode project, I use Rake to rope in the files and automate the build process.

What is Rake?

Rake is a Ruby Domain-Specific Language (DSL) which was originally designed for building projects. What it behaves like is a tool for allowing you to run commands that you define. It’s comparable to make, but it’s many orders of magnitude easier to work with.

In addition to building software, it is also really useful in a lot of other situations that I’ll probably “blog” about later. For now, I want to demonstrate how you can use Rake to build a small Objective-C sketch – although the same Rakefile will also scale up to very large projects without much trouble.

First, try installing Rake. I highly recommend using RVM for all your Ruby needs. If you’re an RVM guy, then all you need to do is run gem install rake. If you’re using OS X’s system Ruby, then I’m afraid you’ll need to run sudo gem install rake.

Seriously, go freaking install RVM. I’m not joking, go do it! There is no better way to work with Ruby than RVM. Don’t worry, I’ll wait.

Next, create a Rakefile. Rake is a command that lives in your PATH, and it looks for a file called Rakefile in the current directory (otherwise known as pwd). The Rakefile is where all your Rake commands live, although you can include other files to add more to your Rakefile, thereby breaking it up into multiple files. Heck, you could use FileList to detect all your Rakefiles and include them! But I find that most of the time all I need is one Rakefile.

Now that you have a Rakefile, let’s show you how to build multiple executable products from a group of shared source files. The rest of the post talks about the techniques shown in this armed and fully operational Rakefile. If you’re already kinda cool with Rake and just want to lift some code for your own use, that link is the way to go. If you want a dissertation on “what is all this black magic?” then keep reading!

To run Rake, you use the rake command, followed by whatever subcommand is in the current Rakefile. An example session:

> rake -T
rake clean                        # Remove any temporary products.
rake clobber                      # Remove any generated file.
rake default_description_example  # Build executable for 'default_descripti...
rake example_main                 # Build executable for 'example_main'
> rake clean clobber
rm -r example_main.o
rm -r NSContainers+DebugPrint.o
rm -r example_main
rm -r default_description_example
> rake
clang example_main.m -DDEBUG -std=c99 -fobjc-arc -c -o example_main.o
clang NSContainers+DebugPrint.m -DDEBUG -std=c99 -fobjc-arc -c -o NSContainers+DebugPrint.o
clang -framework Foundation example_main.o NSContainers+DebugPrint.o -o example_main

Tell it about your files

CC  = 'clang'
CXX = 'clang++'
LD  = CC

PRODUCTS = {
# executable        => source file with the main method
  'main_ex0'        => 'main_ex0.m',
  'main_ex1'        => 'main_ex1.m',
  'pattern_breaker' => 'main_ex2.m'
}

CFLAGS = [
  '-DDEBUG',
  '-std=c99',
  '-fobjc-arc'
].join(' ')
LIBS = ['-framework Foundation'].join(' ')
OBJC_SOURCES = FileList['*.m']
O_FILES = OBJC_SOURCES.ext('.o')

First up, I use clang, which also can chain to ld to link things. I also use the CXX variable for the C++ compiler. Use this for .mm and .cpp files.

Next up, I define the built products. These are the executables that you will want to run. I define a hash of them, so I know the source file their main method occurs in as well as the built executable file I want the linker to create. Using this technique you can easily have a source file that builds a product with a completely different executable name.

CFLAGS is kind of self-explanatory. You set what flags you want to pass into the compiler.

LIBS is where I define the libraries I’m linking against. You’ll almost always have Foundation in there, though on ocassion (if you’re using Objective-C++) you’ll have Boost or something else as well. (Note that for C++ you want to use clang++).

OBJC_SOURCES is a fancy way of telling Rake “all files that end with .m.”

Finally, because Rake is actually a Ruby DSL, you can do almost anything in Rake which you can do in Ruby. So I take the array of Objective-C sources and chop off the .m and add .o, creating the object file each will generate. I could just as easily use any of the following:

# Use a regex
  O_FILES = OBJC_SOURCES.sub(/\.m$/, '.o')
# Because FileList implements Array functions, I can use map on each element
  O_FILES = OBJC_SOURCES.map{|src_file|src_file.ext('.o')}
# Because ext is an extension to string that Rake provides, I could also use a regex
  O_FILES = OBJC_SOURCES.map{|src_file|src_file.gsub(/\.m$/, '.o')}
# If I wanted to do something wonky, I could define a method and use that
# Rake is a DSL, so you can declare functions and classes in a Rakefile
  def change_to_object_file(source_file)
    source_file.ext('.o')
  end
  O_FILES = OBJC_SOURCES.map{|src_file|change_to_object_file(src_file)}

FileList? What Madness is this?

FileList will become your best friend. It’s this freakishly evil cross between a bash prompt and a Ruby Array, allowing you to grab all files of specific names using shell globbing. For example:

FileList['lib/**/*']

Will get me every file in lib, even in subdirectories. But what if I have some crappy situation? How would I exclude something?

FileList['lib/**/*'] do |fl|
  fl.exclude(/^.git*/)
end

They pretty much thought of everything. FileList returns itself, which itself is an extension of an Array. Additionally, a really spiffy method called include is available for tacking on things to an existing FileList later.

o_files = FileList['**/*.o']
o_files.include('product.exe')
DLL_FILES = FileList['**/*.dll']
o_files.include(DLL_FILES)

It’s pretty flexible and can save you a lot of time.

Tell it what to do with your files

First off is rules. You can create a rule to tell Rake that with a specific file extension it is to do something. First off, I’m going to show you a rule to compile everything with a .m into a .o. At first it seems backwards with the result before the source, but you get used to it. (Actually, you just recycle bits of old Rakefiles without knowing what they really do).

rule '.o' => ['.m'] do |t|
  sh "#{CC} #{t.source} #{CFLAGS} -c -o #{t.name}"
end

Next up, file commands. For every source file, I want to tell Rake to make a new file task called the object file that depends on the rule to make the source file (which I showed earlier). Remember that FileList only knows about files that exist right then, so if you have a list of object files generated by FileList before you compile, then it should be empty. It’d be kind of awkward to tell your linker to link nothing to create something when all the files are sitting around on the disk. This is why I made my O_FILES list earlier by replacing .m with .o instead of another FileList declaration.

OBJC_SOURCES.each do |src|
  file src.ext('.o') => src
end

Finally, you need to link everything together to create the executable. This part gets a little hairy because you need to pull the object files for other products out of the object files list before handing it off to the linker. Remember, if the linker finds more than one main method it will fail. This is why my products list is a hash, so I can have executables with different file names. Line two gets pretty weird as it subtracts arrays. The best way to think of this is “I have all my product source files, of which I want to remove the current product’s source file. From these I make them into the object files the compiler generated, then I subtract these unrelated object files from the list of all object files.” Line 3 gives you a spiffy description so you can see the products you can build with rake -T.

PRODUCTS.each do |product, source|
  object_files = O_FILES - (PRODUCTS.values - [source]).map{|f|f.ext('.o')}
  desc 'Build executable for \''+product+'\''
  file product => object_files do |t|
    sh "#{LD} #{LIBS} #{object_files} -o #{t.name}"
  end
end

task :default => PRODUCTS.first

On the last line I set the default task to the first product. The default task is just that: which task runs when you run rake with no arguments.

WTF man that’s weird!

Yeah, it is. Let me try and explain a bit of the task hierarchy:

  • Product knows what object files it needs to link against.
  • Using OBJC_SOURCES we create an empty task for each object file.
  • This task invokes the rule we created earlier, which invokes the compiler. A rule just runs whenever a file task with a matching pattern is run. For more info on rules, see this page. The rule also gets you the incremental build functionality; you could build in the file task itself, but that would constitute a full rebuild every time unless you did some file time checking yourself.

That layer of indirection in the middle threw me off for a long time, and I didn’t see anyone explain it until I was experimenting around with my Rakefiles for this post and it just clicked.

Clean & Clobber

Rake also comes with a handy cleaning tool which can help you get rid of all those nasty .o files when you need to perform a clean build. Clobber allows you to kill the executable files as well. (Don’t forget the .keys part, otherwise you’ll kill a few source files – I learned that one the hard way!)

require 'rake/clean'

CLEAN.include("**/*.o")
CLOBBER.include(PRODUCTS.keys)

Final Notes

Incremental Builds

Rake is pretty intelligent about rule tasks. If the file has changed, then Rake will rebuild that file; if not, then Rake doesn’t force a rebuild of that file. So Rake automatically gets you incremental builds if you have that added layer of indirection to chain a file task to a rule! However, if you change a build flag you will need to run clean first.

Where am I?

Rake is very robust. When you run a Rakefile, the current directory for all commands is the directory in which the Rakefile resides – except if you run a cd command. However, there’s a final trick to this:

cd 'bin' do
  sh "otool -l executable"
  sh "mv executable foo"
end

Yes, it’s pretty spiffy, allowing you to run commands in subdirectories and then automatically pop back to the original directory. It lets you lay out series of commands in a hierarchy without too much trouble.

Inspirational conclusion

In closing, you don’t have to be a crazy Rubyist, or even know too much Ruby, in order to utilize Rake. It’s a powerful tool and can save you a lot of time. I hope you decide to start using Ruby and Rake to get your stuff done faster and more efficiently!