Cuckoo for Cocoa Dev

A blog for all those Cuckoo for Cocoa Development

Using Rake for Command Line Builds

Last post I showed you how to install Cedar using CocoaPods to provide us with a convenient way of handling dependencies, and to get to our end goal (automated builds). Now I demonstrate the wonders of Rake. Rake is short for “Ruby make,” with make being a way to script processes using the terminal (usually UNIX/LINUX wizards use this).

Rake is a powerful tool, and easy way to create “tasks” that can be run on the command line using short Ruby scripts. In this post, I will show you how to create some basic tasks that we will be using for future posts and give some insight to the mysterious Rakefile that is present in the demo project I’ve been using for previous blog posts.

To get started, we need the “rake” gem installed locally in our project Gemfile:

Gemfile
1
2
3
4
source "https://rubygems.org"

gem 'rake'
gem 'cocoapods', '0.32.1'

Since we don’t really care about the rake version itself, we just specify “rake” without any hard version requirement. Let’s bundle!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ bundle
Resolving dependencies...
Using rake 10.3.1
Using i18n 0.6.9
Using multi_json 1.9.2
Using activesupport 3.2.17
Using claide 0.5.0
Using fuzzy_match 2.0.4
Using json_pure 1.8.1
Using nap 0.7.0
Using cocoapods-core 0.32.1
Using cocoapods-downloader 0.5.0
Using cocoapods-try 0.2.0
Using colored 1.2
Using escape 0.0.4
Using open4 1.3.3
Using xcodeproj 0.16.1
Using cocoapods 0.32.1
Using bundler 1.6.1
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.

Now we are ready to roll out a Rakefile:

1
$ touch Rakefile

Using any text editor of your choice, let’s add a simple clean target task:

Rakefile
1
2
3
4
5
6
7
8
9
10
desc "Clean target"
task :clean, :target do |task, args|
  clean args.target
end

private

def clean(target_name)
  sh "xcodebuild -target #{target_name} clean"
end

Lot’s of stuff going on here. To create a task, we provide a description, and then a task beneath it. What is special about this clean task is that it takes a target parameter when you use it via the command line (:target do |task, args|). The task itself just calls a private ruby function called clean() that takes the passed in target argument, and then runs a “xcodebuild” command. Xcodebuild is a command line utility for running builds, cleans, tests via the command line that we will use for the remainder of this blog post. The sh() function is used to run the command string given in the terminal.

Since we have a Rakefile with a task, we can check the existence of tasks in the Rakefile using the following command:

1
2
$ bundle exec rake -T
rake clean[target]  # Clean target

If you look closely, the command and description given is exactly the same as the command description given in the Rakefile itself. Let’s run the clean task on the CuckooForCedar target:

1
2
3
4
5
6
$ bundle exec rake clean["CuckooForCedar"]
xcodebuild -target CuckooForCedar clean

  ... bla bla bla, lots of Xcode clean messages ...

** CLEAN SUCCEEDED **

Let’s create another helpful clean task. Rather than having to specify the target, let’s create a task that will clean all targets in the project:

Rakefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
desc "Clean target"
task :clean, :target do |task, args|
  clean args.target
end

desc "Clean all targets"
task :clean_all_targets do
  clean_all_targets
end

private

def clean(target_name)
  sh "xcodebuild -target #{target_name} clean"
end

def clean_all_targets
  sh "xcodebuild -alltargets clean"
end

Again, if we do -T we will get the new task:

1
2
3
$ bundle exec rake -T
rake clean[target]      # Clean target
rake clean_all_targets  # Clean all targets

Running the new task will clean all targets:

1
2
3
4
5
6
$ bundle exec rake clean_all_targets
xcodebuild -target CuckooForCedar clean

  ... bla bla bla, lots of Xcode clean messages ...

** CLEAN SUCCEEDED **

Let’s step it up a notch and create a rake task to build the CuckooForCedar target. In order to do so in this project, I’m going to use a great xcodebuild tool companion called xc|pretty. This tool allows our build messages to be much cleaner and run the xcodebuild command a tad bit faster. To use it, let’s add it as a gem locally, and bundle:

Gemfile
1
2
3
4
5
source "https://rubygems.org"

gem 'rake'
gem 'cocoapods', '0.32.1'
gem 'xcpretty', '0.1.5'
1
2
3
4
5
6
7
8
9
10
$ bundle
Fetching gem metadata from https://rubygems.org/...........
Fetching additional metadata from https://rubygems.org/..
Resolving dependencies...

... other gems installed/using here ...

Using xcpretty 0.1.5
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.

Now that xcpretty is installed, we can use it to construct a nice build script to use for the CuckooForCedar target:

Rakefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
... clean target tasks ...

desc "Build CuckooForCedar"
task :build_cuckoo_for_cedar do
  Rake::Task[:clean].invoke "CuckooForCedar"
  build "CuckooForCedar"
end

private

... clean private functions ...

def build(target_name)
  execute_xcodebuild target_name
end

def execute_xcodebuild(target_name, build_action = "build")
  sh "xcodebuild -workspace CuckooForCedar.xcworkspace -scheme '#{target_name}' -sdk iphonesimulator -configuration Release #{build_action} | xcpretty -tc ; exit ${PIPESTATUS[0]}" rescue nil
end

Usually when you do a build, you want to do a clean really quick on the target to make sure that everything is in a good reproducible state. In this build task, I call the “clean target” task using “invoke.” Once that completes, the build function then uses the sh() function to fire off the constructed xcodebuild string. Let’s examine this string for a moment:

1
"xcodebuild -workspace CuckooForCedar.xcworkspace -scheme '#{target_name}' -sdk iphonesimulator -configuration Release #{build_action} | xcpretty -tc ; exit ${PIPESTATUS[0]}"

While the flags can further investigated by checking out the xcodebuild page, I want to mention that these are the settings that travis-ci likes. If you don’t want to spend an entire evening checking out the docs, you can just pretend that this is magic and it will all work out fine. I want to mention that xcpretty is used when the output of the xcodebuild is “|” (piped) to it, and the flag -tc means that tests will be displayed as dots and will be colored. Since we will will eventually be using travis-ci, we want xcpretty to return the same status code as xcodebuild. This is accomplished using exit ${PIPESTATUS[0]}. Finally, we do a nil rescue to do nothing if something bad happens. I’ve also created a default parameter called build_action = "build" since we will be using the same function for tests in the next few steps.

Now that we have a build task, let’s run it!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ bundle exec rake -T
rake build_cuckoo_for_cedar  # Build CuckooForCedar
rake clean[target]           # Clean target
rake clean_all_targets       # Clean all targets
$ bundle exec rake build_cuckoo_for_cedar
xcodebuild -target CuckooForCedar clean

... bla bla messages ...

=== CLEAN TARGET CuckooForCedar OF PROJECT CuckooForCedar WITH THE DEFAULT CONFIGURATION (Release) ===

... bla bla more messages ...

** CLEAN SUCCEEDED **

xcodebuild -workspace CuckooForCedar.xcworkspace -scheme 'CuckooForCedar' -sdk iphonesimulator -configuration Release build | xcpretty -tc ; exit ${PIPESTATUS[0]}

... bla bla bla more messages ...

You know what they say, no news is good news. Since no error message is given, that means that everything was built, A-OK.

Now that we are building our main target, let’s create a task to run our specs:

Rakefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
... clean target tasks ...

... build CuckooForCedar task ...

desc "Run Specs"
task :specs do
  Rake::Task[:clean].invoke "Specs"
  run_tests "Specs"
end

private

... clean private functions ...

... build CuckooForCedar private functions ...

def run_tests(test_target_name)
  execute_xcodebuild test_target_name, "test"
  tests_failed test_target_name unless $?.success?
end

def execute_xcodebuild(target_name, build_action = "build")
  sh "xcodebuild -workspace CuckooForCedar.xcworkspace -scheme '#{target_name}' -sdk iphonesimulator -configuration Release #{build_action} | xcpretty -tc ; exit ${PIPESTATUS[0]}" rescue nil
end

def tests_failed(test_target_name)
  puts red "#{test_target_name} failed"
  exit $?.exitstatus
end

def red(string)
  "\033[0;31m! #{string}"
end

Just like with the build task, let’s run the clean task first. Once the target is clean, we run the tests using the same xcodebuild command using the test build_action. We also created a couple of functions to handle failures. The tests_failed function is called is there is no “success.” This is turn calls another function, tests_failed, which prints a message failure in red (using the function red), and then finally exits.

Let’s DO IT!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ bundle exec rake -T
rake build_cuckoo_for_cedar  # Build CuckooForCedar
rake clean[target]           # Clean target
rake clean_all_targets       # Clean all targets
rake specs                   # Run Specs
$ bundle exec rake specs
xcodebuild -target CuckooForCedar clean

... bla bla messages ...

=== CLEAN TARGET CuckooForCedar OF PROJECT CuckooForCedar WITH THE DEFAULT CONFIGURATION (Release) ===

... bla bla more messages ...

** CLEAN SUCCEEDED **

xcodebuild -workspace CuckooForCedar.xcworkspace -scheme 'Specs' -sdk iphonesimulator -configuration Release test | xcpretty -tc ; exit ${PIPESTATUS[0]}

... bla bla bla more messages ...

.......

Executed 7 tests, with 0 failures (0 unexpected) in 0.0017 (0.0017) seconds

To close this up, let’s create a default task to be run when a user enters rake on the terminal:

Rakefile
1
2
3
task :default => :specs

... all other junk below ...

Now we can run the specs simply by using rake at the terminal:

Rakefile
1
2
3
4
5
$ bundle exec rake

... runs the specs ...

Executed 7 tests, with 0 failures (0 unexpected) in 0.0017 (0.0017) seconds

While all of this can be done using Xcode in development, it’s extremely valuable to add automated build tasks to your project. Rake makes this easy, and this Rakefile can also be used as a template for other build scripts for various projects. Special thanks to the guys at AFNetworking, on which this Rakefile was based on.

Whew… A lot of stuff to cover. After all these “Dev-Ops” posts, I feel about as exhausted as I do after I hit up an “all-you-can-eat” buffet.

Comments