Linux で、Ruby スクリプトを単一実行ファイル化したい。 それもできるだけ汎用的な方法がいい。
[方針]
ruby をスタティックリンクで作ると、 PAM でダイナミックロードされてるライブラリが使えない。ううーん。
……ん? 実は WEBrick では etc.so はほとんど使ってないのか。 UserDir オプションさえ使わなければ、 WEBrick に関してはとりあえず問題なさそうだな。 問題棚上げ。
他にダイナミックロードが必要と言えばネームサービスか。 サーバ側なら IP アドレスだけ使ってれば getaddrinfo は動かない……かな? なんかいけそうな気がしてきた。 これも問題棚上げ。
Ruby スクリプトは予定通り objcopy でオブジェクトファイルにして、 これを ruby とリンクさせる……まではよいのだが、 ruby にどうやってこのプログラムを実行させるかが問題だ。
ruby の main.c を差し替えてリコンパイルする方法だと、 ruby の objdir をずっと残しておかないといけない。 できれば ruby とか libruby-static.a だけを使ってどうにかしたい。
普通に ruby 組み込みインターフェイスを使ってしまうと、 拡張ライブラリがリンクされない (libruby-static.a しかリンクされないから)。 拡張ライブラリをスタティックリンクした libruby があるといいんだけどな。
libruby-static.a と拡張ライブラリの *.a を 全部まとめた、専用の libruby.a を自分で作ることにした。 これと独自の main.o をリンクしてバイナリを作る。 独自の main.c は ruby の main.c を基本とするが、 argv をすりかえてメインプログラムを -e でつっこむことにする。 なんかイマイチな方法だけど、まあとりあえず動きゃいい。
さらに、main.c で次のように require をすりかえる。
extern struct script_source rb2exe_file_list[]; static VALUE lookup_script(VALUE name) { struct script_source *ent; for (ent = rb2exe_file_list; ent->name; ent++) { return rb_str_new(ent->start, ent->size); } return Qnil; } static void replace_require(void) { rb_define_global_function("_rb2exe_lookup_script", lookup_script, 1); rb_eval_string("\ module Kernel \n\ alias _rb2exe_original_require require \n\ remove_method :require \n\ def require(feat) \n\ rb = feat.sub(/\\.rb\\z/, '') + '.rb' \n\ if src = _rb2exe_lookup_script(rb) \n\ eval src, ::TOPLEVEL_BINDING, rb \n\ $\".push rb \n\ return true \n\ end \n\ _rb2exe_original_require feat \n\ end \n\ end \n\ "); }
rb2exe_file_list はリンク時にソースコードを自動生成してコンパイルする。
残るは、require される *.rb を検出することだ。
……ちょっと考えたんだけど、必要なファイルだけじゃなくて、 ロードパスに入ってる *.rb は全部入れちゃうというのも一案かもしれない。 当然ながらバイナリはでかくなるけど、どーせオンデマンドロードなんだし、 いまどきバイナリが 10MB デカくなったところでたいした問題ではないのではなかろうか。
まあ、いちおう無駄を最小にする方向で考えてみよう。
exerb だと、実際に実行してみて require されるファイルを検出したりする。 しかし、これはいろいろな理由からできれば避けたい感じだ。 あと、レシピファイルを作るのはなんか面倒なので、 少なくともデフォルトにはしたくない。
なお、俺が前に作った簡単スクリプトでは、 単純にソースコードを require で grep して適当に検出していた。 動的な require さえなければこれでも結構うまくいく。
今回は、適当に grep する方針をデフォルトにしつつ、 残りの方法はオプションで選べるようにしようと思う。 つまり以下の四つを用意する。
(06:16)
ruby-dev summary を書くとき、具体的に何をやっているのか、 この日記に書き出してみようと思う。
まず、要約する範囲を決める。現在は隔週でやってるので、 前回の人が担当した部分のあとから二週間分くらいが目安となる。 前回は立石さんの [ruby-talk:190859] ruby-dev summary 28495-28605 なので、[ruby-dev:28606] から二週間くらい。 スレッドの切れめを考えると、[ruby-dev:28636] あたりまでかなあ。 ……って、30 通しかないのかよ。普通はもうちょっとあるんだけど。
対象範囲が決まったら、 新しいメールを作って Subject に範囲を書いてしまう。
次に、メールを眺めつつ、要約する必要のあるスレッドを抽出する。 「要約する必要のあるスレッド」とは、
という感じのスレッドを指す。今回の範囲ならば、次のようなあたり。 リストアップすると同時に、日本語の Subject はてきとうに英訳してしまう。
ちょっと減らしすぎた? というか、元となるメールが少なすぎるんだよ今回は。
ここまで来たら、あとは地道に一個ずつ要約して訳していく。
(次回に続く)
(07:57)
店員が Mac Book を示してゐる。
大層大事さうに陳列棚に載せてゐる。
眠い目を擦り、いつたい何が入つてゐるのか見極めようとするが、如何にも眠かった。
Xeon か Opteron でも入つてゐるのか。
何とも手頃な善い匣である。
店員は時折笑つたりもする。
「ぽん」
匣の中から聲がした。
鈴でも轉がすようなシンセサイザーの聲だった。
……
……えー、まあ、京極ネタは置いといて。 実物見ると意外にのっぺりしてるのな。
(22:15)
ぎがねむす。今日はもう寝よう。
ああ、しかし、日記に適当なネタだけ書いて寝たりしてると、 ようやく本調子に戻ってきたかなと実感するね、印象があるね、そんな情勢だね (三段活用)。
(22:24)
Copyright (c) 2002-2007 青木峰郎 / Minero Aoki. All rights reserved.
const char *ptr = StringValuePtr(name);
long len = RSTRING(name)->len;
if (strlen(ent->name) == len && strncmp(ptr, ent->name, len) == 0)
あたりが抜けてると予想。
RubyScript2Exeとかはどうでしょうか?
Windows,Linux,MacOSXに対応しているようです。
http://www.erikveen.dds.nl/rubyscript2exe/index.html
やっぱり眠いときにコードを書いちゃいけませんねえ。
あ、何かあったなーと思ってたけど、RubyScript2Exe でしたか。
rb2exe はぐぐったんだけどなー。*.so までまとめてくれるんだなあ。
libc までは、なんとなく入れてくれないっぽい?
まあ、PAM とネームサービスの問題は解決方法が思いつかないしなあ。