nokogiriが(また)インストールできない

2.4.0-prevew3が出たし、そろそろ2.4の準備だねってことでgem install railsしたらコケた。

$ gem install rails
Building native extensions.  This could take a while...
ERROR:  Error installing rails:
    ERROR: Failed to build gem native extension.

[...]

Building nokogiri using packaged libraries.

[...]

************************************************************************
Extracting libxml2-2.9.4.tar.gz into tmp/x86_64-apple-darwin16.1.0/ports/libxml2/2.9.4... OK
Running 'configure' for libxml2 2.9.4... OK
Running 'compile' for libxml2 2.9.4... ERROR, review '/Users/akira/.rbenv/versions/2.4.0-preview3/lib/ruby/gems/2.4.0/gems/nokogiri-1.6.8.1/ext/nokogiri/tmp/x86_64-apple-darwin16.1.0/ports/libxml2/2.9.4/compile.log' to see what happened. Last lines are:
========================================================================
    unsigned short* in = (unsigned short*) inb;
                         ^~~~~~~~~~~~~~~~~~~~~
encoding.c:815:27: warning: cast from 'unsigned char *' to 'unsigned short *' increases required alignment from 1 to 2 [-Wcast-align]
    unsigned short* out = (unsigned short*) outb;
                          ^~~~~~~~~~~~~~~~~~~~~~
4 warnings generated.
  CC       error.lo
  CC       parserInternals.lo
  CC       parser.lo
  CC       tree.lo
  CC       hash.lo
  CC       list.lo
  CC       xmlIO.lo
xmlIO.c:1450:52: error: use of undeclared identifier 'LZMA_OK'
    ret =  (__libxml2_xzclose((xzFile) context) == LZMA_OK ) ? 0 : -1;
                                                   ^
1 error generated.
make[2]: *** [xmlIO.lo] Error 1
make[1]: *** [all-recursive] Error 1
make: *** [all] Error 2

これはnokogiri.gemに含まれているlibxml2をbuildしようとしている中でエラーが起きている。

nokogiriのドキュメントのMac OS X: Error Message About Undeclared Identifier LZMA_OKによれば

When using Homebrew, there are several libraries that use a formula called xz (including thesilversearcher and imagemagick), which by default installs a version of liblzma that is incompatible with most Ruby builds. (Homebrew installs only the 64-bit version of the library, but most Ruby builds are universal.)

とのことで、回避策はa)一時的にbrew unlink xzする、b)brew reinstall xz --universalする、c)nokogiri.gem同梱libxml2を使わない、のどれか。

a)は後になって忘れてしまいそうなのでb)を試してみるも、やはりコケるしエラーにも変化がない(まあ、なんか違うんじゃ?とは思ったんだけど)。

ではc)を、ということでやってみると、これもダメ。

$ gem install nokogiri -- --use-system-libraries
Building native extensions with: '--use-system-libraries'
This could take a while...
ERROR:  Error installing nokogiri:
        ERROR: Failed to build gem native extension.

[...]

Building nokogiri using system libraries.
Using pkg-config gem version 1.1.7
checking for libxml-2.0... no
checking for libxslt... no
checking for libexslt... no
ERROR: cannot discover where libxml2 is located on your system. please make sure `pkg-config` is installed.
*** extconf.rb failed ***

libxml2(やlibxslt)を検出できなかったらしい。

libxml2はhomebrewでインストールしていたはずだが、と確認していくと/usr/local/libにリンクがないことが分かった。

/usr/localにリンクを作らない、こういうタイプのformulaがありkeg-onlyと呼ぶらしい。明示的にリンクを作ろうとするとこうなる:

$ brew link libxml2
Warning: libxml2 is keg-only and must be linked with --force
Note that doing so can interfere with building software.

メッセージにある通り、brew link --forceによってリンクを強制することができる。だが、keg-onlyになっているのにも理由があるはずなので避けられるものなら避けておきたい。となると、libxml2のパスを個別に指定すればよいだろうということでやってみる:

$ gem install nokogiri -- --use-system-libraries --with-xml2-dir=$(brew --prefix libxml2)
Building native extensions with: '--use-system-libraries --with-xml2-dir=/usr/local/opt/libxml2'
This could take a while...
ERROR:  Error installing nokogiri:
        ERROR: Failed to build gem native extension.

[...]

Building nokogiri using system libraries.
Using pkg-config gem version 1.1.7
checking for libxslt... no
checking for libexslt... no
ERROR: cannot discover where libxml2 is located on your system. please make sure `pkg-config` is installed.
*** extconf.rb failed ***

だがこれまたコケた。

ただ、checking for libxml-2.0... noという出力が消えていることから検出できなかったわけではないようだ。ならばextconf.rbでのチェックにハネられたということ。mkmf.logを確認すると、最後はこうなっていた:

"clang -E -I/Users/akira/.rbenv/versions/2.4.0-preview3/include/ruby-2.4.0/x86_64-darwin16 -I/Users/akira/.rbenv/versions/2.4.0-preview3/include/ruby-2.4.0/ruby/backward -I/Users/akira/.rbenv/versions/2.4.0-preview3/include/ruby-2.4.0 -I. -I/usr/local/opt/libxml2/include -I/Users/akira/.rbenv/versions/2.4.0-preview3/include  -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT    -O3 -Wno-error=shorten-64-to-32  -pipe   conftest.c -o conftest.i"
conftest.c:3:10: fatal error: 'libxml/xmlversion.h' file not found
#include <libxml/xmlversion.h>
         ^
1 error generated.
checked program was:
/* begin */
1: #include "ruby.h"
2: 
3: #include <libxml/xmlversion.h>
/* end */

`--with-xml2-dir`で指定したパスが`-I/usr/local/opt/libxml2/include`という形で反映されているのが分かる。しかしその場所に`libxml/xmlversion.h`はない。

$ ls /usr/local/opt/libxml2/include 
libxml2

$ ls /usr/local/opt/libxml2/include/libxml2
libxml

$ ls /usr/local/opt/libxml2/include/libxml2/libxml/xmlversion.h
/usr/local/opt/libxml2/include/libxml2/libxml/xmlversion.h

これがhomebrewによるインストールであるためか、そうではないのか分からないが、include/libxml2まで指定してやる必要があるようだ。

こうなってくると--with-xml2-dirなどではダメで、--with-xml2-includeなどでいちいちパスを指定しなければならない。しかしそこまでいくとCFLAGSLDFLAGSを直接指定するようなもので、さすがにめんどうになる。

ここでextconf.rbからのメッセージを再確認するとpkg-configをなんとかせよと書かれている。extconf.rbのその部分を見てみると、こうなっていた:

case
when using_system_libraries?
  message "Building nokogiri using system libraries.\n"

  [...]
  dir_config('xml2').any?  or package_config('libxml-2.0')
  dir_config('xslt').any?  or package_config('libxslt')
  dir_config('exslt').any? or package_config('libexslt')

pkg-configで情報が得られない? と少し疑問に思ったが、今しがた確認した通り libxml2はkeg-onlyである。.pcファイルも/usr/localにはない。

$ pkg-config --cflags libxml-2.0
Package libxml-2.0 was not found in the pkg-config search path.
Perhaps you should add the directory containing `libxml-2.0.pc'
to the PKG_CONFIG_PATH environment variable
No package 'libxml-2.0' found

.pcファイルのパスを指定するとよいようだ。

$ PKG_CONFIG_PATH=$(brew --prefix libxml2)/lib/pkgconfig pkg-config --cflags libxml-2.0
-I/usr/local/Cellar/libxml2/2.9.4/include/libxml2

ならばgem installするときにもPKG_CONFIG_PATHを指定しておけばよいのではないか。

$ PKG_CONFIG_PATH=$(brew --prefix libxml2)/lib/pkgconfig:$(brew --prefix libxslt)/lib/pkgconfig:$(pkg-config --variable pc_path pkg-config) gem install nokogiri -- --use-system-libraries 
Building native extensions with: '--use-system-libraries'
This could take a while...
Successfully installed nokogiri-1.6.8.1
Parsing documentation for nokogiri-1.6.8.1
Installing ri documentation for nokogiri-1.6.8.1
Done installing documentation for nokogiri after 3 seconds
1 gem installed

ようやくうまくいった! これでrails.gemをインストールできる。

なお、--with-xml2-config--with-xslt-configを個別に指定することでの回避も可能かと思ったのだが、実際に試してみると(homebrew環境では?)うまくいかなかった。

これはxml2-configが無効なオプションを指定されても終了コードが0になるためのようだ。

上で引用した中に出てくるpackage_configは内部でmkmf.rbpkg_configを呼び出している。pkg_configpkg-config○○-configの両方に対応していて、その判別はこうなっている。

  def pkg_config(pkg, option=nil) # pkgには'libxml-2.0'や'libxslt'が渡されてくる
    if pkgconfig = with_config("#{pkg}-config") and find_executable0(pkgconfig)
      # iff package specific config command is given
    elsif ...
      ...
    else
      pkgconfig = nil
    end
    if pkgconfig
      get ||= proc {|opt|
        opt = xpopen("#{pkgconfig} --#{opt}", err:[:child, :out], &:read)
        Logging.open {puts opt.each_line.map{|s|"=> #{s.inspect}"}}
        opt.strip if $?.success?
      }
    end
    orig_ldflags = $LDFLAGS
    if get and option
      get[option]
    elsif get and try_ldflags(ldflags = get['libs'])
      if incflags = get['cflags-only-I']
        $INCFLAGS 
/* end */

なやましい。いや、PKG_CONFIG_PATHで回避はできたんだけど。