はじめに
mini_portile という拡張ライブラリ作者向けの gem がある。
拡張ライブラリが依存しているものを事前にコンパイルし、拡張ライブラリをコンパイルする際にそちらを参照するようにしてくれる gem だ。
nokogiri や sqlite3 でも使われているが、 mini_portile 自体の情報が日本語では見つけられなかったので紹介したい。
なお、筆者は本来想定されている使い方とは違う用途で利用してみたので、そちらについても軽く触れてみたいと思う。
mini_portile の使い方
使い方は README に詳しく載っているが、「そちらを見て欲しい」では紹介記事にならないので利用例を上げたいと思う。
基本的な使用方法
require "mini_portile"
recipe = MiniPortile.new("libiconv", "1.13.1")
recipe.files = ["http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.13.1.tar.gz"]
recipe.cook
recipe.activate
READMEに載っているものをそのまま持ってきた形だが、 iconv をコンパイルして拡張ライブラリのビルド時に使用するようにするには上記のコードのみでよい。
MiniPortile#cook
でファイルの取得から展開、 ./configure
の実行、make
までを自動で行ってくれる。
インストール先は <BASEDIR>/port/<platform>/<ライブラリ名>/<バージョン>
となり、その後の MiniPortile#activate
で環境変数にこのパスを追加するようになっている。
パッケージ管理システムの代替を目指している訳ではないためシステムワイドにインストールする機能は提供されていないが、拡張ライブラリをコンパイルする用途であれば問題ない。
パッチを当てる
mini_portile にはダウンロードしてきたソースにパッチを当てるための機能が存在する。
require "mini_portile"
recipe = MiniPortile.new("libiconv", "1.14")
recipe.files = ["http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.14.tar.gz"]
recipe.patch_files = [File.expand_path("path/to/libiconv-1.14-ja-1.patch")]
recipe.cook
recipe.activate
libiconv-1.14 日本語パッチ にて公開されている日本語エンコーディングに関するパッチを当てる場合などは上記のようにすればよい。
ただし、 MiniPortile#patch_files
は MiniPortile#files
と違い自動でダウンロードしてくれないため事前にダウンロードしておく必要があることと、相対パスを指定した場合うまくいかなかったため File.expand_path
で絶対パスにしてやる必要があった。
さらに内部的に git-apply
を呼び出しているため、 git のインストールが必須となっている。
mini_portile デフォルトでは対応できない場合
基本的には ./configure
が色々と吸収してくれるはずだが、 zlib のように Windows にインストールする場合は別の手順を行う必要があったりと、 mini_portile が想定している方法通りではうまく行かないケースが多々存在する。
ではどうするかというと、 利用例のRakefile を見る限り mini_portile を継承したクラスを作成し、その中にライブラリ固有の処理を書く必要があるようだ。
MiniPortile#cook
は下記メソッドを呼ぶだけの処理のため、これらを適時オーバーライドしてやればよい。
- MiniPortile#download
- MiniPortile#extract
- MiniPortile#patch
- MiniPortile#configure
- MiniPortile#compile
- MiniPortile#install
サンプルは長くなりそうなので、上記の Rakefile を参照してほしい。
おまけ:mini_portileをあえて任意の場所にインストールする用途に使ってみる
話は変わるが、筆者は eucJP-ms が適切に処理出来なければならない環境にいるため、 iconv に上記日本語パッチを当てたものを度々コンパイルする。
shell script で書いてもいいのだが、今回あえて mini_portile を使ってやってみようと思った。
mini_portile は任意の場所にインストールすることを想定していないため、そのあたりに手を入れてやる必要がある。
また、前述の通りパッチファイルは自動でダウンロードしてくれないため、自動でダウンロードしてくれるように手を入れた。
require "mini_portile"
class PatchedPortile < MiniPortile
attr_writer :port_path
attr_accessor :patch_urls
def initialize name, version
super name, version
@patch_urls = []
end
def download_patch
@patch_urls.each do |url|
filename = File.basename(url)
download_file(url, File.join(patches_path, filename))
@patch_files.push File.expand_path(File.join(patches_path, filename))
end
end
def cook
download_patch
super
end
private
def port_path
@port_path.nil? ? super : @port_path
end
def patches_path
"#{@target}/patches"
end
end
class LibiconvRecipe < PatchedPortile
def initialize version
super "libiconv", version
@files << "http://ftp.gnu.org/pub/gnu/#{@name}/#{@name}-#{@version}.tar.gz"
end
end
recipe = LibiconvRecipe.new "1.14"
recipe.patch_urls << "http://apolloron.org/software/libiconv-1.14-ja/libiconv-1.14-ja-1.patch"
recipe.port_path = ENV['LIBICONV_INSTALL_DIR'] || "/usr/local/#{recipe.name}-#{recipe.version}"
recipe.configure_options = [
"--host=#{recipe.host}",
"--enable-shared"
]
recipe.cook
recipe.activate
これで LIBICONV_INSTALL_DIR=/path/to/libiconv ruby script.rb
とすれば /path/to/libiconv
にインストールされ、環境変数 LIBICONV_INSTALL_DIR
を指定しなければ /usr/local/libiconv-1.14
にインストールされる。
アンインストールコマンドが提供されていないため簡単に入れ替えが出来るようなデフォルトインストール先としているが、環境変数をごにょごにょ弄りたくないという場合であれば LIBICONV_INSTALL_DIR=/usr/local
としてもよいのかもしれない。
実際に使ってから思ったのだが、やはりこういう用途で使用するならば Ruby で書くよりも shell scriptで書いたほうがよいようである。
筆者環境では Windows でコンパイルすることもあるため Ruby でやったほうが便利ではないか?とも思ったが、この方法では Cygwin や MinGW/MSYS、さらに Git for Windows をインストールする必要があるため、それならば bash も入っているから shell script で充分ではないか?という話であった...。
自動でパッチをダウンロードするぐらいは mini_portile で対応してくれたら嬉しそうなので、気が向いたら修正して PR するかもしれない。