以前、「Objective-CのテストやるのにOCUnit使うよりも、RSpec使えたら素敵なんじゃね」と思って、色々試行錯誤してみたことがある。

とりあえずそれっぽいことができるところまでは行ったものの、そのあと特に使う機会もなくそのまま放置していたのだけど、今度は本格的にObjective-Cのフレームワークを作らなきゃならなくなりそうなのでまたRubyCocoaなどいじる。そこでふと思い付いた。せっかくMacRubyがあるんだから、今度はMacRubyで同じことをやってみたらどうか、と。RubyとObjCのブリッジであるRubyCocoaよりも、VMのレベルでObjCと融合してるMacRubyのが色々融通効くんじゃね、もしかしたら結構深くObjCのオブジェクトいじりたくなるかもしんないし。

あれ、読み込めない

というわけでまずはObjCのFrameworkを作ってRubyのスクリプトから読み込ませてみる。NSBundle#loadを呼びだすのでもいいけどMacRubyだったらframeworkって構文があるな、これフルパス指定してあげれば任意のFramework読み込めるみたい。

こういうクラスを含むNamake.frameworkというのを作ったとして、

#import 

@interface Sloth : NSObject {
    NSString *name;
}

- (NSString *)name;
- (void)setName:(NSString *)aName;

@end

それを使うRubyスクリプトはこんなふうに書く。

#!/usr/local/bin/macruby

framework 'build/Release/Namake.framework'
# こっちでもOK
#bundle = NSBundle.bundleWithPath('build/Release/Namake.framework')
#bundle.load
# /System/Library/Frameworks以下にあるフレームワークとかだったら、
# フレームワーク名だけで行ける
#framework 'Cocoa'

sloth = Sloth.new # Sloth.alloc.init
sloth.name = 'faultier'
p sloth
puts sloth.name

framework 'hoge'のお陰ですっきりして見える。紛うことなきRubyのコードだ。早速実行。

$ macruby mrb_test.rb
2009-04-18 22:19:09.756 macruby[773:10b] Error loading /Users/faultier/Projects/Namake/build/Debug/Namake.framework/Namake:  dlopen(/Users/faultier/Projects/Namake/build/Debug/Namake.framework/Namake, 265): no suitable image found.  Did find:
	/Users/faultier/Projects/Namake/build/Debug/Namake.framework/Namake: mach-o, but wrong architecture
./mrb_test.rb:3:in `framework': framework at path `build/Debug/Namake.framework' cannot be loaded: Error Domain=NSCocoaErrorDomain Code=3585 UserInfo=0x800621ee0 "The bundle “Namake” could not be loaded because it does not contain a version for the current architecture." (dlopen_preflight(/Users/faultier/Projects/Namake/build/Debug/Namake.framework/Namake): no suitable image found.  Did find: (RuntimeError)
	/Users/faultier/Projects/Namake/build/Debug/Namake.framework/Namake: mach-o, but wrong architecture)
	from ./mrb_test.rb:3:in `<main>'

おおぅ…。何、何言ってるの。architectureがおかしいってなんだよ。だってこれRubyCocoaや素のObjective-Cからはちゃんと読めるよ…?

で、調べてみたところ、MacRubyはデフォルトでは64-bitのバイナリを読むらしい。なるほど。なので、↑のを実行するときに、archで-i386を指定してやると一応実行できる。

$ arch -i386 macruby mrb_test.rb
#<Sloth:0x12d06d0>
faultier

もうちょいちゃんとした解決策としては、ターゲットにx86_64が含まれるようにビルドすればいい。あと、MacRubyはObjCのGCを使ってるので、GCサポートを有効にしてビルドしてないと勿論読み込めないので注意。

macgemがアレげ

さてフレームワークはちゃんと読み込めた。そしたら今度はRSpecだ。えーと、macruby系のコマンドはmacってprefixが付くのでgemはmacgemか。

$ sudo macgem install rspec
Password:
ERROR:  Error installing rspec:
	rspec requires  (, runtime)

えー…。ここでも詰まるか。一応調べては見たけど、なんかバグレポートにも上がってたし、これはまだ未解決なのかな。そうでなくてもRSpecは案外依存関係が多くて、macrubyみたいな環境で使おうと思うとあっちのパスを直してこっちのライブラリを直して、みたいなことになりそうなのでちょっと心が折れかける。

で、しかたないなーtest/unit使うのかーとかぼやいてたら「そんなあなたにBacon」と言われたので、Baconを入れてみることにした。RSpecっぽい構文だけど最低限の要素に絞ってシンプルにしたものらしい。Ramazeで使われているようだ。mockとかが入ってないのは大分物足りないけども、同じ機能でも見た目の差は考え方の差になる程度には大きい。背に腹はかえられない。

$ sudo macgem install bacon
Password:
Successfully installed bacon-1.1.0
1 gem installed
Installing ri documentation for bacon-1.1.0...
Updating class cache with 17 classes...
Installing RDoc documentation for bacon-1.1.0...

おお、サクっと入った。素敵。ところでbaconコマンドが見あたらないんだけどどこに行ったんだ、と思ってたら、

/Library/Frameworks/MacRuby.framework/Versions/0.4/usr/lib/ruby/Gems/1.9.1/gems/bacon-1.1.0/bin/bacon

こんなところにいらっしゃいました。確かにデフォルトのRubyから入れたコマンドを上書きされても困るし、これはこれでいい。他のコマンドにならってmacbaconって名前でPATHの通ってるとこにシンボリックリンクを張ってやることにする

$ macbacon --version
/opt/local/lib/ruby/vendor_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require': no such file to load -- bacon (LoadError)
	from /opt/local/lib/ruby/vendor_ruby/1.8/rubygems/custom_require.rb:31:in `require'
	from /usr/local/bin/macbacon:88
	from /opt/local/lib/ruby/1.8/optparse.rb:1262:in `call'
	from /opt/local/lib/ruby/1.8/optparse.rb:1262:in `parse_in_order'
	from /opt/local/lib/ruby/1.8/optparse.rb:1249:in `catch'
	from /opt/local/lib/ruby/1.8/optparse.rb:1249:in `parse_in_order'
	from /opt/local/lib/ruby/1.8/optparse.rb:1243:in `order!'
	from /opt/local/lib/ruby/1.8/optparse.rb:1334:in `permute!'
	from /opt/local/lib/ruby/1.8/optparse.rb:1355:in `parse!'
	from /usr/local/bin/macbacon:93
	from /opt/local/lib/ruby/1.8/optparse.rb:787:in `initialize'
	from /usr/local/bin/macbacon:11:in `new'
	from /usr/local/bin/macbacon:11

ありゃ。ああそっか、多分shebangとかLOAD_PATHとか書き換えなきゃなんだな。

と、思ったんだけど、実はそんなことしなくても良かったことが判明。この次に書いてあることは実行せず、追記のところまで読んで下さい。

#!/usr/bin/env ruby
# -*- ruby -*-

require 'optparse'
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '../lib/')
module Bacon; end 

ビンゴ。ってことでこれを、

#!/Library/Frameworks/MacRuby.framework/Versions/0.4/usr/bin/macruby                    
# vim: filetype=ruby fileencoding=utf-8                                                 

require 'optparse'
$LOAD_PATH.unshift '/Library/Frameworks/MacRuby.framework/Versions/0.4/usr/lib/ruby/Gems/1.9.1/gems/bacon-1.1.0/lib/'
module Bacon; end

こんな風に書き換える。マジックコメントをvimにしたのは単に俺がvim使いだから。MacRubyは1.9.1相当なので、encodingはちゃんと指定しておいた方がいいな。パスがベタ書きなのは気持ち悪いと言えば悪いけど、まぁ移動することも無いので別にいいか。

$ macbacon --version
bacon 1.1

できたできた。ふー。このくらいのとこまではmacgemでやってくれるようになったら嬉しいんだけど、なんつーか面倒臭そうではあるね。とにかくこれでMacRuby+Bacon環境が出来たので少し休憩。

追記

上の作業をする前にデフォルトのRubyの方でもBaconを入れてたんだけど、今度はそっちの挙動が何か変だ、と思ったら…

$ cat /usr/bin/bacon
#!/Library/Frameworks/MacRuby.framework/Versions/0.4/usr/bin/macruby
#
# This file was generated by RubyGems.
#
# The application 'bacon' is installed as part of a gem, and
# this file is here to facilitate running it.
#

require 'rubygems'

version = ">= 0"

if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
  version = $1
  ARGV.shift
end

gem 'bacon', version
load 'bacon'

おい。待てコラ。さっき「デフォルトのRubyから入れたコマンドを上書きされても困るし」って書いたけど、しっかり上書きされてました。あうー。まぁでもこれを使えば上に書いたような気持ち悪い書き換えはしないで済むみたいだ。ので、

  1. さっき書き換えたところは全部元に戻し
  2. /usr/bin/baconを/usr/local/bin/macbaconにrenameし
  3. 再度/usr/bin/gemの方でBaconをインストール

した。と言うわけで、デフォルトのRuby(/usr/bin/ruby)を使ってる人はmacgemの実行は気を付けた方がいいみたい。まぁ、macgem自体は今殆どのgemがインストール時にコケるし、デフォルトのRubyはバージョンが若干古いので基本的には自分でbuildして/usr/local/binあたりかMacPortsから/opt/local/binあたりに入れてると思うので、そんなに困ることはないかもしれないけど、念のため。

おまけ

Baconってなんだろう、ってググったら当然ベーコンに関する情報がヒットするんだけど、Google先生がなんか妙なものを拾ってきた。

bacon

右端の変態、あんた何やってんだ。