Building a Command Line OS X app with RubyMotion
You can build an OS X app that doesn’t present a UI by by setting
true and not creating any UI, but when you copy the binary out of the bundle and try to run it, you get an error that looks something like this:
This is due to the
NSApplicationMain function not being able to find an
Info.plist due to the binary no longer residing inside a bundle. We need to tweak RubyMotion’s compile process to skip this method.
I tracked down the offending snippet of code within
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
As you can see on line 139, it calls
NSApplicationMain which is the source of the error we’re seeing as
NSApplicationMain tries to load the NIB as well as construct the application object. See Matt Gallager’s article on NSApplicationMain for more details.
Patching RubyMotion’s compile step
We could do this one of two ways:
- Create a new OS X command line template
- Monkey-patch the Config class
Given I’m kinda lazy, monkey-patching the config class was the way to go for me :)
Make a directory called
lib in the root of the RubyMotion project, and make a file named
We need to patch the
main_cpp_file_txt method of the
Motion::Project::OSXConfig class, so your file should look something like this:
1 2 3 4 5 6 7
Then you want to paste the original method body in, and then we can start patching!
Let’s remove the
NSApplicationMain call as we won’t be needing that (yet).
Command line applications don’t need an application delegate either, so let’s delete the following lines:
However, we still need an entry point into our Ruby code. Let’s use the pre-existing
delegate_class variable to define the entry point class.
65 66 67 68 69 70 71
Now rather than calling
NSApplicationMain, we’re now calling the
main instance method on the application delegate class (which is
AppDelegate by default).
To make RubyMotion actually pick our code up, we need to
require it in the
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
When you run your app, it should now call
What if I still need a run loop?
I thought I was home scot-free at this point, however when I tried to initialize a
NSWindow in the daemon component of
drawrect, I ran into these errors:
1 2 3 4 5 6 7 8 9 10
These errors are occuring because the app no longer has a connection to the window server since we have removed the
[NSApplication sharedApplication] line, which connects the app to the window server.
All we need to do is call
NSApplication.sharedApplication and run the main event loop:
66 67 68 69 70 71 72 73 74 75
And boom, everything works!
drawrect is now a self-contained command line application that has the ability to create windows and doesn’t have to reside in an app bundle.
Check out the
drawrect project at https://github.com/chendo/drawrect for a fully functioning example.
Let me know if this has been useful to you!