2010年11月1日月曜日

MeCab を MinGW-w64 でビルド。ついでに、Java バインディングもビルド

MeCab をビルドしたくなった。ついでに Java からも使いたい。そしてそれは、64ビット版でなくてはならない。

環境


MinGW 環境は、これまで色々ビルドしているので、他にも必要なライブラリなどがあるかもしれないが、今回やった手順しか書かないので注意。

libiconv のビルド
tar zxvf libiconv-1.13.1.tar.gz
cd libiconv-1.13.1
./configure --prefix=/mingw --enable-static --host=x86_64-w64-mingw32 --build=x86_64-w64-mingw32
make
make install
libiconv のビルドは、スンナリできた。

MeCab 用 IPA 辞書のビルド
tar zxvf mecab-ipadic-2.7.0-20070801.tar.gz
cd mecab-ipadic-2.7.0-20070801
./configure --prefix=/mingw --with-charset=utf-8
make

MeCab のビルド
パッチファイル(MeCab_0.98_MinGW.patch)については後述する。
以下の手順でビルドすると、 libgcc_s_sjlj-1.dll が実行時に必要となる。このファイルは MinGWディレクトリの ”x86_64-w64-mingw32/bin/”に入っている。パスを通すか、実行ファイルと同じディレクトリにコピーしておく必要がある。依存を無くそうと悶絶してみたが、私のレベルでは、どうしてもできなかった。ちなみに私のレベルは5である。スライムぐらいには勝てる・・・・
tar zxvf /f/Archive/mecab-0.98.tar.gz
cd mecab-0.98
patch -p0 < MeCab_0.98_MinGW.patch
CPPFLAGS='-I/mingw/include' LIBS=-liconv ./configure --prefix=/mingw --host=x86_64-w64-mingw32 --build=x86_64-w64-mingw32 --enable-utf8-only --with-charset=utf8
make

MeCab のパッチファイル
このパッチファイルこそが、今回の血と汗と涙の結晶。若干の説明をおこなう。

【config.h.in】
49 行目
#undef HAVE_PTHREAD_H
↓
//#undef HAVE_PTHREAD_H
これは、pthread を利用しないようにするため。インストールされていなければいらない。うちの環境では、以前 pthread をインストールしていて、それを使うと MeCab.exe 実行して、1行入力後に固まるようなので、利用しないようにした。pthread を利用しない場合は、WindowsAPI の同様の機能を利用するようだ。

【src/feature_index.cpp】
311 行目
case 't':  os_ << (size_t)path->rnode->char_type;     break;
↓
case 't':  os_ << static_cast<unsigned int>((size_t)path->rnode->char_type);     break;
あいまいなキャストだったので、修正。これが正しいのかは神のみぞ知る・・・

【src/libmecab.cpp】
53 行目
#ifdef __cplusplus
↓
#if defined(__cplusplus) && !defined(__MINGW32__)

65 行目
#ifdef __cplusplus
↓
#if defined(__cplusplus) && !defined(__MINGW32__)
ここは、偉大なる先人の知恵。

【src/Makefile.in】
260 行目
INCLUDES = -DDIC_VERSION=$(DIC_VERSION) $(MECAB_WITHOUT_SHARE_DIC) $(MECAB_WITHOUT_MUTEX_LOCK) $(MECAB_USE_UTF8_ONLY) -DMECAB_DEFAULT_RC="\"$(MECAB_DEFAULT_RC)\""
↓
INCLUDES = -DDIC_VERSION=$(DIC_VERSION) $(MECAB_WITHOUT_SHARE_DIC) $(MECAB_WITHOUT_MUTEX_LOCK) $(MECAB_USE_UTF8_ONLY) -DMECAB_DEFAULT_RC='"$(MECAB_DEFAULT_RC)"'
ここの修正は、苦肉の策。どうもうちの環境では、変数の展開に問題があるらしく、思ったようにならない(バックスラッシュだらけになり、バックスラッシュの個数も1個足りないとか・・・)。よって、この値を利用している、src/utils.cpp を修正した。ここは、コンパイルが通るようにしただけで、理想の対処とは程遠い。

【src/mecab.h】
131 行目
#ifdef _WIN32
↓
#if defined(_WIN32) && !defined(__MINGW32__)

254 行目
#ifndef SIWG
↓
#ifndef SWIG
ここも、偉大なる先人の知恵。

【src/utils.cpp】
282 行目
if (rcfile.empty()) rcfile = MECAB_DEFAULT_RC;
↓
if (rcfile.empty()) rcfile = "C:\\appli\\MeCab_utf8\\etc\\mecabrc";
ここは、mecabrc ファイル格納デフォルトディレクトリをハードコーディングした。各自の環境に合わせて、変更する必要がある。
ソースを読むと、ホームディレクトリの”.mecabrc”か、MECABRC 環境変数の値か、レジストリ”HKEY_LOCAL_MACHINE\software\mecab”の”mecabrc”の値か、レジストリ”HKEY_CURRENT_USER\software\mecab”の”mecabrc”の値か、MeCab.dll と同じディレクトリの”mecabrc”を探すらしい。それでも見つからないときにこの値を利用するようだ。
本来的には、Make の変数展開を何とかするべきなんだろうが、LV5 ではなんとも・・・

【src/writer.cpp】
236 行目
case 'L': *os << std::strlen(sentence); break;
↓
case 'L': *os << static_cast<unsigned int>(std::strlen(sentence)); break;
あいまいなキャストってコンパイラに怒られたので、適当にキャストしてみた(おいっ)。いいのかなぁいいのかなぁ。

MeCab Java バインディングのビルド
tar zxvf mecab-java-0.98.tar.gz
cd mecab-java-0.98

先ほどビルドした MeCab から必要なファイルを Java バインディングのディレクトリにコピーする。
cp ../mecab-0.98/src/mecab.h .
cp ../mecab-0.98/src/.libs/libmecab.a .

Java の bin ディレクトリをパスに追加
export PATH=/c/appli/Java/jdk1.6/bin:$PATH

Makefile を修正
TARGET=MeCab
JAVAC=javac
JAVA=java
JAR=jar
CXX=c++
INCLUDE=c:/appli/Java/jdk1.6/include

PACKAGE=org/chasen/mecab

LIBS=-L. -L/mingw/lib -lmecab -liconv
INC=-I$(INCLUDE) -I$(INCLUDE)/win32

all:
    $(CXX) -O3 -c -fpic $(TARGET)_wrap.cxx  $(INC)
    $(CXX) -shared -static $(TARGET)_wrap.o -o $(TARGET).dll $(LIBS)
    $(JAVAC) -encoding utf-8 $(PACKAGE)/*.java
    $(JAVAC) -encoding utf-8 test.java
    $(JAR) cfv $(TARGET).jar $(PACKAGE)/*.class

test:
    LD_LIBRARY_PATH=. MECABRC=./mecabrc $(JAVA) -Djava.file.encoding=euc-jp test

clean:
    rm -fr *.jar *.o *.dll *.class $(PACKAGE)/*.class
    
cleanall:
    rm -fr $(TARGET).java *.cxx


java のコンソール出力が文字化けするので、ファイルにログを吐いておく。、
make > build.log 2>&1

出来上がったもののテストをしてみる。
設定ファイルを作る
cp ../mecab-0.98/mecabrc .

コピーした mecabrc を修正する。辞書ディレクトリを指定する部分に、先ほどビルドした辞書のディレクトリを設定する。
dicdir =  ..\mecab-ipadic-2.7.0-20070801

テストを実施
コンソール出力は、文字化けするのでファイルに吐いて確かめる。
make test > test.log 2>&1

テスト結果
LD_LIBRARY_PATH=. MECABRC=./mecabrc java -Djava.file.encoding=euc-jp test
0.98
太郎 名詞,固有名詞,人名,名,*,*,太郎,タロウ,タロー
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
二郎 名詞,固有名詞,人名,名,*,*,二郎,ジロウ,ジロー
に 助詞,格助詞,一般,*,*,*,に,ニ,ニ
この 連体詞,*,*,*,*,*,この,コノ,コノ
本 名詞,一般,*,*,*,*,本,ホン,ホン
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
渡し 動詞,自立,*,*,五段・サ行,連用形,渡す,ワタシ,ワタシ
た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。 記号,句点,*,*,*,*,。,。,。
EOS

 BOS/EOS,*,*,*,*,*,*,*,*
EOS

ちなみに、辞書を指定せずに実行すると、以下のようなログが出力される。
LD_LIBRARY_PATH=. MECABRC=./mecabrc java -Djava.file.encoding=euc-jp test
0.98
Exception in thread "main" java.lang.RuntimeException: tagger.cpp(151) [load_dictionary_resource(param)] param.cpp(71) [ifs] no such file or directory: ./mecabrc
    at org.chasen.mecab.MeCabJNI.new_Tagger__SWIG_1(Native Method)
    at org.chasen.mecab.Tagger.<init>(Tagger.java:128)
    at test.main(test.java:17)
make: *** [test] Error 1

文字コードの違う辞書を指定してしまうと、文字が化け化けになる。指定しているつもりが無くとも、MeCab をインストーラーでインストールしたことがあると、レジストリに設定ファイルの場所が設定されてしまうので、こういう事態に陥りやすいので注意。

MeCab の設定ファイルの指定方法は複数あるが、今回は、環境変数で指定してある。DLL と同じディレクトリに入れる方法もあるのだが、レジストリでの指定より優先順位が低いので、環境によって問題がある。

これでめでたく Java から MeCab を利用することが出来る。
いや、長かった・・・・この手順も長いが、実はかなり遠回りをしてしまい、実際にはここに書かれていない作業を延々としてしまった。GLib コンパイルしたりとか、gettext コンパイルしたりとか・・・
まあ、いろんな意味でレベルは上がった。怪我の功名というやつですな。次は Python バインディングか・・・・