mysql2 gem を mysql5.6 の libmysqlclient.a と static link させたくて奮闘してた。

なぜ、そんなことをしたいのかというと、capistrano-bundle_rsync なるものを使って、デプロイサーバで mysql2 gem をビルドして、それを production サーバに rsync して撒くという運用をしているためである。

静的リンクできるようになれば、production サーバの OS の違いとか、入っている rpm のバージョン違いとか気にしなくてよくなるのでうれしい。

使った rpm は Oracle が配布しているオフィシャルなやつ で、こいつがおかしい。

$ sudo rpm -qa  | grep -i mysql
MySQL-client-5.6.21-1.el6.x86_64
MySQL-server-5.6.21-1.el6.x86_64
MySQL-devel-5.6.21-1.el6.x86_64
MySQL-shared-5.6.21-1.el6.x86_64

問題1. mysql_config --libs が示すパスがおかしい

$ ls /usr/lib64/libmysqlclient.so
/usr/lib64/libmysqlclient.so

$ ls /usr/lib64/mysql/libmysqlclient.a
/usr/lib64/mysql/libmysqlclient.a

$ mysql_config --libs
-L/usr/lib64 -lmysqlclient -lpthread -lm -lrt -ldl

mysql_config --libs の結果に -L/usr/lib64/mysql が含まれておらず、おかしい。これでは static link されない。 こちらにバグ報告がある => http://bugs.mysql.com/bug.php?id=67851

実際、mysql2 gem は mysql_config --libs の出力結果 を元に extconf.rb で Makefile 中の LIBS を生成しているが、-L/usr/lib64/mysql が含まれていないので、gem install mysql2 で静的リンクしてくれない。

対応方法として二つ考えられたので2番をやってみた。

  1. /usr/bin/mysql_config をいじる
  2. mysql2 gem ビルド時に --with-mysql-lib オプションを使って /usr/lib64/mysql を指定する。

補足: --with-mysql-lib オプションは mysql2 の README には書かれていないやつなので要チェックな。

$ gem install mysql2 -- --with-mysql-lib=/usr/lib64/mysql

# bundler を使いたい場合は以下のようにする。
# $ bundle config build.mysql2 --with-mysql-lib=/usr/lib64/mysql
# $ bundle

$ find ~/.rbenv/versions/2.1.3 -name 'mysql2.so'
/home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/mysql2-0.3.16/ext/mysql2/mysql2.so
/home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/mysql2-0.3.16/lib/mysql2/mysql2.so
/home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/extensions/x86_64-linux/2.1.0-static/mysql2-0.3.16/mysql2/mysql2.so

$ ldd /home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/mysql2-0.3.16/lib/mysql2/mysql2.so
    linux-vdso.so.1 =>  (0x00007fff5bbe8000)
    libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fdacb621000)
    libm.so.6 => /lib64/libm.so.6 (0x00007fdacb39c000)
    librt.so.1 => /lib64/librt.so.1 (0x00007fdacb194000)
    libdl.so.2 => /lib64/libdl.so.2 (0x00007fdacaf90000)
    libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007fdacad58000)
    libc.so.6 => /lib64/libc.so.6 (0x00007fdaca9c4000)
    libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fdaca7ae000)
    /lib64/ld-linux-x86-64.so.2 (0x00000035d6200000)
    libfreebl3.so => /usr/lib64/libfreebl3.so (0x00007fdaca536000)

ちなみに、静的リンクできていないときはこうなる

$ gem install mysql2

$ find ~/.rbenv/versions/2.1.3 -name 'mysql2.so'
/home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/mysql2-0.3.16/ext/mysql2/mysql2.so
/home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/mysql2-0.3.16/lib/mysql2/mysql2.so
/home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/extensions/x86_64-linux/2.1.0-static/mysql2-0.3.16/mysql2/mysql2.so

$ ldd /home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/mysql2-0.3.16/lib/mysql2/mysql2.so
    linux-vdso.so.1 =>  (0x00007fff38bf2000)
    ### libmysqlclient.so への依存が出ていて静的リンクされていないことがわかる
    libmysqlclient.so.18 => /usr/lib64/libmysqlclient.so.18 (0x00007fc893f82000)
    libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fc893d5b000)
    libm.so.6 => /lib64/libm.so.6 (0x00007fc893ad7000)
    librt.so.1 => /lib64/librt.so.1 (0x00007fc8938cf000)
    libdl.so.2 => /lib64/libdl.so.2 (0x00007fc8936ca000)
    libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007fc893493000)
    libc.so.6 => /lib64/libc.so.6 (0x00007fc8930ff000)
    libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007fc892df8000)
    libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fc892be2000)
    /lib64/ld-linux-x86-64.so.2 (0x00000035d6200000)
    libfreebl3.so => /usr/lib64/libfreebl3.so (0x00007fc89296b000)

問題2. undefined symbol: __cxa_pure_virtual と出る

静的リンクできた!!と思って irb を起動して require mysql2 とすると、

irb(main):001:0> require 'mysql2'
LoadError: /home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/extensions/x86_64-linux/2.1.0-static/mysql2-0.3.16/mysql2/mysql2.so: 
undefined symbol: __cxa_pure_virtual - /home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/extensions/x86_64-linux/2.1.0-static/mysql2-0.3.16/mysql2/mysql2.so

のようなエラーが出る。これはどうやら libmysqlclient.a が stdc++ とリンクできていなくておこっているらしい。こちらにバグ報告がある => http://bugs.mysql.com/bug.php?id=51642

また、なにか mysql2 gem にオプションがないかと ext/mysql2/extconf.rb を読んだが、どうも指定の方法がないっぽい(あったら教えてください)

仕方がないので /usr/bin/mysql_config をいじることにした(社内ではいじった状態で rpm を作り直してそれを利用することにした)

diff -u /usr/bin/mysql_config.bak /usr/bin/mysql_config
]--- /usr/bin/mysql_config.bak  2014-10-21 15:52:12.527601338 +0900
+++ /usr/bin/mysql_config       2014-10-21 15:50:56.886736049 +0900
@@ -84,7 +84,7 @@
 bindir='/usr/bin'

 # If installed, search for the compiled in directory first (might be "lib64")
-pkglibdir='/usr/lib64'
+pkglibdir='/usr/lib64/mysql'
 pkglibdir_rel=`echo $pkglibdir | sed -e "s;^$basedir/;;"`
 fix_path pkglibdir $pkglibdir_rel lib64/mysql lib64

@@ -111,7 +111,7 @@

 # Create options 
 # We intentionally add a space to the beginning and end of lib strings, simplifies replace later
-libs=" $ldflags -L$pkglibdir  -lmysqlclient   -lpthread -lm -lrt -ldl"
+libs=" $ldflags -L$pkglibdir  -lmysqlclient   -lpthread -lm -lrt -ldl -lstdc++"
 libs="$libs   "
 libs_r=" $ldflags -L$pkglibdir  -lmysqlclient_r   -lpthread -lm -lrt -ldl   "
 embedded_libs=" $ldflags -L$pkglibdir  -lmysqld    -lpthread -lm -lrt -lcrypt -ldl -laio    "

この状態で、gem uninstall mysql2 して、改めて gem install mysql2 したところ、静的リンクされた壊れていない mysql2 gem のビルドが出来た。