RubyInlineを使ったRubyのスクリプトをocraで実行ファイルにする(まとめ)

簡単結論だけ

Ruby1.9上でRubyInlineを使ったRuby ScriptをWindows用実行ファイルにするには以下を使う。


環境:

RubyInlineへのパッチ

@@ -519,7 +520,7 @@
     def build
       so_name = self.so_name
       so_exists = File.file? so_name
-      unless so_exists and File.mtime(rb_file) < File.mtime(so_name) then
+      unless so_exists and File.mtime(rb_file) <= File.mtime(so_name) then
 
         unless File.directory? Inline.directory then
           warn "NOTE: creating #{Inline.directory} for RubyInline" if $DEBUG
@@ -561,7 +562,7 @@
                           else
                             nil
                           end
-
+	   Config::CONFIG['LDSHARED'] = "gcc -shared"
           cmd = [ Config::CONFIG['LDSHARED'],
                   flags,
                   Config::CONFIG['CCDLFLAGS'],
@@ -622,7 +623,7 @@
       when /mswin32/ then
         " -link /LIBPATH:\"#{Config::CONFIG['libdir']}\" /DEFAULTLIB:\"#{Config::CONFIG['LIBRUBY']}\" /INCREMENTAL:no /EXPORT:Init_#{module_name}"
       when /mingw32/ then
-        " -Wl,--enable-auto-import -L#{Config::CONFIG['libdir']} -lmsvcrt-ruby18"
+        " -Wl,--enable-auto-import -L#{Config::CONFIG['libdir']} -l#{RbConfig::CONFIG['RUBY_SO_NAME']}"
       when /i386-cygwin/ then
         ' -L/usr/local/lib -lruby.dll'
       else

Ocraの使い方:

  1. 「C:\Ruby19\lib\ruby\gems\1.9.1\gems\ocra-1.1.3\bin」にある「ocra」をSJISで保存し直すか、マジックコメントをつけてその文字コードで保存し直す。
  2. ocraの実行は、実行ファイル化したいファイルがあるフォルダで行う

自作コードへの追加:

#RubyInline対応
ENV['INLINEDIR'] = File.dirname(File.expand_path(__FILE__))

そのほか:
http://ocra.rubyforge.org/ でocraでのパス設定参照

詳細説明編

RubyスクリプトからWindows用実行ファイルを作る手順と、その問題、そしてそれらの回避方法を説明します。なお今回実行ファイル化の対象とした自作スクリプトには、RubyInlineを使用しています。さらにそのスクリプトRuby 1.9系用に書かれています。そのため、以下の環境を実行ファイル化に使う必要がありました。

まずはこの環境を構築する手順を説明し、そして実際に実行ファイル化の際に問題になったこと、その回避方法を説明します。


なおこれ以外の参考サイト
「ocraの動作と、エラーの原因を考えてみる」(http://d.hatena.ne.jp/mirichi/20090611/p1)
「wxRuby + ocra 2009」(http://www.up-cat.net/wxRuby%2B%252B%2Bocra%2B2009.html)
Ruby1.9.1でRubyInline動かした」(http://d.hatena.ne.jp/okmount/20090223/1235317264)
Rubyスクリプトをexe化するためのソフト、Ocra」(http://route477.net/d/?date=20090606#p03)

1.環境作り

自分はVMPlayer上でクリーン環境を用意して、0から実行ファイル作成用の環境を作りました。その手順は以下の通りです。

  1. 素直にRubyinstaller 1.9.1-p378をインストール
    • DLして、ダブルクリックするだけ。すごく簡単です。
    • Cygwin環境でも作れますが、その実行ファイルには問題があります。まず「Cygwin1.dllが追加で必要になる」こと。もう一つが「Windowsのパスセパレータを認識しない」ことです。ここは素直にMinGWRubyであるRubyinstallerを使用した方が幸せになれると思います。
  2. devkit-3.4.5r3-20091110を入れる。
    • 解凍すると、「devkit」、「bin」フォルダができます。これらをRubyinstallerをインストールしたフォルダ(例えば、「C:\Ruby19」など)にそのままコピーしてください。
  3. Rubyinstallerのfstabファイルを編集してください。
  4. gemからOcra, RubyInlineを入れます
$gem install RubyInline
$gem install ocra

とするだけでOKです。

これで環境は整いました。

2.inline.rb について

RubyInlineのソースである inline.rb は、基本的にRuby1.9へ対応していません。またOcraを使って実行ファイルを作成する際に問題になる箇所もあります。さらに、MinGW環境で特有の問題もあります。そのためinline.rbを修正しなければなりませんでした。詳細は以下に書きます。なお、inline.rbは、デフォルトのインストールを上記の手順で行った場合、「C:\Ruby19\lib\ruby\gems\1.9.1\gems\RubyInline-3.8.4\lib」にあります。

2.1: コンパイルコマンドラインについて

inline.rb:564行あたりで、

-
+Config::CONFIG['LDSHARED'] = "gcc -shared"

として、Config::CONFIG['LDSHARED']を強制上書きしてください。Config::CONFIG['LDSHARED']には元々、

gcc -shared $(if $(filter-out -g -g0, -g),,-s)

という値が入っています。しかし、現状の環境(不備があるわけではないと思います)にあるMinGWのmake、もしくはrakeではこの関数表現が解釈できていない? (そんな馬鹿な、たぶんこのままコマンドラインで実行されてるような気がする)のか、そのまま実行すると「$(if」の部分でErrorになってしまいました。ここの修正は、そのための措置です。なお他のファイルに影響が出ないように、inline.rbの実行最後で、元の値に戻した方が良いかもしれません。(しかしこれってメインスクリプト側でやった方が良いのかな?)

2.2: 1.9.1対応

inline.rb:624行あたりを

-" -Wl,--enable-auto-import -L#{Config::CONFIG['libdir']} -lmsvcrt-ruby18"
+" -Wl,--enable-auto-import -L#{Config::CONFIG['libdir']} -l#{RbCongig::CONFIG['RUBY_SO_NAME']}"

と変更してください。これでRubyInlineが、インライン部分のソースコードコンパイルする時に1.9系のライブラリを使用するようになります。もともとライブラリ名が決め打ちというのもおかしいんですけどね。

2.3: ocraへの対応

inline.rbのRecompile条件である528行を以下のようにしてください。

-      unless so_exists and File.mtime(rb_file) < File.mtime(so_name) then
+      unless so_exists and File.mtime(rb_file) <= File.mtime(so_name) then

理由は、RubyInlineのリコンパイル条件が、ocraではOnになってしまうからです。Ocraはスクリプトを一度実行し、そのときに読み込まれたファイルを収集して実行ファイル化するツールです。つまりOcraで作成されたファイルが実行されるときは、

  1. 収集されたファイルをtmpフォルダへ展開
  2. tmpフォルダに展開されたスクリプトを、同じく収集されたruby.exeで実行

という手順が取られます。そのことから、rbのスクリプトファイル、事前にコンパイルされて収集されているンライン部分のsoファイル、それぞれファイル作成時間が同じになってしまいます。

ファイル作成時間が同じだと上記のオリジナルのRecompile条件は充たされてしまい、コンパイルがかかります。しかし当然ながらOcraが収集したファイルだけではCompileできません。結果的にファイルを実行したときにエラーが出てしまいます。この修正はそれを回避するためです。

ただし、この修正方法には懸念があります。それは、もしも展開するファイルが大きいと、解凍に時間がかかり、必ずしもタイムスタンプが「==」にはならないかも知れないことです。ここはもっとちゃんとした対応する必要があるとは思いますが、現状良い方法が思いつきません。

思いついた方法は、自作スクリプト側に、ocraでの実行用オプションをつけて、そのときに限り必ずコンパイル、それ以外はコンパイルしないという方法ですが、余りスマートとは思えません。(是非コメントが欲しいところです)

3. Ocraの修正

Ocraもそのままでは、作成ファイルがtmpフォルダに展開用ディレクトリを作成できないなどの問題があります。正確な原因は分かっていないのですが、以下のようにすれば問題を回避できるようです。なお、ocraのファイルは「C:\Ruby19\lib\ruby\gems\1.9.1\gems\ocra-1.1.3\bin」にある「ocra」。拡張子がないので注意してください。

3.1: Ocraファイルの文字コード

Ocraファイルをエディタで開き、以下のいずれかを行ってください。

  1. SJISでそのまま保存し直す
  2. マジックコメントを書いて、UTF8で保存し直す

1.9系としては、2のマジックコメントをつける方法を勧めます。具体的には、行頭に

#-*- coding: utf-8 -*-

を追加し、その文字コード(UTF8N)で保存することです。

これを行う理由は正直分かりません。ただ、そうしないと展開用ディレクトリのPATHがおかしくなるようです。(元々の原因は、元々のファイルがマジックコメント無しでUTF8だったような気がします(気のせいかも)。)

3.2: ocraの実行に関して

ocraの実行は、実行ファイルにしたいスクリプトがあるフォルダに移動して行ってください。例えば以下のようになります。

$ls .
  hoge.rb
$ocra hoge.rb

この理由は作者Blogの以下から。

"Failed to create directory" Error

One user reported receiving the error "Failed to create directory"
when running the compiled executable. As a possible workaround,
try running ocra.rb from the directory where your script is located.
For example, instead of running...

ocra.rb "C:\code\rubyscripts\application.rbw"

...navigate to the "C:\code\rubyscripts" directory, then run:

ocra.rb application.rbw

Note that while this resolved the problem on my machine,
it didn't help the person who originally reported the problem.

これも展開用ディレクトリのPATH問題なのですが、上記3.1をせずにこの方法をとってもダメでした。どっちが本当の原因なのかは調べてないのでちょっと分かりません。

4.実行ファイル化対象スクリプトの修正

自作の実行ファイル化対象ファイルにもやや修正が必要になります。

4.1: ocraとRubyInlineへの対応

以下のコードをルートスクリプト先頭付近に追加してください。

#同フォルダへ分割したファイルがある場合
$LOAD_PATH << File.dirname(File.expand_path(__FILE__))

#RubyInline対応
ENV['INLINEDIR'] = File.dirname(File.expand_path(__FILE__))

一つ目は、もし「hoge.rb、foo.rb、main.rb」のように複数のファイルへ自作スクリプトを分割していた場合のocraへの対応です。これは「main.rb」のような実際に実行されるスクリプトファイルと同じフォルダに、それらクラスファイルが置かれていることを想定しています。もしそうでない場合は適宜変更してください。ただし、必ずルートスクリプトがあるフォルダのサブフォルダにした方が良いと思います。この辺はOcraのReadmeに有ります。

二つ目は、RubyInlineが読み込むライブラリファイルのPath設定です。RubyInlineはEVN['HOME']または、EVN['INLINEDIR']に指定されているフォルダを、コンパイル場所やsoファイルの格納場所にしています。Ocraで作った実行ファイルは、実行場所や環境、そしてマシンなどが違った場所で実行されます。したがって、それらの場合、そのフォルダPATHが変わると問題が起きてしまうのです。そこでこのようにして、スクリプトファイルと同じフォルダを使用するようにメインスクリプト側で設定しているわけです。

5.実行ファイルの作成

以上のinline.rbとocraへの修正と自作スクリプトへの調整を行った後に、3.2で説明した、実行ファイル化の手順

$ls .
  hoge.rb
$ocra hoge.rb

を行ってください。これでruby 1.9で動くRubyInlineを使ったスクリプトを、Windowsで実行可能なファイルへ変換できます。上記以外で出てくるバグはおおよそ、作成したスクリプトデバッグでつぶせるレベルだと思います。なんだか埋まってある地雷を片っ端から踏んでいったような気分でした。