簡単結論だけ
Ruby1.9上でRubyInlineを使ったRuby ScriptをWindows用実行ファイルにするには以下を使う。
環境:
- RubyInline(3.8.4) (http://rubyforge.org/projects/rubyinline/)
- Ocra(1.1.3) (http://rubyforge.org/projects/ocra/)
- Rubyinstaller 1.9.1-p378 (http://rubyinstaller.org/)
- devkit-3.4.5r3-20091110 (http://wiki.github.com/oneclick/rubyinstaller/development-kit)
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の使い方:
- 「C:\Ruby19\lib\ruby\gems\1.9.1\gems\ocra-1.1.3\bin」にある「ocra」をSJISで保存し直すか、マジックコメントをつけてその文字コードで保存し直す。
- ocraの実行は、実行ファイル化したいファイルがあるフォルダで行う
自作コードへの追加:
#RubyInline対応 ENV['INLINEDIR'] = File.dirname(File.expand_path(__FILE__))
そのほか:
http://ocra.rubyforge.org/ でocraでのパス設定参照
詳細説明編
RubyスクリプトからWindows用実行ファイルを作る手順と、その問題、そしてそれらの回避方法を説明します。なお今回実行ファイル化の対象とした自作スクリプトには、RubyInlineを使用しています。さらにそのスクリプトはRuby 1.9系用に書かれています。そのため、以下の環境を実行ファイル化に使う必要がありました。
- RubyInline(3.8.4) (http://rubyforge.org/projects/rubyinline/)
- Ocra(1.1.3) (http://rubyforge.org/projects/ocra/)
- Rubyinstaller 1.9.1-p378 (http://rubyinstaller.org/)
- devkit-3.4.5r3-20091110 (http://wiki.github.com/oneclick/rubyinstaller/development-kit)
まずはこの環境を構築する手順を説明し、そして実際に実行ファイル化の際に問題になったこと、その回避方法を説明します。
なおこれ以外の参考サイト
「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から実行ファイル作成用の環境を作りました。その手順は以下の通りです。
- 素直にRubyinstaller 1.9.1-p378をインストール
- devkit-3.4.5r3-20091110を入れる。
- 解凍すると、「devkit」、「bin」フォルダができます。これらをRubyinstallerをインストールしたフォルダ(例えば、「C:\Ruby19」など)にそのままコピーしてください。
- Rubyinstallerのfstabファイルを編集してください。
- 具体的には、http://wiki.github.com/oneclick/rubyinstaller/development-kit、のStep-2を参照してください。
- 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で作成されたファイルが実行されるときは、
という手順が取られます。そのことから、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ファイルをエディタで開き、以下のいずれかを行ってください。
- SJISでそのまま保存し直す
- マジックコメントを書いて、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が変わると問題が起きてしまうのです。そこでこのようにして、スクリプトファイルと同じフォルダを使用するようにメインスクリプト側で設定しているわけです。