Having fun with MacRuby on Snow Leopard

macruby_logo
As the owner of the semi-official MacRuby github mirror I would like to introduce you MacRuby.

MacRuby is a Ruby bridge to Apple’s Cocoa/Objective-C that enables a developer to take fully advantage of the OS X frameworks while developing the application in Ruby programming language.

Quoting the official README:

MacRuby is a Ruby implementation based on Mac OS X technologies, such as the Objective-C runtime and garbage collector, the CoreFoundation framework and the LLVM compiler infrastructure.

Neat I’d say.

Let’s start by installing MacRuby (remember: you have to install Xcode from official Snow Leopard DVD if you want to compile from source).

Update: check out the MacRuby nightly builds.

First we need the LLVM trunk, and keep in mind that PowerPC is not supported.

Start by grabbing a stable version of LLVM from their svn server.

Update: the recommended LLVM revision is now 82747.

svn co -r 82747 https://llvm.org/svn/llvm-project/llvm/trunk llvm-trunk
cd llvm-trunk
./configure

Now you should know that compiling LLVM takes a long while.
To help speed up things you can take advantage of using two cores of your CPU, which is the case of the majority of the newer machines.

UNIVERSAL=1 UNIVERSAL_ARCH="i386 x86_64" ENABLE_OPTIMIZED=1 make -j2

However this can leave your machine in a sort of unresponsitive state, so if you prefer you can take away the “-j2″ parameter.

After your usual cup of coffee we can install LLVM with this command:

sudo env UNIVERSAL=1 UNIVERSAL_ARCH="i386 x86_64" ENABLE_OPTIMIZED=1 make install

Now we are ready to build MacRuby.

Start by cd-ing, if you’re not there already, into your preferred directory, we are going to use Git to download from trunk.

Update: instructions on how to install git on Snow Leopard are here.

git clone git://github.com/masterkain/macruby.git
cd macruby
rake
sudo rake install

That’s it.

Installing MacRuby won’t touch your existing ruby binaries, in fact it will install them prefixed with “mac”.
For instance you can open up MacRuby’s version of IRB with the command:

macirb  --simple-prompt

and start playing around.

There are other commands, just type “mac” and press tab in your terminal to see other binaries.

The very first thing to try out is to load a framework, namely ‘Cocoa’, so fire up your macirb and give it a try:

kain-osx:~ kain$ macirb
irb: warn: can't alias exit from irb_exit.
irb(main):001:0> framework 'Cocoa'
=> true
irb(main):002:0>

Go on and try loading an Objective-C class:

irb(main):002:0> NSSound.ancestors
=> [NSSound, NSObject, Kernel]

All classes in MacRuby always inherit from NSObject:

irb(main):003:0> Regexp.ancestors
=> [Regexp, NSObject, Kernel]

There are many things that are cool/exciting in this technology, for example take a look at this:

irb(main):004:0> String
=> NSMutableString

Uh. Seems like Ruby classes are mapped to their equivalents in Objective-C.
That’s right, and this happen with no data loss nor they require manual conversion!

One difference with RubyCocoa is that MacRuby supports keyed arguments, for instance:

MacRuby:

1
2
3
4
window = NSWindow.alloc.initWithContentRect frame, 
    styleMask:NSBorderlessWindowMask,
    backing:NSBackingStoreBuffered,
    defer:false

RubyCocoa:

1
2
3
4
5
window = NSWindow.alloc.initWithContentRect_styleMask_backing_defer(
    frame,
    NSBorderlessWindowMask,
    NSBackingStoreBuffered,
    false)

Objective-C:

1
2
3
4
5
NSWindow *window = [[NSWindow alloc]
    initWithContentRect:frame
    styleMask:NSBorderlessWindowMask
    backing:NSBackingStoreBuffered
    defer:false];

Let’s see how one can send message to our objects using MacRuby.
There are different syntaxes to accomplish this, let’s see some examples along with their Objective-C equivalent:

1
2
person.setName(name) # [person setName:name];
person.name = name # [person setName:name];

As we already said every class here inherits from NSObject, therefore is not necessary to explicitly define subclassing, like we did in RubyCocoa:

1
2
3
4
# RubyCocoa
class TestController < OSX::NSObject
# MacRuby
class TestController

Let’s build a very simple app to demonstrate that MacRuby works, open Xcode and choose New Project; from User Templates go into Application and select MacRuby Application, press “Choose..”.

A dialog will appear, give the application a name you prefer (“Hello World” will suit) and press Save. I usually save my project in a proper dedicated folder.

At this point Xcode will show you its main window, click on the left on “Other sources”.

You will see two files in there, main.m and rb_main.rb .
Xcode main interface
main.m is a small file which contains the method that calls the MacRuby initializer.
rb_main.rb is the ruby script that will be executed once your application started.

If you click on “Build and run” the application will start, showing you an empty window. cmd+Q for quitting the application.

Now click from the top menu File -> New file or just press cmd+N, from the left select “Other” and click on “Empty file”.
New file dialog

Press next and in the File name input write “MyController.rb”.
Controller generation

Press “Finish”.

If you see that MyController.rb on the left is floating around, just drag it into “Classes” folder. Be aware that Xcode folders doesn’t reflect the actual data structure on filesystem, but rather represent a logical approach.

Edit MyController.rb by selecting it and writing in the editor window:

1
2
3
4
5
6
  class MyController < NSWindowController
    attr_writer :button
    def clicked(sender)
      puts "Button clicked!"
    end
  end

Adding code

cmd+s to save the file.

Now, this code defines a subclass of NSWindowController and have two methods, a setter:

1
attr_writer :button

and clicked method:

1
def clicked(sender)

Those will serve the purpose especially with Interface Builder. In fact IB will see them respectively as an outlet and an action (for Obj-C users IBOutlet, IBAction).

To make sure that Interface Builder will pick up our method as an action, remember that it must have only one argument and that must be named sender.

We now have to wire our class with Interface Builder (from now on “IB”, go into the “Resources” folder and double click on MainMenu.nib .

In IB we need to instantiate our class to make it aware that we want to use one, to do that from the Library pane drag and drop a NSObject (blue cube) to the main window and select it.

From the inspector pane, as show in the next picture, select MyController as the Object Class.

ib

Now drag a NSButton always from the Library pane to your main window.
window

While having the just placed button selected Press CTRL and left click, this will make a line appear; place it over My Controller we defined earlier (blue cube) in your MainMenu.nib window.
When releasing the mouse a little hud-like menu will appear, named “Received Actions”.
Release the mouse when hovering “clicked:”.

That’s it, we made a connection from this button to our method in our MyController.

Save the Nib file, go back to Xcode and press “Build and run”.

Your application will start, showing a window and a button.
Leaving the application running let’s go back to xcode, and press the little icon on the right with “gdb” on it, this will open up the debugger window.

Now, leaving this window visible let’s try clicking on our button and see what happen.
If all went right we should see a bold “Button clicked!”.

That’s it for today, however there is much more to talk about, say HotCocoa.

For further documentation please refer to the official website of MacRuby project.

$1.99 domains with SSL purchase!

19 thoughts on “Having fun with MacRuby on Snow Leopard

  1. Pingback: Having fun with MacRuby « The Android Life

  2. Pingback: Having fun with MacRuby | Mac Affinity

  3. Thanks for the detailed instructions except that the git command is not installed by the Snow Leopard CD. Could you include instructions for installing the git command?

  4. After building/installing llvm when trying to build macruby `rake` I get this:

    /usr/bin/ranlib libmacruby-static.a
    cd ext/ripper
    ../../miniruby -I../.. -I../../lib -r rbconfig -e “RbConfig::CONFIG['libdir'] = ‘../..’; require ‘./extconf.rb’”
    no such file to load — libyaml (LoadError)
    rake aborted!
    Command failed with status (1): [../../miniruby -I../.. -I../../lib -r rbco...]

  5. @Michael Guterl I tried compiling again against most recent trunk and got no errors so far, so I have no idea why is that happening to you, but I see a certain movement on trac/mailing list about similar problems.

    if you can’t get around file a ticket on http://www.macruby.org/trac/report including your info, eg. system, architecture and env variables, ask the mailing list or include more useful variables here and I’ll try to help.

  6. Hello Again:
    Thanks for your quick response, but now I have another newbe question. I didn’t notice any errors doing the MacRuby install but where did it put the Unix executables? I think my commands still load the 0.4 release:

    Last login: Mon Aug 31 02:46:16 on ttys000
    new-host:~ robertrice$ macirb -v
    /Library/Frameworks/MacRuby.framework/Versions/0.4/usr/bin/macruby: [BUG] Segmentation fault
    MacRuby version 0.4 (ruby 1.9.1) [universal-darwin9.5, x86_64]

    – stack frame ————
    0000 (0×302000060): 00000004
    0001 (0×302000068): 00000000
    0002 (0×302000070): 00000004
    0003 (0×302000078): 00000000 <- lfp <- dfp
    – control frame ———-
    c:0002 p:-1073823344 s:0004 b:0004 l:000003 d:000003 TOP
    c:0001 p:0000 s:0002 b:0002 l:000001 d:000001 TOP
    —————————
    – backtrace of native function call (Use addr2line) –
    0x10010f301
    0×100029544
    0×100029628
    0x1000c245d
    0x7fff8224a14a
    0×100400010
    0x1000eb00f
    0x1000e9685
    0x1000e6b66
    0x1000edcfb
    0x1000f1a25
    0x1000f1a84
    0x1000f1be5
    0x100126cbb
    0x1000bfadb
    0x1000f83df
    0x1000bf250
    0x10002fa65
    0x100000f17
    0x100000ea4
    0×3
    ——————————————————-
    Abort trap
    new-host:~ robertrice$

  7. @Robert Rice
    kain-osx:~ kain$ which macruby
    /usr/local/bin/macruby
    kain-osx:~ kain$ macirb -v
    irb 0.9.5(05/04/13)
    kain-osx:~ kain$ macruby -v
    MacRuby version 0.5 (ruby 1.9.0) [universal-darwin10.0, x86_64]

    you may want to check your $PATH variable, either in .bash_login or .bash_profile in your homedir

    btw this tutorial is about a fresh copy of Snow Leopard, not an upgrade from Leopard.

  8. Hi,

    Cool howto! You might want to note that now and for the forseeable future ths will only work on 64bit capable machines. There are a few machines that can install 10.6 and run it just fine, but have no support for 64 bit and cannot build current macruby (as confirmed on the mailing list in Aug), although it is planned to have 32 bit support (back) in the 0.5 release.

    If anyone is in doubt, this snippet may assist, run in Terminal:
    sysctl hw.cpu64bit_capable

    hth,
    adric

  9. Hi,
    I’ve installed MacRuby using your howto, but still not working. When I write controller (eg. MainController) and make an Object in IB, set class to MainController, IB still doesn’t see MainController attributes and methods :( I’m using Xcode 3.2

  10. @alfanick
    if you mean that it doesn’t evaluate attribute and method in the inspector window I experienced this too, if you notice the window is different to the previous versions of xcode (this is User Defined Runtime Attributes).
    however if you try to link some outlets you will find that they are present in IB.

    See the screenshot after “From the inspector pane, as show in the next picture, select MyController as the Object Class.”

  11. Pingback: MacRuby & Snow Leopard « CJ-Chung's Blog

  12. @Michael Guterl
    Michael, I came across the same problem. Could you provide some pointers how to fix it? It is not clear to me how to do it from your last posting. Thanks!

  13. I just tried re-installing MacRuby after an upgrade to Snow Leopard and ran into this error:

    /Users/peterschroder/macruby/lib/rubygems/spec_fetcher.rb:1:in `’: no such file to load — zlib (LoadError)
    from /Users/peterschroder/macruby/lib/rubygems/source_index.rb:14:in `’
    from /Users/peterschroder/macruby/lib/rubygems.rb:864:in `’
    rake aborted!
    Command failed with status (1): [./miniruby -I. -I./lib bin/rubyc --interna...]

    I did not find anything on track or via google.

    Any hints?!

  14. failed to compile MacRuby on

    % uname -a
    10.2.0 Darwin Kernel Version 10.2.0: Tue Nov 3 10:37:10 PST 2009; root:xnu-1486.2.11~1/RELEASE_I386 i386

    llvm is compiled installed at /usr/local/

    the error:

    /usr/bin/g++-4.2 -I/usr/local/include -D_DEBUG -D_GNU_SOURCE -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -O3 -fno-common -Woverloaded-virtual -I. -I./include -g -Wall -arch i386 -arch x86_64 -Wno-parentheses -Wno-deprecated-declarations -Werror -I./icu-1060 -c compiler.cpp -o compiler.o
    compiler.cpp: In constructor ‘RoxorCompiler::RoxorCompiler(bool)’:
    compiler.cpp:196: error: ‘class llvm::LLVMContext’ has no member named ‘getMetadata’
    compiler.cpp:198: error: ‘class llvm::LLVMContext’ has no member named ‘getMetadata’
    compiler.cpp: In member function ‘void RoxorCompiler::attach_current_line_metadata(llvm::Instruction*)’:
    compiler.cpp:744: error: ‘DILocation’ was not declared in this scope
    compiler.cpp:744: error: expected `;’ before ‘loc’
    compiler.cpp:749: error: ‘class llvm::LLVMContext’ has no member named ‘getMetadata’
    compiler.cpp:749: error: ‘loc’ was not declared in this scope
    compiler.cpp: In member function ‘llvm::Value* RoxorCompiler::compile_slot_cache(long unsigned int)’:
    compiler.cpp:1151: error: no matching function for call to ‘llvm::Instruction::clone()’
    /usr/local/include/llvm/Instruction.h:50: note: candidates are: virtual llvm::Instruction* llvm::Instruction::clone(llvm::LLVMContext&) const
    compiler.cpp: In member function ‘llvm::Value* RoxorCompiler::compile_landing_pad_header(const std::type_info&)’:
    compiler.cpp:1893: error: ‘eh_selector’ is not a member of ‘llvm::Intrinsic’
    compiler.cpp:1921: error: ‘eh_typeid_for’ is not a member of ‘llvm::Intrinsic’
    compiler.cpp: In member function ‘void RoxorCompiler::compile_ivar_slots(llvm::Value*, llvm::iplist<llvm::Instruction, llvm::ilist_traits >&, llvm::ilist_iterator)’:
    compiler.cpp:2974: error: no matching function for call to ‘llvm::Instruction::clone()’
    /usr/local/include/llvm/Instruction.h:50: note: candidates are: virtual llvm::Instruction* llvm::Instruction::clone(llvm::LLVMContext&) const
    compiler.cpp: In constructor ‘RoxorCompiler::RoxorCompiler(bool)’:
    compiler.cpp:196: error: ‘class llvm::LLVMContext’ has no member named ‘getMetadata’
    compiler.cpp:198: error: ‘class llvm::LLVMContext’ has no member named ‘getMetadata’
    compiler.cpp: In member function ‘void RoxorCompiler::attach_current_line_metadata(llvm::Instruction*)’:
    compiler.cpp:744: error: ‘DILocation’ was not declared in this scope
    compiler.cpp:744: error: expected `;’ before ‘loc’
    compiler.cpp:749: error: ‘class llvm::LLVMContext’ has no member named ‘getMetadata’
    compiler.cpp:749: error: ‘loc’ was not declared in this scope
    compiler.cpp: In member function ‘llvm::Value* RoxorCompiler::compile_slot_cache(long unsigned int)’:
    compiler.cpp:1151: error: no matching function for call to ‘llvm::Instruction::clone()’
    /usr/local/include/llvm/Instruction.h:50: note: candidates are: virtual llvm::Instruction* llvm::Instruction::clone(llvm::LLVMContext&) const
    compiler.cpp: In member function ‘llvm::Value* RoxorCompiler::compile_landing_pad_header(const std::type_info&)’:
    compiler.cpp:1893: error: ‘eh_selector’ is not a member of ‘llvm::Intrinsic’
    compiler.cpp:1921: error: ‘eh_typeid_for’ is not a member of ‘llvm::Intrinsic’
    compiler.cpp: In member function ‘void RoxorCompiler::compile_ivar_slots(llvm::Value*, llvm::iplist<llvm::Instruction, llvm::ilist_traits >&, llvm::ilist_iterator)’:
    compiler.cpp:2974: error: no matching function for call to ‘llvm::Instruction::clone()’
    /usr/local/include/llvm/Instruction.h:50: note: candidates are: virtual llvm::Instruction* llvm::Instruction::clone(llvm::LLVMContext&) const
    lipo: can’t figure out the architecture type of: /var/folders/Xj/Xj7yKiCCE6eivYLdz-EJqE+++TI/-Tmp-//ccUtC56R.out
    rake aborted!
    Command failed with status (1): [/usr/bin/g++-4.2 -I/usr/local/include -D_...]

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">